@ai-ecoverse/slicc-agent 3.41.0 → 3.42.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +27 -4
  2. package/bin/agent +9 -4
  3. package/dist/browser/guard.js +106 -0
  4. package/dist/browser/guard.js.map +7 -0
  5. package/dist/browser/origins.js +41 -0
  6. package/dist/browser/origins.js.map +7 -0
  7. package/dist/browser/shell.js +179 -0
  8. package/dist/browser/shell.js.map +7 -0
  9. package/dist/client.js +4 -1
  10. package/dist/client.js.map +2 -2
  11. package/dist/host.js +59 -21
  12. package/dist/host.js.map +3 -3
  13. package/dist/index.js +6 -0
  14. package/dist/index.js.map +2 -2
  15. package/dist/main.js +6 -0
  16. package/dist/main.js.map +2 -2
  17. package/dist/owner.js +2 -1
  18. package/dist/owner.js.map +2 -2
  19. package/dist/readonly.js +13 -0
  20. package/dist/readonly.js.map +2 -2
  21. package/dist/remote/host.js +10 -4
  22. package/dist/remote/host.js.map +2 -2
  23. package/dist/remote/pages.js +216 -0
  24. package/dist/remote/pages.js.map +7 -0
  25. package/dist/scoops/cli.js +17 -0
  26. package/dist/scoops/cli.js.map +2 -2
  27. package/dist/scoops/index.js +4 -2
  28. package/dist/scoops/index.js.map +2 -2
  29. package/dist/scoops/requests.js +14 -2
  30. package/dist/scoops/requests.js.map +2 -2
  31. package/dist/scoops/service.js.map +1 -1
  32. package/dist/services.js +2 -0
  33. package/dist/services.js.map +2 -2
  34. package/dist/spectrum/index.js +7 -0
  35. package/dist/spectrum/index.js.map +3 -3
  36. package/dist/spectrum/page-port.js +132 -0
  37. package/dist/spectrum/page-port.js.map +7 -0
  38. package/dist/sprinkles/index.js +1 -2
  39. package/dist/sprinkles/index.js.map +2 -2
  40. package/dist/types/browser/guard.d.ts +9 -0
  41. package/dist/types/browser/origins.d.ts +5 -0
  42. package/dist/types/browser/shell.d.ts +7 -0
  43. package/dist/types/client.d.ts +2 -0
  44. package/dist/types/host.d.ts +8 -3
  45. package/dist/types/index.d.ts +3 -2
  46. package/dist/types/owner.d.ts +1 -0
  47. package/dist/types/readonly.d.ts +2 -0
  48. package/dist/types/remote/host.d.ts +10 -0
  49. package/dist/types/remote/pages.d.ts +76 -0
  50. package/dist/types/scoops/cli.d.ts +7 -0
  51. package/dist/types/scoops/index.d.ts +1 -0
  52. package/dist/types/scoops/requests.d.ts +2 -1
  53. package/dist/types/scoops/service.d.ts +1 -0
  54. package/dist/types/services.d.ts +2 -0
  55. package/dist/types/spectrum/index.d.ts +4 -0
  56. package/dist/types/spectrum/page-port.d.ts +32 -0
  57. package/dist/types/sprinkles/index.d.ts +1 -1
  58. package/dist/worker.js +2 -1
  59. package/dist/worker.js.map +2 -2
  60. package/package.json +2 -2
  61. package/packages/vfs-root/skills/agent/SKILL.md +2 -0
  62. package/packages/vfs-root/skills/browser/SKILL.md +1 -0
  63. package/packages/vfs-root/skills/sprinkles/SKILL.md +1 -1
package/README.md CHANGED
@@ -31,7 +31,7 @@ await agent.prompt('What is in /home?');
31
31
  - `hostAgent(agent)` serves the agent with pi's protocol: a pi-server with one session, `agent`, whose Chord services are `slicc.agent.control` (send with `whenBusy` `steer`, `followUp` or `reject`, wait, withdraw, abort, compact) and `slicc.agent.transcript` (the conversation's durable view as replicated state, plus `deliveries`: how each user entry was delivered, `run`, `steer` or `follow-up`, derived by the host from where each send it accepted was placed: after a tool result it steered, queued first it's a follow-up or a late steer per its `whenBusy`, placed at once it started a run). `serveConnections(self, host)` hands it every `MessagePort` the page sends.
32
32
  - `@ai-ecoverse/slicc-agent/page` has what the page needs (`startAgent`, `connectAgent`) without the agent's runtime: no pi-durable, pi-ai, providers or SQLite, which load only in the worker. `@ai-ecoverse/slicc-agent/spectrum` is just as light, and a test keeps both that way.
33
33
  - `startAgent({ worker })` takes the Web Lock `slicc-agent`, so one tab at a time owns the agent worker, and waits while another tab holds it. `connect()` returns an `AgentConnection` (`control`, `transcript`, `prompt()`, and `webhook(name, { id?, headers?, body })`, which hands a webhook delivery, such as one from the tray hub, to the licks without a chord `Context`), `restart()` replaces the worker (durable resumes from SQLite), and `release()` stops it and lets the next tab take over. `port()` returns a raw `MessagePort` into the worker instead, for another tab: hand it over (through a SharedWorker, say), and that tab calls `connectAgent(port)`. A caller that already holds the lock passes `held: true`. A connection's `closed` resolves when its port ends (with the error, if any), and from then on its states keep their last values.
34
- - **Followers over a link** (PR 18): `port({ readOnly: true })` and `connect({ readOnly: true })` give a view-only connection.
34
+ - **Followers over a link** (PR 18): `port({ readOnly: true })` and `connect({ readOnly: true })` give a view-only connection, and `port({ pages: false })` a full one that never takes `panel` requests (see Panels).
35
35
  - **What a view-only connection can do:** it sees the transcript, the agents, settings state and remote-control `watch`, and may call `wait` and `changesOpen`.
36
36
  - **What it can't:** every other `AgentControl` method, settings action and remote-control `prompt` or `abort` is refused with `EACCES` ("this follower can watch, but not send, steer or abort"). Send and compact answer `accepted: false` with that code. The worker serves view-only ports from a second pi-server whose session gets these narrowed services.
37
37
  - **Carrying the connection over a byte stream:** `linkEndpoint({ write, close })` is a `PortEndpoint` that `connectAgent` accepts. Feed it the stream's bytes with `receive(bytes)` and its end with `end(error?)`. On the owner's side, `bridgePort(port, { write, close })` does the same for the `MessagePort` from `port()`.
@@ -128,7 +128,7 @@ const agent = await owner.connect();
128
128
  app.model = { ...createKernelModel({ kernel, root }), ...createAgentModel(agent, { storage: localStorage }) };
129
129
  ```
130
130
 
131
- `createAgentModel()` returns the `agent`, `settings`, `tray`, `sprinkles`, `memory`, `changes` and `monitor` ports. Settings keeps the UI preferences in `storage`; model and thinking belong to the conversation, and accounts and models come from the worker.
131
+ `createAgentModel()` returns the `agent`, `settings`, `tray`, `sprinkles`, `memory`, `changes` and `monitor` ports, and `page`, the tab's end of the `panel` bridge, when it's given `page: { tab, run }` (see Panels). Settings keeps the UI preferences in `storage`; model and thinking belong to the conversation, and accounts and models come from the worker.
132
132
 
133
133
  The adapter keeps the replicated transcript and maps it on every update. User, assistant and compaction entries become messages; tool results fill in the tool calls they answer; the generation in flight shows as a streaming message; retries and running compactions show as system messages. A failed generation ends in an error card whose action fits the error: credential errors (an invalid or missing key, 401/403, not authorized) open settings, a model the account can't use offers the model picker, and anything else offers Retry. A content-filter stop is different: every later request resends the content that tripped the filter, so Retry, and any new message, would be stopped again. Content-filter stops are Bedrock's `content_filtered` and `guardrail_intervened`, Anthropic's `sensitive` and refusals, and OpenAI's `content_filter`. Their card leads with "The model's content filter stopped this reply." and offers **Drop the last turn** (spectrum ≥ 1.12.0). `AgentControl.rewind(messageId)` finds the user turn before that message and forks the cone's conversation at the entry before it (`Conversation.fork`, so `pi.agent` comes back as it was there). It points the cone at the fork and returns the turn's prompt, which spectrum puts back into the composer. Forking the very first turn creates a fresh conversation with the same model and thinking level. The filtered branch stays in storage, off the active path. The fork starts with a `slicc.rewound` entry that shows as a "Rewound 1 turn" notice, and clicking the card of an earlier filtered turn rewinds that turn.
134
134
 
@@ -186,7 +186,7 @@ A **cone** is a top-level agent the user talks to; a **scoop** is a helper agent
186
186
 
187
187
  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
188
 
189
- **The `agent` command.** One bash script is installed under seven names, `agent`, `subagent`, `sprinkle`, `memory`, `gelatiere`, `freezer` and `models`, 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`, 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; 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.
189
+ **The `agent` command.** One bash script is installed under eight names, `agent`, `subagent`, `sprinkle`, `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.
190
190
  - `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
191
  - `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
192
  - 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".
@@ -363,6 +363,10 @@ pi core has no browser tooling, and Mario's pi-skills `browser-tools` launches i
363
363
  - **`curlwright`** (slicc-cdp ≥ 1.2.1) runs curl-style requests as a page-context `fetch()` in an open tab, with that tab's cookies and origin. The skill prefers it to UI clicks for reading data the app already fetches, and plain `curl` when no login is needed. Writes need the user's yes like clicks do, and response bodies are data.
364
364
  - **The `system` section** says the browser is connected when `SLICC_CDP_URL` is set and `playwright-cli` is on PATH (naming `curlwright` too when it's there), and lists it under "Not here yet" otherwise.
365
365
  - **Images:** durable's `read` doesn't return images, so a wrapper (`slicc-images`) answers `read` of a PNG, JPEG, GIF or WebP with image content. In a worker that can draw, images wider than 1600 px are scaled down, and an image over 1 MB of base64 is re-encoded as JPEG or refused with a hint. The adapter puts image content from a tool result on the tool card (`ToolCall.image`), so screenshots show in the chat.
366
+ - **SLICC's own pages are off limits** (PRE-1). The `slicc-browser-guard` extension ([`src/browser/guard.ts`](src/browser/guard.ts)) refuses a `bash` call, direct or from codemode, that runs `playwright-cli` or `curlwright` and names a URL on SLICC's origin: `sliccy.ai`, `www.sliccy.ai`, any single-label `<name>.sliccy.ai` over https on port 443, and the origin the worker runs on (`location.origin`). It also refuses `*.kernel.localhost` names other than the agent's own kernel servers, `<port>.kernel.localhost` for a port 1–65535 other than the CDP facade's 9222. The answer is "SLICC's own pages can't be opened or driven from the browser tools.", and the `browser` skill says so too.
367
+ - **How it reads a command:** a shell tokenizer ([`src/browser/shell.ts`](src/browser/shell.ts)) removes quotes and escapes (`'…'`, `"…"`, `$'…'`, `$"…"`, `\`), and reads into `$(…)`, backticks, `<(…)` and words that are themselves scripts, such as `bash -c` and `eval` arguments, four levels deep. If any word is `playwright-cli` or `curlwright` (by its last path segment), every word is checked: as a URL, as `https://` plus the word, after a `NAME=` or `--flag=` prefix, and for URLs and `//host` inside it. `new URL()` normalises the host (case, IDNA, full-width characters, percent escapes, userinfo); a trailing dot is dropped, `ws:` counts as `http:`, `wss:` as `https:`, and `blob:`, `view-source:` and `filesystem:` are checked by their inner URL. A `http://` URL on a sliccy.ai name is refused too, since it redirects to https.
368
+ - **The list** lives in [`src/browser/origins.ts`](src/browser/origins.ts) (`protectedOrigins`, `isProtected(url, extra)`) until `@ai-ecoverse/slicc-browsers` ships it, with the same patterns and shape. `*.kernel.localhost` isn't on the shared list; that rule is this guard's own.
369
+ - **What it can't catch:** it's defence in depth, and the browser tools themselves (slicc-cdp, the kernel's CDP facade and slicc-cli's `cdp` link) enforce the rule. It doesn't see a URL built at run time (from a variable set elsewhere, `printf`, brace expansion or a file), a browser command whose name is built at run time, a script or program file that drives the browser, a tab id (`--tab`, `tab-select`) that points at a SLICC tab, or a page that navigates there by itself. It may refuse a command that only names a SLICC URL alongside a browser command, such as `curl https://seven.sliccy.ai && playwright-cli open https://example.com`.
366
370
  - There are no browser licks yet.
367
371
 
368
372
  ### The freezer
@@ -494,7 +498,7 @@ A sprinkle is a small HTML panel next to the chat. pi has nothing for this; it's
494
498
  - `sprinkle show <name>`: posts a `slicc.sprinkle` entry in the caller's chat, which the adapter shows as an inline sprinkle;
495
499
  - `sprinkle own <name> [<agent>]`.
496
500
 
497
- v6's `open`, `reload`, `close`, `send` and `chat` answer with what to do instead.
501
+ `sprinkle open <name>` and `sprinkle close <name>` forward to `panel open|close sprinkle:<name>` (see Panels). v6's `reload`, `send` and `chat` answer with what to do instead.
498
502
 
499
503
  **Shipped sprinkles.** Only `welcome` and `suggestions`, with slicc-spectrum's reviewed copy.
500
504
  - **Welcome.** The worker posts it inline in the first cone's chat once per session. Its `onboarding-complete` send writes `/home/.welcomed`, which the card checks, and the profile to `/home/.welcome.json` for the gelatiere's catalog ranking, and the built-in `welcome` skill answers the lick.
@@ -502,6 +506,25 @@ v6's `open`, `reload`, `close`, `send` and `chat` answer with what to do instead
502
506
 
503
507
  The built-in `sprinkles` skill teaches the files, the bridge and the command.
504
508
 
509
+ ### Panels
510
+
511
+ The `panel` command lets an agent, or the user in a terminal, list, open, close, focus and move the panels of a SLICC tab. slicc-spectrum owns the command: `panelCommand(app, argv, caller)` parses argv, knows the panel ids and the guard rails, and answers `{ stdout, stderr, code }` with codes 0, 1, 2 and 5. The agent only carries the request to one tab and brings the answer back, so new verbs and flags need no agent release. pi has nothing for this; it's SLICC's UI.
512
+
513
+ **The command** is the same script as `agent`, installed as `panel`. It forwards argv untouched with the caller `{ agentId, cone, readOnly: false }`:
514
+ - `agentId` is the calling cone or scoop (`cone`, `cone-2`, `scoop:<id>`), or `null` from a terminal;
515
+ - `cone` is the caller's cone: a scoop's own cone, or the active cone for a terminal;
516
+ - a terminal with `SLICC_TAB` set (bios sets it in each tab's terminals) sends that tab's id, and the request goes to that tab, with its cone.
517
+
518
+ It prints the tab's stdout and stderr on their own streams and exits with its code. The script transport has an optional `.err` file next to `.out` for this. The agent owns two codes:
519
+ - **3:** no open tab shows the caller's cone ("panel: no open SLICC tab shows cone <cone>; …"). Nothing is queued;
520
+ - **4:** the tab didn't pick the request up within 5 s, didn't answer within 5 s more, or went away meanwhile. Late answers are dropped.
521
+
522
+ **The bridge** is the `slicc.agent.pages` service, a sibling of `slicc.agent.remote` with the same long-poll stream: `open(tab)`, `report(stream, tab)`, `next(stream, waitMs)`, `answer(stream, id, result)` and `close(stream)`. A tab reports `{ tab, kinds: ['panel'], cone, visible, focused, lastActive }`. Each request goes to exactly one tab and is never retried elsewhere: the `SLICC_TAB` tab if it's open, otherwise the tab on the caller's cone that is visible and focused, then visible, then the latest active. A tab is dropped when it closes its stream, when its attachment goes, or after 30 s without a poll, and each attachment reaches only its own streams.
523
+
524
+ **The page side.** `createAgentModel(connection, { page: { tab, run } })` starts a `PageAdapter`. It reports the tab's selected cone (`AgentAdapter.active()`, or a scoop's cone), `document.visibilityState`, `document.hasFocus()` and the time of the last pointer or key event, debounced by 250 ms. It answers each request with `run(argv, caller)`, which bios points at spectrum's `panelCommand`. Without `run` the tab never registers, so the bridge works with older spectrums too.
525
+
526
+ **Followers never execute.** Read-only attachments (`owner.port({ readOnly: true })`) and attachments made with `owner.port({ pages: false })`, which bios uses for link followers, get a `slicc.agent.pages` whose `open` answers `{ error }`, so they are never chosen.
527
+
505
528
  ### Remote control
506
529
 
507
530
  `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.
package/bin/agent CHANGED
@@ -1,15 +1,17 @@
1
1
  #!/bin/bash
2
2
  dir=/var/lib/slicc/agent/requests
3
3
  as=$(basename "$0")
4
- case "$as" in subagent|sprinkle|memory|gelatiere|freezer|models) ;; *) as=agent ;; esac
4
+ case "$as" in subagent|sprinkle|memory|gelatiere|freezer|models|panel) ;; *) as=agent ;; esac
5
5
  mkdir -p "$dir/in" "$dir/out" 2>/dev/null
6
+ who="${SLICC_AGENT:-}"
7
+ if [ -z "$who" ] && [ -n "${SLICC_TAB:-}" ]; then who="tab:$SLICC_TAB"; fi
6
8
 
7
9
  submit() {
8
10
  local id="$1"
9
11
  shift
10
12
  local tmp="$dir/in/.tmp-$id"
11
13
  {
12
- printf '%s\0' "slicc-agent/1" "$as" "${SLICC_AGENT:-}" "$PWD" "$$" "$#"
14
+ printf '%s\0' "slicc-agent/1" "$as" "$who" "$PWD" "$$" "$#"
13
15
  if [ $# -gt 0 ]; then printf '%s\0' "$@"; fi
14
16
  if [ "$stdin" = 1 ]; then cat; fi
15
17
  } > "$tmp" && mv "$tmp" "$dir/in/$id"
@@ -67,10 +69,13 @@ while [ ! -f "$dir/out/$id.code" ]; do
67
69
  fi
68
70
  done
69
71
  code=$(cat "$dir/out/$id.code")
70
- if [ "$code" = 0 ]; then
72
+ if [ -f "$dir/out/$id.err" ]; then
73
+ cat "$dir/out/$id.out"
74
+ cat "$dir/out/$id.err" >&2
75
+ elif [ "$code" = 0 ]; then
71
76
  cat "$dir/out/$id.out"
72
77
  else
73
78
  cat "$dir/out/$id.out" >&2
74
79
  fi
75
- rm -f "$dir/out/$id.out" "$dir/out/$id.code" "$dir/out/$id.ack"
80
+ rm -f "$dir/out/$id.out" "$dir/out/$id.err" "$dir/out/$id.code" "$dir/out/$id.ack"
76
81
  exit "$code"
@@ -0,0 +1,106 @@
1
+ import {
2
+ defineExtension,
3
+ hook,
4
+ ToolTask,
5
+ wrapTool
6
+ } from "@earendil-works/pi-durable";
7
+ import { isProtected } from "./origins.js";
8
+ import { words } from "./shell.js";
9
+ const BROWSER_GUARD = "slicc-browser-guard";
10
+ const REFUSAL = "SLICC's own pages can't be opened or driven from the browser tools.";
11
+ const FACADE_PORT = 9222;
12
+ const BROWSER = /* @__PURE__ */ new Set(["playwright-cli", "curlwright"]);
13
+ const WEB = /* @__PURE__ */ new Set(["http:", "https:", "ws:", "wss:"]);
14
+ const WRAPPED = /* @__PURE__ */ new Set(["blob:", "view-source:", "filesystem:"]);
15
+ const SECURE = {
16
+ "http:": "http:",
17
+ "https:": "https:",
18
+ "ws:": "http:",
19
+ "wss:": "https:"
20
+ };
21
+ const SCHEMED = /^[a-z][a-z0-9+.-]*:/i;
22
+ const EMBEDDED = /[a-z][a-z0-9+.-]*:\/\/[^\s'"`<>(){}\\]+/gi;
23
+ const RELATIVE = /(?<![:\w/])\/\/[^\s'"`<>(){}\\/]+/g;
24
+ const PREFIX = /^(?:[A-Za-z_][A-Za-z0-9_]*|-{1,2}[A-Za-z0-9-]+)=/;
25
+ const KERNEL = "kernel.localhost";
26
+ const OWN_KERNEL = /^([1-9][0-9]{0,4})\.kernel\.localhost$/;
27
+ function parse(url) {
28
+ try {
29
+ return new URL(url);
30
+ } catch {
31
+ return void 0;
32
+ }
33
+ }
34
+ function ownKernelServer(host) {
35
+ const port = OWN_KERNEL.exec(host)?.[1];
36
+ return port !== void 0 && Number(port) <= 65535 && Number(port) !== FACADE_PORT;
37
+ }
38
+ function forbidden(url, origins, depth = 0) {
39
+ const parsed = parse(url);
40
+ if (!parsed) return false;
41
+ if (WRAPPED.has(parsed.protocol)) {
42
+ const inner = parsed.href.slice(parsed.protocol.length);
43
+ return depth < 2 && [inner, `https://${inner}`].some((each) => forbidden(each, origins, depth + 1));
44
+ }
45
+ if (!WEB.has(parsed.protocol) || !parsed.hostname) return false;
46
+ const host = parsed.hostname.replace(/\.+$/, "");
47
+ if (host === KERNEL || host.endsWith(`.${KERNEL}`)) return !ownKernelServer(host);
48
+ const port = parsed.port ? `:${parsed.port}` : "";
49
+ return isProtected(`${SECURE[parsed.protocol]}//${host}${port}`, origins) || isProtected(`https://${host}${port}`);
50
+ }
51
+ function candidates(word) {
52
+ const value = word.replace(PREFIX, "");
53
+ const found = [
54
+ word,
55
+ value,
56
+ ...value.match(EMBEDDED) ?? [],
57
+ ...(value.match(RELATIVE) ?? []).map((relative) => `https:${relative}`)
58
+ ];
59
+ if (!/^[/.~-]/.test(value) && !/^[a-z][a-z0-9+.-]*:\/\//i.test(value))
60
+ found.push(`https://${value}`);
61
+ if (value.startsWith("//")) found.push(`https:${value}`);
62
+ return found.filter((each) => SCHEMED.test(each));
63
+ }
64
+ function browserCommand(word) {
65
+ return BROWSER.has(word.slice(word.lastIndexOf("/") + 1));
66
+ }
67
+ function refused(command, origins) {
68
+ const all = words(command);
69
+ if (!all.some(browserCommand)) return false;
70
+ return all.some((word) => candidates(word).some((url) => forbidden(url, origins)));
71
+ }
72
+ function commandOf(args) {
73
+ const command = args?.command;
74
+ return typeof command === "string" ? command : "";
75
+ }
76
+ function browserGuard(origins, tools) {
77
+ const bash = tools.find((tool) => tool.name === "bash");
78
+ return defineExtension({
79
+ name: BROWSER_GUARD,
80
+ hooks: [
81
+ hook(ToolTask, {
82
+ beforeTool: (call) => call.name === bash.name && refused(commandOf(call.arguments), origins) ? { block: REFUSAL } : void 0
83
+ })
84
+ ],
85
+ wraps: [
86
+ wrapTool(bash, (inner) => ({
87
+ ...inner,
88
+ execute(args, api, context) {
89
+ if (!refused(commandOf(args), origins)) return inner.execute(args, api, context);
90
+ return Promise.resolve({ content: [{ type: "text", text: REFUSAL }], isError: true });
91
+ }
92
+ }))
93
+ ]
94
+ });
95
+ }
96
+ export {
97
+ BROWSER_GUARD,
98
+ FACADE_PORT,
99
+ REFUSAL,
100
+ browserGuard,
101
+ candidates,
102
+ forbidden,
103
+ ownKernelServer,
104
+ refused
105
+ };
106
+ //# sourceMappingURL=guard.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/browser/guard.ts"],
4
+ "sourcesContent": ["import {\n defineExtension,\n type Extension,\n hook,\n type ToolRegistration,\n ToolTask,\n wrapTool,\n} from '@earendil-works/pi-durable';\nimport { isProtected } from './origins.ts';\nimport { words } from './shell.ts';\n\nexport const BROWSER_GUARD = 'slicc-browser-guard';\nexport const REFUSAL = \"SLICC's own pages can't be opened or driven from the browser tools.\";\nexport const FACADE_PORT = 9222;\n\nconst BROWSER = new Set(['playwright-cli', 'curlwright']);\nconst WEB = new Set(['http:', 'https:', 'ws:', 'wss:']);\nconst WRAPPED = new Set(['blob:', 'view-source:', 'filesystem:']);\nconst SECURE: Record<string, string> = {\n 'http:': 'http:',\n 'https:': 'https:',\n 'ws:': 'http:',\n 'wss:': 'https:',\n};\nconst SCHEMED = /^[a-z][a-z0-9+.-]*:/i;\nconst EMBEDDED = /[a-z][a-z0-9+.-]*:\\/\\/[^\\s'\"`<>(){}\\\\]+/gi;\nconst RELATIVE = /(?<![:\\w/])\\/\\/[^\\s'\"`<>(){}\\\\/]+/g;\nconst PREFIX = /^(?:[A-Za-z_][A-Za-z0-9_]*|-{1,2}[A-Za-z0-9-]+)=/;\nconst KERNEL = 'kernel.localhost';\nconst OWN_KERNEL = /^([1-9][0-9]{0,4})\\.kernel\\.localhost$/;\n\nfunction parse(url: string): URL | undefined {\n try {\n return new URL(url);\n } catch {\n return undefined;\n }\n}\n\nexport function ownKernelServer(host: string): boolean {\n const port = OWN_KERNEL.exec(host)?.[1];\n return port !== undefined && Number(port) <= 65535 && Number(port) !== FACADE_PORT;\n}\n\nexport function forbidden(url: string, origins: readonly string[], depth = 0): boolean {\n const parsed = parse(url);\n if (!parsed) return false;\n if (WRAPPED.has(parsed.protocol)) {\n const inner = parsed.href.slice(parsed.protocol.length);\n return (\n depth < 2 && [inner, `https://${inner}`].some((each) => forbidden(each, origins, depth + 1))\n );\n }\n if (!WEB.has(parsed.protocol) || !parsed.hostname) return false;\n const host = parsed.hostname.replace(/\\.+$/, '');\n if (host === KERNEL || host.endsWith(`.${KERNEL}`)) return !ownKernelServer(host);\n const port = parsed.port ? `:${parsed.port}` : '';\n return (\n isProtected(`${SECURE[parsed.protocol]}//${host}${port}`, origins) ||\n isProtected(`https://${host}${port}`)\n );\n}\n\nexport function candidates(word: string): string[] {\n const value = word.replace(PREFIX, '');\n const found = [\n word,\n value,\n ...(value.match(EMBEDDED) ?? []),\n ...(value.match(RELATIVE) ?? []).map((relative) => `https:${relative}`),\n ];\n if (!/^[/.~-]/.test(value) && !/^[a-z][a-z0-9+.-]*:\\/\\//i.test(value))\n found.push(`https://${value}`);\n if (value.startsWith('//')) found.push(`https:${value}`);\n return found.filter((each) => SCHEMED.test(each));\n}\n\nfunction browserCommand(word: string): boolean {\n return BROWSER.has(word.slice(word.lastIndexOf('/') + 1));\n}\n\nexport function refused(command: string, origins: readonly string[]): boolean {\n const all = words(command);\n if (!all.some(browserCommand)) return false;\n return all.some((word) => candidates(word).some((url) => forbidden(url, origins)));\n}\n\nfunction commandOf(args: unknown): string {\n const command = (args as { command?: unknown } | undefined)?.command;\n return typeof command === 'string' ? command : '';\n}\n\nexport function browserGuard(\n origins: readonly string[],\n tools: readonly ToolRegistration[]\n): Extension {\n const bash = tools.find((tool) => tool.name === 'bash') as ToolRegistration;\n return defineExtension({\n name: BROWSER_GUARD,\n hooks: [\n hook(ToolTask, {\n beforeTool: (call) =>\n call.name === bash.name && refused(commandOf(call.arguments), origins)\n ? { block: REFUSAL }\n : undefined,\n }),\n ],\n wraps: [\n wrapTool(bash, (inner) => ({\n ...inner,\n execute(args, api, context) {\n if (!refused(commandOf(args), origins)) return inner.execute(args, api, context);\n return Promise.resolve({ content: [{ type: 'text', text: REFUSAL }], isError: true });\n },\n })),\n ],\n });\n}\n"],
5
+ "mappings": "AAAA;AAAA,EACE;AAAA,EAEA;AAAA,EAEA;AAAA,EACA;AAAA,OACK;AACP,SAAS,mBAAmB;AAC5B,SAAS,aAAa;AAEf,MAAM,gBAAgB;AACtB,MAAM,UAAU;AAChB,MAAM,cAAc;AAE3B,MAAM,UAAU,oBAAI,IAAI,CAAC,kBAAkB,YAAY,CAAC;AACxD,MAAM,MAAM,oBAAI,IAAI,CAAC,SAAS,UAAU,OAAO,MAAM,CAAC;AACtD,MAAM,UAAU,oBAAI,IAAI,CAAC,SAAS,gBAAgB,aAAa,CAAC;AAChE,MAAM,SAAiC;AAAA,EACrC,SAAS;AAAA,EACT,UAAU;AAAA,EACV,OAAO;AAAA,EACP,QAAQ;AACV;AACA,MAAM,UAAU;AAChB,MAAM,WAAW;AACjB,MAAM,WAAW;AACjB,MAAM,SAAS;AACf,MAAM,SAAS;AACf,MAAM,aAAa;AAEnB,SAAS,MAAM,KAA8B;AAC3C,MAAI;AACF,WAAO,IAAI,IAAI,GAAG;AAAA,EACpB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,gBAAgB,MAAuB;AACrD,QAAM,OAAO,WAAW,KAAK,IAAI,IAAI,CAAC;AACtC,SAAO,SAAS,UAAa,OAAO,IAAI,KAAK,SAAS,OAAO,IAAI,MAAM;AACzE;AAEO,SAAS,UAAU,KAAa,SAA4B,QAAQ,GAAY;AACrF,QAAM,SAAS,MAAM,GAAG;AACxB,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI,QAAQ,IAAI,OAAO,QAAQ,GAAG;AAChC,UAAM,QAAQ,OAAO,KAAK,MAAM,OAAO,SAAS,MAAM;AACtD,WACE,QAAQ,KAAK,CAAC,OAAO,WAAW,KAAK,EAAE,EAAE,KAAK,CAAC,SAAS,UAAU,MAAM,SAAS,QAAQ,CAAC,CAAC;AAAA,EAE/F;AACA,MAAI,CAAC,IAAI,IAAI,OAAO,QAAQ,KAAK,CAAC,OAAO,SAAU,QAAO;AAC1D,QAAM,OAAO,OAAO,SAAS,QAAQ,QAAQ,EAAE;AAC/C,MAAI,SAAS,UAAU,KAAK,SAAS,IAAI,MAAM,EAAE,EAAG,QAAO,CAAC,gBAAgB,IAAI;AAChF,QAAM,OAAO,OAAO,OAAO,IAAI,OAAO,IAAI,KAAK;AAC/C,SACE,YAAY,GAAG,OAAO,OAAO,QAAQ,CAAC,KAAK,IAAI,GAAG,IAAI,IAAI,OAAO,KACjE,YAAY,WAAW,IAAI,GAAG,IAAI,EAAE;AAExC;AAEO,SAAS,WAAW,MAAwB;AACjD,QAAM,QAAQ,KAAK,QAAQ,QAAQ,EAAE;AACrC,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA;AAAA,IACA,GAAI,MAAM,MAAM,QAAQ,KAAK,CAAC;AAAA,IAC9B,IAAI,MAAM,MAAM,QAAQ,KAAK,CAAC,GAAG,IAAI,CAAC,aAAa,SAAS,QAAQ,EAAE;AAAA,EACxE;AACA,MAAI,CAAC,UAAU,KAAK,KAAK,KAAK,CAAC,2BAA2B,KAAK,KAAK;AAClE,UAAM,KAAK,WAAW,KAAK,EAAE;AAC/B,MAAI,MAAM,WAAW,IAAI,EAAG,OAAM,KAAK,SAAS,KAAK,EAAE;AACvD,SAAO,MAAM,OAAO,CAAC,SAAS,QAAQ,KAAK,IAAI,CAAC;AAClD;AAEA,SAAS,eAAe,MAAuB;AAC7C,SAAO,QAAQ,IAAI,KAAK,MAAM,KAAK,YAAY,GAAG,IAAI,CAAC,CAAC;AAC1D;AAEO,SAAS,QAAQ,SAAiB,SAAqC;AAC5E,QAAM,MAAM,MAAM,OAAO;AACzB,MAAI,CAAC,IAAI,KAAK,cAAc,EAAG,QAAO;AACtC,SAAO,IAAI,KAAK,CAAC,SAAS,WAAW,IAAI,EAAE,KAAK,CAAC,QAAQ,UAAU,KAAK,OAAO,CAAC,CAAC;AACnF;AAEA,SAAS,UAAU,MAAuB;AACxC,QAAM,UAAW,MAA4C;AAC7D,SAAO,OAAO,YAAY,WAAW,UAAU;AACjD;AAEO,SAAS,aACd,SACA,OACW;AACX,QAAM,OAAO,MAAM,KAAK,CAAC,SAAS,KAAK,SAAS,MAAM;AACtD,SAAO,gBAAgB;AAAA,IACrB,MAAM;AAAA,IACN,OAAO;AAAA,MACL,KAAK,UAAU;AAAA,QACb,YAAY,CAAC,SACX,KAAK,SAAS,KAAK,QAAQ,QAAQ,UAAU,KAAK,SAAS,GAAG,OAAO,IACjE,EAAE,OAAO,QAAQ,IACjB;AAAA,MACR,CAAC;AAAA,IACH;AAAA,IACA,OAAO;AAAA,MACL,SAAS,MAAM,CAAC,WAAW;AAAA,QACzB,GAAG;AAAA,QACH,QAAQ,MAAM,KAAK,SAAS;AAC1B,cAAI,CAAC,QAAQ,UAAU,IAAI,GAAG,OAAO,EAAG,QAAO,MAAM,QAAQ,MAAM,KAAK,OAAO;AAC/E,iBAAO,QAAQ,QAAQ,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,QAAQ,CAAC,GAAG,SAAS,KAAK,CAAC;AAAA,QACtF;AAAA,MACF,EAAE;AAAA,IACJ;AAAA,EACF,CAAC;AACH;",
6
+ "names": []
7
+ }
@@ -0,0 +1,41 @@
1
+ const protectedOrigins = {
2
+ patterns: ["https://sliccy.ai", "https://www.sliccy.ai", "https://*.sliccy.ai"]
3
+ };
4
+ const LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
5
+ function parse(url) {
6
+ try {
7
+ return new URL(url);
8
+ } catch {
9
+ return void 0;
10
+ }
11
+ }
12
+ function hostOf(url) {
13
+ return url.hostname.replace(/\.+$/, "");
14
+ }
15
+ function originOf(url) {
16
+ const parsed = parse(url);
17
+ if (!parsed || !["http:", "https:"].includes(parsed.protocol) || !parsed.hostname)
18
+ return void 0;
19
+ return `${parsed.protocol}//${hostOf(parsed)}${parsed.port ? `:${parsed.port}` : ""}`;
20
+ }
21
+ function matches(pattern, url) {
22
+ const host = hostOf(url);
23
+ const wanted = pattern.slice("https://".length);
24
+ if (!wanted.startsWith("*.")) return host === wanted;
25
+ const suffix = wanted.slice(1);
26
+ return host.endsWith(suffix) && LABEL.test(host.slice(0, -suffix.length));
27
+ }
28
+ function isProtected(url, extra = []) {
29
+ const origin = originOf(url);
30
+ if (!origin) return false;
31
+ if (extra.some((other) => originOf(other) === origin)) return true;
32
+ const parsed = parse(url);
33
+ if (parsed.protocol !== "https:" || parsed.port) return false;
34
+ return protectedOrigins.patterns.some((pattern) => matches(pattern, parsed));
35
+ }
36
+ export {
37
+ isProtected,
38
+ originOf,
39
+ protectedOrigins
40
+ };
41
+ //# sourceMappingURL=origins.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/browser/origins.ts"],
4
+ "sourcesContent": ["export const protectedOrigins = {\n patterns: ['https://sliccy.ai', 'https://www.sliccy.ai', 'https://*.sliccy.ai'],\n} as const;\n\nconst LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;\n\nfunction parse(url: string): URL | undefined {\n try {\n return new URL(url);\n } catch {\n return undefined;\n }\n}\n\nfunction hostOf(url: URL): string {\n return url.hostname.replace(/\\.+$/, '');\n}\n\nexport function originOf(url: string): string | undefined {\n const parsed = parse(url);\n if (!parsed || !['http:', 'https:'].includes(parsed.protocol) || !parsed.hostname)\n return undefined;\n return `${parsed.protocol}//${hostOf(parsed)}${parsed.port ? `:${parsed.port}` : ''}`;\n}\n\nfunction matches(pattern: string, url: URL): boolean {\n const host = hostOf(url);\n const wanted = pattern.slice('https://'.length);\n if (!wanted.startsWith('*.')) return host === wanted;\n const suffix = wanted.slice(1);\n return host.endsWith(suffix) && LABEL.test(host.slice(0, -suffix.length));\n}\n\nexport function isProtected(url: string, extra: readonly string[] = []): boolean {\n const origin = originOf(url);\n if (!origin) return false;\n if (extra.some((other) => originOf(other) === origin)) return true;\n const parsed = parse(url) as URL;\n if (parsed.protocol !== 'https:' || parsed.port) return false;\n return protectedOrigins.patterns.some((pattern) => matches(pattern, parsed));\n}\n"],
5
+ "mappings": "AAAO,MAAM,mBAAmB;AAAA,EAC9B,UAAU,CAAC,qBAAqB,yBAAyB,qBAAqB;AAChF;AAEA,MAAM,QAAQ;AAEd,SAAS,MAAM,KAA8B;AAC3C,MAAI;AACF,WAAO,IAAI,IAAI,GAAG;AAAA,EACpB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,OAAO,KAAkB;AAChC,SAAO,IAAI,SAAS,QAAQ,QAAQ,EAAE;AACxC;AAEO,SAAS,SAAS,KAAiC;AACxD,QAAM,SAAS,MAAM,GAAG;AACxB,MAAI,CAAC,UAAU,CAAC,CAAC,SAAS,QAAQ,EAAE,SAAS,OAAO,QAAQ,KAAK,CAAC,OAAO;AACvE,WAAO;AACT,SAAO,GAAG,OAAO,QAAQ,KAAK,OAAO,MAAM,CAAC,GAAG,OAAO,OAAO,IAAI,OAAO,IAAI,KAAK,EAAE;AACrF;AAEA,SAAS,QAAQ,SAAiB,KAAmB;AACnD,QAAM,OAAO,OAAO,GAAG;AACvB,QAAM,SAAS,QAAQ,MAAM,WAAW,MAAM;AAC9C,MAAI,CAAC,OAAO,WAAW,IAAI,EAAG,QAAO,SAAS;AAC9C,QAAM,SAAS,OAAO,MAAM,CAAC;AAC7B,SAAO,KAAK,SAAS,MAAM,KAAK,MAAM,KAAK,KAAK,MAAM,GAAG,CAAC,OAAO,MAAM,CAAC;AAC1E;AAEO,SAAS,YAAY,KAAa,QAA2B,CAAC,GAAY;AAC/E,QAAM,SAAS,SAAS,GAAG;AAC3B,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI,MAAM,KAAK,CAAC,UAAU,SAAS,KAAK,MAAM,MAAM,EAAG,QAAO;AAC9D,QAAM,SAAS,MAAM,GAAG;AACxB,MAAI,OAAO,aAAa,YAAY,OAAO,KAAM,QAAO;AACxD,SAAO,iBAAiB,SAAS,KAAK,CAAC,YAAY,QAAQ,SAAS,MAAM,CAAC;AAC7E;",
6
+ "names": []
7
+ }
@@ -0,0 +1,179 @@
1
+ const MAX_DEPTH = 4;
2
+ const BREAKS = /* @__PURE__ */ new Set([" ", " ", "\n", ";", "&", "|", "(", ")", "<", ">"]);
3
+ const NESTING = /[\s;&|`$()<>'"\\]/;
4
+ const ESCAPES = {
5
+ n: "\n",
6
+ t: " ",
7
+ r: "\r",
8
+ a: "\x07",
9
+ b: "\b",
10
+ e: "\x1B",
11
+ f: "\f",
12
+ v: "\v"
13
+ };
14
+ function closing(source, start, open, close) {
15
+ let depth = 1;
16
+ let index = start;
17
+ while (index < source.length) {
18
+ const char = source[index];
19
+ if (char === "\\") index += 2;
20
+ else if (char === "'" || char === '"') {
21
+ const end = source.indexOf(char, index + 1);
22
+ index = end < 0 ? source.length : end + 1;
23
+ } else {
24
+ if (char === open) depth++;
25
+ else if (char === close && --depth === 0) return index;
26
+ index++;
27
+ }
28
+ }
29
+ return source.length;
30
+ }
31
+ function ansi(source, start) {
32
+ let text = "";
33
+ let index = start;
34
+ while (index < source.length && source[index] !== "'") {
35
+ const char = source[index];
36
+ if (char !== "\\") {
37
+ text += char;
38
+ index++;
39
+ continue;
40
+ }
41
+ const next = source[index + 1] ?? "";
42
+ const hex = /^(?:x([0-9a-fA-F]{1,2})|u([0-9a-fA-F]{1,4})|U([0-9a-fA-F]{1,8})|([0-7]{1,3}))/.exec(
43
+ source.slice(index + 1)
44
+ );
45
+ if (hex) {
46
+ const code = hex[4] ? Number.parseInt(hex[4], 8) : Number.parseInt(hex.slice(1).join(""), 16);
47
+ text += String.fromCodePoint(Math.min(code, 1114111));
48
+ index += 1 + hex[0].length;
49
+ } else {
50
+ text += ESCAPES[next] ?? next;
51
+ index += 2;
52
+ }
53
+ }
54
+ return { text, end: index + 1 };
55
+ }
56
+ class Scanner {
57
+ words = [];
58
+ nested = [];
59
+ #word;
60
+ #index = 0;
61
+ source;
62
+ constructor(source) {
63
+ this.source = source;
64
+ }
65
+ #at(offset = 0) {
66
+ return this.source[this.#index + offset] ?? "";
67
+ }
68
+ #append(text) {
69
+ this.#word = (this.#word ?? "") + text;
70
+ }
71
+ #end() {
72
+ if (this.#word !== void 0) this.words.push(this.#word);
73
+ this.#word = void 0;
74
+ }
75
+ #substitute(from, open, close, prefix) {
76
+ const stop = closing(this.source, from, open, close);
77
+ const inner = this.source.slice(from, stop);
78
+ this.nested.push(inner);
79
+ this.#append(`${prefix}${inner}${close}`);
80
+ this.#index = stop + 1;
81
+ }
82
+ #backtick() {
83
+ let stop = this.#index + 1;
84
+ while (stop < this.source.length && this.source[stop] !== "`")
85
+ stop += this.source[stop] === "\\" ? 2 : 1;
86
+ this.nested.push(this.source.slice(this.#index + 1, stop).replace(/\\(.)/g, "$1"));
87
+ this.#append(this.source.slice(this.#index, stop + 1));
88
+ this.#index = stop + 1;
89
+ }
90
+ #dollar() {
91
+ const next = this.#at(1);
92
+ if (next === "(") this.#substitute(this.#index + 2, "(", ")", "$(");
93
+ else if (next === "{") this.#substitute(this.#index + 2, "{", "}", "${");
94
+ else return false;
95
+ return true;
96
+ }
97
+ #single() {
98
+ const stop = this.source.indexOf("'", this.#index + 1);
99
+ const last = stop < 0 ? this.source.length : stop;
100
+ this.#append(this.source.slice(this.#index + 1, last));
101
+ this.#index = last + 1;
102
+ }
103
+ #double() {
104
+ this.#append("");
105
+ this.#index++;
106
+ while (this.#index < this.source.length && this.#at() !== '"') {
107
+ const char = this.#at();
108
+ if (char === "\\" && '$`"\\\n'.includes(this.#at(1) || "x")) {
109
+ if (this.#at(1) !== "\n") this.#append(this.#at(1));
110
+ this.#index += 2;
111
+ } else if (char === "`") this.#backtick();
112
+ else if (!(char === "$" && this.#dollar())) this.#plain(char);
113
+ }
114
+ this.#index++;
115
+ }
116
+ #plain(char) {
117
+ this.#append(char);
118
+ this.#index++;
119
+ }
120
+ #comment() {
121
+ const stop = this.source.indexOf("\n", this.#index);
122
+ this.#index = stop < 0 ? this.source.length : stop;
123
+ }
124
+ #escape() {
125
+ if (this.#at(1) !== "\n") this.#append(this.#at(1));
126
+ this.#index += 2;
127
+ }
128
+ #quote(char) {
129
+ if (char === "'") this.#single();
130
+ else if (char === '"') this.#double();
131
+ else if (char === "$" && this.#at(1) === "'") {
132
+ const quoted = ansi(this.source, this.#index + 2);
133
+ this.#append(quoted.text);
134
+ this.#index = quoted.end;
135
+ } else if (char === "$" && this.#at(1) === '"') {
136
+ this.#index++;
137
+ this.#double();
138
+ } else return false;
139
+ return true;
140
+ }
141
+ #step(char) {
142
+ if ((char === "<" || char === ">") && this.#at(1) === "(") {
143
+ this.#end();
144
+ this.#substitute(this.#index + 2, "(", ")", `${char}(`);
145
+ this.#end();
146
+ } else if (char === "#" && this.#word === void 0) this.#comment();
147
+ else if (BREAKS.has(char)) {
148
+ this.#end();
149
+ this.#index++;
150
+ } else if (char === "\\") this.#escape();
151
+ else if (this.#quote(char)) return;
152
+ else if (char === "`") this.#backtick();
153
+ else if (!(char === "$" && this.#dollar())) this.#plain(char);
154
+ }
155
+ scan() {
156
+ while (this.#index < this.source.length) this.#step(this.#at());
157
+ this.#end();
158
+ return { words: this.words, nested: this.nested };
159
+ }
160
+ }
161
+ function split(source) {
162
+ return new Scanner(source).scan();
163
+ }
164
+ function words(source, depth = 0, seen = /* @__PURE__ */ new Set()) {
165
+ seen.add(source);
166
+ const { words: found, nested } = split(source);
167
+ if (depth >= MAX_DEPTH) return found;
168
+ const deeper = [...nested, ...found.filter((word) => NESTING.test(word))].filter(
169
+ (inner) => !seen.has(inner)
170
+ );
171
+ for (const inner of deeper) seen.add(inner);
172
+ return [...found, ...deeper.flatMap((inner) => words(inner, depth + 1, seen))];
173
+ }
174
+ export {
175
+ MAX_DEPTH,
176
+ split,
177
+ words
178
+ };
179
+ //# sourceMappingURL=shell.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/browser/shell.ts"],
4
+ "sourcesContent": ["export const MAX_DEPTH = 4;\n\nconst BREAKS = new Set([' ', '\\t', '\\n', ';', '&', '|', '(', ')', '<', '>']);\nconst NESTING = /[\\s;&|`$()<>'\"\\\\]/;\nconst ESCAPES: Record<string, string> = {\n n: '\\n',\n t: '\\t',\n r: '\\r',\n a: '\\x07',\n b: '\\b',\n e: '\\x1b',\n f: '\\f',\n v: '\\v',\n};\n\nexport interface Split {\n words: string[];\n nested: string[];\n}\n\nfunction closing(source: string, start: number, open: string, close: string): number {\n let depth = 1;\n let index = start;\n while (index < source.length) {\n const char = source[index] as string;\n if (char === '\\\\') index += 2;\n else if (char === \"'\" || char === '\"') {\n const end = source.indexOf(char, index + 1);\n index = end < 0 ? source.length : end + 1;\n } else {\n if (char === open) depth++;\n else if (char === close && --depth === 0) return index;\n index++;\n }\n }\n return source.length;\n}\n\nfunction ansi(source: string, start: number): { text: string; end: number } {\n let text = '';\n let index = start;\n while (index < source.length && source[index] !== \"'\") {\n const char = source[index] as string;\n if (char !== '\\\\') {\n text += char;\n index++;\n continue;\n }\n const next = source[index + 1] ?? '';\n const hex =\n /^(?:x([0-9a-fA-F]{1,2})|u([0-9a-fA-F]{1,4})|U([0-9a-fA-F]{1,8})|([0-7]{1,3}))/.exec(\n source.slice(index + 1)\n );\n if (hex) {\n const code = hex[4] ? Number.parseInt(hex[4], 8) : Number.parseInt(hex.slice(1).join(''), 16);\n text += String.fromCodePoint(Math.min(code, 0x10ffff));\n index += 1 + hex[0].length;\n } else {\n text += ESCAPES[next] ?? next;\n index += 2;\n }\n }\n return { text, end: index + 1 };\n}\n\nclass Scanner {\n readonly words: string[] = [];\n readonly nested: string[] = [];\n #word: string | undefined;\n #index = 0;\n\n readonly source: string;\n\n constructor(source: string) {\n this.source = source;\n }\n\n #at(offset = 0): string {\n return this.source[this.#index + offset] ?? '';\n }\n\n #append(text: string): void {\n this.#word = (this.#word ?? '') + text;\n }\n\n #end(): void {\n if (this.#word !== undefined) this.words.push(this.#word);\n this.#word = undefined;\n }\n\n #substitute(from: number, open: string, close: string, prefix: string): void {\n const stop = closing(this.source, from, open, close);\n const inner = this.source.slice(from, stop);\n this.nested.push(inner);\n this.#append(`${prefix}${inner}${close}`);\n this.#index = stop + 1;\n }\n\n #backtick(): void {\n let stop = this.#index + 1;\n while (stop < this.source.length && this.source[stop] !== '`')\n stop += this.source[stop] === '\\\\' ? 2 : 1;\n this.nested.push(this.source.slice(this.#index + 1, stop).replace(/\\\\(.)/g, '$1'));\n this.#append(this.source.slice(this.#index, stop + 1));\n this.#index = stop + 1;\n }\n\n #dollar(): boolean {\n const next = this.#at(1);\n if (next === '(') this.#substitute(this.#index + 2, '(', ')', '$(');\n else if (next === '{') this.#substitute(this.#index + 2, '{', '}', '${');\n else return false;\n return true;\n }\n\n #single(): void {\n const stop = this.source.indexOf(\"'\", this.#index + 1);\n const last = stop < 0 ? this.source.length : stop;\n this.#append(this.source.slice(this.#index + 1, last));\n this.#index = last + 1;\n }\n\n #double(): void {\n this.#append('');\n this.#index++;\n while (this.#index < this.source.length && this.#at() !== '\"') {\n const char = this.#at();\n if (char === '\\\\' && '$`\"\\\\\\n'.includes(this.#at(1) || 'x')) {\n if (this.#at(1) !== '\\n') this.#append(this.#at(1));\n this.#index += 2;\n } else if (char === '`') this.#backtick();\n else if (!(char === '$' && this.#dollar())) this.#plain(char);\n }\n this.#index++;\n }\n\n #plain(char: string): void {\n this.#append(char);\n this.#index++;\n }\n\n #comment(): void {\n const stop = this.source.indexOf('\\n', this.#index);\n this.#index = stop < 0 ? this.source.length : stop;\n }\n\n #escape(): void {\n if (this.#at(1) !== '\\n') this.#append(this.#at(1));\n this.#index += 2;\n }\n\n #quote(char: string): boolean {\n if (char === \"'\") this.#single();\n else if (char === '\"') this.#double();\n else if (char === '$' && this.#at(1) === \"'\") {\n const quoted = ansi(this.source, this.#index + 2);\n this.#append(quoted.text);\n this.#index = quoted.end;\n } else if (char === '$' && this.#at(1) === '\"') {\n this.#index++;\n this.#double();\n } else return false;\n return true;\n }\n\n #step(char: string): void {\n if ((char === '<' || char === '>') && this.#at(1) === '(') {\n this.#end();\n this.#substitute(this.#index + 2, '(', ')', `${char}(`);\n this.#end();\n } else if (char === '#' && this.#word === undefined) this.#comment();\n else if (BREAKS.has(char)) {\n this.#end();\n this.#index++;\n } else if (char === '\\\\') this.#escape();\n else if (this.#quote(char)) return;\n else if (char === '`') this.#backtick();\n else if (!(char === '$' && this.#dollar())) this.#plain(char);\n }\n\n scan(): Split {\n while (this.#index < this.source.length) this.#step(this.#at());\n this.#end();\n return { words: this.words, nested: this.nested };\n }\n}\n\nexport function split(source: string): Split {\n return new Scanner(source).scan();\n}\n\nexport function words(source: string, depth = 0, seen = new Set<string>()): string[] {\n seen.add(source);\n const { words: found, nested } = split(source);\n if (depth >= MAX_DEPTH) return found;\n const deeper = [...nested, ...found.filter((word) => NESTING.test(word))].filter(\n (inner) => !seen.has(inner)\n );\n for (const inner of deeper) seen.add(inner);\n return [...found, ...deeper.flatMap((inner) => words(inner, depth + 1, seen))];\n}\n"],
5
+ "mappings": "AAAO,MAAM,YAAY;AAEzB,MAAM,SAAS,oBAAI,IAAI,CAAC,KAAK,KAAM,MAAM,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG,CAAC;AAC3E,MAAM,UAAU;AAChB,MAAM,UAAkC;AAAA,EACtC,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AACL;AAOA,SAAS,QAAQ,QAAgB,OAAe,MAAc,OAAuB;AACnF,MAAI,QAAQ;AACZ,MAAI,QAAQ;AACZ,SAAO,QAAQ,OAAO,QAAQ;AAC5B,UAAM,OAAO,OAAO,KAAK;AACzB,QAAI,SAAS,KAAM,UAAS;AAAA,aACnB,SAAS,OAAO,SAAS,KAAK;AACrC,YAAM,MAAM,OAAO,QAAQ,MAAM,QAAQ,CAAC;AAC1C,cAAQ,MAAM,IAAI,OAAO,SAAS,MAAM;AAAA,IAC1C,OAAO;AACL,UAAI,SAAS,KAAM;AAAA,eACV,SAAS,SAAS,EAAE,UAAU,EAAG,QAAO;AACjD;AAAA,IACF;AAAA,EACF;AACA,SAAO,OAAO;AAChB;AAEA,SAAS,KAAK,QAAgB,OAA8C;AAC1E,MAAI,OAAO;AACX,MAAI,QAAQ;AACZ,SAAO,QAAQ,OAAO,UAAU,OAAO,KAAK,MAAM,KAAK;AACrD,UAAM,OAAO,OAAO,KAAK;AACzB,QAAI,SAAS,MAAM;AACjB,cAAQ;AACR;AACA;AAAA,IACF;AACA,UAAM,OAAO,OAAO,QAAQ,CAAC,KAAK;AAClC,UAAM,MACJ,gFAAgF;AAAA,MAC9E,OAAO,MAAM,QAAQ,CAAC;AAAA,IACxB;AACF,QAAI,KAAK;AACP,YAAM,OAAO,IAAI,CAAC,IAAI,OAAO,SAAS,IAAI,CAAC,GAAG,CAAC,IAAI,OAAO,SAAS,IAAI,MAAM,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE;AAC5F,cAAQ,OAAO,cAAc,KAAK,IAAI,MAAM,OAAQ,CAAC;AACrD,eAAS,IAAI,IAAI,CAAC,EAAE;AAAA,IACtB,OAAO;AACL,cAAQ,QAAQ,IAAI,KAAK;AACzB,eAAS;AAAA,IACX;AAAA,EACF;AACA,SAAO,EAAE,MAAM,KAAK,QAAQ,EAAE;AAChC;AAEA,MAAM,QAAQ;AAAA,EACH,QAAkB,CAAC;AAAA,EACnB,SAAmB,CAAC;AAAA,EAC7B;AAAA,EACA,SAAS;AAAA,EAEA;AAAA,EAET,YAAY,QAAgB;AAC1B,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,IAAI,SAAS,GAAW;AACtB,WAAO,KAAK,OAAO,KAAK,SAAS,MAAM,KAAK;AAAA,EAC9C;AAAA,EAEA,QAAQ,MAAoB;AAC1B,SAAK,SAAS,KAAK,SAAS,MAAM;AAAA,EACpC;AAAA,EAEA,OAAa;AACX,QAAI,KAAK,UAAU,OAAW,MAAK,MAAM,KAAK,KAAK,KAAK;AACxD,SAAK,QAAQ;AAAA,EACf;AAAA,EAEA,YAAY,MAAc,MAAc,OAAe,QAAsB;AAC3E,UAAM,OAAO,QAAQ,KAAK,QAAQ,MAAM,MAAM,KAAK;AACnD,UAAM,QAAQ,KAAK,OAAO,MAAM,MAAM,IAAI;AAC1C,SAAK,OAAO,KAAK,KAAK;AACtB,SAAK,QAAQ,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,EAAE;AACxC,SAAK,SAAS,OAAO;AAAA,EACvB;AAAA,EAEA,YAAkB;AAChB,QAAI,OAAO,KAAK,SAAS;AACzB,WAAO,OAAO,KAAK,OAAO,UAAU,KAAK,OAAO,IAAI,MAAM;AACxD,cAAQ,KAAK,OAAO,IAAI,MAAM,OAAO,IAAI;AAC3C,SAAK,OAAO,KAAK,KAAK,OAAO,MAAM,KAAK,SAAS,GAAG,IAAI,EAAE,QAAQ,UAAU,IAAI,CAAC;AACjF,SAAK,QAAQ,KAAK,OAAO,MAAM,KAAK,QAAQ,OAAO,CAAC,CAAC;AACrD,SAAK,SAAS,OAAO;AAAA,EACvB;AAAA,EAEA,UAAmB;AACjB,UAAM,OAAO,KAAK,IAAI,CAAC;AACvB,QAAI,SAAS,IAAK,MAAK,YAAY,KAAK,SAAS,GAAG,KAAK,KAAK,IAAI;AAAA,aACzD,SAAS,IAAK,MAAK,YAAY,KAAK,SAAS,GAAG,KAAK,KAAK,IAAI;AAAA,QAClE,QAAO;AACZ,WAAO;AAAA,EACT;AAAA,EAEA,UAAgB;AACd,UAAM,OAAO,KAAK,OAAO,QAAQ,KAAK,KAAK,SAAS,CAAC;AACrD,UAAM,OAAO,OAAO,IAAI,KAAK,OAAO,SAAS;AAC7C,SAAK,QAAQ,KAAK,OAAO,MAAM,KAAK,SAAS,GAAG,IAAI,CAAC;AACrD,SAAK,SAAS,OAAO;AAAA,EACvB;AAAA,EAEA,UAAgB;AACd,SAAK,QAAQ,EAAE;AACf,SAAK;AACL,WAAO,KAAK,SAAS,KAAK,OAAO,UAAU,KAAK,IAAI,MAAM,KAAK;AAC7D,YAAM,OAAO,KAAK,IAAI;AACtB,UAAI,SAAS,QAAQ,UAAU,SAAS,KAAK,IAAI,CAAC,KAAK,GAAG,GAAG;AAC3D,YAAI,KAAK,IAAI,CAAC,MAAM,KAAM,MAAK,QAAQ,KAAK,IAAI,CAAC,CAAC;AAClD,aAAK,UAAU;AAAA,MACjB,WAAW,SAAS,IAAK,MAAK,UAAU;AAAA,eAC/B,EAAE,SAAS,OAAO,KAAK,QAAQ,GAAI,MAAK,OAAO,IAAI;AAAA,IAC9D;AACA,SAAK;AAAA,EACP;AAAA,EAEA,OAAO,MAAoB;AACzB,SAAK,QAAQ,IAAI;AACjB,SAAK;AAAA,EACP;AAAA,EAEA,WAAiB;AACf,UAAM,OAAO,KAAK,OAAO,QAAQ,MAAM,KAAK,MAAM;AAClD,SAAK,SAAS,OAAO,IAAI,KAAK,OAAO,SAAS;AAAA,EAChD;AAAA,EAEA,UAAgB;AACd,QAAI,KAAK,IAAI,CAAC,MAAM,KAAM,MAAK,QAAQ,KAAK,IAAI,CAAC,CAAC;AAClD,SAAK,UAAU;AAAA,EACjB;AAAA,EAEA,OAAO,MAAuB;AAC5B,QAAI,SAAS,IAAK,MAAK,QAAQ;AAAA,aACtB,SAAS,IAAK,MAAK,QAAQ;AAAA,aAC3B,SAAS,OAAO,KAAK,IAAI,CAAC,MAAM,KAAK;AAC5C,YAAM,SAAS,KAAK,KAAK,QAAQ,KAAK,SAAS,CAAC;AAChD,WAAK,QAAQ,OAAO,IAAI;AACxB,WAAK,SAAS,OAAO;AAAA,IACvB,WAAW,SAAS,OAAO,KAAK,IAAI,CAAC,MAAM,KAAK;AAC9C,WAAK;AACL,WAAK,QAAQ;AAAA,IACf,MAAO,QAAO;AACd,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,MAAoB;AACxB,SAAK,SAAS,OAAO,SAAS,QAAQ,KAAK,IAAI,CAAC,MAAM,KAAK;AACzD,WAAK,KAAK;AACV,WAAK,YAAY,KAAK,SAAS,GAAG,KAAK,KAAK,GAAG,IAAI,GAAG;AACtD,WAAK,KAAK;AAAA,IACZ,WAAW,SAAS,OAAO,KAAK,UAAU,OAAW,MAAK,SAAS;AAAA,aAC1D,OAAO,IAAI,IAAI,GAAG;AACzB,WAAK,KAAK;AACV,WAAK;AAAA,IACP,WAAW,SAAS,KAAM,MAAK,QAAQ;AAAA,aAC9B,KAAK,OAAO,IAAI,EAAG;AAAA,aACnB,SAAS,IAAK,MAAK,UAAU;AAAA,aAC7B,EAAE,SAAS,OAAO,KAAK,QAAQ,GAAI,MAAK,OAAO,IAAI;AAAA,EAC9D;AAAA,EAEA,OAAc;AACZ,WAAO,KAAK,SAAS,KAAK,OAAO,OAAQ,MAAK,MAAM,KAAK,IAAI,CAAC;AAC9D,SAAK,KAAK;AACV,WAAO,EAAE,OAAO,KAAK,OAAO,QAAQ,KAAK,OAAO;AAAA,EAClD;AACF;AAEO,SAAS,MAAM,QAAuB;AAC3C,SAAO,IAAI,QAAQ,MAAM,EAAE,KAAK;AAClC;AAEO,SAAS,MAAM,QAAgB,QAAQ,GAAG,OAAO,oBAAI,IAAY,GAAa;AACnF,OAAK,IAAI,MAAM;AACf,QAAM,EAAE,OAAO,OAAO,OAAO,IAAI,MAAM,MAAM;AAC7C,MAAI,SAAS,UAAW,QAAO;AAC/B,QAAM,SAAS,CAAC,GAAG,QAAQ,GAAG,MAAM,OAAO,CAAC,SAAS,QAAQ,KAAK,IAAI,CAAC,CAAC,EAAE;AAAA,IACxE,CAAC,UAAU,CAAC,KAAK,IAAI,KAAK;AAAA,EAC5B;AACA,aAAW,SAAS,OAAQ,MAAK,IAAI,KAAK;AAC1C,SAAO,CAAC,GAAG,OAAO,GAAG,OAAO,QAAQ,CAAC,UAAU,MAAM,OAAO,QAAQ,GAAG,IAAI,CAAC,CAAC;AAC/E;",
6
+ "names": []
7
+ }
package/dist/client.js CHANGED
@@ -6,6 +6,7 @@ import { Client, createClientServiceTransport } from "@earendil-works/pi-client"
6
6
  import {
7
7
  AGENT_SESSION,
8
8
  AgentControl,
9
+ AgentPagesService,
9
10
  AgentRemoteService,
10
11
  AgentSessions,
11
12
  AgentSettings,
@@ -66,7 +67,7 @@ async function connectAgent(endpoint, options = {}) {
66
67
  await server.use(AgentSessions).attach(AGENT_SESSION, context);
67
68
  await ready;
68
69
  const session = createRemoteServiceBinding({
69
- services: [AgentControl, AgentTranscript, AgentSettings, AgentRemoteService],
70
+ services: [AgentControl, AgentTranscript, AgentSettings, AgentRemoteService, AgentPagesService],
70
71
  transport: createClientServiceTransport(client, () => client.attachment),
71
72
  bound: true
72
73
  });
@@ -86,6 +87,7 @@ async function connectAgent(endpoint, options = {}) {
86
87
  } = session.use(AgentTranscript);
87
88
  const settings = session.use(AgentSettings);
88
89
  const remote = session.use(AgentRemoteService);
90
+ const pages = session.use(AgentPagesService);
89
91
  await session.ready(context);
90
92
  const held = [];
91
93
  const hold = (state) => {
@@ -116,6 +118,7 @@ async function connectAgent(endpoint, options = {}) {
116
118
  monitor: hold(monitor),
117
119
  settings: lastingSettings(settings, hold),
118
120
  remote,
121
+ pages,
119
122
  closed,
120
123
  webhook(name, delivery) {
121
124
  return control.webhook(
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/client.ts"],
4
- "sourcesContent": ["import {\n type Context,\n createRemoteServiceBinding,\n type ReplicatedState,\n} from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport { Client, createClientServiceTransport } from '@earendil-works/pi-client';\nimport type { ConversationView } from '@earendil-works/pi-durable';\nimport type { ChangesView } from './changes/git.ts';\nimport type { FrozenCone } from './freezer/index.ts';\nimport type { MemoryEntry, MemoryScope } from './memory/format.ts';\nimport type { AgentRemote } from './remote/host.ts';\nimport {\n AGENT_SESSION,\n AgentControl,\n AgentRemoteService,\n AgentSessions,\n AgentSettings,\n type AgentsSummary,\n AgentTranscript,\n type Command,\n type Delivered,\n type MonitorFeed,\n type SendMode,\n} from './services.ts';\nimport type { Sprinkle } from './sprinkles/kind.ts';\nimport { PortChannel, type PortEndpoint } from './wire.ts';\n\nexport interface AgentConnection {\n readonly serverId: string;\n readonly control: AgentControl;\n readonly transcript: ReplicatedState<ConversationView>;\n readonly deliveries: ReplicatedState<Record<string, Delivered>>;\n readonly agents?: ReplicatedState<AgentsSummary>;\n readonly views?: ReplicatedState<Record<string, ConversationView>>;\n readonly commands?: ReplicatedState<Command[]>;\n readonly sprinkles?: ReplicatedState<Sprinkle[]>;\n readonly memories?: ReplicatedState<MemoryEntry[]>;\n readonly memoryScopes?: ReplicatedState<MemoryScope[]>;\n readonly frozen?: ReplicatedState<FrozenCone[]>;\n readonly changes?: ReplicatedState<ChangesView>;\n readonly monitor?: ReplicatedState<MonitorFeed>;\n readonly settings: AgentSettings;\n readonly remote?: AgentRemote;\n readonly closed: Promise<Error | undefined>;\n prompt(text: string, whenBusy?: SendMode): Promise<string>;\n webhook(\n name: string,\n delivery: { id?: string | null; headers?: Record<string, string>; body: string }\n ): Promise<{ delivered: boolean }>;\n close(): Promise<void>;\n}\n\nexport interface Lasting<T> {\n readonly state: ReplicatedState<T>;\n freeze(): void;\n}\n\nexport function lasting<T>(state: ReplicatedState<T>): Lasting<T> {\n let last: T | undefined;\n let frozen = false;\n const read = () => {\n if (frozen) return last;\n try {\n last = state.value;\n } catch {}\n return last;\n };\n return {\n state: {\n get value() {\n return read();\n },\n subscribe: (listener) => (frozen ? () => undefined : state.subscribe(listener)),\n },\n freeze() {\n read();\n frozen = true;\n },\n };\n}\n\nfunction lastingSettings(\n settings: AgentSettings,\n hold: <T>(state: ReplicatedState<T>) => ReplicatedState<T>\n): AgentSettings {\n return {\n state: hold(settings.state),\n connect: (providerId, secret, region, context) =>\n settings.connect(providerId, secret, region, context),\n disconnect: (providerId, context) => settings.disconnect(providerId, context),\n signIn: (providerId, context) => settings.signIn(providerId, context),\n };\n}\n\nfunction attached(client: Client): Promise<void> {\n return new Promise((resolve) => {\n const stop = client.onAttachmentChange(() => {\n stop();\n resolve();\n });\n });\n}\n\nexport async function connectAgent(\n endpoint: PortEndpoint,\n options: { context?: Context } = {}\n): Promise<AgentConnection> {\n const context = options.context ?? BACKGROUND_CONTEXT;\n const channel = new PortChannel(endpoint);\n const serverId = await channel.hello;\n const client = await Client.connect({ serverId, transportFactory: channel.transport });\n const server = createRemoteServiceBinding({\n services: [AgentSessions],\n transport: createClientServiceTransport(client, () => ({ serverId })),\n bound: true,\n });\n const ready = attached(client);\n await server.use(AgentSessions).attach(AGENT_SESSION, context);\n await ready;\n const session = createRemoteServiceBinding({\n services: [AgentControl, AgentTranscript, AgentSettings, AgentRemoteService],\n transport: createClientServiceTransport(client, () => client.attachment),\n bound: true,\n });\n const control = session.use(AgentControl);\n const {\n state: transcript,\n deliveries,\n agents,\n views,\n commands,\n sprinkles,\n memories,\n memoryScopes,\n frozen,\n changes,\n monitor,\n } = session.use(AgentTranscript);\n const settings = session.use(AgentSettings);\n const remote = session.use(AgentRemoteService);\n await session.ready(context);\n const held: Lasting<unknown>[] = [];\n const hold = <T>(state: ReplicatedState<T>): ReplicatedState<T> => {\n const kept = lasting(state);\n held.push(kept as Lasting<unknown>);\n return kept.state;\n };\n const freeze = () => {\n for (const kept of held) kept.freeze();\n };\n const closed = channel.ended.then((reason) => {\n freeze();\n return reason;\n });\n return {\n serverId,\n control,\n transcript: hold(transcript),\n deliveries: hold(deliveries),\n agents: hold(agents),\n views: hold(views),\n commands: hold(commands),\n sprinkles: hold(sprinkles),\n memories: hold(memories),\n memoryScopes: hold(memoryScopes),\n frozen: hold(frozen),\n changes: hold(changes),\n monitor: hold(monitor),\n settings: lastingSettings(settings, hold),\n remote,\n closed,\n webhook(name, delivery) {\n return control.webhook(\n name,\n { id: delivery.id ?? null, headers: delivery.headers ?? {}, body: delivery.body },\n context\n );\n },\n async prompt(text, whenBusy = 'followUp') {\n const sent = await control.send({ text, whenBusy, requestId: null }, context);\n if (!sent.accepted) throw new Error(`${sent.error.code}: ${sent.error.message}`);\n const settled = await control.wait(sent.submissionId, context);\n if (settled.status !== 'done') throw new Error(`unanswered: ${settled.reason}`);\n return settled.text;\n },\n async close() {\n freeze();\n await server.use(AgentSessions).detach(context);\n await session.dispose(context);\n await server.dispose(context);\n await client.dispose();\n },\n };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import {\n type Context,\n createRemoteServiceBinding,\n type ReplicatedState,\n} from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport { Client, createClientServiceTransport } from '@earendil-works/pi-client';\nimport type { ConversationView } from '@earendil-works/pi-durable';\nimport type { ChangesView } from './changes/git.ts';\nimport type { FrozenCone } from './freezer/index.ts';\nimport type { MemoryEntry, MemoryScope } from './memory/format.ts';\nimport type { AgentRemote } from './remote/host.ts';\nimport type { AgentPages } from './remote/pages.ts';\nimport {\n AGENT_SESSION,\n AgentControl,\n AgentPagesService,\n AgentRemoteService,\n AgentSessions,\n AgentSettings,\n type AgentsSummary,\n AgentTranscript,\n type Command,\n type Delivered,\n type MonitorFeed,\n type SendMode,\n} from './services.ts';\nimport type { Sprinkle } from './sprinkles/kind.ts';\nimport { PortChannel, type PortEndpoint } from './wire.ts';\n\nexport interface AgentConnection {\n readonly serverId: string;\n readonly control: AgentControl;\n readonly transcript: ReplicatedState<ConversationView>;\n readonly deliveries: ReplicatedState<Record<string, Delivered>>;\n readonly agents?: ReplicatedState<AgentsSummary>;\n readonly views?: ReplicatedState<Record<string, ConversationView>>;\n readonly commands?: ReplicatedState<Command[]>;\n readonly sprinkles?: ReplicatedState<Sprinkle[]>;\n readonly memories?: ReplicatedState<MemoryEntry[]>;\n readonly memoryScopes?: ReplicatedState<MemoryScope[]>;\n readonly frozen?: ReplicatedState<FrozenCone[]>;\n readonly changes?: ReplicatedState<ChangesView>;\n readonly monitor?: ReplicatedState<MonitorFeed>;\n readonly settings: AgentSettings;\n readonly remote?: AgentRemote;\n readonly pages?: AgentPages;\n readonly closed: Promise<Error | undefined>;\n prompt(text: string, whenBusy?: SendMode): Promise<string>;\n webhook(\n name: string,\n delivery: { id?: string | null; headers?: Record<string, string>; body: string }\n ): Promise<{ delivered: boolean }>;\n close(): Promise<void>;\n}\n\nexport interface Lasting<T> {\n readonly state: ReplicatedState<T>;\n freeze(): void;\n}\n\nexport function lasting<T>(state: ReplicatedState<T>): Lasting<T> {\n let last: T | undefined;\n let frozen = false;\n const read = () => {\n if (frozen) return last;\n try {\n last = state.value;\n } catch {}\n return last;\n };\n return {\n state: {\n get value() {\n return read();\n },\n subscribe: (listener) => (frozen ? () => undefined : state.subscribe(listener)),\n },\n freeze() {\n read();\n frozen = true;\n },\n };\n}\n\nfunction lastingSettings(\n settings: AgentSettings,\n hold: <T>(state: ReplicatedState<T>) => ReplicatedState<T>\n): AgentSettings {\n return {\n state: hold(settings.state),\n connect: (providerId, secret, region, context) =>\n settings.connect(providerId, secret, region, context),\n disconnect: (providerId, context) => settings.disconnect(providerId, context),\n signIn: (providerId, context) => settings.signIn(providerId, context),\n };\n}\n\nfunction attached(client: Client): Promise<void> {\n return new Promise((resolve) => {\n const stop = client.onAttachmentChange(() => {\n stop();\n resolve();\n });\n });\n}\n\nexport async function connectAgent(\n endpoint: PortEndpoint,\n options: { context?: Context } = {}\n): Promise<AgentConnection> {\n const context = options.context ?? BACKGROUND_CONTEXT;\n const channel = new PortChannel(endpoint);\n const serverId = await channel.hello;\n const client = await Client.connect({ serverId, transportFactory: channel.transport });\n const server = createRemoteServiceBinding({\n services: [AgentSessions],\n transport: createClientServiceTransport(client, () => ({ serverId })),\n bound: true,\n });\n const ready = attached(client);\n await server.use(AgentSessions).attach(AGENT_SESSION, context);\n await ready;\n const session = createRemoteServiceBinding({\n services: [AgentControl, AgentTranscript, AgentSettings, AgentRemoteService, AgentPagesService],\n transport: createClientServiceTransport(client, () => client.attachment),\n bound: true,\n });\n const control = session.use(AgentControl);\n const {\n state: transcript,\n deliveries,\n agents,\n views,\n commands,\n sprinkles,\n memories,\n memoryScopes,\n frozen,\n changes,\n monitor,\n } = session.use(AgentTranscript);\n const settings = session.use(AgentSettings);\n const remote = session.use(AgentRemoteService);\n const pages = session.use(AgentPagesService);\n await session.ready(context);\n const held: Lasting<unknown>[] = [];\n const hold = <T>(state: ReplicatedState<T>): ReplicatedState<T> => {\n const kept = lasting(state);\n held.push(kept as Lasting<unknown>);\n return kept.state;\n };\n const freeze = () => {\n for (const kept of held) kept.freeze();\n };\n const closed = channel.ended.then((reason) => {\n freeze();\n return reason;\n });\n return {\n serverId,\n control,\n transcript: hold(transcript),\n deliveries: hold(deliveries),\n agents: hold(agents),\n views: hold(views),\n commands: hold(commands),\n sprinkles: hold(sprinkles),\n memories: hold(memories),\n memoryScopes: hold(memoryScopes),\n frozen: hold(frozen),\n changes: hold(changes),\n monitor: hold(monitor),\n settings: lastingSettings(settings, hold),\n remote,\n pages,\n closed,\n webhook(name, delivery) {\n return control.webhook(\n name,\n { id: delivery.id ?? null, headers: delivery.headers ?? {}, body: delivery.body },\n context\n );\n },\n async prompt(text, whenBusy = 'followUp') {\n const sent = await control.send({ text, whenBusy, requestId: null }, context);\n if (!sent.accepted) throw new Error(`${sent.error.code}: ${sent.error.message}`);\n const settled = await control.wait(sent.submissionId, context);\n if (settled.status !== 'done') throw new Error(`unanswered: ${settled.reason}`);\n return settled.text;\n },\n async close() {\n freeze();\n await server.use(AgentSessions).detach(context);\n await session.dispose(context);\n await server.dispose(context);\n await client.dispose();\n },\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }