@ai-ecoverse/slicc-agent 3.47.3 → 3.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +18 -7
  2. package/dist/attachments.js +168 -0
  3. package/dist/attachments.js.map +7 -0
  4. package/dist/changes/git.js +2 -2
  5. package/dist/changes/git.js.map +1 -1
  6. package/dist/control.js +30 -10
  7. package/dist/control.js.map +2 -2
  8. package/dist/host.js +26 -9
  9. package/dist/host.js.map +3 -3
  10. package/dist/images/fit.js +50 -0
  11. package/dist/images/fit.js.map +7 -0
  12. package/dist/images/index.js +7 -47
  13. package/dist/images/index.js.map +3 -3
  14. package/dist/index.js +15 -0
  15. package/dist/index.js.map +2 -2
  16. package/dist/main.js +11 -2
  17. package/dist/main.js.map +2 -2
  18. package/dist/mcp/runtime.js +1 -1
  19. package/dist/mcp/runtime.js.map +2 -2
  20. package/dist/net.js +10 -1
  21. package/dist/net.js.map +2 -2
  22. package/dist/readonly.js +7 -1
  23. package/dist/readonly.js.map +2 -2
  24. package/dist/remote/client.js +9 -4
  25. package/dist/remote/client.js.map +2 -2
  26. package/dist/remote/host.js +1 -0
  27. package/dist/remote/host.js.map +2 -2
  28. package/dist/scoops/identity.js +4 -1
  29. package/dist/scoops/identity.js.map +2 -2
  30. package/dist/services.js.map +2 -2
  31. package/dist/spectrum/agent-port.js +17 -2
  32. package/dist/spectrum/agent-port.js.map +2 -2
  33. package/dist/spectrum/messages.js +7 -2
  34. package/dist/spectrum/messages.js.map +2 -2
  35. package/dist/spectrum/outgoing.js +73 -0
  36. package/dist/spectrum/outgoing.js.map +7 -0
  37. package/dist/system.js +11 -6
  38. package/dist/system.js.map +2 -2
  39. package/dist/thumbs.js +105 -0
  40. package/dist/thumbs.js.map +7 -0
  41. package/dist/types/attachments.d.ts +58 -0
  42. package/dist/types/changes/git.d.ts +2 -2
  43. package/dist/types/control.d.ts +2 -1
  44. package/dist/types/host.d.ts +4 -0
  45. package/dist/types/images/fit.d.ts +10 -0
  46. package/dist/types/images/index.d.ts +2 -10
  47. package/dist/types/index.d.ts +2 -0
  48. package/dist/types/main.d.ts +2 -0
  49. package/dist/types/net.d.ts +8 -0
  50. package/dist/types/remote/client.d.ts +2 -0
  51. package/dist/types/remote/host.d.ts +2 -0
  52. package/dist/types/services.d.ts +16 -0
  53. package/dist/types/spectrum/outgoing.d.ts +8 -0
  54. package/dist/types/system.d.ts +1 -1
  55. package/dist/types/thumbs.d.ts +19 -0
  56. package/dist/types/uploads.d.ts +14 -0
  57. package/dist/types/views.d.ts +2 -1
  58. package/dist/uploads.js +43 -0
  59. package/dist/uploads.js.map +7 -0
  60. package/dist/views.js +6 -2
  61. package/dist/views.js.map +2 -2
  62. package/package.json +3 -3
  63. package/packages/vfs-root/skills/browser/SKILL.md +2 -2
package/README.md CHANGED
@@ -85,7 +85,7 @@ At boot, the worker sets `/.slicc`, where the session database lives in OPFS, to
85
85
  ### The worker seven starts
86
86
 
87
87
  `@ai-ecoverse/slicc-agent/agent-worker` is a complete agent worker. It waits for the page kernel's port, then:
88
- - attaches to the kernel and routes its cross-origin `fetch` through the kernel's transport when that transport isn't bound by CORS (slicc-node, slicc-swift or slicc-extension), and every `<port>.kernel.localhost` request to the kernel's loopback (`loopbackFetch`);
88
+ - attaches to the kernel and routes every cross-origin `fetch` through the kernel's transport, whose router picks a linked `slicc` exit, the extension or the page's own fetch, and every `<port>.kernel.localhost` request to the kernel's loopback (`loopbackFetch`). Same-origin and credentialed (`credentials: 'include'`) requests, and all of them when the kernel has no transport (`traits.unavailable`), use the worker's own fetch, since only it carries the browser's cookies. The system prompt names the route from the traits at attach: `traits.exit` (`{ kind: 'local' | 'remote' | 'tailnet', caps }`, from slicc-bios while it has a default exit) says a linked slicc exit, one on another device or the tailnet's exit node, and any other kind a linked exit, and without it `crossOrigin: 'cors'` says the page's own fetch;
89
89
  - opens the encrypted credential store, the AWS Bedrock provider and the session in OPFS SQLite;
90
90
  - installs durable's coding tools and SLICC's system prompt, and serves the agent.
91
91
 
@@ -141,6 +141,17 @@ Each tool call shows its input in the way that suits it:
141
141
  - `edit`: the path, and its change as a `diff` (spectrum ≥ 1.13.0). While the edit runs, the diff comes from its `oldText`/`newText` pairs. Once done, it comes from the hunks of the unified `patch` that pi's edit tool returns in its result details, with context lines;
142
142
  - other tools: their arguments as pretty-printed JSON.
143
143
 
144
+ #### Attachments
145
+
146
+ A send's `Outgoing.attachments` reach the model. In the page, `send()` reads each attachment's `url` (a `data:` or `blob:` URL) into bytes before anything else, so a `blob:` URL spectrum revokes later has already been read, and passes them on `SendRequest.attachments` as `{ name, mimeType, kind, data?, text?, path? }`, where `data` is base64 because chord carries JSON only. It skips attachments with an `error` and secrets. In the worker, `control.send` turns them into the user message:
147
+ - an image (`image/png`, `jpeg`, `gif` or `webp`) becomes a pi-ai `ImageContent` part after a short `<attachment kind="image" …>` text part that names it;
148
+ - a text file is inlined as `<attachment kind="text" name="…" type="…">` with its text, and with its `path` when it is an @-mention;
149
+ - any other file is written with the conversation's own environment, so the user's own client in users mode, into `uploads/` under the conversation's `cwd` (`/home` by default), and the text says `Saved at <path>.` A free name gets `-1`, `-2` and so on;
150
+ - for a model whose `input` lacks `image`, an image is saved the same way and the text adds "This model can't see images, so the image was saved as a file instead." (pi-ai alone would put `(image omitted: model does not support images)` in its place, and the image would be lost to the tools.)
151
+ - an attachment with only a `path` is mentioned by its path.
152
+
153
+ Images are limited to 5 MiB, other files to 8 MiB, and one message's attachments to 10 MiB together, because one pi protocol frame holds 16 MiB and providers refuse bigger images anyway. The page first shrinks an image over the limit (to 1600 px, as image reads do). Anything still too big isn't sent, and a notice says why, for example "photo.png is larger than 5 MiB, so it can't be sent.". Text and inlined files go through the vault's scrubbing like any send; images can't be scrubbed as text. The replicated transcript and views don't carry the full images: the host swaps every image part over 48 KB, in entries and in the inbox, for a JPEG thumbnail of at most 256 px and 48 KB (made once per image in the worker with `OffscreenCanvas`) plus `full: { mimeType, size }`, so a conversation with many images still attaches in one 16 MiB frame. The model still gets the full image from storage. The transcript maps the parts back to spectrum's user message `attachments`, with the thumbnail as `url` and the id `ref:<entry>:<message>:<part>`. When such an attachment is sent again, by Retry or from the composer after a rewind, the adapter fetches the full image with `AgentControl.image({ entry, message, part })`. A remote `prompt()` takes the same `attachments`.
154
+
144
155
  ### Licks
145
156
 
146
157
  A lick is an event from outside the conversation (a file change, a schedule, a webhook, a reload, an upgrade) that reaches the agent as a steering input. pi durable's `submit({ type: 'input', whenBusy: 'steer' })` does the delivery: an idle cone starts a run, and a busy one gets the lick at its next boundary, after the current tool round, like a steer the user types. So the agent sees events during a long run instead of after it. Durable has no custom message role, so the input's text carries the lick:
@@ -186,7 +197,7 @@ A **cone** is a top-level agent the user talks to; a **scoop** is a helper agent
186
197
 
187
198
  Each scoop is owned by a background durable task, its **anchor**, so Stop in its cone doesn't end it; `agent stop` and Drop do. They keep its transcript in durable storage and its reports in `/scoops/<handle>/reports/`, which report licks point at, and remove its working folder `/scoops/<handle>/workspace`. Each request handed to a scoop gets a **reporter** task that waits for the run, writes the answer to `/scoops/<handle>/reports/<task id>.md` and reports it to whoever asked. Requests from a cone's or a scoop's bash report into that agent's `pi.inbox` as a lick, through the licks outbox, so they steer and coalesce like any lick; requests from a terminal report nowhere, and a user typing in a scoop's chat gets the answer there. In a scoop's chat, a request someone else sent shows who sent it (`UserMessage.from`): the asking cone's or scoop's name, with `origin: 'agent'` so spectrum (≥ 1.26.0) shows it as that agent's request with an "Agent" badge, or `terminal` without an origin. Once the request is placed, the reporter appends a `slicc.from` entry (`{ from, entry }`) without model messages, and the adapter sets `from` on that entry's message. A request withdrawn before it was placed leaves no entry. The report lick's text is `[scoop <name> (<role>) <status>]`, and its body has the report's path and line count and a 1,000-character preview. A scoop's prompt has a `scoop` section with its name, its cone, its folders and its limits. The `agent` command is taught by the built-in `agent` skill (below), whose description says that roles exist and that `agent list --agents` lists them.
188
199
 
189
- **The `agent` command.** One bash script is installed under seven names, `agent`, `subagent`, `memory`, `gelatiere`, `freezer`, `models` and `panel`, in `/usr/local/bin`, mode `0755`, which is on every user's `PATH`, so the user, a cone or a scoop runs it in any bash. A file at one of those paths that isn't slicc-agent's (it lacks the `slicc-agent/1` request marker), such as a root global package's `memory`, is left alone, with one warning per boot in the worker's console. Root's `$PNPM_HOME/bin` (`/usr/local/share/pnpm/bin`) is on root's `PATH` only: other users find root's installed packages in `/usr/bin`, but not loose scripts. Copies that earlier versions put in the kernel's `$PNPM_HOME/bin` are removed, so they can't shadow the new ones for root. It writes a request (NUL-separated fields: `slicc-agent/1`, the name it was called by, `$SLICC_AGENT`, or `tab:$SLICC_TAB` from a terminal that has only that, the working directory, its pid, argc, the arguments and stdin) to `/var/lib/slicc/agent/requests/in/` and polls `out/` for the answer and exit code (with an optional `<id>.err`, the answer's stdout is `<id>.out` and its stderr `<id>.err`); the worker writes `<id>.ack` when it picks a request up, and the script gives up after 20 s without one ("the SLICC agent isn't running", exit 1). The kernel environment exports `SLICC_AGENT`, `PI_PROVIDER`, `PI_MODEL` and `PI_REASONING_LEVEL` into every agent command, so the worker knows the caller. `/var/lib/slicc/agent/scoops/<handle>/metadata.json` mirrors each scoop's state for scripts.
200
+ **The `agent` command.** One bash script is installed under seven names, `agent`, `subagent`, `memory`, `gelatiere`, `freezer`, `models` and `panel`, in `/usr/local/bin`, mode `0755`, which is on every user's `PATH`, so the user, a cone or a scoop runs it in any bash. A file at one of those paths that isn't slicc-agent's (it lacks the `slicc-agent/1` request marker), such as a root global package's `memory`, is left alone, with one warning per boot in the worker's console. Root's `$PNPM_HOME/bin` (`/usr/local/share/pnpm/bin`) is on root's `PATH` only: other users find root's installed packages in `/usr/bin`, but not loose scripts. Copies that earlier versions put in the kernel's `$PNPM_HOME/bin` are removed, so they can't shadow the new ones for root. It writes a request (NUL-separated fields: `slicc-agent/1`, the name it was called by, `$SLICC_AGENT`, or `tab:$SLICC_TAB` from a terminal that has only that, the working directory, its pid, argc, the arguments and stdin) to `/var/lib/slicc/agent/requests/in/` and polls `out/` for the answer and exit code (with an optional `<id>.err`, the answer's stdout is `<id>.out` and its stderr `<id>.err`); the worker writes `<id>.ack` when it picks a request up, and the script gives up after 20 s without one ("the SLICC agent isn't running", exit 1). The kernel environment exports `SLICC_AGENT`, `PI_PROVIDER`, `PI_MODEL` and `PI_REASONING_LEVEL` into every agent command, so the worker knows the caller, and `PLAYWRIGHT_CLI_SESSION` with the same agent id, so each agent's `playwright-cli` keeps its own current tab (slicc-cdp #129). The user's terminals get none of these. `/var/lib/slicc/agent/scoops/<handle>/metadata.json` mirrors each scoop's state for scripts.
190
201
  - `agent [options] --prompt <text>` (or `--file <path>`, or `-` for stdin, or v6's `agent <cwd> <allowed-commands> <prompt>`) is **synchronous**: an ephemeral scoop runs, and the command prints its final answer and exits 0, or prints the reason on stderr and exits 1. Afterwards the scoop is stopped, its folder removed, its conversation marked dropped and its transcript archived as Markdown in `/tmp/agent-sessions/<handle>.md` (`/home/sessions/` with `--persist-session`, nowhere with `--no-persist-session`). The script traps TERM, INT and HUP and cancels the scoop, and the worker checks the script's pid in the kernel's process table every second for a SIGKILL. If the caller's run is still going when the bash call dies (a bash timeout), the scoop continues and its answer arrives as a `bash` lick; `--background-after <seconds>` does the same on purpose. Sync calls nest up to three deep. Allowed commands in v6's form are told to the scoop, not enforced.
191
202
  - `agent --async …` (= `subagent spawn …`) starts a persistent scoop and prints its handle; only cones start them. `list [--agents]`, `status`, `rename`, `send [--follow-up]`, `wait [--timeout <s>] [--notify]` (30 s by default; `--notify` returns at once and brings the answers together in one `scoop-wait` lick) and `stop` manage them. From a scoop, `agent send parent "<note>"` posts a progress note to its cone as a lick, coalesced per scoop.
192
203
  - Options: `--name`, `--agent <role>`, `--model <provider/model>`, `--thinking <level>` or `--effort low|medium|high|max`, `--tools <a,b>` (or `auto`/`full`), `--read-only <paths>`, `--system-prompt[-file]`. `--schema-b64`, `--tools output`, `--session`, `--resume`, `--image`, `--no-escalate`, `--minimal` and `--usage` answer "not supported in SLICC yet".
@@ -306,7 +317,7 @@ MCP servers reachable over Streamable HTTP become native agent tools. The client
306
317
 
307
318
  A codemode script waits up to 10 s for servers still connecting. For the model, text results over 20 KB keep their start and end around a `…N chars truncated…` marker, and the full text goes to `/tmp/slicc-mcp-<call>.txt`. Images pass through, and binary resources are saved to `/tmp`. Servers with resources add pi's `list_mcp_resources`, `list_mcp_resource_templates` and `read_mcp_resource`, with the widest exposure among those servers. They skip servers whose exposure is `hidden`. MCP prompts, sampling and elicitation aren't supported, so a server can't call the model or ask the user anything.
308
319
 
309
- **Network.** Requests go through the worker's transport fetch, as Bedrock's do. On the page's own fetch, a server must send CORS headers allowing `Authorization`, `Content-Type`, `Mcp-Session-Id`, `Mcp-Protocol-Version` and `Last-Event-ID`, and exposing `Mcp-Session-Id`. A server that doesn't gets a lick saying to run slicc-node, slicc-swift or the extension. Stop or a steer that aborts the turn cancels a running call (`notifications/cancelled`). Lists and reads are retried once after 408, 429 or 5xx, and tool calls never are. An interrupted call isn't rerun after a worker restart. Server log messages go to `~/.pi/agent/mcp.log`, which rolls over to `mcp.log.1` at 5 MB.
320
+ **Network.** Requests go through the worker's transport fetch, as Bedrock's do. On the page's own fetch, a server must send CORS headers allowing `Authorization`, `Content-Type`, `Mcp-Session-Id`, `Mcp-Protocol-Version` and `Last-Event-ID`, and exposing `Mcp-Session-Id`. A server that doesn't gets a lick saying to link the `slicc` CLI or connect the extension (only when no exit was linked at attach). Stop or a steer that aborts the turn cancels a running call (`notifications/cancelled`). Lists and reads are retried once after 408, 429 or 5xx, and tool calls never are. An interrupted call isn't rerun after a worker restart. Server log messages go to `~/.pi/agent/mcp.log`, which rolls over to `mcp.log.1` at 5 MB.
310
321
 
311
322
  **Kernel servers.** A server a kernel program serves with `ctx.net.listen` on the kernel's loopback works as `"url": "http://<port>.kernel.localhost/mcp"`, as for [mcpcat](https://github.com/ai-ecoverse/mcpcat)'s `mcpcat serve --proxy <name> --http <port>`, which bridges a stdio server to HTTP. The worker's fetch hands every `<port>.kernel.localhost` request to the kernel client's `loopbackFetch` on that port, whatever the transport, so it needs no CORS headers, no service worker and no open `/os/` tab, SSE streams have no idle limit, and the request carries no `Origin`. The URL's own port is ignored, as slicc-bios ignores it. The route shares the browser guard's rule (`ownKernelServer`): any other `*.kernel.localhost` name, the CDP facade's `9222.kernel.localhost` included, fails before anything is sent, so no URL in a config can drive SLICC's own pages. `test/integration/mcp-loopback.test.mjs` runs a JS kernel program as the server and calls its tool on the page and relay routes, with a JSON-RPC answer streamed as SSE, and checks that an entry on 9222 is refused.
312
323
 
@@ -348,7 +359,7 @@ The `memory` section, read before every request, holds the first 200 lines (at m
348
359
  - The company slug is v6's `slugifyCompany` of the profile's `company`; company rows replace global ones of the same `name`.
349
360
  - Skills the agent has loaded (from any skills root) or that sit in `~/.pi/agent/skills`, and dismissed or taken ids (`catalog-<name>`, `catalog-use-<name>`) are left out. `--json` prints candidates the gelatiere can pass to `gelatiere suggest` as they are.
350
361
  - Each file is cached in `/home/.gelatiere/catalog/` and fetched again after 12 hours or with `--refresh`, through the agent's fetch (the kernel transport). A 404 is cached as empty. Offline or on an error the cached copy is used, and with none the pass goes on without a catalog.
351
- - www.sliccy.com must send `Access-Control-Allow-Origin` for `/skills/*.json` for the plain browser transport; the extension, slicc-node and the proxy routes don't need it. `runAgentWorker({ catalog })` changes the base URL.
362
+ - www.sliccy.com must send `Access-Control-Allow-Origin` for `/skills/*.json` for the plain browser transport; the extension and a linked `slicc` don't need it. `runAgentWorker({ catalog })` changes the base URL.
352
363
  - The gelatiere doesn't consolidate other agents' memory, which only cones and the user write.
353
364
 
354
365
  **The panel.** The transcript service adds `memories` and `memoryScopes`, and `AgentControl` `memorySave` and `memoryRemove`; `createAgentModel()` returns a `MemoryAdapter` for spectrum's `MemoryPort`, with `scopes()` (global, cones, roles) for its picker. `memory show [<scope>]` and `memory scopes` in bash print the same files, and the built-in `memory` skill teaches all of it.
@@ -414,8 +425,8 @@ pi has no git integration and no changes list; pi coding-agent leaves that to th
414
425
  - Untracked files show as added, and a rename shows as a deleted plus an added file.
415
426
  - `before` is the index version (`git show :<path>`) and `after` is the working file. Binary files and files over 1 MB get null for both, which spectrum shows as "no diff".
416
427
  - At most 200 files per repo are listed. There's no store of our own, and `agentId` is null, because git doesn't know which agent wrote a file.
417
- - **Without git,** `unavailable()` says git isn't installed and how to get it (`pnpm add -g @ai-ecoverse/wasm-git`). **With git but no repo,** it says to `git init` a folder under /home or clone one with slicc-node or the extension connected; cloning over HTTPS through the page alone fails. Seven doesn't preinstall git; the user or the agent installs it with that command.
418
- - **The `system` section** has a Git line. With `git` on PATH, it says local repositories work and that clone, fetch and push need slicc-node or the extension. Without it, it gives the agent `pnpm add -g @ai-ecoverse/wasm-git` to install it when needed. Seven doesn't preinstall git (Lars, 2026-10-09). The install line also names the other tested packages that aren't preinstalled: `@ai-ecoverse/wasi-esbuild` (esbuild), `@ai-ecoverse/wasi-typescript` (tsc), `@ai-ecoverse/wasi-biome` (biome), for PDFs `@ai-ecoverse/wasm-poppler` (pdftotext and friends) and `@ai-ecoverse/wasm-qpdf` (qpdf), and for Python `@ai-ecoverse/wasix-python` (python3, pip) with `@ai-ecoverse/wasix-uv-shim@0.1.0-1` (a uv-compatible shim, pinned while the package is new; only `uv run` uses the venv until kernel#168).
428
+ - **Without git,** `unavailable()` says git isn't installed and how to get it (`pnpm add -g @ai-ecoverse/wasm-git`). **With git but no repo,** it says to `git init` a folder under /home, and that cloning from the internet needs slicc or the SLICC extension linked; cloning over HTTPS through the page alone fails. Seven doesn't preinstall git; the user or the agent installs it with that command.
429
+ - **The `system` section** has a Git line. With `git` on PATH, it says local repositories work and that clone, fetch and push need slicc linked or the extension connected. Without it, it gives the agent `pnpm add -g @ai-ecoverse/wasm-git` to install it when needed. Seven doesn't preinstall git (Lars, 2026-10-09). The install line also names the other tested packages that aren't preinstalled: `@ai-ecoverse/wasi-esbuild` (esbuild), `@ai-ecoverse/wasi-typescript` (tsc), `@ai-ecoverse/wasi-biome` (biome), for PDFs `@ai-ecoverse/wasm-poppler` (pdftotext and friends) and `@ai-ecoverse/wasm-qpdf` (qpdf), and for Python `@ai-ecoverse/wasix-python` (python3, pip) with `@ai-ecoverse/wasix-uv-shim@0.1.0-1` (a uv-compatible shim, pinned while the package is new; only `uv run` uses the venv until kernel#168).
419
430
  - **Which repos:**
420
431
  - the repo of each live agent's cwd (`git rev-parse --show-toplevel`), with the active cone's first;
421
432
  - plus a scan of /home and each /mnt/<name>, three levels deep and at most 200 directories per level, skipping node_modules and dot-directories.
@@ -536,7 +547,7 @@ It prints the tab's stdout and stderr on their own streams and exits with its co
536
547
 
537
548
  `remoteControl(connection, { agent? })` from `@ai-ecoverse/slicc-agent/page` gives a linked CLI's `slicc prompt`, `slicc watch` and `slicc abort` (the `agent` stream of the slicc link protocol, §5.11) as plain objects the page writes to the link as NDJSON. `agent()` names the agent, by default the cone the worker treats as current.
538
549
 
539
- - **`prompt({ text, whenBusy = 'followUp', thinking = false })`** sends the prompt and resolves `{ delivered, events }`. `delivered` is `run`, `steer` or `follow-up`. A `reject` while busy throws an `Error` with `code: 'EBUSY'`, and any other refusal `EIO`. `events` starts at the prompt's own `user` event and ends with `{ t: 'end', reason, message? }` after the run that holds it. Leaving the loop closes the stream but never aborts the run.
550
+ - **`prompt({ text, whenBusy = 'followUp', thinking = false, attachments? })`** sends the prompt, with attachments as on `SendRequest` (see Attachments), and resolves `{ delivered, events }`. `delivered` is `run`, `steer` or `follow-up`. A `reject` while busy throws an `Error` with `code: 'EBUSY'`, and any other refusal `EIO`. `events` starts at the prompt's own `user` event and ends with `{ t: 'end', reason, message? }` after the run that holds it. Leaving the loop closes the stream but never aborts the run.
540
551
  - **`watch({ since, thinking })`** resolves `{ head, gap?, events }`. Without `since` it follows from the next event; with it, it first replays `since`'s own text or thinking whole, then everything after it, or answers `gap: true` when the transcript no longer holds it.
541
552
  - **`abort()`** resolves `{ aborted }`, `false` when nothing was running.
542
553
  - **Events** are `user`, `text`, `thinking` (only with `thinking`), `tool`, `tool-result`, `run`, `notice` and `error`. They are mapped in the worker from pi-durable's agent events (`watchEvents`: `run_start`/`run_end` with their inputs, message deltas, tool ends, retries, compactions) and polled by the page over the `slicc.agent.remote` service.
@@ -0,0 +1,168 @@
1
+ const MIB = 1024 * 1024;
2
+ const IMAGE_LIMIT = 5 * MIB;
3
+ const FILE_LIMIT = 8 * MIB;
4
+ const TOTAL_LIMIT = 10 * MIB;
5
+ const SEEN = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/gif", "image/webp"]);
6
+ const BLIND = "This model can't see images, so the image was saved as a file instead.";
7
+ const TOGETHER = `These attachments add up to more than ${TOTAL_LIMIT / MIB} MiB; send them in separate messages.`;
8
+ function mib(bytes) {
9
+ return `${Math.round(bytes / MIB)} MiB`;
10
+ }
11
+ function limitOf(kind) {
12
+ return kind === "image" ? IMAGE_LIMIT : FILE_LIMIT;
13
+ }
14
+ function tooLarge(name, kind) {
15
+ return `${name} is larger than ${mib(limitOf(kind))}, so it can't be sent.`;
16
+ }
17
+ function textBytes(text) {
18
+ return new TextEncoder().encode(text).length;
19
+ }
20
+ function decodedSize(data) {
21
+ return Math.floor(data.length * 3 / 4) - (data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0);
22
+ }
23
+ function fromBase64(data) {
24
+ return Uint8Array.from(atob(data), (char) => char.charCodeAt(0));
25
+ }
26
+ const ESCAPES = { "&": "&amp;", '"': "&quot;", "<": "&lt;", ">": "&gt;" };
27
+ function escape(value) {
28
+ return value.replace(/[&"<>]/g, (char) => ESCAPES[char]);
29
+ }
30
+ function unescape(value) {
31
+ return value.replace(
32
+ /&(amp|quot|lt|gt);/g,
33
+ (_, name) => name === "amp" ? "&" : name === "quot" ? '"' : name === "lt" ? "<" : ">"
34
+ );
35
+ }
36
+ function wrap(header, body) {
37
+ const path = header.path === void 0 ? "" : ` path="${escape(header.path)}"`;
38
+ const size = header.size === void 0 ? "" : ` size="${header.size}"`;
39
+ return `<attachment kind="${header.kind}" name="${escape(header.name)}" type="${escape(header.type)}"${path}${size}>
40
+ ${body}
41
+ </attachment>`;
42
+ }
43
+ const WRAPPED = /^<attachment kind="(image|text|file)" name="([^"]*)" type="([^"]*)"(?: path="([^"]*)")?(?: size="(\d+)")?>\n([\s\S]*)\n<\/attachment>$/;
44
+ function unwrap(text) {
45
+ const match = WRAPPED.exec(text);
46
+ if (!match) return void 0;
47
+ const [, kind, name, type, path, size, body] = match;
48
+ return {
49
+ kind,
50
+ name: unescape(name),
51
+ type: unescape(type),
52
+ ...path === void 0 ? {} : { path: unescape(path) },
53
+ ...size === void 0 ? {} : { size: Number(size) },
54
+ body
55
+ };
56
+ }
57
+ const REF = /^ref:(\d+):(\d+):(\d+)$/;
58
+ function picture(base, block, ref) {
59
+ const url = block.data ? { url: `data:${block.mimeType};base64,${block.data}` } : {};
60
+ if (!block.full) return { ...base, size: decodedSize(block.data ?? ""), ...url };
61
+ return {
62
+ ...base,
63
+ ...ref ? { id: ref } : {},
64
+ mimeType: block.full.mimeType,
65
+ size: block.full.size,
66
+ ...url
67
+ };
68
+ }
69
+ function shown(header, index, next, ref) {
70
+ const base = {
71
+ id: `a${index}`,
72
+ name: header.name,
73
+ kind: header.kind,
74
+ mimeType: header.type,
75
+ ...header.path === void 0 ? {} : { path: header.path }
76
+ };
77
+ if (header.kind === "text")
78
+ return { ...base, size: header.size ?? header.body.length, text: header.body };
79
+ if (next) return picture(base, next, ref);
80
+ return { ...base, size: header.size ?? 0 };
81
+ }
82
+ function splitUser(content, prefix) {
83
+ if (typeof content === "string") return { text: content, attachments: [] };
84
+ const blocks = Array.isArray(content) ? content : [];
85
+ const attachments = [];
86
+ let text = "";
87
+ for (let index = 0; index < blocks.length; index++) {
88
+ const block = blocks[index];
89
+ const header = block.type === "text" ? unwrap(block.text ?? "") : void 0;
90
+ const next = blocks[index + 1];
91
+ const ref = (at) => prefix ? `${prefix}:${at}` : void 0;
92
+ if (header) {
93
+ const image = header.kind === "image" && next?.type === "image" ? next : void 0;
94
+ attachments.push(shown(header, attachments.length, image, ref(index + 1)));
95
+ if (image) index++;
96
+ } else if (block.type === "text") text += block.text ?? "";
97
+ else if (block.type === "image" && (block.data || block.full))
98
+ attachments.push(
99
+ shown(
100
+ { kind: "image", name: "image", type: String(block.mimeType), body: "" },
101
+ attachments.length,
102
+ block,
103
+ ref(index)
104
+ )
105
+ );
106
+ }
107
+ return { text, attachments };
108
+ }
109
+ async function encoded(attachment, data, saver) {
110
+ const size = decodedSize(data);
111
+ if (size > limitOf(attachment.kind)) throw new Error(tooLarge(attachment.name, attachment.kind));
112
+ const header = { kind: attachment.kind, name: attachment.name, type: attachment.mimeType };
113
+ if (attachment.kind === "image" && saver.sees && SEEN.has(attachment.mimeType))
114
+ return [
115
+ { type: "text", text: wrap(header, "") },
116
+ { type: "image", data, mimeType: attachment.mimeType }
117
+ ];
118
+ if (!saver.save) throw new Error(`This agent can't save ${attachment.name} as a file.`);
119
+ const path = await saver.save(attachment.name, fromBase64(data));
120
+ const blind = attachment.kind === "image" && !saver.sees ? ` ${BLIND}` : "";
121
+ return [{ type: "text", text: wrap({ ...header, path, size }, `Saved at ${path}.${blind}`) }];
122
+ }
123
+ async function partsOf(attachment, saver) {
124
+ const header = {
125
+ kind: attachment.kind,
126
+ name: attachment.name,
127
+ type: attachment.mimeType,
128
+ ...attachment.path === void 0 ? {} : { path: attachment.path }
129
+ };
130
+ if (attachment.kind === "text" && attachment.text !== void 0) {
131
+ if (textBytes(attachment.text) > FILE_LIMIT)
132
+ throw new Error(tooLarge(attachment.name, attachment.kind));
133
+ return [{ type: "text", text: wrap(header, attachment.text) }];
134
+ }
135
+ if (attachment.data !== void 0) return encoded(attachment, attachment.data, saver);
136
+ if (attachment.path !== void 0)
137
+ return [{ type: "text", text: wrap(header, `It is at ${attachment.path}.`) }];
138
+ throw new Error(`${attachment.name} has nothing to send.`);
139
+ }
140
+ async function userParts(text, attachments, saver) {
141
+ const total = attachments.reduce(
142
+ (sum, item) => sum + textBytes(item.text ?? "") + decodedSize(item.data ?? ""),
143
+ 0
144
+ );
145
+ if (total > TOTAL_LIMIT) throw new Error(TOGETHER);
146
+ const parts = text ? [{ type: "text", text }] : [];
147
+ for (const attachment of attachments) parts.push(...await partsOf(attachment, saver));
148
+ return parts;
149
+ }
150
+ export {
151
+ BLIND,
152
+ FILE_LIMIT,
153
+ IMAGE_LIMIT,
154
+ REF,
155
+ SEEN,
156
+ TOGETHER,
157
+ TOTAL_LIMIT,
158
+ decodedSize,
159
+ fromBase64,
160
+ limitOf,
161
+ mib,
162
+ splitUser,
163
+ textBytes,
164
+ tooLarge,
165
+ userParts,
166
+ wrap
167
+ };
168
+ //# sourceMappingURL=attachments.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/attachments.ts"],
4
+ "sourcesContent": ["export type AttachmentKind = 'image' | 'text' | 'file';\n\nexport interface SendAttachment {\n name: string;\n mimeType: string;\n kind: AttachmentKind;\n data?: string;\n text?: string;\n path?: string;\n}\n\nexport interface ShownAttachment {\n id: string;\n name: string;\n kind: AttachmentKind;\n mimeType: string;\n size: number;\n url?: string;\n text?: string;\n path?: string;\n}\n\nexport type Part =\n | { type: 'text'; text: string }\n | { type: 'image'; data: string; mimeType: string };\n\nexport interface Saver {\n sees: boolean;\n save?(name: string, bytes: Uint8Array): Promise<string>;\n}\n\nconst MIB = 1024 * 1024;\n\nexport const IMAGE_LIMIT = 5 * MIB;\nexport const FILE_LIMIT = 8 * MIB;\nexport const TOTAL_LIMIT = 10 * MIB;\n\nexport const SEEN = new Set(['image/png', 'image/jpeg', 'image/gif', 'image/webp']);\n\nexport const BLIND = \"This model can't see images, so the image was saved as a file instead.\";\n\nexport const TOGETHER = `These attachments add up to more than ${TOTAL_LIMIT / MIB} MiB; send them in separate messages.`;\n\nexport function mib(bytes: number): string {\n return `${Math.round(bytes / MIB)} MiB`;\n}\n\nexport function limitOf(kind: AttachmentKind): number {\n return kind === 'image' ? IMAGE_LIMIT : FILE_LIMIT;\n}\n\nexport function tooLarge(name: string, kind: AttachmentKind): string {\n return `${name} is larger than ${mib(limitOf(kind))}, so it can't be sent.`;\n}\n\nexport function textBytes(text: string): number {\n return new TextEncoder().encode(text).length;\n}\n\nexport function decodedSize(data: string): number {\n return Math.floor((data.length * 3) / 4) - (data.endsWith('==') ? 2 : data.endsWith('=') ? 1 : 0);\n}\n\nexport function fromBase64(data: string): Uint8Array {\n return Uint8Array.from(atob(data), (char) => char.charCodeAt(0));\n}\n\nconst ESCAPES: Record<string, string> = { '&': '&amp;', '\"': '&quot;', '<': '&lt;', '>': '&gt;' };\n\nfunction escape(value: string): string {\n return value.replace(/[&\"<>]/g, (char) => ESCAPES[char] as string);\n}\n\nfunction unescape(value: string): string {\n return value.replace(/&(amp|quot|lt|gt);/g, (_, name: string) =>\n name === 'amp' ? '&' : name === 'quot' ? '\"' : name === 'lt' ? '<' : '>'\n );\n}\n\ninterface Header {\n kind: AttachmentKind;\n name: string;\n type: string;\n path?: string;\n size?: number;\n}\n\nexport function wrap(header: Header, body: string): string {\n const path = header.path === undefined ? '' : ` path=\"${escape(header.path)}\"`;\n const size = header.size === undefined ? '' : ` size=\"${header.size}\"`;\n return `<attachment kind=\"${header.kind}\" name=\"${escape(header.name)}\" type=\"${escape(header.type)}\"${path}${size}>\\n${body}\\n</attachment>`;\n}\n\nconst WRAPPED =\n /^<attachment kind=\"(image|text|file)\" name=\"([^\"]*)\" type=\"([^\"]*)\"(?: path=\"([^\"]*)\")?(?: size=\"(\\d+)\")?>\\n([\\s\\S]*)\\n<\\/attachment>$/;\n\nfunction unwrap(text: string): (Header & { body: string }) | undefined {\n const match = WRAPPED.exec(text);\n if (!match) return undefined;\n const [, kind, name, type, path, size, body] = match as unknown as string[];\n return {\n kind: kind as AttachmentKind,\n name: unescape(name as string),\n type: unescape(type as string),\n ...(path === undefined ? {} : { path: unescape(path) }),\n ...(size === undefined ? {} : { size: Number(size) }),\n body: body as string,\n };\n}\n\ntype Block = {\n type?: string;\n text?: string;\n data?: string;\n mimeType?: string;\n full?: { mimeType: string; size: number };\n};\n\nexport const REF = /^ref:(\\d+):(\\d+):(\\d+)$/;\n\nfunction picture(base: Omit<ShownAttachment, 'size'>, block: Block, ref?: string): ShownAttachment {\n const url = block.data ? { url: `data:${block.mimeType};base64,${block.data}` } : {};\n if (!block.full) return { ...base, size: decodedSize(block.data ?? ''), ...url };\n return {\n ...base,\n ...(ref ? { id: ref } : {}),\n mimeType: block.full.mimeType,\n size: block.full.size,\n ...url,\n };\n}\n\nfunction shown(\n header: Header & { body: string },\n index: number,\n next?: Block,\n ref?: string\n): ShownAttachment {\n const base = {\n id: `a${index}`,\n name: header.name,\n kind: header.kind,\n mimeType: header.type,\n ...(header.path === undefined ? {} : { path: header.path }),\n };\n if (header.kind === 'text')\n return { ...base, size: header.size ?? header.body.length, text: header.body };\n if (next) return picture(base, next, ref);\n return { ...base, size: header.size ?? 0 };\n}\n\nexport function splitUser(\n content: unknown,\n prefix?: string\n): { text: string; attachments: ShownAttachment[] } {\n if (typeof content === 'string') return { text: content, attachments: [] };\n const blocks = Array.isArray(content) ? (content as Block[]) : [];\n const attachments: ShownAttachment[] = [];\n let text = '';\n for (let index = 0; index < blocks.length; index++) {\n const block = blocks[index] as Block;\n const header = block.type === 'text' ? unwrap(block.text ?? '') : undefined;\n const next = blocks[index + 1];\n const ref = (at: number) => (prefix ? `${prefix}:${at}` : undefined);\n if (header) {\n const image = header.kind === 'image' && next?.type === 'image' ? next : undefined;\n attachments.push(shown(header, attachments.length, image, ref(index + 1)));\n if (image) index++;\n } else if (block.type === 'text') text += block.text ?? '';\n else if (block.type === 'image' && (block.data || block.full))\n attachments.push(\n shown(\n { kind: 'image', name: 'image', type: String(block.mimeType), body: '' },\n attachments.length,\n block,\n ref(index)\n )\n );\n }\n return { text, attachments };\n}\n\nasync function encoded(attachment: SendAttachment, data: string, saver: Saver): Promise<Part[]> {\n const size = decodedSize(data);\n if (size > limitOf(attachment.kind)) throw new Error(tooLarge(attachment.name, attachment.kind));\n const header = { kind: attachment.kind, name: attachment.name, type: attachment.mimeType };\n if (attachment.kind === 'image' && saver.sees && SEEN.has(attachment.mimeType))\n return [\n { type: 'text', text: wrap(header, '') },\n { type: 'image', data, mimeType: attachment.mimeType },\n ];\n if (!saver.save) throw new Error(`This agent can't save ${attachment.name} as a file.`);\n const path = await saver.save(attachment.name, fromBase64(data));\n const blind = attachment.kind === 'image' && !saver.sees ? ` ${BLIND}` : '';\n return [{ type: 'text', text: wrap({ ...header, path, size }, `Saved at ${path}.${blind}`) }];\n}\n\nasync function partsOf(attachment: SendAttachment, saver: Saver): Promise<Part[]> {\n const header = {\n kind: attachment.kind,\n name: attachment.name,\n type: attachment.mimeType,\n ...(attachment.path === undefined ? {} : { path: attachment.path }),\n };\n if (attachment.kind === 'text' && attachment.text !== undefined) {\n if (textBytes(attachment.text) > FILE_LIMIT)\n throw new Error(tooLarge(attachment.name, attachment.kind));\n return [{ type: 'text', text: wrap(header, attachment.text) }];\n }\n if (attachment.data !== undefined) return encoded(attachment, attachment.data, saver);\n if (attachment.path !== undefined)\n return [{ type: 'text', text: wrap(header, `It is at ${attachment.path}.`) }];\n throw new Error(`${attachment.name} has nothing to send.`);\n}\n\nexport async function userParts(\n text: string,\n attachments: readonly SendAttachment[],\n saver: Saver\n): Promise<Part[]> {\n const total = attachments.reduce(\n (sum, item) => sum + textBytes(item.text ?? '') + decodedSize(item.data ?? ''),\n 0\n );\n if (total > TOTAL_LIMIT) throw new Error(TOGETHER);\n const parts: Part[] = text ? [{ type: 'text', text }] : [];\n for (const attachment of attachments) parts.push(...(await partsOf(attachment, saver)));\n return parts;\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -2,8 +2,8 @@ const MAX_TEXT = 1024 * 1024;
2
2
  const MAX_FILES = 200;
3
3
  const SCAN_DEPTH = 3;
4
4
  const SCAN_ENTRIES = 200;
5
- const NO_GIT = "Changes needs git, and git isn't installed. Install it with `pnpm add -g @ai-ecoverse/wasm-git`, then run `git init` in a folder under /home, or clone with slicc-node or the extension connected.";
6
- const NO_REPO = "Changes lists the changes in git repositories, and there is none here yet. Run `git init` in a folder under /home, or clone one with slicc-node or the extension connected.";
5
+ const NO_GIT = "Changes needs git, and git isn't installed. Install it with `pnpm add -g @ai-ecoverse/wasm-git`, then run `git init` in a folder under /home. To clone from the internet, link slicc or the SLICC extension.";
6
+ const NO_REPO = "Changes lists the changes in git repositories, and there are none here yet. Run `git init` in a folder under /home. To clone from the internet, link slicc or the SLICC extension.";
7
7
  const UNMERGED = ["DD", "AU", "UD", "UA", "DU", "AA", "UU"];
8
8
  function parseStatus(out) {
9
9
  const parts = out.split("\0");
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/changes/git.ts"],
4
- "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport type { ExecutionEnv } from '@earendil-works/pi-durable/env';\n\nexport const MAX_TEXT = 1024 * 1024;\nexport const MAX_FILES = 200;\nexport const SCAN_DEPTH = 3;\nexport const SCAN_ENTRIES = 200;\nexport const NO_GIT =\n \"Changes needs git, and git isn't installed. Install it with `pnpm add -g @ai-ecoverse/wasm-git`, then run `git init` in a folder under /home, or clone with slicc-node or the extension connected.\";\nexport const NO_REPO =\n 'Changes lists the changes in git repositories, and there is none here yet. Run `git init` in a folder under /home, or clone one with slicc-node or the extension connected.';\n\nexport type ChangeStatus = 'added' | 'modified' | 'deleted';\nexport type ChangeKind = 'tracked' | 'untracked' | 'conflict';\n\nconst UNMERGED = ['DD', 'AU', 'UD', 'UA', 'DU', 'AA', 'UU'];\n\nexport interface FileChange {\n path: string;\n repo: string;\n status: ChangeStatus;\n kind: ChangeKind;\n before: string | null;\n after: string | null;\n}\n\nexport interface ChangesView {\n unavailable: string | null;\n changes: FileChange[];\n}\n\nexport interface Pending {\n path: string;\n status: ChangeStatus;\n kind: ChangeKind;\n}\n\nexport function parseStatus(out: string): Pending[] {\n const parts = out.split('\\0');\n const found: Pending[] = [];\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i] as string;\n if (part.length < 4) continue;\n const index = part[0] as string;\n const tree = part[1] as string;\n const path = part.slice(3);\n if (index === 'R' || index === 'C') i++;\n if (UNMERGED.includes(index + tree)) found.push({ path, status: 'modified', kind: 'conflict' });\n else if (index === '?' && tree === '?')\n found.push({ path, status: 'added', kind: 'untracked' });\n else if (tree === 'D') found.push({ path, status: 'deleted', kind: 'tracked' });\n else if (tree === 'A') found.push({ path, status: 'added', kind: 'tracked' });\n else if (tree !== ' ' && tree !== '!')\n found.push({ path, status: 'modified', kind: 'tracked' });\n }\n return found;\n}\n\nexport function textOf(bytes: Uint8Array): string | null {\n if (bytes.length > MAX_TEXT || bytes.includes(0)) return null;\n try {\n return new TextDecoder('utf-8', { fatal: true }).decode(bytes);\n } catch {\n return null;\n }\n}\n\nexport function shown(text: string): string | null {\n return text.length > MAX_TEXT || text.includes('\\0') || text.includes('\uFFFD') ? null : text;\n}\n\nexport function scanned(name: string): boolean {\n return name !== 'node_modules' && !name.startsWith('.');\n}\n\nexport function within(path: string, root: string): boolean {\n return root === '/' || path === root || path.startsWith(`${root}/`);\n}\n\nexport function relative(path: string, root: string): string {\n return root === '/' ? path.slice(1) : path.slice(root.length + 1);\n}\n\nexport function joined(root: string, rel: string): string {\n return root === '/' ? `/${rel}` : `${root}/${rel}`;\n}\n\nexport type Ran = { code: number; out: string; err: string };\n\nexport const quote = (word: string): string => `'${word.replaceAll(\"'\", \"'\\\\''\")}'`;\n\nexport async function run(\n env: Pick<ExecutionEnv, 'exec'>,\n command: string | readonly string[],\n cwd: string,\n context: Context\n): Promise<Ran> {\n let out = '';\n let err = '';\n const ran = await env.exec(\n typeof command === 'string' ? command : command.map(quote).join(' '),\n {\n cwd,\n onOutput: (text, _context, info) => {\n if (info.stream === 'stdout') out += text;\n else err += text;\n },\n },\n context\n );\n return ran.ok\n ? { code: ran.value.exitCode, out, err }\n : { code: -1, out, err: ran.error.message };\n}\n\nexport function failure(ran: Ran, what: string): Error {\n return new Error(ran.err.trim() || ran.out.trim() || `${what} failed`);\n}\n"],
4
+ "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport type { ExecutionEnv } from '@earendil-works/pi-durable/env';\n\nexport const MAX_TEXT = 1024 * 1024;\nexport const MAX_FILES = 200;\nexport const SCAN_DEPTH = 3;\nexport const SCAN_ENTRIES = 200;\nexport const NO_GIT =\n \"Changes needs git, and git isn't installed. Install it with `pnpm add -g @ai-ecoverse/wasm-git`, then run `git init` in a folder under /home. To clone from the internet, link slicc or the SLICC extension.\";\nexport const NO_REPO =\n 'Changes lists the changes in git repositories, and there are none here yet. Run `git init` in a folder under /home. To clone from the internet, link slicc or the SLICC extension.';\n\nexport type ChangeStatus = 'added' | 'modified' | 'deleted';\nexport type ChangeKind = 'tracked' | 'untracked' | 'conflict';\n\nconst UNMERGED = ['DD', 'AU', 'UD', 'UA', 'DU', 'AA', 'UU'];\n\nexport interface FileChange {\n path: string;\n repo: string;\n status: ChangeStatus;\n kind: ChangeKind;\n before: string | null;\n after: string | null;\n}\n\nexport interface ChangesView {\n unavailable: string | null;\n changes: FileChange[];\n}\n\nexport interface Pending {\n path: string;\n status: ChangeStatus;\n kind: ChangeKind;\n}\n\nexport function parseStatus(out: string): Pending[] {\n const parts = out.split('\\0');\n const found: Pending[] = [];\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i] as string;\n if (part.length < 4) continue;\n const index = part[0] as string;\n const tree = part[1] as string;\n const path = part.slice(3);\n if (index === 'R' || index === 'C') i++;\n if (UNMERGED.includes(index + tree)) found.push({ path, status: 'modified', kind: 'conflict' });\n else if (index === '?' && tree === '?')\n found.push({ path, status: 'added', kind: 'untracked' });\n else if (tree === 'D') found.push({ path, status: 'deleted', kind: 'tracked' });\n else if (tree === 'A') found.push({ path, status: 'added', kind: 'tracked' });\n else if (tree !== ' ' && tree !== '!')\n found.push({ path, status: 'modified', kind: 'tracked' });\n }\n return found;\n}\n\nexport function textOf(bytes: Uint8Array): string | null {\n if (bytes.length > MAX_TEXT || bytes.includes(0)) return null;\n try {\n return new TextDecoder('utf-8', { fatal: true }).decode(bytes);\n } catch {\n return null;\n }\n}\n\nexport function shown(text: string): string | null {\n return text.length > MAX_TEXT || text.includes('\\0') || text.includes('\uFFFD') ? null : text;\n}\n\nexport function scanned(name: string): boolean {\n return name !== 'node_modules' && !name.startsWith('.');\n}\n\nexport function within(path: string, root: string): boolean {\n return root === '/' || path === root || path.startsWith(`${root}/`);\n}\n\nexport function relative(path: string, root: string): string {\n return root === '/' ? path.slice(1) : path.slice(root.length + 1);\n}\n\nexport function joined(root: string, rel: string): string {\n return root === '/' ? `/${rel}` : `${root}/${rel}`;\n}\n\nexport type Ran = { code: number; out: string; err: string };\n\nexport const quote = (word: string): string => `'${word.replaceAll(\"'\", \"'\\\\''\")}'`;\n\nexport async function run(\n env: Pick<ExecutionEnv, 'exec'>,\n command: string | readonly string[],\n cwd: string,\n context: Context\n): Promise<Ran> {\n let out = '';\n let err = '';\n const ran = await env.exec(\n typeof command === 'string' ? command : command.map(quote).join(' '),\n {\n cwd,\n onOutput: (text, _context, info) => {\n if (info.stream === 'stdout') out += text;\n else err += text;\n },\n },\n context\n );\n return ran.ok\n ? { code: ran.value.exitCode, out, err }\n : { code: -1, out, err: ran.error.message };\n}\n\nexport function failure(ran: Ran, what: string): Error {\n return new Error(ran.err.trim() || ran.out.trim() || `${what} failed`);\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/control.js CHANGED
@@ -3,9 +3,11 @@ import {
3
3
  ConversationBusy
4
4
  } from "@earendil-works/pi-durable";
5
5
  import { answerText } from "./agent.js";
6
+ import { splitUser, userParts } from "./attachments.js";
6
7
  import { LICK_STATE_KIND } from "./licks/state.js";
7
8
  import { pinned, SECRETS_COMMAND, secretsEntry } from "./secrets/extension.js";
8
- import { scrubText } from "./secrets/scrub.js";
9
+ import { scrubItems, scrubText } from "./secrets/scrub.js";
10
+ import { saverFor } from "./uploads.js";
9
11
  function readable(out) {
10
12
  const text = out.replace(/^(?:sub)?agent: /, "").trim();
11
13
  const sentence = text.charAt(0).toUpperCase() + text.slice(1);
@@ -112,7 +114,7 @@ function lastTurn(view, messageId) {
112
114
  if (turn < 0) return null;
113
115
  const user = entries[turn];
114
116
  return {
115
- text: answerText(user.model?.[0]?.content),
117
+ ...splitUser(user.model?.[0]?.content),
116
118
  at: entries[turn - 1]?.id ?? null,
117
119
  files: writesIn(entries.slice(turn))
118
120
  };
@@ -167,9 +169,26 @@ async function rewindAgent(deps, agentId, messageId, context) {
167
169
  state.scoops[agentId].conversation = fork.id;
168
170
  }, context);
169
171
  else await cone.switchCone(fork, context);
170
- return { done: true, text: found.turn.text, reason: null };
172
+ const { attachments } = found.turn;
173
+ return {
174
+ done: true,
175
+ text: found.turn.text,
176
+ reason: null,
177
+ ...attachments.length ? { attachments } : {}
178
+ };
179
+ }
180
+ async function storedImage(harness, ref, context) {
181
+ const entry = await harness.commit((tx) => tx.entry(ref.entry), context);
182
+ const content = entry?.model?.[ref.message]?.content;
183
+ const part = Array.isArray(content) ? content[ref.part] : void 0;
184
+ return part?.type === "image" && part.data && part.mimeType ? { data: part.data, mimeType: part.mimeType } : { data: null, mimeType: null };
171
185
  }
172
- function createAgentControl(harness, target, deliveries, licks, scoops, skills, sprinkles, memory, freezer, changes, monitor, secrets) {
186
+ async function contentOf(text, request, conversation, uploads, context) {
187
+ if (!request.attachments?.length) return scrubText(text);
188
+ const saver = await saverFor(conversation, uploads, context);
189
+ return scrubItems(await userParts(text, request.attachments, saver));
190
+ }
191
+ function createAgentControl(harness, target, deliveries, licks, scoops, skills, sprinkles, memory, freezer, changes, monitor, secrets, uploads) {
173
192
  const cone = cones(target);
174
193
  const current = () => cone.cone();
175
194
  const agents = target.agents;
@@ -234,18 +253,18 @@ function createAgentControl(harness, target, deliveries, licks, scoops, skills,
234
253
  )
235
254
  );
236
255
  const expanded = skills ? await skills.expand(request.text, context) : request.text;
237
- const text = await scrubText(expanded);
238
- const response = await accepted(
239
- async () => (await conversationFor(request.agentId, context)).submit(
256
+ const response = await accepted(async () => {
257
+ const conversation = await conversationFor(request.agentId, context);
258
+ return conversation.submit(
240
259
  {
241
260
  type: "input",
242
- content: text,
261
+ content: await contentOf(expanded, request, conversation, uploads, context),
243
262
  whenBusy: request.whenBusy,
244
263
  ...request.requestId ? { requestId: request.requestId } : {}
245
264
  },
246
265
  context
247
- )
248
- );
266
+ );
267
+ });
249
268
  const id = response.submissionId === null ? void 0 : submissionId(response.submissionId);
250
269
  if (id !== void 0) deliveries?.expect(id, request.whenBusy);
251
270
  return response;
@@ -270,6 +289,7 @@ function createAgentControl(harness, target, deliveries, licks, scoops, skills,
270
289
  ...changesControl(changes),
271
290
  monitorOpen: async (lease, resync, context) => monitor ? monitor.open(lease, resync, context) : { lease: "", ttlMs: 0 },
272
291
  monitorClose: async (lease, context) => monitor?.close(lease, context),
292
+ image: (ref, context) => storedImage(harness, ref, context),
273
293
  async unqueue(agentId, id, context) {
274
294
  return withdraw(await conversationFor(agentId, context), id, context);
275
295
  },
@@ -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 type AgentChange,\n AssistantEntry,\n type Conversation,\n ConversationBusy,\n type ConversationView,\n type EntryId,\n type EntryRecord,\n type Harness,\n type SubmissionId,\n} from '@earendil-works/pi-durable';\nimport { answerText } from './agent.ts';\nimport type { Agents } from './agents.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 { 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 { pinned, SECRETS_COMMAND, secretsEntry } from './secrets/extension.ts';\nimport type { SecretName } from './secrets/names.ts';\nimport { scrubText } from './secrets/scrub.ts';\nimport type {\n AgentControl,\n Created,\n LickResolved,\n OperationError,\n Rewound,\n SendResponse,\n} from './services.ts';\nimport type { SkillsRuntime } from './skills/index.ts';\nimport type { SprinklesRuntime } from './sprinkles/index.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 freezerControl(\n freezer: Pick<FreezerRuntime, 'freeze' | 'newChat' | 'thaw' | 'discard'> | undefined\n): Pick<AgentControl, 'freeze' | 'newChat' | 'thaw' | 'discard'> {\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 };\n}\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 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 text: answerText(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 agent = await conversation.agent(context);\n const change: AgentChange = {\n model: agent.model ?? null,\n thinkingLevel: agent.thinkingLevel,\n extensions: agent.extensions,\n tools: agent.tools,\n instructions: agent.instructions ?? null,\n cwd: agent.cwd ?? null,\n };\n return harness.createConversation({ ownership, agent: pinned(change) }, 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 (state.scoops[agentId as string] as { conversation: number }).conversation = fork.id;\n }, context);\n else await cone.switchCone(fork, context);\n return { done: true, text: found.turn.text, reason: null };\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'>,\n sprinkles?: Pick<SprinklesRuntime, 'send' | 'call'>,\n memory?: Pick<MemoryRuntime, 'save' | 'remove'>,\n freezer?: Pick<FreezerRuntime, 'freeze' | 'newChat' | 'thaw' | 'discard'>,\n changes?: Pick<ChangesRuntime, 'open' | 'accept' | 'revert'>,\n monitor?: Pick<Monitor, 'open' | 'close'>,\n secrets?: () => readonly SecretName[]\n): AgentControl {\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 expanded = skills ? await skills.expand(request.text, context) : request.text;\n const text = await scrubText(expanded);\n const response = await accepted(async () =>\n (await conversationFor(request.agentId, context)).submit(\n {\n type: 'input',\n content: text,\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 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 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 };\n}\n"],
5
- "mappings": "AACA;AAAA,EAEE;AAAA,EAEA;AAAA,OAMK;AACP,SAAS,kBAAkB;AAO3B,SAAS,uBAAuB;AAIhC,SAAS,QAAQ,iBAAiB,oBAAoB;AAEtD,SAAS,iBAAiB;AAwBnB,SAAS,SAAS,KAAqB;AAC5C,QAAM,OAAO,IAAI,QAAQ,oBAAoB,EAAE,EAAE,KAAK;AACtD,QAAM,WAAW,KAAK,OAAO,CAAC,EAAE,YAAY,IAAI,KAAK,MAAM,CAAC;AAC5D,SAAO,SAAS,KAAK,QAAQ,IAAI,WAAW,GAAG,QAAQ;AACzD;AAEA,SAAS,QAAQ,KAA8C;AAC7D,SAAO,IAAI,EAAE;AAAA,IACX,CAAC,QAAQ,EAAE,IAAI,OAAO,KAAK;AAAA,IAC3B,CAAC,WAAkB,EAAE,IAAI,MAAM,OAAO,MAAM,QAAQ;AAAA,EACtD;AACF;AAEA,SAAS,eACP,SAC+D;AAC/D,QAAM,SAAS,MAAM;AACnB,QAAI,CAAC,QAAS,OAAM,IAAI,MAAM,4BAA4B;AAC1D,WAAO;AAAA,EACT;AACA,SAAO;AAAA,IACL,QAAQ,CAAC,SAAS,YAAY,QAAQ,YAAY,OAAO,EAAE,OAAO,SAAS,OAAO,CAAC;AAAA,IACnF,SAAS,CAAC,SAAS,YACjB,QAAQ,YAAa,MAAM,OAAO,EAAE,QAAQ,SAAS,OAAO,KAAM,EAAE;AAAA,IACtE,MAAM,CAAC,IAAI,YAAY,QAAQ,YAAY,OAAO,EAAE,KAAK,IAAI,OAAO,CAAC;AAAA,IACrE,SAAS,CAAC,IAAI,YACZ,QAAQ,YAAY;AAClB,YAAM,OAAO,EAAE,QAAQ,IAAI,OAAO;AAClC,aAAO;AAAA,IACT,CAAC;AAAA,EACL;AACF;AAEA,SAAS,eACP,SACqE;AACrE,QAAM,SAAS,MAAM;AACnB,QAAI,CAAC,QAAS,OAAM,IAAI,MAAM,+BAA+B;AAC7D,WAAO;AAAA,EACT;AACA,QAAM,OAAO,CAAC,MAAc,QAC1B,QAAQ,YAAY;AAClB,UAAM,IAAI,OAAO,CAAC;AAClB,WAAO;AAAA,EACT,CAAC;AACH,SAAO;AAAA,IACL,aAAa,OAAO,YAAY,SAAS,KAAK,OAAO;AAAA,IACrD,cAAc,CAAC,MAAM,YAAY,KAAK,MAAM,CAAC,YAAY,QAAQ,OAAO,MAAM,OAAO,CAAC;AAAA,IACtF,cAAc,CAAC,MAAM,YAAY,KAAK,MAAM,CAAC,YAAY,QAAQ,OAAO,MAAM,OAAO,CAAC;AAAA,EACxF;AACF;AAEA,SAAS,cACP,QACmD;AACnD,SAAO;AAAA,IACL,MAAM,WAAW,OAAO,SAAS;AAC/B,UAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,2BAA2B;AACxD,YAAM,EAAE,IAAI,GAAG,KAAK,IAAI;AACxB,aAAO,OAAO,KAAK,EAAE,GAAG,MAAM,GAAI,KAAK,EAAE,GAAG,IAAI,CAAC,EAAG,GAAG,OAAO;AAAA,IAChE;AAAA,IACA,MAAM,aAAa,IAAI,SAAS;AAC9B,aAAO,EAAE,SAAS,SAAS,MAAM,OAAO,OAAO,IAAI,OAAO,IAAI,MAAM;AAAA,IACtE;AAAA,EACF;AACF;AAEA,SAAS,gBACP,WACqD;AACrD,SAAO;AAAA,IACL,cAAc,OAAO,IAAI,SAAS,YAChC,YAAY,UAAU,KAAK,IAAI,SAAS,OAAO,IAAI,EAAE,WAAW,MAAM;AAAA,IACxE,MAAM,aAAa,IAAI,QAAQ,MAAM,SAAS;AAC5C,UAAI,CAAC,UAAW,OAAM,IAAI,MAAM,8BAA8B;AAC9D,aAAO,UAAU,KAAK,IAAI,QAAQ,MAAM,OAAO;AAAA,IACjD;AAAA,EACF;AACF;AAEO,SAAS,aAAa,OAAyC;AACpE,QAAM,KAAK,OAAO,KAAK;AACvB,SAAO,gBAAgB,KAAK,KAAK,KAAK,OAAO,cAAc,EAAE,IAAK,KAAsB;AAC1F;AAEA,SAAS,eAAe,OAAgC;AACtD,MAAI,iBAAiB,iBAAkB,QAAO,EAAE,MAAM,QAAQ,SAAS,MAAM,QAAQ;AACrF,SAAO,EAAE,MAAM,UAAU,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE;AAC3F;AAEA,eAAe,SAAS,WAA0E;AAChG,MAAI;AACF,UAAM,SAAS,MAAM,UAAU;AAC/B,UAAM,KAAK,OAAO,WAAW,WAAW,SAAS,OAAO;AACxD,WAAO,EAAE,UAAU,MAAM,cAAc,OAAO,EAAE,GAAG,OAAO,KAAK;AAAA,EACjE,SAAS,OAAO;AACd,WAAO,EAAE,UAAU,OAAO,cAAc,MAAM,OAAO,eAAe,KAAK,EAAE;AAAA,EAC7E;AACF;AAEA,SAAS,MAAM,QAAqC;AAClD,MAAI,UAAU,OAAQ,QAAO;AAC7B,SAAO;AAAA,IACL,MAAM,MAAM;AAAA,IACZ,YAAY,YAAY;AACtB,YAAM,IAAI,MAAM,yCAAyC;AAAA,IAC3D;AAAA,EACF;AACF;AAUA,MAAM,UAAU,CAAC,SAAS,QAAQ,QAAQ,UAAU;AAEpD,SAAS,SAAS,SAA0C;AAC1D,SAAO,QAAQ;AAAA,IAAK,CAAC,WAClB,MAAM,SAAS,CAAC,GAAG;AAAA,MAClB,CAAC,YACC,MAAM,QAAQ,QAAQ,OAAO,KAC5B,QAAQ,QAAoB;AAAA,QAC3B,CAAC,UAAU,MAAM,SAAS,cAAc,QAAQ,SAAS,OAAO,MAAM,IAAI,CAAC;AAAA,MAC7E;AAAA,IACJ;AAAA,EACF;AACF;AAEA,SAAS,SAAS,MAAwB,WAAuC;AAC/E,QAAM,EAAE,QAAQ,IAAI;AACpB,QAAM,SAAS,QAAQ,UAAU,CAAC,UAAU,IAAI,MAAM,EAAE,OAAO,SAAS;AACxE,MAAI,SAAS,EAAG,QAAO;AACvB,QAAM,OAAO,QAAQ,MAAM,GAAG,MAAM,EAAE,cAAc,CAAC,UAAU,MAAM,SAAS,SAAS;AACvF,MAAI,OAAO,EAAG,QAAO;AACrB,QAAM,OAAO,QAAQ,IAAI;AACzB,SAAO;AAAA,IACL,MAAM,WAAW,KAAK,QAAQ,CAAC,GAAG,OAAO;AAAA,IACzC,IAAI,QAAQ,OAAO,CAAC,GAAG,MAAM;AAAA,IAC7B,OAAO,SAAS,QAAQ,MAAM,IAAI,CAAC;AAAA,EACrC;AACF;AAEA,SAAS,KAAK,MAAiC;AAC7C,QAAM,OAAO,KAAK,KAAK,SAAS;AAChC,QAAM,QAAQ,KAAK,KAAK,UAAU;AAClC,SAAO,QAAQ,MAAM,GAAG,KAAK,QAAQ,OAAO,OAAO,MAAM;AAC3D;AASA,eAAe,OACb,SACA,cACA,WACA,SAC2D;AAC3D,QAAM,WAAW,MAAM,aAAa,UAAU,OAAO;AACrD,QAAM,OAAO,SAAS;AACtB,WAAS,QAAQ;AACjB,QAAM,WAAW,MAAM,QAAQ,QAAQ,OAAO,GAAG,YAAY;AAAA,IAC3D,CAAC,eAAe,WAAW,mBAAmB,aAAa;AAAA,EAC7D;AACA,MAAI,WAAW,KAAK,IAAI,EAAG,QAAO,EAAE,MAAM,OAAO,MAAM,MAAM,QAAQ,OAAO;AAC5E,QAAM,OAAO,SAAS,MAAM,SAAS;AACrC,SAAO,OAAO,EAAE,MAAM,KAAK,IAAI,EAAE,MAAM,OAAO,MAAM,MAAM,QAAQ,UAAU;AAC9E;AAEA,eAAe,OACb,SACA,cACA,OACA,SACuB;AACvB,QAAM,YAAY,EAAE,MAAM,YAAY;AACtC,MAAI,MAAM,KAAK,OAAO,KAAM,QAAO,aAAa,KAAK,MAAM,KAAK,IAAI,EAAE,UAAU,GAAG,OAAO;AAC1F,QAAM,QAAQ,MAAM,aAAa,MAAM,OAAO;AAC9C,QAAM,SAAsB;AAAA,IAC1B,OAAO,MAAM,SAAS;AAAA,IACtB,eAAe,MAAM;AAAA,IACrB,YAAY,MAAM;AAAA,IAClB,OAAO,MAAM;AAAA,IACb,cAAc,MAAM,gBAAgB;AAAA,IACpC,KAAK,MAAM,OAAO;AAAA,EACpB;AACA,SAAO,QAAQ,mBAAmB,EAAE,WAAW,OAAO,OAAO,MAAM,EAAE,GAAG,OAAO;AACjF;AAEA,eAAe,YACb,MACA,SACA,WACA,SACkB;AAClB,QAAM,EAAE,SAAS,QAAQ,MAAM,OAAO,IAAI;AAC1C,QAAM,QAAQ,WAAW,SAAS,OAAO,MAAM,EAAE,OAAO,OAAO,IAAI;AACnE,MAAI,WAAW,UAAU,CAAC,SAAS,YAAY,OAAO,WAAW;AAC/D,UAAM,OAAO,WAAW,SAAS,OAAO;AAC1C,QAAM,eACJ,SAAS,SACH,MAAM,OAAO,aAAa,SAAmB,OAAO,IACtD,KAAK,KAAK;AAChB,QAAM,QAAQ,MAAM,OAAO,SAAS,cAAc,WAAW,OAAO;AACpE,MAAI,YAAY,MAAO,QAAO;AAC9B,QAAM,OAAO,MAAM,OAAO,SAAS,cAAc,OAAO,OAAO;AAC/D,QAAM,UACJ,UAAU,UAAU,CAAC,QACjB,MAAM,OAAO,OAAO,QAAQ,OAAO,WAAW,GAAG,MAAM,OAAO,IAC9D,EAAE,SAAS,CAAC,GAAG,UAAU,CAAC,EAAE;AAClC,QAAM,QAAQ,MAAM,KAAK;AACzB,QAAM,KAAK;AAAA,IACT,EAAE,MAAM,SAAS,OAAO,EAAE,MAAM,iBAAiB,MAAM,EAAE,OAAO,GAAG,GAAG,SAAS,MAAM,EAAE,EAAE;AAAA,IACzF;AAAA,EACF;AACA,MAAI,SAAS;AACX,UAAM,OAAO,OAAO,CAAC,KAAK,UAAU;AAClC,MAAC,MAAM,OAAO,OAAiB,EAA+B,eAAe,KAAK;AAAA,IACpF,GAAG,OAAO;AAAA,MACP,OAAM,KAAK,WAAW,MAAM,OAAO;AACxC,SAAO,EAAE,MAAM,MAAM,MAAM,MAAM,KAAK,MAAM,QAAQ,KAAK;AAC3D;AAEO,SAAS,mBACd,SACA,QACA,YACA,OACA,QACA,QACA,WACA,QACA,SACA,SACA,SACA,SACc;AACd,QAAM,OAAO,MAAM,MAAM;AACzB,QAAM,UAAU,MAAM,KAAK,KAAK;AAChC,QAAM,SAAU,OAAsB;AACtC,QAAM,kBAAkB,OAAO,SAAoC,YAAqB;AACtF,QAAI,CAAC,WAAW,CAAC,OAAQ,QAAO,QAAQ;AACxC,UAAM,QAAQ,MAAM,OAAO,aAAa,SAAS,OAAO;AACxD,QAAI,CAAC,MAAO,OAAM,IAAI,MAAM,qBAAqB,OAAO,GAAG;AAC3D,WAAO;AAAA,EACT;AACA,QAAM,WAAW,OAAO,cAA4B,IAAY,YAAqB;AACnF,UAAM,SAAS,aAAa,EAAE;AAC9B,QAAI,WAAW,OAAW,QAAO,EAAE,SAAS,YAAqB;AACjE,UAAM,SAAS,MAAM,QAAQ,gBAAgB,QAAQ,SAAS,aAAa,EAAE;AAC7E,QAAI,WAAW,UAAW,QAAO,EAAE,SAAS,YAAqB;AACjE,WAAO;AAAA,MACL,SAAS,WAAW,cAAe,cAAyB;AAAA,IAC9D;AAAA,EACF;AACA,MAAI,OAAyB,QAAQ,QAAQ;AAC7C,QAAM,SAAS,CAAI,cAA4C;AAC7D,UAAM,MAAM,KAAK,KAAK,WAAW,SAAS;AAC1C,WAAO,IAAI,MAAM,MAAM,MAAS;AAChC,WAAO;AAAA,EACT;AACA,QAAM,WAAW,CAAC,SAAwB,WAA0B,YAClE,OAAO,MAAM,YAAY,EAAE,SAAS,QAAQ,MAAM,OAAO,GAAG,SAAS,WAAW,OAAO,CAAC;AAC1F,QAAM,SAAS,CACb,SACA,QACA,OACA,YAEA,OAAO,YAAY;AACjB,QAAI,CAAC,MAAO,QAAO,EAAE,MAAM,OAAO,MAAM,MAAM,OAAO,0BAA0B;AAC/E,UAAM,SAAS,UAAU,cAAc,YAAY;AACnD,QAAI;AACF,YAAM,eAAe,MAAM,gBAAgB,SAAS,OAAO;AAC3D,YAAM,OAAO,MAAM,MAAM,MAAM,OAAO,cAAc,QAAQ,QAAQ,QAAW,OAAO;AACtF,YAAM,aAAa;AAAA,QACjB;AAAA,UACE,MAAM;AAAA,UACN,OAAO;AAAA,YACL,MAAM;AAAA,YACN,MAAM,EAAE,MAAM,QAAQ,OAAO,IAAI,OAAO;AAAA,YACxC,OAAO;AAAA,cACL;AAAA,gBACE,MAAM;AAAA,gBACN,SAAS,YAAY,KAAK,SAAS,MAAM,KAAK,IAAI;AAAA,gBAClD,WAAW,KAAK,IAAI;AAAA,cACtB;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,QACA;AAAA,MACF;AACA,aAAO,EAAE,MAAM,MAAM,MAAM,OAAO,KAAK;AAAA,IACzC,SAAS,OAAO;AACd,aAAO,EAAE,MAAM,OAAO,MAAM,MAAM,OAAQ,MAAgB,QAAQ;AAAA,IACpE;AAAA,EACF,CAAC;AACH,SAAO;AAAA,IACL,KAAK,SAAS,SAAS;AACrB,aAAO,OAAO,YAAY;AACxB,YAAI,WAAW,QAAQ,KAAK,KAAK,MAAM;AACrC,iBAAO;AAAA,YAAS,aACb,MAAM,gBAAgB,QAAQ,SAAS,OAAO,GAAG;AAAA,cAChD,EAAE,MAAM,SAAS,OAAO,aAAa,QAAQ,CAAC,EAAE;AAAA,cAChD;AAAA,YACF;AAAA,UACF;AACF,cAAM,WAAW,SAAS,MAAM,OAAO,OAAO,QAAQ,MAAM,OAAO,IAAI,QAAQ;AAC/E,cAAM,OAAO,MAAM,UAAU,QAAQ;AACrC,cAAM,WAAW,MAAM;AAAA,UAAS,aAC7B,MAAM,gBAAgB,QAAQ,SAAS,OAAO,GAAG;AAAA,YAChD;AAAA,cACE,MAAM;AAAA,cACN,SAAS;AAAA,cACT,UAAU,QAAQ;AAAA,cAClB,GAAI,QAAQ,YAAY,EAAE,WAAW,QAAQ,UAAU,IAAI,CAAC;AAAA,YAC9D;AAAA,YACA;AAAA,UACF;AAAA,QACF;AACA,cAAM,KAAK,SAAS,iBAAiB,OAAO,SAAY,aAAa,SAAS,YAAY;AAC1F,YAAI,OAAO,OAAW,aAAY,OAAO,IAAI,QAAQ,QAAQ;AAC7D,eAAO;AAAA,MACT,CAAC;AAAA,IACH;AAAA,IACA,QAAQ,CAAC,WAAW,YAAY,SAAS,MAAM,WAAW,OAAO;AAAA,IACjE,aAAa,CAAC,SAAS,WAAW,YAAY,SAAS,SAAS,WAAW,OAAO;AAAA,IAElF,MAAM,KAAK,IAAI,SAAS;AACtB,YAAM,SAAS,aAAa,EAAE;AAC9B,YAAM,aACJ,WAAW,SAAY,SAAY,MAAM,QAAQ,WAAW,QAAQ,OAAO;AAC7E,UAAI,CAAC,WAAY,OAAM,IAAI,MAAM,sBAAsB,EAAE,EAAE;AAC3D,YAAM,UAAU,MAAM,WAAW,KAAK,OAAO;AAC7C,YAAM,EAAE,QAAQ,OAAO,IAAI;AAC3B,UAAI,WAAW,OAAW,QAAO,EAAE,QAAQ,cAAc,MAAM,MAAM,QAAQ,OAAO,MAAM,EAAE;AAC5F,YAAM,QAAQ,MAAM,QAAQ,OAAO,CAAC,OAAO,GAAG,MAAM,gBAAgB,MAAM,GAAG,OAAO;AACpF,aAAO,EAAE,QAAQ,QAAQ,MAAM,WAAW,OAAO,QAAQ,CAAC,GAAG,OAAO,GAAG,QAAQ,KAAK;AAAA,IACtF;AAAA,IACA,UAAU,CAAC,IAAI,YAAY,SAAS,QAAQ,GAAG,IAAI,OAAO;AAAA,IAC1D,GAAG,gBAAgB,SAAS;AAAA,IAC5B,GAAG,cAAc,MAAM;AAAA,IACvB,GAAG,eAAe,OAAO;AAAA,IACzB,GAAG,eAAe,OAAO;AAAA,IACzB,aAAa,OAAO,OAAO,QAAQ,YACjC,UAAU,QAAQ,KAAK,OAAO,QAAQ,OAAO,IAAI,EAAE,OAAO,IAAI,OAAO,EAAE;AAAA,IACzE,cAAc,OAAO,OAAO,YAAY,SAAS,MAAM,OAAO,OAAO;AAAA,IACrE,MAAM,QAAQ,SAAS,IAAI,SAAS;AAClC,aAAO,SAAS,MAAM,gBAAgB,SAAS,OAAO,GAAG,IAAI,OAAO;AAAA,IACtE;AAAA,IACA,OAAO,CAAC,YAAY,QAAQ,EAAE,MAAM,OAAO;AAAA,IAC3C,SAAS,CAAC,cAAc,YACtB,SAAS,MAAM,QAAQ,EAAE,QAAQ,gBAAgB,QAAW,OAAO,CAAC;AAAA,IACtE,cAAc,CAAC,SAAS,cAAc,YACpC;AAAA,MAAS,aACN,MAAM,gBAAgB,SAAS,OAAO,GAAG,QAAQ,gBAAgB,QAAW,OAAO;AAAA,IACtF;AAAA,IACF,OAAO,CAAC,SAAS,YAAY,QAAQ,EAAE,MAAM,WAAW,QAAW,OAAO;AAAA,IAC1E,aAAa,CAAC,QAAQ,OAAO,YAAY,OAAO,MAAM,QAAQ,OAAO,OAAO;AAAA,IAC5E,kBAAkB,CAAC,SAAS,QAAQ,OAAO,YAAY,OAAO,SAAS,QAAQ,OAAO,OAAO;AAAA,IAC7F,MAAM,QAAQ,MAAM,UAAU,SAAS;AACrC,YAAM,YAAY,QACd,MAAM,MAAM,QAAQ;AAAA,QAClB;AAAA,QACA;AAAA,UACE,GAAI,SAAS,KAAK,EAAE,IAAI,SAAS,GAAG,IAAI,CAAC;AAAA,UACzC,SAAS,SAAS;AAAA,UAClB,MAAM,SAAS;AAAA,QACjB;AAAA,QACA;AAAA,MACF,IACA;AACJ,aAAO,EAAE,UAAU;AAAA,IACrB;AAAA,IACA,MAAM,UAAU,SAAS,SAAS;AAChC,aAAO,MAAM,gBAAgB,SAAS,OAAO,GAAG,MAAM,OAAO;AAAA,IAC/D;AAAA,IACA,MAAM,WAAW,SAAS,SAAS;AACjC,YAAM,QAAQ,WAAW,SAAS,OAAO;AAAA,IAC3C;AAAA,IACA,WAAW,MAAM,SAAS;AACxB,aAAO,QAAQ,YAAY;AACzB,YAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,+BAA+B;AAC5D,eAAO,OAAO,WAAW,MAAM,OAAO;AAAA,MACxC,CAAC;AAAA,IACH;AAAA,IACA,YAAY,UAAU,MAAM,SAAS;AACnC,aAAO,QAAQ,YAAY;AACzB,YAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,2BAA2B;AACxD,cAAM,QAAQ,MAAM,OAAO,QAAQ,MAAM,OAAO;AAChD,cAAM,SAAS,MAAM,OAAO,OAAO;AAAA,UACjC,EAAE,MAAM,UAAU,MAAM,SAAS,CAAC,GAAG,WAAW,OAAO,QAAQ,MAAM,OAAO;AAAA,UAC5E;AAAA,QACF;AACA,YAAI,OAAO,SAAS,EAAG,OAAM,IAAI,MAAM,SAAS,OAAO,GAAG,CAAC;AAC3D,eAAO,SAAS,OAAO,IAAI,KAAK,CAAC;AAAA,MACnC,CAAC;AAAA,IACH;AAAA,IACA,KAAK,SAAS,SAAS;AACrB,aAAO,QAAQ,YAAY;AACzB,YAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,2BAA2B;AACxD,cAAM,SAAS,MAAM,OAAO,OAAO,KAAK,SAAS,OAAO,OAAO;AAC/D,YAAI,OAAO,SAAS,EAAG,OAAM,IAAI,MAAM,SAAS,OAAO,GAAG,CAAC;AAC3D,eAAO;AAAA,MACT,CAAC;AAAA,IACH;AAAA,IACA,WAAW,OAAO,QAAQ,aACvB,MAAM,gBAAgB,OAAO,SAAS,OAAO,GAAG;AAAA,MAC/C;AAAA,QACE,GAAI,OAAO,QAAQ,EAAE,OAAO,OAAO,MAAM,IAAI,CAAC;AAAA,QAC9C,GAAI,OAAO,gBAAgB,EAAE,eAAe,OAAO,cAAc,IAAI,CAAC;AAAA,MACxE;AAAA,MACA;AAAA,IACF;AAAA,EACJ;AACF;",
4
+ "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {\n type AgentChange,\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 } from './agents.ts';\nimport { 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 { 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 { pinned, SECRETS_COMMAND, secretsEntry } from './secrets/extension.ts';\nimport type { SecretName } from './secrets/names.ts';\nimport { scrubItems, scrubText } from './secrets/scrub.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 { 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 freezerControl(\n freezer: Pick<FreezerRuntime, 'freeze' | 'newChat' | 'thaw' | 'discard'> | undefined\n): Pick<AgentControl, 'freeze' | 'newChat' | 'thaw' | 'discard'> {\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 };\n}\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 agent = await conversation.agent(context);\n const change: AgentChange = {\n model: agent.model ?? null,\n thinkingLevel: agent.thinkingLevel,\n extensions: agent.extensions,\n tools: agent.tools,\n instructions: agent.instructions ?? null,\n cwd: agent.cwd ?? null,\n };\n return harness.createConversation({ ownership, agent: pinned(change) }, 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 (state.scoops[agentId as string] as { conversation: number }).conversation = fork.id;\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'>,\n sprinkles?: Pick<SprinklesRuntime, 'send' | 'call'>,\n memory?: Pick<MemoryRuntime, 'save' | 'remove'>,\n freezer?: Pick<FreezerRuntime, 'freeze' | 'newChat' | 'thaw' | 'discard'>,\n changes?: Pick<ChangesRuntime, 'open' | 'accept' | 'revert'>,\n monitor?: Pick<Monitor, 'open' | 'close'>,\n secrets?: () => readonly SecretName[],\n uploads?: Uploads\n): AgentControl {\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 expanded = skills ? await skills.expand(request.text, context) : request.text;\n const response = await accepted(async () => {\n const conversation = await conversationFor(request.agentId, context);\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 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 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 };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }