@ai-ecoverse/slicc-agent 3.42.3 → 3.43.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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 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.
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.
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".
@@ -337,7 +337,7 @@ The `memory` section, read before every request, holds the first 200 lines (at m
337
337
  - secrets redacted;
338
338
  - at most 50 open;
339
339
  - dismissed and taken ids are never reopened.
340
- - `gelatiere deliver` sends each cone named in a suggestion's `cones` (or the first cone) one `sprinkle` lick with the action `gelatiere-suggestions`. The built-in `gelatiere` skill answers it with `sprinkle show suggestions`, and looks ids up in the store before installing or trying anything.
340
+ - `gelatiere deliver` sends each cone named in a suggestion's `cones` (or the first cone) one `sprinkle` lick with the action `gelatiere-suggestions`. The built-in `gelatiere` skill answers it with `panel open sprinkle:suggestions`, and looks ids up in the store before installing or trying anything.
341
341
  - `gelatiere install <id>` is what a card's Install leads to. It installs [upskill](https://github.com/ai-ecoverse/gh-upskill) if it's missing (`command -v upskill || curl -fsSL …/install.sh | bash`, into `$PNPM_HOME`), runs `upskill <repo> [--path <folder>] (--skill <name> | --all) --dest-path ~/.pi/agent/skills`, checks that the skill (or, with `all`, at least one new skill) is there for the skills loader, and marks the suggestion taken. It refuses a single skill that's already installed. A `skill` suggestion may carry `path` (a folder in the repo) and `all: true` instead of `skill`. In seven, upskill reaches GitHub through the page's fetch, which `raw.githubusercontent.com` and `api.github.com` allow with CORS; `GITHUB_TOKEN` raises GitHub's rate limit. Before anyone clicks, the card shows where the skill comes from: "Installs github from github.com/example/skills".
342
342
  - `gelatiere run`, `list [--all] [--json]`, `dismiss <id>` and `status` complete the command.
343
343
  - `gelatiere catalog [--refresh] [--all] [--json]` reads v6's catalog: `https://www.sliccy.com/skills/catalog.json`, the company's `<slug>.json` and the optional `use-cases.json`. Each is the Edge Delivery sheet shape `{ data: [rows] }` with string fields.
@@ -363,9 +363,9 @@ 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.
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 protected URL: one that [`@ai-ecoverse/slicc-browsers`](https://github.com/ai-ecoverse/slicc-browsers#protected-origins)' `protectedOrigins` matcher protects, with the origin the worker runs on (`location.origin`) as its extra origin. That covers `sliccy.ai` and every name under it (including `www` and `vault`) over http or https on any port, the worker's origin, `devtools:`, and `blob:`, `filesystem:` and `view-source:` by their inner URL. 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. Tabs and newlines are removed and surrounding spaces trimmed, as the URL parser does; the matcher parses with the WHATWG `URL`, so case, IDNA, full-width characters, percent escapes, backslashes and userinfo all count. `about:blank`, `about:srcdoc` and the empty URL are allowed: a tab `playwright-cli` opens has no SLICC opener, and a command that also names a protected URL is refused anyway.
368
+ - **The list** is slicc-browsers' `catalog.json` `protectedOrigins` with its reference matcher `protected.mjs`, wrapped by [`src/browser/origins.ts`](src/browser/origins.ts) (`protectedMatcher(extra)`); the unit tests run every URL and target vector in its `protected.json`. `*.kernel.localhost` isn't on the shared list; that rule is this guard's own. The build keeps the `with { type: 'json' }` import attribute, so the catalog loads unbundled.
369
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`.
370
370
  - There are no browser licks yet.
371
371
 
@@ -484,7 +484,7 @@ A sprinkle is a small HTML panel next to the chat. pi has nothing for this; it's
484
484
 
485
485
  **Files.** Every `.shtml` file in `/home/sprinkles`, as `<name>.shtml` or `<name>/<name>.shtml`, is a sprinkle. `<name>` is 1–64 of `a-z 0-9 . _ -`, and `welcome` is reserved for the built-in one. Its `<title>` and `<link rel="icon" href="<lucide name>">` give its title and rail icon (default `sparkles`). The worker watches the folder: the transcript service's `sprinkles` lists every one with its HTML, so spectrum gives each a rail button, and editing a file reloads its panel. Deleting the file retires it.
486
486
 
487
- **Owners.** Each sprinkle talks to one agent, its owner. The owner is whoever first showed it, or the agent named with `sprinkle own`, otherwise the active cone at the time of the send. Owners live in the session document `slicc.sprinkles`.
487
+ **Owners.** Each sprinkle talks to one agent, its owner. The first `panel open sprinkle:<name>` from a cone or a scoop makes the caller's cone its owner; until then it's the active cone at the time of the send. Nothing reassigns an owner afterwards, and `suggestions` always follows the active cone. Owners live in the session document `slicc.sprinkles`.
488
488
 
489
489
  **Talking back.**
490
490
  - **Sends.** `SprinklePort.send(id, { action, data, target })` becomes `AgentControl.sprinkleSend`, then a lick on the `sprinkle` channel for the owner: source `sprinkle:<name>`, the action as its text and the data as JSON in its body. It steers and coalesces per sprinkle like any lick.
@@ -493,33 +493,33 @@ A sprinkle is a small HTML panel next to the chat. pi has nothing for this; it's
493
493
  - `getState` and `setState` keep one JSON value per sprinkle in `slicc.sprinkles`;
494
494
  - there's no `exec`: a sprinkle that asks for one gets an error telling it to send the agent a message with `slicc.lick()` instead, so every command goes through an agent and its transcript.
495
495
 
496
- **The `sprinkle` command** is the same script as `agent`, installed as `sprinkle`:
497
- - `sprinkle list`;
498
- - `sprinkle show <name>`: posts a `slicc.sprinkle` entry in the caller's chat, which the adapter shows as an inline sprinkle;
499
- - `sprinkle own <name> [<agent>]`.
500
-
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.
496
+ **There's no `sprinkle` command.** An agent opens a sprinkle with `panel open sprinkle:<name>` and lists them with `panel list` (see Panels). An agent version that installed `sprinkle` leaves a copy in `/usr/local/bin` or `$PNPM_HOME/bin`; the worker removes it when that copy is slicc-agent's.
502
497
 
503
498
  **Shipped sprinkles.** Only `welcome` and `suggestions`, with slicc-spectrum's reviewed copy.
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.
499
+ - **Welcome.** The worker posts it inline in the first cone's chat once per session, as a `slicc.sprinkle` entry that the adapter shows as an inline sprinkle. It's the only inline sprinkle. 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.
505
500
  - **Suggestions.** It's listed once the gelatiere's store `/home/.gelatiere/suggestions.json` exists, and owned by the active cone. Its `gelatiere-dismiss` send marks the suggestion in the store and never reaches an agent; `gelatiere-install` and `gelatiere-try` mark it taken and go to the owner as licks. Its `exec` for use cases is refused, so it shows its static empty state.
506
501
 
507
- The built-in `sprinkles` skill teaches the files, the bridge and the command.
502
+ The built-in `sprinkles` skill teaches the files, the bridge and `panel open sprinkle:<name>`.
508
503
 
509
504
  ### Panels
510
505
 
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.
506
+ The `panel` command lets an agent, or the user in a terminal, list, open, close, focus and move the panels of a SLICC tab, sprinkles included as `sprinkle:<name>`. 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
507
 
513
- **The command** is the same script as `agent`, installed as `panel`. It forwards argv untouched with the caller `{ agentId, cone, readOnly: false }`:
508
+ **The command** is the same script as `agent`, installed as `panel`. It forwards argv with the caller `{ agentId, cone, readOnly: false }`, untouched apart from `--tab`:
514
509
  - `agentId` is the calling cone or scoop (`cone`, `cone-2`, `scoop:<id>`), or `null` from a terminal;
515
510
  - `cone` is the caller's cone: a scoop's own cone, or the active cone for a terminal;
516
511
  - 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
512
 
513
+ The agent reads three things itself:
514
+ - **`panel tabs [--json]`** never reaches a tab: the agent answers it from the bridge's registered tabs, one line per tab with its id, cone, `visible` or `hidden`, `focused` or `-`, and how long ago it was last active, best first; `--json` prints `[{ tab, cone, visible, focused, lastActive }]`. A cone or a scoop sees only the tabs showing its cone, the user at a terminal sees every tab.
515
+ - **`--tab <tab>`** or `--tab=<tab>`, anywhere in argv, sends the command to that tab and is removed before argv is forwarded. It overrides `SLICC_TAB`. A cone or a scoop can only name a tab showing its own cone; the user at a terminal can name any tab, and the tab's cone becomes the caller's cone. A tab that isn't open, or isn't on the caller's cone, exits 3 with a sentence that points to `panel tabs`; `--tab` without a value exits 2.
516
+ - **`panel open sprinkle:<name>`** from a cone or a scoop sets the sprinkle's owner to the caller's cone first, if it has none (see Sprinkles).
517
+
518
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;
519
+ - **3:** no open tab shows the caller's cone ("panel: no open SLICC tab shows cone <cone>; …"), or the `--tab` tab isn't open or reachable. Nothing is queued;
520
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
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.
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 `--tab` tab, 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
523
 
524
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` (≥ 1.59.0). `PageRun` uses the agent's own types, so the peer range stays `^1.39.0`, and `test/integration/panel-run.ts` type-checks `(argv, caller) => panelCommand(app, argv, caller)` against it. The integration test runs the real `panelCommand` on a `<slicc-app>` with the dummy model under the agent's ports. Without `run` the tab never registers, so the bridge works with older spectrums too.
525
525
 
package/bin/agent CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/bin/bash
2
2
  dir=/var/lib/slicc/agent/requests
3
3
  as="${0##*/}"
4
- case "$as" in subagent|sprinkle|memory|gelatiere|freezer|models|panel) ;; *) as=agent ;; esac
4
+ case "$as" in subagent|memory|gelatiere|freezer|models|panel) ;; *) as=agent ;; esac
5
5
  [ -d "$dir/in" ] && [ -d "$dir/out" ] || mkdir -p "$dir/in" "$dir/out" 2>/dev/null
6
6
  who="${SLICC_AGENT:-}"
7
7
  if [ -z "$who" ] && [ -n "${SLICC_TAB:-}" ]; then who="tab:$SLICC_TAB"; fi
@@ -4,20 +4,14 @@ import {
4
4
  ToolTask,
5
5
  wrapTool
6
6
  } from "@earendil-works/pi-durable";
7
- import { isProtected } from "./origins.js";
7
+ import { PROTECTED_RULES, protectedMatcher } from "./origins.js";
8
8
  import { words } from "./shell.js";
9
9
  const BROWSER_GUARD = "slicc-browser-guard";
10
10
  const REFUSAL = "SLICC's own pages can't be opened or driven from the browser tools.";
11
11
  const FACADE_PORT = 9222;
12
12
  const BROWSER = /* @__PURE__ */ new Set(["playwright-cli", "curlwright"]);
13
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
- };
14
+ const INNER = new Set(PROTECTED_RULES.innerSchemes.map((scheme) => `${scheme}:`));
21
15
  const SCHEMED = /^[a-z][a-z0-9+.-]*:/i;
22
16
  const EMBEDDED = /[a-z][a-z0-9+.-]*:\/\/[^\s'"`<>(){}\\]+/gi;
23
17
  const RELATIVE = /(?<![:\w/])\/\/[^\s'"`<>(){}\\/]+/g;
@@ -35,20 +29,19 @@ function ownKernelServer(host) {
35
29
  const port = OWN_KERNEL.exec(host)?.[1];
36
30
  return port !== void 0 && Number(port) <= 65535 && Number(port) !== FACADE_PORT;
37
31
  }
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;
32
+ function foreignKernel(url) {
33
+ let parsed = parse(url);
34
+ for (let depth = 0; parsed && INNER.has(parsed.protocol) && depth < 8; depth++)
35
+ parsed = parse(parsed.href.slice(parsed.protocol.length));
36
+ if (!parsed || !WEB.has(parsed.protocol)) return false;
46
37
  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}`);
38
+ return (host === KERNEL || host.endsWith(`.${KERNEL}`)) && !ownKernelServer(host);
39
+ }
40
+ function forbidden(url, matcher) {
41
+ return matcher.url(url) || foreignKernel(url);
50
42
  }
51
- function candidates(word) {
43
+ function candidates(raw) {
44
+ const word = raw.replace(/[\t\n\r]/g, "").replace(/^[\0- ]+|[\0- ]+$/g, "");
52
45
  const value = word.replace(PREFIX, "");
53
46
  const found = [
54
47
  word,
@@ -64,10 +57,10 @@ function candidates(word) {
64
57
  function browserCommand(word) {
65
58
  return BROWSER.has(word.slice(word.lastIndexOf("/") + 1));
66
59
  }
67
- function refused(command, origins) {
60
+ function refused(command, matcher) {
68
61
  const all = words(command);
69
62
  if (!all.some(browserCommand)) return false;
70
- return all.some((word) => candidates(word).some((url) => forbidden(url, origins)));
63
+ return all.some((word) => candidates(word).some((url) => forbidden(url, matcher)));
71
64
  }
72
65
  function commandOf(args) {
73
66
  const command = args?.command;
@@ -75,18 +68,19 @@ function commandOf(args) {
75
68
  }
76
69
  function browserGuard(origins, tools) {
77
70
  const bash = tools.find((tool) => tool.name === "bash");
71
+ const matcher = protectedMatcher(origins);
78
72
  return defineExtension({
79
73
  name: BROWSER_GUARD,
80
74
  hooks: [
81
75
  hook(ToolTask, {
82
- beforeTool: (call) => call.name === bash.name && refused(commandOf(call.arguments), origins) ? { block: REFUSAL } : void 0
76
+ beforeTool: (call) => call.name === bash.name && refused(commandOf(call.arguments), matcher) ? { block: REFUSAL } : void 0
83
77
  })
84
78
  ],
85
79
  wraps: [
86
80
  wrapTool(bash, (inner) => ({
87
81
  ...inner,
88
82
  execute(args, api, context) {
89
- if (!refused(commandOf(args), origins)) return inner.execute(args, api, context);
83
+ if (!refused(commandOf(args), matcher)) return inner.execute(args, api, context);
90
84
  return Promise.resolve({ content: [{ type: "text", text: REFUSAL }], isError: true });
91
85
  }
92
86
  }))
@@ -100,6 +94,7 @@ export {
100
94
  browserGuard,
101
95
  candidates,
102
96
  forbidden,
97
+ foreignKernel,
103
98
  ownKernelServer,
104
99
  refused
105
100
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
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;",
4
+ "sourcesContent": ["import {\n defineExtension,\n type Extension,\n hook,\n type ToolRegistration,\n ToolTask,\n wrapTool,\n} from '@earendil-works/pi-durable';\nimport { PROTECTED_RULES, type ProtectedMatcher, protectedMatcher } 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 INNER = new Set(PROTECTED_RULES.innerSchemes.map((scheme) => `${scheme}:`));\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 foreignKernel(url: string): boolean {\n let parsed = parse(url);\n for (let depth = 0; parsed && INNER.has(parsed.protocol) && depth < 8; depth++)\n parsed = parse(parsed.href.slice(parsed.protocol.length));\n if (!parsed || !WEB.has(parsed.protocol)) return false;\n const host = parsed.hostname.replace(/\\.+$/, '');\n return (host === KERNEL || host.endsWith(`.${KERNEL}`)) && !ownKernelServer(host);\n}\n\nexport function forbidden(url: string, matcher: ProtectedMatcher): boolean {\n return matcher.url(url) || foreignKernel(url);\n}\n\nexport function candidates(raw: string): string[] {\n const word = raw.replace(/[\\t\\n\\r]/g, '').replace(/^[\\0- ]+|[\\0- ]+$/g, '');\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, matcher: ProtectedMatcher): boolean {\n const all = words(command);\n if (!all.some(browserCommand)) return false;\n return all.some((word) => candidates(word).some((url) => forbidden(url, matcher)));\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 const matcher = protectedMatcher(origins);\n return defineExtension({\n name: BROWSER_GUARD,\n hooks: [\n hook(ToolTask, {\n beforeTool: (call) =>\n call.name === bash.name && refused(commandOf(call.arguments), matcher)\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), matcher)) 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": "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",
6
6
  "names": []
7
7
  }
@@ -1,41 +1,11 @@
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));
1
+ import catalog from "@ai-ecoverse/slicc-browsers/catalog.json" with { type: "json" };
2
+ import { protectedOrigins } from "@ai-ecoverse/slicc-browsers/protected.mjs";
3
+ const PROTECTED_RULES = catalog.protectedOrigins;
4
+ function protectedMatcher(extra = []) {
5
+ return protectedOrigins(PROTECTED_RULES, [...extra]);
35
6
  }
36
7
  export {
37
- isProtected,
38
- originOf,
39
- protectedOrigins
8
+ PROTECTED_RULES,
9
+ protectedMatcher
40
10
  };
41
11
  //# sourceMappingURL=origins.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
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;",
4
+ "sourcesContent": ["import catalog from '@ai-ecoverse/slicc-browsers/catalog.json' with { type: 'json' };\nimport { protectedOrigins } from '@ai-ecoverse/slicc-browsers/protected.mjs';\n\nexport const PROTECTED_RULES = catalog.protectedOrigins;\n\nexport interface ProtectedMatcher {\n url(text: string): boolean;\n target(target: { url: string; previous?: boolean; opener?: string }): boolean;\n cookieDomain(domain: string): boolean;\n storageKey(key: string): boolean;\n}\n\nexport function protectedMatcher(extra: readonly string[] = []): ProtectedMatcher {\n return protectedOrigins(PROTECTED_RULES, [...extra]);\n}\n"],
5
+ "mappings": "AAAA,OAAO,aAAa,2CAA2C,KAAK,EAAE,MAAM,OAAO;AACnF,SAAS,wBAAwB;AAE1B,MAAM,kBAAkB,QAAQ;AAShC,SAAS,iBAAiB,QAA2B,CAAC,GAAqB;AAChF,SAAO,iBAAiB,iBAAiB,CAAC,GAAG,KAAK,CAAC;AACrD;",
6
6
  "names": []
7
7
  }
package/dist/main.js CHANGED
@@ -193,7 +193,7 @@ async function start(scope, ports, options) {
193
193
  });
194
194
  licks.licks.guard(freezer.frozenTarget);
195
195
  const settings = await createAgentSettings(models, credentials, providers, { mcp: mcpRuntime });
196
- const pages = createPages();
196
+ const pages = createPages({ claim: sprinkles.claim });
197
197
  const runtime = await scoops.attach(
198
198
  {
199
199
  harness: agent.harness,
@@ -203,7 +203,6 @@ async function start(scope, ports, options) {
203
203
  home: HOME,
204
204
  alive: alive(client),
205
205
  reads: skillsRuntime.dirs,
206
- sprinkle: sprinkles.command,
207
206
  memory: memoryRuntime.command,
208
207
  gelatiere: gelatiere.command,
209
208
  freezer: freezer.command,
package/dist/main.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/main.ts"],
4
- "sourcesContent": ["import { attachKernel } from '@ai-ecoverse/slicc-kernel';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { CredentialStore, Provider } from '@earendil-works/pi-ai';\nimport {\n createRegistry,\n type ModelRef,\n type Storage,\n type ToolRegistration,\n} from '@earendil-works/pi-durable';\nimport { CodingTools } from '@earendil-works/pi-durable/tools';\nimport { type Agent, openAgent } from './agent.ts';\nimport { browserGuard } from './browser/guard.ts';\nimport { attachChanges, CHANGES_MS } from './changes/index.ts';\nimport {\n codemodeExtension,\n codemodeTool,\n disabledBySettings,\n locateWasm,\n sandboxFactory,\n} from './codemode/index.ts';\nimport { EncryptedCredentialStore } from './credentials.ts';\nimport { attachFreezer } from './freezer/index.ts';\nimport { settingsText, titler } from './freezer/title.ts';\nimport { lateGelatiere } from './gelatiere/index.ts';\nimport { type AgentHost, hostAgent } from './host.ts';\nimport { imageReads } from './images/index.ts';\nimport { createActivity } from './kernel/activity.ts';\nimport type { KernelClient } from './kernel/client.ts';\nimport { HOME, kernelEnvironment, SliccKernelEnv } from './kernel/env.ts';\nimport { processGroups } from './kernel/groups.ts';\nimport { adminOver, type UserClient, type UserClients, userClients } from './kernel/users.ts';\nimport { setupLicks } from './licks/index.ts';\nimport { lickTriage } from './licks/triage.ts';\nimport { setupMcp } from './mcp/index.ts';\nimport { setupMemory } from './memory/index.ts';\nimport { modelsCommand } from './models/index.ts';\nimport { type Transport, transportFetch } from './net.ts';\nimport { sliccPrompt } from './prompt.ts';\nimport { createPages } from './remote/pages.ts';\nimport { identity } from './scoops/identity.ts';\nimport { type Assets, packageAssets, type ScoopsRuntime, setupScoops } from './scoops/index.ts';\nimport {\n createAgentSettings,\n createSliccModels,\n DEFAULT_MODEL,\n sliccProviders,\n} from './settings.ts';\nimport { RELOAD_MS, setupSkills } from './skills/index.ts';\nimport { setupSprinkles } from './sprinkles/index.ts';\nimport { openOpfsSqliteStorage } from './sqlite.ts';\nimport {\n agentVersion,\n browserConnected,\n commandsOnPath,\n kernelBoot,\n transportName,\n} from './system.ts';\nimport { type KernelPorts, kernelPorts, serveConnections, type WorkerScope } from './worker.ts';\n\nexport interface AgentWorkerScope extends WorkerScope {\n fetch: typeof fetch;\n location: { origin: string };\n}\n\nexport type KernelAttach = (port: MessagePort) => Promise<KernelClient & { transport: Transport }>;\n\nexport interface AgentWorkerOptions {\n model?: ModelRef;\n providers?: Provider[];\n attach?: KernelAttach;\n credentials?: () => Promise<CredentialStore>;\n storage?: () => Promise<Storage>;\n assets?: Assets;\n codemodeWasm?: () => Promise<WebAssembly.Module | undefined>;\n codemodeWorker?: string | URL;\n catalog?: string;\n users?: boolean;\n mcpRedirect?: string;\n}\n\nexport function usersOf(\n admin: MessagePort | undefined,\n wanted: boolean | undefined,\n attach: KernelAttach\n): UserClients | undefined {\n if (!admin) return undefined;\n if (wanted === false) {\n admin.close();\n return undefined;\n }\n return userClients(adminOver(admin), attach as (port: MessagePort) => Promise<UserClient>);\n}\n\nexport const STORE_DIR = '/.slicc';\n\nasync function attached(ports: Promise<KernelPorts>, options: AgentWorkerOptions) {\n const attach: KernelAttach = options.attach ?? attachKernel;\n const { kernel, admin } = await ports;\n const client = await attach(kernel);\n return { client, users: usersOf(admin, options.users, attach) };\n}\n\nfunction privateStore(env: SliccKernelEnv): Promise<unknown> {\n return env.exec(`mkdir -p ${STORE_DIR} && chmod 700 ${STORE_DIR}`, undefined, BACKGROUND_CONTEXT);\n}\n\nasync function codemodeTools(\n client: KernelClient,\n options: AgentWorkerOptions,\n native: typeof fetch,\n extra: Parameters<typeof codemodeTool>[0]['extra']\n) {\n const settings = await client.fs\n .readFile(`${HOME}/.pi/agent/settings.json`)\n .catch(() => undefined);\n if (disabledBySettings(settings === undefined ? undefined : new TextDecoder().decode(settings)))\n return [];\n const sandbox = sandboxFactory(\n options.codemodeWasm ?? (() => locateWasm(native, new URL(import.meta.url))),\n options.codemodeWorker\n );\n return [\n codemodeTool({ declared: CodingTools.tools as readonly ToolRegistration[], sandbox, extra }),\n ];\n}\n\nexport const MCP_CALLBACK = '/oauth/mcp-callback';\n\nasync function start(\n scope: AgentWorkerScope,\n ports: Promise<KernelPorts>,\n options: AgentWorkerOptions\n): Promise<AgentHost> {\n const { client, users } = await attached(ports, options);\n const home = new SliccKernelEnv(client, { cwd: HOME });\n await privateStore(home);\n const native = scope.fetch.bind(scope);\n scope.fetch = transportFetch(client.transport, scope.location.origin, native);\n const facts = {\n version: await agentVersion((url) => native(url)),\n commands: await commandsOnPath(client),\n transport: transportName(client.transport),\n browser: await browserConnected(client),\n };\n const credentials = await (options.credentials ?? (() => EncryptedCredentialStore.open()))();\n const providers = options.providers ?? sliccProviders();\n const models = createSliccModels(credentials, providers);\n const registry = createRegistry();\n registry.install(CodingTools);\n registry.install(sliccPrompt(facts));\n registry.install(\n imageReads(\n (CodingTools.tools as readonly ToolRegistration[]).find(\n (tool) => tool.name === 'read'\n ) as ToolRegistration\n )\n );\n const memory = setupMemory(registry);\n const coding = CodingTools.tools as readonly ToolRegistration[];\n registry.install(browserGuard([scope.location.origin], coding));\n const mcp = setupMcp(registry);\n const codemode = await codemodeTools(client, options, native, mcp.codemode);\n registry.install(codemodeExtension(codemode));\n const licks = setupLicks(registry);\n licks.licks.triage(lickTriage(models));\n const scoops = setupScoops(registry, licks.licks, [...coding, ...codemode, ...memory.tools]);\n const skills = setupSkills(registry, licks.licks);\n const activity = createActivity();\n const groups = processGroups(client);\n const model = options.model ?? DEFAULT_MODEL;\n let opened: Agent | undefined;\n const environment = kernelEnvironment(client, {\n activity,\n groups,\n identify: identity(() => opened?.agents, model),\n });\n const agent = await openAgent({\n models,\n model,\n storage: await (options.storage ?? (() => openOpfsSqliteStorage()))(),\n registry,\n env: environment,\n });\n opened = agent;\n const sources = licks.attach(agent, { env: home, home: HOME, activity });\n await sources.start(BACKGROUND_CONTEXT);\n await sources.boot(\n { version: facts.version, boot: await kernelBoot(client) },\n BACKGROUND_CONTEXT\n );\n const skillsRuntime = await skills.attach(\n { env: home, home: HOME, assets: options.assets ?? packageAssets(), reloadMs: RELOAD_MS },\n BACKGROUND_CONTEXT\n );\n const mcpRuntime = await mcp.attach(\n {\n env: home,\n home: HOME,\n credentials,\n licks: licks.licks,\n fetch: scope.fetch,\n version: facts.version,\n reloadMs: RELOAD_MS,\n codemode: codemode.length > 0,\n cors: client.transport.traits?.crossOrigin === 'cors',\n redirect: options.mcpRedirect ?? new URL(MCP_CALLBACK, scope.location.origin).href,\n },\n BACKGROUND_CONTEXT\n );\n const gelatiere = lateGelatiere();\n const sprinkles = await setupSprinkles().attach(\n {\n intercept: gelatiere.intercept,\n harness: agent.harness,\n agents: agent.agents,\n licks: licks.licks,\n env: home,\n assets: options.assets ?? packageAssets(),\n reloadMs: RELOAD_MS,\n },\n BACKGROUND_CONTEXT\n );\n let roles: ScoopsRuntime | undefined;\n const memoryRuntime = await memory.attach(\n {\n harness: agent.harness,\n agents: agent.agents,\n env: home,\n home: HOME,\n reloadMs: RELOAD_MS,\n roles: async (using) => roles?.roles(using),\n },\n BACKGROUND_CONTEXT\n );\n const freezer = attachFreezer({\n harness: agent.harness,\n agents: agent.agents,\n env: home,\n extract: memory.extract,\n title: titler(models, settingsText(home, HOME)),\n });\n licks.licks.guard(freezer.frozenTarget);\n const settings = await createAgentSettings(models, credentials, providers, { mcp: mcpRuntime });\n const pages = createPages();\n const runtime = await scoops.attach(\n {\n harness: agent.harness,\n agents: agent.agents,\n groups,\n env: home,\n home: HOME,\n alive: alive(client),\n reads: skillsRuntime.dirs,\n sprinkle: sprinkles.command,\n memory: memoryRuntime.command,\n gelatiere: gelatiere.command,\n freezer: freezer.command,\n models: modelsCommand({ agents: agent.agents, settings, catalog: models }),\n panel: pages.command,\n mcp: mcp.direct,\n ...(options.assets ? { assets: options.assets } : {}),\n },\n BACKGROUND_CONTEXT\n );\n roles = runtime;\n gelatiere.attach({\n agents: agent.agents,\n licks: licks.licks,\n env: home,\n home: HOME,\n scoops: scoops.scoops,\n roles: runtime.roles,\n assets: options.assets ?? packageAssets(),\n fetch: scope.fetch,\n skills: skillsRuntime.skills,\n ...(options.catalog ? { catalog: options.catalog } : {}),\n });\n await memoryRuntime.reload(BACKGROUND_CONTEXT).catch(() => undefined);\n const changes = attachChanges({ agents: agent.agents, env: home, reloadMs: CHANGES_MS });\n return hostAgent(agent, {\n settings,\n licks: { licks: licks.licks, sources },\n scoops: { scoops: scoops.scoops, runtime },\n skills: skillsRuntime,\n sprinkles,\n memory: memoryRuntime,\n freezer,\n changes,\n pages,\n mcp: mcpRuntime,\n ...(users ? { users } : {}),\n monitor: {\n kernel: client,\n groups,\n ...(users ? { users: () => users.users.list() } : {}),\n secrets: () => mcpRuntime.secrets(),\n },\n });\n}\n\nexport function alive(client: KernelClient) {\n return async (pid: number): Promise<boolean> => {\n if (!client.ps) return true;\n return (await client.ps()).some((process) => process.pid === pid);\n };\n}\n\nexport function runAgentWorker(\n scope: AgentWorkerScope,\n options: AgentWorkerOptions = {}\n): Promise<AgentHost> {\n const host = start(scope, kernelPorts(scope), options);\n host.catch(() => undefined);\n serveConnections(scope, host);\n return host;\n}\n"],
5
- "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AAEnC;AAAA,EACE;AAAA,OAIK;AACP,SAAS,mBAAmB;AAC5B,SAAqB,iBAAiB;AACtC,SAAS,oBAAoB;AAC7B,SAAS,eAAe,kBAAkB;AAC1C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,gCAAgC;AACzC,SAAS,qBAAqB;AAC9B,SAAS,cAAc,cAAc;AACrC,SAAS,qBAAqB;AAC9B,SAAyB,iBAAiB;AAC1C,SAAS,kBAAkB;AAC3B,SAAS,sBAAsB;AAE/B,SAAS,MAAM,mBAAmB,sBAAsB;AACxD,SAAS,qBAAqB;AAC9B,SAAS,WAA8C,mBAAmB;AAC1E,SAAS,kBAAkB;AAC3B,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB;AACzB,SAAS,mBAAmB;AAC5B,SAAS,qBAAqB;AAC9B,SAAyB,sBAAsB;AAC/C,SAAS,mBAAmB;AAC5B,SAAS,mBAAmB;AAC5B,SAAS,gBAAgB;AACzB,SAAsB,eAAmC,mBAAmB;AAC5E;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,WAAW,mBAAmB;AACvC,SAAS,sBAAsB;AAC/B,SAAS,6BAA6B;AACtC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAA2B,aAAa,wBAA0C;AAuB3E,SAAS,QACd,OACA,QACA,QACyB;AACzB,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI,WAAW,OAAO;AACpB,UAAM,MAAM;AACZ,WAAO;AAAA,EACT;AACA,SAAO,YAAY,UAAU,KAAK,GAAG,MAAoD;AAC3F;AAEO,MAAM,YAAY;AAEzB,eAAe,SAAS,OAA6B,SAA6B;AAChF,QAAM,SAAuB,QAAQ,UAAU;AAC/C,QAAM,EAAE,QAAQ,MAAM,IAAI,MAAM;AAChC,QAAM,SAAS,MAAM,OAAO,MAAM;AAClC,SAAO,EAAE,QAAQ,OAAO,QAAQ,OAAO,QAAQ,OAAO,MAAM,EAAE;AAChE;AAEA,SAAS,aAAa,KAAuC;AAC3D,SAAO,IAAI,KAAK,YAAY,SAAS,iBAAiB,SAAS,IAAI,QAAW,kBAAkB;AAClG;AAEA,eAAe,cACb,QACA,SACA,QACA,OACA;AACA,QAAM,WAAW,MAAM,OAAO,GAC3B,SAAS,GAAG,IAAI,0BAA0B,EAC1C,MAAM,MAAM,MAAS;AACxB,MAAI,mBAAmB,aAAa,SAAY,SAAY,IAAI,YAAY,EAAE,OAAO,QAAQ,CAAC;AAC5F,WAAO,CAAC;AACV,QAAM,UAAU;AAAA,IACd,QAAQ,iBAAiB,MAAM,WAAW,QAAQ,IAAI,IAAI,YAAY,GAAG,CAAC;AAAA,IAC1E,QAAQ;AAAA,EACV;AACA,SAAO;AAAA,IACL,aAAa,EAAE,UAAU,YAAY,OAAsC,SAAS,MAAM,CAAC;AAAA,EAC7F;AACF;AAEO,MAAM,eAAe;AAE5B,eAAe,MACb,OACA,OACA,SACoB;AACpB,QAAM,EAAE,QAAQ,MAAM,IAAI,MAAM,SAAS,OAAO,OAAO;AACvD,QAAM,OAAO,IAAI,eAAe,QAAQ,EAAE,KAAK,KAAK,CAAC;AACrD,QAAM,aAAa,IAAI;AACvB,QAAM,SAAS,MAAM,MAAM,KAAK,KAAK;AACrC,QAAM,QAAQ,eAAe,OAAO,WAAW,MAAM,SAAS,QAAQ,MAAM;AAC5E,QAAM,QAAQ;AAAA,IACZ,SAAS,MAAM,aAAa,CAAC,QAAQ,OAAO,GAAG,CAAC;AAAA,IAChD,UAAU,MAAM,eAAe,MAAM;AAAA,IACrC,WAAW,cAAc,OAAO,SAAS;AAAA,IACzC,SAAS,MAAM,iBAAiB,MAAM;AAAA,EACxC;AACA,QAAM,cAAc,OAAO,QAAQ,gBAAgB,MAAM,yBAAyB,KAAK,IAAI;AAC3F,QAAM,YAAY,QAAQ,aAAa,eAAe;AACtD,QAAM,SAAS,kBAAkB,aAAa,SAAS;AACvD,QAAM,WAAW,eAAe;AAChC,WAAS,QAAQ,WAAW;AAC5B,WAAS,QAAQ,YAAY,KAAK,CAAC;AACnC,WAAS;AAAA,IACP;AAAA,MACG,YAAY,MAAsC;AAAA,QACjD,CAAC,SAAS,KAAK,SAAS;AAAA,MAC1B;AAAA,IACF;AAAA,EACF;AACA,QAAM,SAAS,YAAY,QAAQ;AACnC,QAAM,SAAS,YAAY;AAC3B,WAAS,QAAQ,aAAa,CAAC,MAAM,SAAS,MAAM,GAAG,MAAM,CAAC;AAC9D,QAAM,MAAM,SAAS,QAAQ;AAC7B,QAAM,WAAW,MAAM,cAAc,QAAQ,SAAS,QAAQ,IAAI,QAAQ;AAC1E,WAAS,QAAQ,kBAAkB,QAAQ,CAAC;AAC5C,QAAM,QAAQ,WAAW,QAAQ;AACjC,QAAM,MAAM,OAAO,WAAW,MAAM,CAAC;AACrC,QAAM,SAAS,YAAY,UAAU,MAAM,OAAO,CAAC,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,KAAK,CAAC;AAC3F,QAAM,SAAS,YAAY,UAAU,MAAM,KAAK;AAChD,QAAM,WAAW,eAAe;AAChC,QAAM,SAAS,cAAc,MAAM;AACnC,QAAM,QAAQ,QAAQ,SAAS;AAC/B,MAAI;AACJ,QAAM,cAAc,kBAAkB,QAAQ;AAAA,IAC5C;AAAA,IACA;AAAA,IACA,UAAU,SAAS,MAAM,QAAQ,QAAQ,KAAK;AAAA,EAChD,CAAC;AACD,QAAM,QAAQ,MAAM,UAAU;AAAA,IAC5B;AAAA,IACA;AAAA,IACA,SAAS,OAAO,QAAQ,YAAY,MAAM,sBAAsB,IAAI;AAAA,IACpE;AAAA,IACA,KAAK;AAAA,EACP,CAAC;AACD,WAAS;AACT,QAAM,UAAU,MAAM,OAAO,OAAO,EAAE,KAAK,MAAM,MAAM,MAAM,SAAS,CAAC;AACvE,QAAM,QAAQ,MAAM,kBAAkB;AACtC,QAAM,QAAQ;AAAA,IACZ,EAAE,SAAS,MAAM,SAAS,MAAM,MAAM,WAAW,MAAM,EAAE;AAAA,IACzD;AAAA,EACF;AACA,QAAM,gBAAgB,MAAM,OAAO;AAAA,IACjC,EAAE,KAAK,MAAM,MAAM,MAAM,QAAQ,QAAQ,UAAU,cAAc,GAAG,UAAU,UAAU;AAAA,IACxF;AAAA,EACF;AACA,QAAM,aAAa,MAAM,IAAI;AAAA,IAC3B;AAAA,MACE,KAAK;AAAA,MACL,MAAM;AAAA,MACN;AAAA,MACA,OAAO,MAAM;AAAA,MACb,OAAO,MAAM;AAAA,MACb,SAAS,MAAM;AAAA,MACf,UAAU;AAAA,MACV,UAAU,SAAS,SAAS;AAAA,MAC5B,MAAM,OAAO,UAAU,QAAQ,gBAAgB;AAAA,MAC/C,UAAU,QAAQ,eAAe,IAAI,IAAI,cAAc,MAAM,SAAS,MAAM,EAAE;AAAA,IAChF;AAAA,IACA;AAAA,EACF;AACA,QAAM,YAAY,cAAc;AAChC,QAAM,YAAY,MAAM,eAAe,EAAE;AAAA,IACvC;AAAA,MACE,WAAW,UAAU;AAAA,MACrB,SAAS,MAAM;AAAA,MACf,QAAQ,MAAM;AAAA,MACd,OAAO,MAAM;AAAA,MACb,KAAK;AAAA,MACL,QAAQ,QAAQ,UAAU,cAAc;AAAA,MACxC,UAAU;AAAA,IACZ;AAAA,IACA;AAAA,EACF;AACA,MAAI;AACJ,QAAM,gBAAgB,MAAM,OAAO;AAAA,IACjC;AAAA,MACE,SAAS,MAAM;AAAA,MACf,QAAQ,MAAM;AAAA,MACd,KAAK;AAAA,MACL,MAAM;AAAA,MACN,UAAU;AAAA,MACV,OAAO,OAAO,UAAU,OAAO,MAAM,KAAK;AAAA,IAC5C;AAAA,IACA;AAAA,EACF;AACA,QAAM,UAAU,cAAc;AAAA,IAC5B,SAAS,MAAM;AAAA,IACf,QAAQ,MAAM;AAAA,IACd,KAAK;AAAA,IACL,SAAS,OAAO;AAAA,IAChB,OAAO,OAAO,QAAQ,aAAa,MAAM,IAAI,CAAC;AAAA,EAChD,CAAC;AACD,QAAM,MAAM,MAAM,QAAQ,YAAY;AACtC,QAAM,WAAW,MAAM,oBAAoB,QAAQ,aAAa,WAAW,EAAE,KAAK,WAAW,CAAC;AAC9F,QAAM,QAAQ,YAAY;AAC1B,QAAM,UAAU,MAAM,OAAO;AAAA,IAC3B;AAAA,MACE,SAAS,MAAM;AAAA,MACf,QAAQ,MAAM;AAAA,MACd;AAAA,MACA,KAAK;AAAA,MACL,MAAM;AAAA,MACN,OAAO,MAAM,MAAM;AAAA,MACnB,OAAO,cAAc;AAAA,MACrB,UAAU,UAAU;AAAA,MACpB,QAAQ,cAAc;AAAA,MACtB,WAAW,UAAU;AAAA,MACrB,SAAS,QAAQ;AAAA,MACjB,QAAQ,cAAc,EAAE,QAAQ,MAAM,QAAQ,UAAU,SAAS,OAAO,CAAC;AAAA,MACzE,OAAO,MAAM;AAAA,MACb,KAAK,IAAI;AAAA,MACT,GAAI,QAAQ,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;AAAA,IACrD;AAAA,IACA;AAAA,EACF;AACA,UAAQ;AACR,YAAU,OAAO;AAAA,IACf,QAAQ,MAAM;AAAA,IACd,OAAO,MAAM;AAAA,IACb,KAAK;AAAA,IACL,MAAM;AAAA,IACN,QAAQ,OAAO;AAAA,IACf,OAAO,QAAQ;AAAA,IACf,QAAQ,QAAQ,UAAU,cAAc;AAAA,IACxC,OAAO,MAAM;AAAA,IACb,QAAQ,cAAc;AAAA,IACtB,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;AAAA,EACxD,CAAC;AACD,QAAM,cAAc,OAAO,kBAAkB,EAAE,MAAM,MAAM,MAAS;AACpE,QAAM,UAAU,cAAc,EAAE,QAAQ,MAAM,QAAQ,KAAK,MAAM,UAAU,WAAW,CAAC;AACvF,SAAO,UAAU,OAAO;AAAA,IACtB;AAAA,IACA,OAAO,EAAE,OAAO,MAAM,OAAO,QAAQ;AAAA,IACrC,QAAQ,EAAE,QAAQ,OAAO,QAAQ,QAAQ;AAAA,IACzC,QAAQ;AAAA,IACR;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,IACA,KAAK;AAAA,IACL,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;AAAA,IACzB,SAAS;AAAA,MACP,QAAQ;AAAA,MACR;AAAA,MACA,GAAI,QAAQ,EAAE,OAAO,MAAM,MAAM,MAAM,KAAK,EAAE,IAAI,CAAC;AAAA,MACnD,SAAS,MAAM,WAAW,QAAQ;AAAA,IACpC;AAAA,EACF,CAAC;AACH;AAEO,SAAS,MAAM,QAAsB;AAC1C,SAAO,OAAO,QAAkC;AAC9C,QAAI,CAAC,OAAO,GAAI,QAAO;AACvB,YAAQ,MAAM,OAAO,GAAG,GAAG,KAAK,CAAC,YAAY,QAAQ,QAAQ,GAAG;AAAA,EAClE;AACF;AAEO,SAAS,eACd,OACA,UAA8B,CAAC,GACX;AACpB,QAAM,OAAO,MAAM,OAAO,YAAY,KAAK,GAAG,OAAO;AACrD,OAAK,MAAM,MAAM,MAAS;AAC1B,mBAAiB,OAAO,IAAI;AAC5B,SAAO;AACT;",
4
+ "sourcesContent": ["import { attachKernel } from '@ai-ecoverse/slicc-kernel';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { CredentialStore, Provider } from '@earendil-works/pi-ai';\nimport {\n createRegistry,\n type ModelRef,\n type Storage,\n type ToolRegistration,\n} from '@earendil-works/pi-durable';\nimport { CodingTools } from '@earendil-works/pi-durable/tools';\nimport { type Agent, openAgent } from './agent.ts';\nimport { browserGuard } from './browser/guard.ts';\nimport { attachChanges, CHANGES_MS } from './changes/index.ts';\nimport {\n codemodeExtension,\n codemodeTool,\n disabledBySettings,\n locateWasm,\n sandboxFactory,\n} from './codemode/index.ts';\nimport { EncryptedCredentialStore } from './credentials.ts';\nimport { attachFreezer } from './freezer/index.ts';\nimport { settingsText, titler } from './freezer/title.ts';\nimport { lateGelatiere } from './gelatiere/index.ts';\nimport { type AgentHost, hostAgent } from './host.ts';\nimport { imageReads } from './images/index.ts';\nimport { createActivity } from './kernel/activity.ts';\nimport type { KernelClient } from './kernel/client.ts';\nimport { HOME, kernelEnvironment, SliccKernelEnv } from './kernel/env.ts';\nimport { processGroups } from './kernel/groups.ts';\nimport { adminOver, type UserClient, type UserClients, userClients } from './kernel/users.ts';\nimport { setupLicks } from './licks/index.ts';\nimport { lickTriage } from './licks/triage.ts';\nimport { setupMcp } from './mcp/index.ts';\nimport { setupMemory } from './memory/index.ts';\nimport { modelsCommand } from './models/index.ts';\nimport { type Transport, transportFetch } from './net.ts';\nimport { sliccPrompt } from './prompt.ts';\nimport { createPages } from './remote/pages.ts';\nimport { identity } from './scoops/identity.ts';\nimport { type Assets, packageAssets, type ScoopsRuntime, setupScoops } from './scoops/index.ts';\nimport {\n createAgentSettings,\n createSliccModels,\n DEFAULT_MODEL,\n sliccProviders,\n} from './settings.ts';\nimport { RELOAD_MS, setupSkills } from './skills/index.ts';\nimport { setupSprinkles } from './sprinkles/index.ts';\nimport { openOpfsSqliteStorage } from './sqlite.ts';\nimport {\n agentVersion,\n browserConnected,\n commandsOnPath,\n kernelBoot,\n transportName,\n} from './system.ts';\nimport { type KernelPorts, kernelPorts, serveConnections, type WorkerScope } from './worker.ts';\n\nexport interface AgentWorkerScope extends WorkerScope {\n fetch: typeof fetch;\n location: { origin: string };\n}\n\nexport type KernelAttach = (port: MessagePort) => Promise<KernelClient & { transport: Transport }>;\n\nexport interface AgentWorkerOptions {\n model?: ModelRef;\n providers?: Provider[];\n attach?: KernelAttach;\n credentials?: () => Promise<CredentialStore>;\n storage?: () => Promise<Storage>;\n assets?: Assets;\n codemodeWasm?: () => Promise<WebAssembly.Module | undefined>;\n codemodeWorker?: string | URL;\n catalog?: string;\n users?: boolean;\n mcpRedirect?: string;\n}\n\nexport function usersOf(\n admin: MessagePort | undefined,\n wanted: boolean | undefined,\n attach: KernelAttach\n): UserClients | undefined {\n if (!admin) return undefined;\n if (wanted === false) {\n admin.close();\n return undefined;\n }\n return userClients(adminOver(admin), attach as (port: MessagePort) => Promise<UserClient>);\n}\n\nexport const STORE_DIR = '/.slicc';\n\nasync function attached(ports: Promise<KernelPorts>, options: AgentWorkerOptions) {\n const attach: KernelAttach = options.attach ?? attachKernel;\n const { kernel, admin } = await ports;\n const client = await attach(kernel);\n return { client, users: usersOf(admin, options.users, attach) };\n}\n\nfunction privateStore(env: SliccKernelEnv): Promise<unknown> {\n return env.exec(`mkdir -p ${STORE_DIR} && chmod 700 ${STORE_DIR}`, undefined, BACKGROUND_CONTEXT);\n}\n\nasync function codemodeTools(\n client: KernelClient,\n options: AgentWorkerOptions,\n native: typeof fetch,\n extra: Parameters<typeof codemodeTool>[0]['extra']\n) {\n const settings = await client.fs\n .readFile(`${HOME}/.pi/agent/settings.json`)\n .catch(() => undefined);\n if (disabledBySettings(settings === undefined ? undefined : new TextDecoder().decode(settings)))\n return [];\n const sandbox = sandboxFactory(\n options.codemodeWasm ?? (() => locateWasm(native, new URL(import.meta.url))),\n options.codemodeWorker\n );\n return [\n codemodeTool({ declared: CodingTools.tools as readonly ToolRegistration[], sandbox, extra }),\n ];\n}\n\nexport const MCP_CALLBACK = '/oauth/mcp-callback';\n\nasync function start(\n scope: AgentWorkerScope,\n ports: Promise<KernelPorts>,\n options: AgentWorkerOptions\n): Promise<AgentHost> {\n const { client, users } = await attached(ports, options);\n const home = new SliccKernelEnv(client, { cwd: HOME });\n await privateStore(home);\n const native = scope.fetch.bind(scope);\n scope.fetch = transportFetch(client.transport, scope.location.origin, native);\n const facts = {\n version: await agentVersion((url) => native(url)),\n commands: await commandsOnPath(client),\n transport: transportName(client.transport),\n browser: await browserConnected(client),\n };\n const credentials = await (options.credentials ?? (() => EncryptedCredentialStore.open()))();\n const providers = options.providers ?? sliccProviders();\n const models = createSliccModels(credentials, providers);\n const registry = createRegistry();\n registry.install(CodingTools);\n registry.install(sliccPrompt(facts));\n registry.install(\n imageReads(\n (CodingTools.tools as readonly ToolRegistration[]).find(\n (tool) => tool.name === 'read'\n ) as ToolRegistration\n )\n );\n const memory = setupMemory(registry);\n const coding = CodingTools.tools as readonly ToolRegistration[];\n registry.install(browserGuard([scope.location.origin], coding));\n const mcp = setupMcp(registry);\n const codemode = await codemodeTools(client, options, native, mcp.codemode);\n registry.install(codemodeExtension(codemode));\n const licks = setupLicks(registry);\n licks.licks.triage(lickTriage(models));\n const scoops = setupScoops(registry, licks.licks, [...coding, ...codemode, ...memory.tools]);\n const skills = setupSkills(registry, licks.licks);\n const activity = createActivity();\n const groups = processGroups(client);\n const model = options.model ?? DEFAULT_MODEL;\n let opened: Agent | undefined;\n const environment = kernelEnvironment(client, {\n activity,\n groups,\n identify: identity(() => opened?.agents, model),\n });\n const agent = await openAgent({\n models,\n model,\n storage: await (options.storage ?? (() => openOpfsSqliteStorage()))(),\n registry,\n env: environment,\n });\n opened = agent;\n const sources = licks.attach(agent, { env: home, home: HOME, activity });\n await sources.start(BACKGROUND_CONTEXT);\n await sources.boot(\n { version: facts.version, boot: await kernelBoot(client) },\n BACKGROUND_CONTEXT\n );\n const skillsRuntime = await skills.attach(\n { env: home, home: HOME, assets: options.assets ?? packageAssets(), reloadMs: RELOAD_MS },\n BACKGROUND_CONTEXT\n );\n const mcpRuntime = await mcp.attach(\n {\n env: home,\n home: HOME,\n credentials,\n licks: licks.licks,\n fetch: scope.fetch,\n version: facts.version,\n reloadMs: RELOAD_MS,\n codemode: codemode.length > 0,\n cors: client.transport.traits?.crossOrigin === 'cors',\n redirect: options.mcpRedirect ?? new URL(MCP_CALLBACK, scope.location.origin).href,\n },\n BACKGROUND_CONTEXT\n );\n const gelatiere = lateGelatiere();\n const sprinkles = await setupSprinkles().attach(\n {\n intercept: gelatiere.intercept,\n harness: agent.harness,\n agents: agent.agents,\n licks: licks.licks,\n env: home,\n assets: options.assets ?? packageAssets(),\n reloadMs: RELOAD_MS,\n },\n BACKGROUND_CONTEXT\n );\n let roles: ScoopsRuntime | undefined;\n const memoryRuntime = await memory.attach(\n {\n harness: agent.harness,\n agents: agent.agents,\n env: home,\n home: HOME,\n reloadMs: RELOAD_MS,\n roles: async (using) => roles?.roles(using),\n },\n BACKGROUND_CONTEXT\n );\n const freezer = attachFreezer({\n harness: agent.harness,\n agents: agent.agents,\n env: home,\n extract: memory.extract,\n title: titler(models, settingsText(home, HOME)),\n });\n licks.licks.guard(freezer.frozenTarget);\n const settings = await createAgentSettings(models, credentials, providers, { mcp: mcpRuntime });\n const pages = createPages({ claim: sprinkles.claim });\n const runtime = await scoops.attach(\n {\n harness: agent.harness,\n agents: agent.agents,\n groups,\n env: home,\n home: HOME,\n alive: alive(client),\n reads: skillsRuntime.dirs,\n memory: memoryRuntime.command,\n gelatiere: gelatiere.command,\n freezer: freezer.command,\n models: modelsCommand({ agents: agent.agents, settings, catalog: models }),\n panel: pages.command,\n mcp: mcp.direct,\n ...(options.assets ? { assets: options.assets } : {}),\n },\n BACKGROUND_CONTEXT\n );\n roles = runtime;\n gelatiere.attach({\n agents: agent.agents,\n licks: licks.licks,\n env: home,\n home: HOME,\n scoops: scoops.scoops,\n roles: runtime.roles,\n assets: options.assets ?? packageAssets(),\n fetch: scope.fetch,\n skills: skillsRuntime.skills,\n ...(options.catalog ? { catalog: options.catalog } : {}),\n });\n await memoryRuntime.reload(BACKGROUND_CONTEXT).catch(() => undefined);\n const changes = attachChanges({ agents: agent.agents, env: home, reloadMs: CHANGES_MS });\n return hostAgent(agent, {\n settings,\n licks: { licks: licks.licks, sources },\n scoops: { scoops: scoops.scoops, runtime },\n skills: skillsRuntime,\n sprinkles,\n memory: memoryRuntime,\n freezer,\n changes,\n pages,\n mcp: mcpRuntime,\n ...(users ? { users } : {}),\n monitor: {\n kernel: client,\n groups,\n ...(users ? { users: () => users.users.list() } : {}),\n secrets: () => mcpRuntime.secrets(),\n },\n });\n}\n\nexport function alive(client: KernelClient) {\n return async (pid: number): Promise<boolean> => {\n if (!client.ps) return true;\n return (await client.ps()).some((process) => process.pid === pid);\n };\n}\n\nexport function runAgentWorker(\n scope: AgentWorkerScope,\n options: AgentWorkerOptions = {}\n): Promise<AgentHost> {\n const host = start(scope, kernelPorts(scope), options);\n host.catch(() => undefined);\n serveConnections(scope, host);\n return host;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -41,6 +41,52 @@ function noAnswer(cone) {
41
41
  code: 4
42
42
  };
43
43
  }
44
+ function noSuchTab(tab, cone) {
45
+ const err = cone === null ? `panel: there is no open SLICC tab ${tab}; panel tabs lists them
46
+ ` : `panel: no open SLICC tab ${tab} shows cone ${cone}; panel tabs lists the ones that do
47
+ `;
48
+ return { code: 3, out: "", err };
49
+ }
50
+ const usage = (err) => ({ code: 2, out: "", err: `panel: ${err}
51
+ ` });
52
+ function tabFlag(argv) {
53
+ const rest = [];
54
+ let tab;
55
+ for (let at = 0; at < argv.length; at++) {
56
+ const arg = argv[at];
57
+ if (arg === "--tab") {
58
+ const value = argv[++at];
59
+ if (!value) return usage("--tab needs a tab id; panel tabs lists them");
60
+ tab = value;
61
+ } else if (arg.startsWith("--tab=")) {
62
+ if (arg === "--tab=") return usage("--tab needs a tab id; panel tabs lists them");
63
+ tab = arg.slice("--tab=".length);
64
+ } else rest.push(arg);
65
+ }
66
+ return tab === void 0 ? { argv: rest } : { argv: rest, tab };
67
+ }
68
+ function ago(lastActive, now) {
69
+ return lastActive ? `${Math.max(0, Math.round((now - lastActive) / 1e3))}s ago` : "never";
70
+ }
71
+ function tabsOf(argv, reachable, now) {
72
+ const json = argv.includes("--json");
73
+ if (argv.some((arg) => arg !== "--json")) return usage("usage: panel tabs [--json]");
74
+ const rows = reachable.sort((a, b) => rank(b) - rank(a) || b.tab.lastActive - a.tab.lastActive || b.serial - a.serial).map(({ tab }) => ({
75
+ tab: tab.tab,
76
+ cone: tab.cone,
77
+ visible: tab.visible,
78
+ focused: tab.focused,
79
+ lastActive: tab.lastActive
80
+ }));
81
+ if (json) return { code: 0, out: `${JSON.stringify(rows, null, 2)}
82
+ ` };
83
+ if (!rows.length) return { code: 0, out: "no tabs\n" };
84
+ const lines = rows.map(
85
+ (row) => `${row.tab} ${row.cone ?? "-"} ${row.visible ? "visible" : "hidden"} ${row.focused ? "focused" : "-"} ${ago(row.lastActive, now)}`
86
+ );
87
+ return { code: 0, out: `${lines.join("\n")}
88
+ ` };
89
+ }
44
90
  function ownedPages(pages) {
45
91
  const mine = /* @__PURE__ */ new Set();
46
92
  return {
@@ -98,7 +144,9 @@ function createPages(options = {}) {
98
144
  };
99
145
  const choose = (ask) => {
100
146
  const live = [...entries.values()].filter((entry) => entry.tab.kinds.includes(ask.kind));
101
- const named = ask.tab ? live.filter((entry) => entry.tab.tab === ask.tab).sort((a, b) => b.serial - a.serial)[0] : void 0;
147
+ const named = ask.tab ? live.filter(
148
+ (entry) => entry.tab.tab === ask.tab && (ask.agentId === null || entry.tab.cone === ask.cone)
149
+ ).sort((a, b) => b.serial - a.serial)[0] : void 0;
102
150
  if (named) return named;
103
151
  return live.filter((entry) => entry.tab.cone === ask.cone).sort(
104
152
  (a, b) => rank(b) - rank(a) || b.tab.lastActive - a.tab.lastActive || b.serial - a.serial
@@ -189,8 +237,26 @@ function createPages(options = {}) {
189
237
  drop(id);
190
238
  },
191
239
  request,
192
- async command(argv, caller) {
193
- const result = await request({ kind: "panel", argv, ...caller });
240
+ async command(argv, caller, context) {
241
+ const parsed = tabFlag(argv);
242
+ if ("code" in parsed) return parsed;
243
+ sweep();
244
+ const reachable = [...entries.values()].filter(
245
+ (entry) => entry.tab.kinds.includes("panel") && (caller.agentId === null || entry.tab.cone === caller.cone)
246
+ );
247
+ if (parsed.argv[0] === "tabs") return tabsOf(parsed.argv.slice(1), reachable, now());
248
+ if (parsed.tab !== void 0 && !reachable.some((entry) => entry.tab.tab === parsed.tab))
249
+ return noSuchTab(parsed.tab, caller.agentId === null ? null : caller.cone);
250
+ const [verb, id] = parsed.argv;
251
+ if (caller.agentId !== null && verb === "open" && id?.startsWith("sprinkle:"))
252
+ await options.claim?.(id.slice("sprinkle:".length), caller.cone, context);
253
+ const result = await request({
254
+ kind: "panel",
255
+ argv: parsed.argv,
256
+ agentId: caller.agentId,
257
+ cone: caller.cone,
258
+ tab: parsed.tab ?? caller.tab
259
+ });
194
260
  return { code: result.code, out: result.stdout, err: result.stderr };
195
261
  },
196
262
  tabs: () => [...entries.values()].map((entry) => ({ ...entry.tab })),
@@ -210,7 +276,9 @@ export {
210
276
  PICK_MS,
211
277
  createPages,
212
278
  noAnswer,
279
+ noSuchTab,
213
280
  noTab,
214
- ownedPages
281
+ ownedPages,
282
+ tabFlag
215
283
  };
216
284
  //# sourceMappingURL=pages.js.map