@ai-ecoverse/slicc-agent 6.19.14 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -29
- package/dist/agent.js +10 -1
- package/dist/agent.js.map +2 -2
- package/dist/agents.js +2 -0
- package/dist/agents.js.map +2 -2
- package/dist/approvals/policy.js +2 -0
- package/dist/approvals/policy.js.map +2 -2
- package/dist/approvals/record.js +2 -0
- package/dist/approvals/record.js.map +2 -2
- package/dist/checkpoints.js +5 -0
- package/dist/checkpoints.js.map +7 -0
- package/dist/codemode/index.js +2 -0
- package/dist/codemode/index.js.map +2 -2
- package/dist/cone.js +2 -0
- package/dist/cone.js.map +2 -2
- package/dist/guests/guard.js +3 -0
- package/dist/guests/guard.js.map +2 -2
- package/dist/guests/review.js +4 -1
- package/dist/guests/review.js.map +2 -2
- package/dist/guests/scoop.js +3 -0
- package/dist/guests/scoop.js.map +2 -2
- package/dist/guests/seats.js +2 -0
- package/dist/guests/seats.js.map +2 -2
- package/dist/guests/tags.js +2 -0
- package/dist/guests/tags.js.map +2 -2
- package/dist/licks/boot.js +2 -0
- package/dist/licks/boot.js.map +2 -2
- package/dist/licks/licks.js +2 -0
- package/dist/licks/licks.js.map +2 -2
- package/dist/mcp/allow.js +2 -0
- package/dist/mcp/allow.js.map +2 -2
- package/dist/memory/extract.js +2 -0
- package/dist/memory/extract.js.map +2 -2
- package/dist/monitor/index.js +3 -1
- package/dist/monitor/index.js.map +2 -2
- package/dist/pause.js +26 -0
- package/dist/pause.js.map +7 -0
- package/dist/preferred.js +6 -6
- package/dist/preferred.js.map +1 -1
- package/dist/scoops/extension.js +2 -1
- package/dist/scoops/extension.js.map +2 -2
- package/dist/scoops/service.js +2 -0
- package/dist/scoops/service.js.map +2 -2
- package/dist/secrets/legacy.js +2 -0
- package/dist/secrets/legacy.js.map +2 -2
- package/dist/selection.js +2 -0
- package/dist/selection.js.map +2 -2
- package/dist/services.js.map +2 -2
- package/dist/spectrum/monitor-port.js +10 -2
- package/dist/spectrum/monitor-port.js.map +2 -2
- package/dist/sprinkles/sprinkles.js +2 -0
- package/dist/sprinkles/sprinkles.js.map +2 -2
- package/dist/titles/labels.js +2 -0
- package/dist/titles/labels.js.map +2 -2
- package/dist/types/agent.d.ts +4 -0
- package/dist/types/checkpoints.d.ts +1 -0
- package/dist/types/pause.d.ts +6 -0
- package/dist/types/services.d.ts +4 -2
- package/dist/workflow/run.js +5 -0
- package/dist/workflow/run.js.map +2 -2
- package/package.json +12 -12
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ await agent.prompt('What is in /home?');
|
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
- `openAgent({ models, model, storage?, registry?, settings? })` opens a durable Harness (in memory unless `storage` is given) and its root conversation, and resumes work a previous worker left unfinished.
|
|
30
|
-
- `openOpfsSqliteStorage({ directory?, file? })` keeps the session in wasm SQLite ([`@sqlite.org/sqlite-wasm`](https://www.npmjs.com/package/@sqlite.org/sqlite-wasm)) in an `opfs-sahpool` pool, by default `/.slicc/agent/` in OPFS. It needs a dedicated worker. Only one worker holds a pool at a time: the opener takes a Web Lock per directory and waits until every file of the pool can be opened before sqlite-wasm installs it, because a failed install deletes the pool's directory. `openMemorySqliteStorage()` keeps the same database in memory.
|
|
30
|
+
- `openOpfsSqliteStorage({ directory?, file? })` keeps the session in wasm SQLite ([`@sqlite.org/sqlite-wasm`](https://www.npmjs.com/package/@sqlite.org/sqlite-wasm)) in an `opfs-sahpool` pool, by default `/.slicc/agent/` in OPFS. It needs a dedicated worker. Only one worker holds a pool at a time: the opener takes a Web Lock per directory and waits until every file of the pool can be opened before sqlite-wasm installs it, because a failed install deletes the pool's directory. `openMemorySqliteStorage()` keeps the same database in memory. On pi durable 1.4, opening a database written by an earlier agent migrates it to durable's SQLite schema 2, once and in place; an agent on pi durable 1.3 or earlier (slicc-agent 6.19.13 and earlier) can't open it afterwards, so the agent can't be rolled back past this version. Requests through the Harness are attributed to SLICC (`identity: { attribution: { name: 'SLICC', url: 'https://www.sliccy.com' } }`), which pi-ai sends as OpenRouter's `HTTP-Referer` and `X-OpenRouter-Title`, NVIDIA's `X-BILLING-INVOKE-ORIGIN` and OpenCode's `x-opencode-client`; OpenRouter's preflight allows them from `https://seven.sliccy.ai`, and NVIDIA and OpenCode answer no browser preflight at all, so they go through a CORS-free transport either way. The Harness keeps one 1.3 behaviour: a task cut off by reloads is never parked (`restartLimit: Infinity`, since a reload ends the worker without closing the session).
|
|
31
31
|
- `hostAgent(agent)` serves the agent from one Chord host named `agent` (`serveAgent` in [`src/host.ts`](src/host.ts)). Its remote contracts, in [`src/services.ts`](src/services.ts), 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), `slicc.agent.settings`, `slicc.agent.remote` (remote control), `slicc.agent.pages` (Panels) and `slicc.agent.guest` (Guest seats). `serveConnections(self, host)` hands it every `MessagePort` the page sends, and each port becomes one Chord link whose principal is a seat: `owner` (a page of the owner, with its own Panels), `control` (a follower with Control), `view` (a follower without) or `guest` (a guest seat, with its id, label and epoch), plus the link's number, so every link gets its own objects. The principal is typed (`AgentPrincipal` in [`src/serving.ts`](src/serving.ts)); the host's own code acts as `{ seat: 'host' }`, which no link gets. Each factory picks a variant per seat. Every contract places its objects on the host `agent` (`hostNamed`), so no caller names a host. The contracts take their methods' arguments as one JSON array, and the client in [`src/serving.ts`](src/serving.ts) turns them back into the `AgentControl`, `AgentSettings`, `AgentRemote` and `AgentPages` interfaces and their replicated states, so callers don't see Chord.
|
|
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.
|
|
@@ -138,7 +138,7 @@ const owner = await startAgent({
|
|
|
138
138
|
|
|
139
139
|
`runAgentWorker(self, options)` is the same with its parts replaceable (model, providers, kernel attach, the account store, storage).
|
|
140
140
|
|
|
141
|
-
**Providers.** `sliccProviders()` registers Bedrock, Adobe and every pi-ai provider that takes a plain API key: Anthropic, OpenAI, Mistral, Groq, DeepSeek, Together, Hugging Face, Xiaomi and its three token plans, Cerebras, OpenRouter, Fireworks, Baseten, Moonshot AI and its China platform, xAI, Meta, Vercel AI Gateway, MiniMax and MiniMax CN, Z.AI and Z.AI Coding CN, NVIDIA, Kimi For Coding, Ant Ling, OpenCode Zen and Go, and the three Qwen token plans (`KEY_PROVIDERS` in `src/providers.ts`, each with its id, pi-ai factory, pi-ai env name, a description, a key URL and whether it answers browsers). Three more take a key and non-secret fields (`auth: 'fields'`), each checked in `connect` and mapped to pi-ai's env: Azure (`endpoint`, required: a resource name becomes `AZURE_OPENAI_RESOURCE_NAME`, an `https:` URL without a port or user name `AZURE_OPENAI_BASE_URL`; `deployments`, optional `model=deployment` pairs, `AZURE_OPENAI_DEPLOYMENT_NAME_MAP`), Cloudflare Workers AI (`account`, 32 hex characters, `CLOUDFLARE_ACCOUNT_ID`) and Cloudflare AI Gateway (`account` and `gateway`, `CLOUDFLARE_GATEWAY_ID`). Bedrock's `region` is on the same mechanism. A bad value rejects `connect` with a sentence, a required field left out keeps its stored value or rejects with “{label} can’t be empty.”, and a stored value that no longer passes is left out of the env. A fields provider whose required fields are missing or no longer pass shows as not connected, gives pi-ai no credential and offers no models, even with its key already in the vault (spectrum puts the key before `connect` checks the fields). Bedrock's region is any AWS region name (`eusc-de-east-1` and `us-gov-west-1` included), and stored values are trimmed. OpenCode Zen and OpenCode Go need seven's vault 1.12.2 or later, whose built-in table has their `/inference` endpoints; their descriptions say so. Azure's `endpoint` field says `customBase: true`: when its value is a full URL, spectrum registers it with the vault (the page's `endpoints`) before `connect`, since the agent never registers model endpoints for pi-ai providers; a resource name needs nothing, as `*.openai.azure.com` is built into the vault. The worker routes requests to an Azure account's base URL as Azure's. Google and Google Vertex wait for slicc-bios to stop removing `@google/genai`; the sign-ins come later. A provider's preferred model is pi-coding-agent 1.
|
|
141
|
+
**Providers.** `sliccProviders()` registers Bedrock, Adobe and every pi-ai provider that takes a plain API key: Anthropic, OpenAI, Mistral, Groq, DeepSeek, Together, Hugging Face, Xiaomi and its three token plans, Cerebras, OpenRouter, Fireworks, Baseten, Moonshot AI and its China platform, xAI, Meta, Vercel AI Gateway, MiniMax and MiniMax CN, Z.AI and Z.AI Coding CN, NVIDIA, Kimi For Coding, Ant Ling, OpenCode Zen and Go, and the three Qwen token plans (`KEY_PROVIDERS` in `src/providers.ts`, each with its id, pi-ai factory, pi-ai env name, a description, a key URL and whether it answers browsers). Three more take a key and non-secret fields (`auth: 'fields'`), each checked in `connect` and mapped to pi-ai's env: Azure (`endpoint`, required: a resource name becomes `AZURE_OPENAI_RESOURCE_NAME`, an `https:` URL without a port or user name `AZURE_OPENAI_BASE_URL`; `deployments`, optional `model=deployment` pairs, `AZURE_OPENAI_DEPLOYMENT_NAME_MAP`), Cloudflare Workers AI (`account`, 32 hex characters, `CLOUDFLARE_ACCOUNT_ID`) and Cloudflare AI Gateway (`account` and `gateway`, `CLOUDFLARE_GATEWAY_ID`). Bedrock's `region` is on the same mechanism. A bad value rejects `connect` with a sentence, a required field left out keeps its stored value or rejects with “{label} can’t be empty.”, and a stored value that no longer passes is left out of the env. A fields provider whose required fields are missing or no longer pass shows as not connected, gives pi-ai no credential and offers no models, even with its key already in the vault (spectrum puts the key before `connect` checks the fields). Bedrock's region is any AWS region name (`eusc-de-east-1` and `us-gov-west-1` included), and stored values are trimmed. OpenCode Zen and OpenCode Go need seven's vault 1.12.2 or later, whose built-in table has their `/inference` endpoints; their descriptions say so. Azure's `endpoint` field says `customBase: true`: when its value is a full URL, spectrum registers it with the vault (the page's `endpoints`) before `connect`, since the agent never registers model endpoints for pi-ai providers; a resource name needs nothing, as `*.openai.azure.com` is built into the vault. The worker routes requests to an Azure account's base URL as Azure's. Google and Google Vertex wait for slicc-bios to stop removing `@google/genai`; the sign-ins come later. A provider's preferred model is pi-coding-agent 1.4.0's `defaultModelPerProvider` (Bedrock keeps `us.anthropic.claude-sonnet-5-5`), and the first model when that one is missing. Providers that don't answer a browser from `https://seven.sliccy.ai` with the headers pi-ai and the AWS SDK send say `needs: 'cors-free-transport'`; the direct ones are Bedrock, Adobe, Anthropic, OpenAI, Azure, Mistral, Groq, DeepSeek, Together, Hugging Face, Baseten, both Moonshot AI platforms, xAI, Meta, Z.AI Coding CN and the four Xiaomi providers. Chrome accepts `Authorization` under a bare `Access-Control-Allow-Headers: *` (as xAI, Meta and Adobe's proxy answer), checked in Chrome 156. The worker's transport fetch drops the Stainless SDKs' `x-stainless-*` telemetry headers from every request it sends, which is what Baseten's and Moonshot AI's preflights refuse.
|
|
142
142
|
|
|
143
143
|
**Reachability, nightly.** `npm run reach` (`scripts/reach.mjs`, through `verdict.mjs`) checks every listing from `https://seven.sliccy.ai`. It captures the request pi-ai builds for each of the provider's APIs (with a dummy key and dummy fields, and the fetch faked; Bedrock's from the AWS SDK's own request, caught at its Node HTTP handler, which carries the same headers as in a browser: `amz-sdk-invocation-id`, `amz-sdk-request`, `authorization`, `content-type`, `x-amz-user-agent`). Then it sends that request's preflight and, when the preflight passes, the request itself with dummy credentials, whose answer must carry `Access-Control-Allow-Origin` too. Besides each chat API it probes what the provider's models need beyond a turn: the requests its model discovery sends under its base URL (Adobe's `/v1/config`), one classifier call per classifier API (Adobe's `/v1/ai/run`, OpenAI's `/v1/decisions`, OpenRouter's and Cloudflare's), and Adobe's `/v1/usage`. It prints `reach: <id> direct|cors-free|unknown|unprobed` per provider (`unknown` when the network gave no answer; `unprobed` when it captured no request at all, which counts as a disagreement, so a provider can't silently drop out of the check) and one `reach: PASS` line. A provider that stays `unknown` three runs in a row counts as a disagreement too: the workflow keeps the streaks in the Actions cache (`REACH_HISTORY`), and the line says how many runs in a row. A direct provider that passes only because Chrome lets `Authorization` through under a bare `Access-Control-Allow-Headers: *` reads `direct (wildcard auth)` (today Adobe, xAI and Meta); the PASS line, the run's summary and the issue body list them, without failing, so we know at once which break if Chrome ever enforces the Fetch spec. `.github/workflows/reach.yml` runs it every night on `ubuntu-latest`, never on pull requests, and opens or updates the issue titled “Provider reachability disagrees with the listings” when a provider's verdict isn't its `needs`, and closes it with a comment once every provider's verdict matches its listing again (a run with an `unknown` provider leaves it open); a run without the PASS line fails.
|
|
144
144
|
|
|
@@ -613,7 +613,7 @@ pi has no git integration and no changes list; pi coding-agent leaves that to th
|
|
|
613
613
|
### The live monitor
|
|
614
614
|
|
|
615
615
|
Spectrum's Monitor panel (`MonitorPort`, opened from the Budget chip) shows the agent's task graph, its usage and the kernel's process table. Most of it comes from pi durable as is:
|
|
616
|
-
- **Tasks:** `harness.watchTaskGraph()` gives every live task with its kind, owner, conversation, state and abort mark.
|
|
616
|
+
- **Tasks:** `harness.watchTaskGraph()` gives every live task with its kind, owner, conversation, state and abort mark, a sleeping task's `until` and a blocked task's reason. The adapter shows a sleeping task with how long it still sleeps (`sleeps 30s`) and a blocked one (`blocked: restart limit`, `blocked: no definition`, …) as a warning. Generation retry backoffs sleep on pi durable 1.4, so a retrying agent's generation task sleeps until its next attempt.
|
|
617
617
|
- **Usage:** `harness.usage()` gives the session's tokens and cost. The cost is pi-ai's, recorded with each response.
|
|
618
618
|
- **Failures, retries and usage changes:** one `subscribeCommits` listener catches them, the way `watchEvents` derives `task_failed`.
|
|
619
619
|
- **Agent state:** each agent's `pi.live`, `pi.inbox` and `pi.agent` come from the views the host already mounts. `inspect()` runs only on a resync.
|
|
@@ -848,7 +848,7 @@ Fixes to pi stay in this repository. [`patches/patches.json`](patches/patches.js
|
|
|
848
848
|
|
|
849
849
|
`npm run lint:patches` (part of `npm run lint`) fails when an installed version differs from `patchedVersion`, a marker is gone, a monkeypatch isn't named in `src/patches.ts`, a patch file and its entry don't match, or `renovate.json` doesn't route the package to the `patched dependencies` group with automerge off. That group covers every `@earendil-works/*` package and `@aws-sdk/middleware-websocket`, so a pi bump is always a reviewed PR: rerun each entry's `verify`, then move `patchedVersion` forward or drop the entry.
|
|
850
850
|
|
|
851
|
-
**One Chord.** `@earendil-works/chord` is pinned to the version pi-durable pins (1.
|
|
851
|
+
**One Chord.** `@earendil-works/chord` is pinned to the version pi-durable pins (1.4.0), so the link, the serving code and pi-durable's state layer share one copy. `npm run lint:chord` (part of `npm run lint`) fails if the lockfile has a second Chord that the agent would load, or none. The `slicc-agent-5` devDependency brings its own Chords 1.2 and 1.3 for the version tests only, which the check skips because it's dev-only.
|
|
852
852
|
|
|
853
853
|
**pi-ai in lockstep with the vault.** The vault fills a model key only under the endpoints of its built-in table, which it generates from its own pi-ai pin and publishes as `"piAi"` in its package.json (slicc-vault 1.12.2 and later). `npm run lint:vault-pi` (part of `npm run lint`) fails when the installed `@ai-ecoverse/slicc-vault`'s `piAi` has another minor than our `@earendil-works/pi-ai`, or when it has none (an older vault). Renovate's `patched dependencies` group carries the `pi-ai-lockstep` label, as the vault's pi-ai updates do, so the two are found and bumped together.
|
|
854
854
|
|
|
@@ -864,28 +864,10 @@ npm run lint
|
|
|
864
864
|
|
|
865
865
|
`npm test` builds `dist/`, bundles a test worker that runs the agent on pi's faux provider, and runs the integration tests in Chromium through the [slicc-shared-web harness](https://github.com/ai-ecoverse/slicc-shared-web#integration-test-harness). Unit tests live in `test/unit/`, which stays out of git; the pre-commit hook requires every changed line in `src/` to be covered. No test calls a real model.
|
|
866
866
|
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
###
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
-
|
|
874
|
-
- **Plan:** packs the base and the pull request merged onto it with `npm pack --dry-run --ignore-scripts`. A change touches behaviour when it changes a file either side ships (`dist`, `bin`, `packages/vfs-root` and whatever `files` lists, so a shipped prompt or agent definition counts), a build input (`src/`, `bin/`, `build.mjs`, `tsconfig*.json`, `patches/`, `package.json` and its lock, `.releaserc*`, `scripts/publish.mjs`), `release.yml` or `ci.yml`, or the reviewer itself.
|
|
875
|
-
- **Not behaviour:** anything else, such as `evals/`, `test/`, `README.md`, `AGENTS.md`, `renovate.json`, `lefthook.yml` and `scripts/check-*.mjs`. It gets a green `not behaviour: <files>`.
|
|
876
|
-
- **Evidence comes by id from the job that made it:** the jobs that run the pull request's code (`pack candidate` builds it; both `suite` legs run its tests) share the run's artifact store with the jobs that don't, and code in them can reach the runner's artifact token. So each producer job hands on its artifact's `artifact-id` and `artifact-digest`, consumers download by `artifact-ids` with `digest-mismatch: error`, no upload uses `overwrite`, and the base build has its own job, `pack base`, which runs only main's code and restores no Actions cache, because the candidate job shares the run's cache scope. `tools/cert-review/artifacts.mjs` lists the run's artifacts and fails on any in `agent-tgz-` or `cert-review-` that its producer didn't upload with that id, name and digest. `same` runs it before it compares the tarballs, and it uploads its evidence before it posts, so "not behaviour (identical dist)" can't be bought with a planted artifact. `record` runs it on everything, and a failure makes the record INCOMPLETE. **Protected:** the base build, the delta, the same-dist evidence and the review's verdict and evidence. **Not protected, by design:** the candidate tarball and both suites' results (the base leg runs the pull request's tests on main's tree), because code on a runner can always lie about its own results. They're taken by exact name, at most one each.
|
|
877
|
-
- **Same dist:** a behaviour change is packed for real on both sides. If the tarballs carry the same files, entry types, modes and bytes once each side's exact version and `gitHead` are normalised (source maps without their `mappings`), and `package.json` differs only in `devDependencies`, `version` and `gitHead`, the status is `not behaviour (identical dist)` with both sha256. A change to the reviewer, `release.yml`, `ci.yml`, `.releaserc*` or `scripts/publish.mjs` always gets the full review.
|
|
878
|
-
- **When it runs:** on every same-repo pull request that isn't a draft: `opened`, `ready_for_review`, `reopened` and every push. Draft status replaces the old `merge-ok` label: **mark a pull request ready = it gets reviewed, and merges on green**; a pull request held for anything stays a draft. A draft that changes shipped behaviour is red with `draft: not reviewed, mark it ready for review` and gets no review; `converted_to_draft` cancels a running review (one concurrency group per pull request) and sets that red status, so an older result on the same head can't satisfy the check. Renovate's pull requests aren't drafts, so they're reviewed on every push like anyone's: a devDependency bump settles as `not behaviour (identical dist)` by itself, and a bump that changes what ships gets the full review. Labels other than `cert-review-approved` don't start or cancel anything.
|
|
879
|
-
- **The full review:**
|
|
880
|
-
- The integration suite runs on the merged tree and on main's tree, on `vars.CERT_RUNNER`, with no secrets in those steps. The agent's tests bundle `src/`, so they run on the tree the tarball was packed from.
|
|
881
|
-
- **The NEGATIVE:** the pull request's `test/` diff is applied to main's tree, and its integration tests must fail there for a fix (a test file that fails to load is never a NEGATIVE). Unit tests stay out of git and out of it.
|
|
882
|
-
- A Claude review on Bedrock reads only that evidence. It has read-only tools, and the key exists only in its step. It writes a verdict file.
|
|
883
|
-
- Code turns the result into the line. Missing evidence (a suite that didn't finish, a missing artifact, a review that left nothing) is `cert-ci: INCOMPLETE`, never certified on the model's judgement. The status reads `certified @ai-ecoverse/slicc-agent PR #<n> at <head>, build <v> sha256=<hex> (…; downstream: not run (slicc-bios#480))`, and a sticky comment carries the review.
|
|
884
|
-
- **Downstream:** a bios/seven boot of the candidate isn't part of it yet. It waits for slicc-bios#480, and the line says so.
|
|
885
|
-
- **The merge group:** looks up each pull request's `cert-review` on the head being merged. Every pull request needs a green one (certified, or not behaviour), or the group is red.
|
|
886
|
-
- **The self-guard:** the shared `cert-self-guard` action, pinned. It holds the reviewer's own paths: `.github/workflows/cert*.yml`, `tools/cert-review/**`, `.github/actions/cert-self-guard/**`, and `.github/runner.mjs`, which picks CI's runner and is loaded from the base. A change to them is red with `reviewer change: needs coordinator review` until the agent coordinator adds `cert-review-approved` to that head. Node's glob `**` skips dot segments, so the guard's paths list the dot forms of each reviewer directory too (`REVIEWER_GLOBS` in `lib.mjs`), and the job's `cert` step reads the guard's receipt and fails closed: a reviewer path the globs still miss (anything under `tools/cert-review/` or the guard's directory, by prefix) without the label on that head is a FAIL, and a PASS without a receipt, or with a member that lacks its `paths` or `touched` list, is INCOMPLETE. Workers never apply that label, which is a procedural rule: every agent acts as the same GitHub account. The guard refuses a bot's label, checks admin or maintain permission and binds the label to the head, so a later push removes it.
|
|
887
|
-
- **Bootstrap:** the pull request that adds this workflow can't pass its own check, because its base has no tools yet.
|
|
888
|
-
- **Releases:** after `publish`, `release / verify on npm` (light runner, no secrets) re-hashes the npm tarball against its SLSA provenance and writes one line into the GitHub release's notes, between `<!-- cert-verify -->` markers. It waits up to an hour for npm to serve the version. A version npm still doesn't serve after that is a "not served yet, re-run this job" failure, never a supply-chain alert. npm serves the attestations a little after the version, so a 404 or a missing `dist.attestations`, an `npm view` or network error while looking for them, and `npm audit signatures`' E404 on them are retried within the same hour. That's a supply-chain alert only once the version has been served for 30 min (counted from the first tarball 200) and its packument has been still for 10 min, or the hour has run out. Any other status, or attestations that don't match, fail at once.
|
|
889
|
-
- The line starts with `certified` only when every pull request it covers was certified, or not behaviour, at its merge. That's read as the newest finished `cert-review` at or before `merged_at`, so a later re-review changes nothing.
|
|
890
|
-
- Otherwise it reads `verified … (pr-certified: not all: …)`, as for pull requests merged before the check was required.
|
|
891
|
-
- Another sha for the same version is a supply-chain alert.
|
|
867
|
+
Locally the harness runs Chrome headless; with `CI` set it runs headed, as on the pipeline's runners, and `SLICC_HEADLESS=1` or `0` overrides either way.
|
|
868
|
+
|
|
869
|
+
### CI and releases
|
|
870
|
+
|
|
871
|
+
`.github/workflows/ci.yml` is the only CI and release workflow: it calls [slicc-shared-web's `pipeline.yml`](https://github.com/ai-ecoverse/slicc-shared-web#pipeline), pinned to the same version as the `@ai-ecoverse/slicc-shared-web` devDependency. A pull request runs `npm run lint` (slicc-lint plus `lint:patches`, `lint:chord` and `lint:vault-pi`), then the integration suite (`test/integration/*.test.mjs`) split over four shards on headed Chrome, then the Claude and Codex reviews on Bedrock with this repo's review prompt; Renovate's `patched dependencies` group (pi and the AWS WebSocket middleware, kept in lockstep with the vault's pi-ai) gets the reviews too, other Renovate pull requests don't. The merge queue runs lint and the integration suite. `ci / integration` and `ci / review` are the required checks.
|
|
872
|
+
|
|
873
|
+
A push to `main` runs semantic-release in the pipeline's `release` job, on a GitHub-hosted runner: it computes the version, tags the commit, creates the GitHub release and publishes to npm with provenance through npm's trusted publishing, which names `ci.yml` (`npm run trust` syncs it with fledgling). `main` only takes merges that the merge queue tested on that same commit, so the release runs no tests. Only `fix`, `feat` and `perf` commits release; `ci`, `chore`, `docs`, `test` and `refactor` don't.
|
package/dist/agent.js
CHANGED
|
@@ -22,6 +22,13 @@ async function prompt(root, text, context) {
|
|
|
22
22
|
const entry = await root.commit((tx) => tx.entry(AssistantEntry, answer), context);
|
|
23
23
|
return answerText(entry?.model?.[0]?.content);
|
|
24
24
|
}
|
|
25
|
+
const SLICC_ATTRIBUTION = { name: "SLICC", url: "https://www.sliccy.com" };
|
|
26
|
+
const IDENTITY = { attribution: SLICC_ATTRIBUTION };
|
|
27
|
+
const RESTARTS = { restartLimit: Number.POSITIVE_INFINITY };
|
|
28
|
+
function withRestarts(settings) {
|
|
29
|
+
if (typeof settings === "function") return () => ({ ...RESTARTS, ...settings() });
|
|
30
|
+
return { ...RESTARTS, ...settings };
|
|
31
|
+
}
|
|
25
32
|
async function openAgent(options) {
|
|
26
33
|
const context = options.context ?? BACKGROUND_CONTEXT;
|
|
27
34
|
const registry = options.registry ?? createRegistry();
|
|
@@ -37,7 +44,8 @@ async function openAgent(options) {
|
|
|
37
44
|
{
|
|
38
45
|
models: options.models,
|
|
39
46
|
registry,
|
|
40
|
-
|
|
47
|
+
identity: IDENTITY,
|
|
48
|
+
settings: withRestarts(options.settings),
|
|
41
49
|
...options.env ? { env: options.env } : {},
|
|
42
50
|
...options.conversationCreated ? { conversationCreated: options.conversationCreated } : {}
|
|
43
51
|
},
|
|
@@ -63,6 +71,7 @@ async function openAgent(options) {
|
|
|
63
71
|
};
|
|
64
72
|
}
|
|
65
73
|
export {
|
|
74
|
+
SLICC_ATTRIBUTION,
|
|
66
75
|
answerText,
|
|
67
76
|
openAgent
|
|
68
77
|
};
|
package/dist/agent.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/agent.ts"],
|
|
4
|
-
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { Models } from '@earendil-works/pi-ai/models';\nimport {\n AssistantEntry,\n type Conversation,\n createRegistry,\n type EntryId,\n Harness,\n type HarnessOptions,\n MemoryStorage,\n type ModelRef,\n type Registry,\n type Storage,\n} from '@earendil-works/pi-durable';\nimport { type Agents, openAgents } from './agents.ts';\nimport type { Cones } from './cone.ts';\nimport { type GuestToolGate, guestToolGate } from './guests/guard.ts';\nimport { type GuestReviews, guestReviews } from './guests/review.ts';\nimport { type ScoopReviews, scoopReviews } from './guests/scoop.ts';\n\nexport interface AgentOptions {\n models: Models;\n model: ModelRef;\n storage?: Storage;\n registry?: Registry;\n settings?: HarnessOptions['settings'];\n env?: HarnessOptions['env'];\n conversationCreated?: HarnessOptions['conversationCreated'];\n context?: Context;\n beforeResume?: (harness: Harness, context: Context) => Promise<unknown>;\n}\n\nexport interface Agent extends Cones {\n readonly harness: Harness;\n readonly root: Conversation;\n readonly agents: Agents;\n readonly reviews: GuestReviews;\n readonly gate: GuestToolGate;\n readonly scoopReviews: ScoopReviews;\n onCone(listener: (conversation: Conversation) => void | Promise<void>): () => void;\n prompt(text: string): Promise<string>;\n close(): Promise<void>;\n}\n\ninterface Block {\n type: string;\n text?: string;\n}\n\nexport function answerText(content: unknown): string {\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return '';\n return (content as Block[])\n .filter((block) => block.type === 'text')\n .map((block) => block.text ?? '')\n .join('');\n}\n\nasync function prompt(root: Conversation, text: string, context: Context): Promise<string> {\n const submission = await root.submit({ type: 'input', content: text }, context);\n const settled = await submission.wait(context);\n const { answer, reason } = settled as { answer?: EntryId; reason?: string };\n if (answer === undefined) throw new Error(`unanswered: ${reason}`);\n const entry = await root.commit((tx) => tx.entry(AssistantEntry, answer), context);\n return answerText(entry?.model?.[0]?.content);\n}\n\nexport async function openAgent(options: AgentOptions): Promise<Agent> {\n const context = options.context ?? BACKGROUND_CONTEXT;\n const registry = options.registry ?? createRegistry();\n let opened: Harness | undefined;\n const gate = guestToolGate(() => opened);\n registry.install(gate.extension);\n const reviews = guestReviews();\n registry.install(reviews.extension);\n const scooped = scoopReviews();\n registry.install(scooped.extension);\n const harness = await Harness.open(\n options.storage ?? new MemoryStorage(),\n {\n models: options.models,\n registry,\n
|
|
5
|
-
"mappings": "AACA,SAAS,0BAA0B;AAEnC;AAAA,EACE;AAAA,EAEA;AAAA,EAEA;AAAA,EAEA;AAAA,OAIK;AACP,SAAsB,kBAAkB;AAExC,SAA6B,qBAAqB;AAClD,SAA4B,oBAAoB;AAChD,SAA4B,oBAAoB;AA+BzC,SAAS,WAAW,SAA0B;AACnD,MAAI,OAAO,YAAY,SAAU,QAAO;AACxC,MAAI,CAAC,MAAM,QAAQ,OAAO,EAAG,QAAO;AACpC,SAAQ,QACL,OAAO,CAAC,UAAU,MAAM,SAAS,MAAM,EACvC,IAAI,CAAC,UAAU,MAAM,QAAQ,EAAE,EAC/B,KAAK,EAAE;AACZ;AAEA,eAAe,OAAO,MAAoB,MAAc,SAAmC;AACzF,QAAM,aAAa,MAAM,KAAK,OAAO,EAAE,MAAM,SAAS,SAAS,KAAK,GAAG,OAAO;AAC9E,QAAM,UAAU,MAAM,WAAW,KAAK,OAAO;AAC7C,QAAM,EAAE,QAAQ,OAAO,IAAI;AAC3B,MAAI,WAAW,OAAW,OAAM,IAAI,MAAM,eAAe,MAAM,EAAE;AACjE,QAAM,QAAQ,MAAM,KAAK,OAAO,CAAC,OAAO,GAAG,MAAM,gBAAgB,MAAM,GAAG,OAAO;AACjF,SAAO,WAAW,OAAO,QAAQ,CAAC,GAAG,OAAO;AAC9C;AAEA,eAAsB,UAAU,SAAuC;AACrE,QAAM,UAAU,QAAQ,WAAW;AACnC,QAAM,WAAW,QAAQ,YAAY,eAAe;AACpD,MAAI;AACJ,QAAM,OAAO,cAAc,MAAM,MAAM;AACvC,WAAS,QAAQ,KAAK,SAAS;AAC/B,QAAM,UAAU,aAAa;AAC7B,WAAS,QAAQ,QAAQ,SAAS;AAClC,QAAM,UAAU,aAAa;AAC7B,WAAS,QAAQ,QAAQ,SAAS;AAClC,QAAM,UAAU,MAAM,QAAQ;AAAA,IAC5B,QAAQ,WAAW,IAAI,cAAc;AAAA,IACrC;AAAA,MACE,QAAQ,QAAQ;AAAA,MAChB;AAAA,MACA,
|
|
4
|
+
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { Models } from '@earendil-works/pi-ai/models';\nimport {\n AssistantEntry,\n type Conversation,\n createRegistry,\n type EntryId,\n Harness,\n type HarnessOptions,\n MemoryStorage,\n type ModelRef,\n type Registry,\n type Storage,\n} from '@earendil-works/pi-durable';\nimport { type Agents, openAgents } from './agents.ts';\nimport type { Cones } from './cone.ts';\nimport { type GuestToolGate, guestToolGate } from './guests/guard.ts';\nimport { type GuestReviews, guestReviews } from './guests/review.ts';\nimport { type ScoopReviews, scoopReviews } from './guests/scoop.ts';\n\nexport interface AgentOptions {\n models: Models;\n model: ModelRef;\n storage?: Storage;\n registry?: Registry;\n settings?: HarnessOptions['settings'];\n env?: HarnessOptions['env'];\n conversationCreated?: HarnessOptions['conversationCreated'];\n context?: Context;\n beforeResume?: (harness: Harness, context: Context) => Promise<unknown>;\n}\n\nexport interface Agent extends Cones {\n readonly harness: Harness;\n readonly root: Conversation;\n readonly agents: Agents;\n readonly reviews: GuestReviews;\n readonly gate: GuestToolGate;\n readonly scoopReviews: ScoopReviews;\n onCone(listener: (conversation: Conversation) => void | Promise<void>): () => void;\n prompt(text: string): Promise<string>;\n close(): Promise<void>;\n}\n\ninterface Block {\n type: string;\n text?: string;\n}\n\nexport function answerText(content: unknown): string {\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return '';\n return (content as Block[])\n .filter((block) => block.type === 'text')\n .map((block) => block.text ?? '')\n .join('');\n}\n\nasync function prompt(root: Conversation, text: string, context: Context): Promise<string> {\n const submission = await root.submit({ type: 'input', content: text }, context);\n const settled = await submission.wait(context);\n const { answer, reason } = settled as { answer?: EntryId; reason?: string };\n if (answer === undefined) throw new Error(`unanswered: ${reason}`);\n const entry = await root.commit((tx) => tx.entry(AssistantEntry, answer), context);\n return answerText(entry?.model?.[0]?.content);\n}\n\nexport const SLICC_ATTRIBUTION = { name: 'SLICC', url: 'https://www.sliccy.com' };\nconst IDENTITY = { attribution: SLICC_ATTRIBUTION };\nconst RESTARTS = { restartLimit: Number.POSITIVE_INFINITY };\n\nfunction withRestarts(settings: HarnessOptions['settings']): HarnessOptions['settings'] {\n if (typeof settings === 'function') return () => ({ ...RESTARTS, ...settings() });\n return { ...RESTARTS, ...settings };\n}\n\nexport async function openAgent(options: AgentOptions): Promise<Agent> {\n const context = options.context ?? BACKGROUND_CONTEXT;\n const registry = options.registry ?? createRegistry();\n let opened: Harness | undefined;\n const gate = guestToolGate(() => opened);\n registry.install(gate.extension);\n const reviews = guestReviews();\n registry.install(reviews.extension);\n const scooped = scoopReviews();\n registry.install(scooped.extension);\n const harness = await Harness.open(\n options.storage ?? new MemoryStorage(),\n {\n models: options.models,\n registry,\n identity: IDENTITY,\n settings: withRestarts(options.settings),\n ...(options.env ? { env: options.env } : {}),\n ...(options.conversationCreated ? { conversationCreated: options.conversationCreated } : {}),\n },\n context\n );\n opened = harness;\n const root = await harness.root(context, { agent: { model: options.model } });\n const agents = await openAgents(harness, root, context);\n await options.beforeResume?.(harness, context);\n harness.resume();\n return {\n harness,\n root,\n agents,\n reviews,\n gate,\n scoopReviews: scooped,\n cone: () => agents.cone(),\n switchCone: (conversation, switching) => agents.switchCone(conversation, switching),\n onCone: (listener) => agents.onCone(listener),\n prompt: (text) => prompt(agents.cone(), text, context),\n close: () => harness.close(context),\n };\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,0BAA0B;AAEnC;AAAA,EACE;AAAA,EAEA;AAAA,EAEA;AAAA,EAEA;AAAA,OAIK;AACP,SAAsB,kBAAkB;AAExC,SAA6B,qBAAqB;AAClD,SAA4B,oBAAoB;AAChD,SAA4B,oBAAoB;AA+BzC,SAAS,WAAW,SAA0B;AACnD,MAAI,OAAO,YAAY,SAAU,QAAO;AACxC,MAAI,CAAC,MAAM,QAAQ,OAAO,EAAG,QAAO;AACpC,SAAQ,QACL,OAAO,CAAC,UAAU,MAAM,SAAS,MAAM,EACvC,IAAI,CAAC,UAAU,MAAM,QAAQ,EAAE,EAC/B,KAAK,EAAE;AACZ;AAEA,eAAe,OAAO,MAAoB,MAAc,SAAmC;AACzF,QAAM,aAAa,MAAM,KAAK,OAAO,EAAE,MAAM,SAAS,SAAS,KAAK,GAAG,OAAO;AAC9E,QAAM,UAAU,MAAM,WAAW,KAAK,OAAO;AAC7C,QAAM,EAAE,QAAQ,OAAO,IAAI;AAC3B,MAAI,WAAW,OAAW,OAAM,IAAI,MAAM,eAAe,MAAM,EAAE;AACjE,QAAM,QAAQ,MAAM,KAAK,OAAO,CAAC,OAAO,GAAG,MAAM,gBAAgB,MAAM,GAAG,OAAO;AACjF,SAAO,WAAW,OAAO,QAAQ,CAAC,GAAG,OAAO;AAC9C;AAEO,MAAM,oBAAoB,EAAE,MAAM,SAAS,KAAK,yBAAyB;AAChF,MAAM,WAAW,EAAE,aAAa,kBAAkB;AAClD,MAAM,WAAW,EAAE,cAAc,OAAO,kBAAkB;AAE1D,SAAS,aAAa,UAAkE;AACtF,MAAI,OAAO,aAAa,WAAY,QAAO,OAAO,EAAE,GAAG,UAAU,GAAG,SAAS,EAAE;AAC/E,SAAO,EAAE,GAAG,UAAU,GAAG,SAAS;AACpC;AAEA,eAAsB,UAAU,SAAuC;AACrE,QAAM,UAAU,QAAQ,WAAW;AACnC,QAAM,WAAW,QAAQ,YAAY,eAAe;AACpD,MAAI;AACJ,QAAM,OAAO,cAAc,MAAM,MAAM;AACvC,WAAS,QAAQ,KAAK,SAAS;AAC/B,QAAM,UAAU,aAAa;AAC7B,WAAS,QAAQ,QAAQ,SAAS;AAClC,QAAM,UAAU,aAAa;AAC7B,WAAS,QAAQ,QAAQ,SAAS;AAClC,QAAM,UAAU,MAAM,QAAQ;AAAA,IAC5B,QAAQ,WAAW,IAAI,cAAc;AAAA,IACrC;AAAA,MACE,QAAQ,QAAQ;AAAA,MAChB;AAAA,MACA,UAAU;AAAA,MACV,UAAU,aAAa,QAAQ,QAAQ;AAAA,MACvC,GAAI,QAAQ,MAAM,EAAE,KAAK,QAAQ,IAAI,IAAI,CAAC;AAAA,MAC1C,GAAI,QAAQ,sBAAsB,EAAE,qBAAqB,QAAQ,oBAAoB,IAAI,CAAC;AAAA,IAC5F;AAAA,IACA;AAAA,EACF;AACA,WAAS;AACT,QAAM,OAAO,MAAM,QAAQ,KAAK,SAAS,EAAE,OAAO,EAAE,OAAO,QAAQ,MAAM,EAAE,CAAC;AAC5E,QAAM,SAAS,MAAM,WAAW,SAAS,MAAM,OAAO;AACtD,QAAM,QAAQ,eAAe,SAAS,OAAO;AAC7C,UAAQ,OAAO;AACf,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd,MAAM,MAAM,OAAO,KAAK;AAAA,IACxB,YAAY,CAAC,cAAc,cAAc,OAAO,WAAW,cAAc,SAAS;AAAA,IAClF,QAAQ,CAAC,aAAa,OAAO,OAAO,QAAQ;AAAA,IAC5C,QAAQ,CAAC,SAAS,OAAO,OAAO,KAAK,GAAG,MAAM,OAAO;AAAA,IACrD,OAAO,MAAM,QAAQ,MAAM,OAAO;AAAA,EACpC;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/agents.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
defineDoc
|
|
3
3
|
} from "@earendil-works/pi-durable";
|
|
4
|
+
import { deltasOnly } from "./checkpoints.js";
|
|
4
5
|
import { ConeDoc } from "./cone.js";
|
|
5
6
|
const FIRST_CONE = "cone";
|
|
6
7
|
function live(record) {
|
|
@@ -8,6 +9,7 @@ function live(record) {
|
|
|
8
9
|
}
|
|
9
10
|
const AgentsDoc = defineDoc({
|
|
10
11
|
kind: "slicc.agents",
|
|
12
|
+
checkpointWhen: deltasOnly,
|
|
11
13
|
version: 1,
|
|
12
14
|
scope: "session",
|
|
13
15
|
initial: () => ({ cones: {}, active: FIRST_CONE, next: 2, scoops: {} })
|
package/dist/agents.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/agents.ts"],
|
|
4
|
-
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {\n type Conversation,\n type ConversationId,\n defineDoc,\n type Harness,\n type Tx,\n} from '@earendil-works/pi-durable';\nimport { ConeDoc } from './cone.ts';\nimport type { RoleMemory } from './memory/store.ts';\n\nexport const FIRST_CONE = 'cone';\n\nexport type ConeRecord = { name: string; conversation: number; title?: string; titled?: number };\n\nexport type Mark = { in: number; at: number };\n\nexport type ScoopRecord = {\n name: string;\n folder: string;\n cone: string;\n conversation: number;\n anchor: number;\n role: string | null;\n memory?: RoleMemory;\n origin?: string;\n context?: { project: boolean; global: boolean };\n kind: 'async' | 'sync';\n parent: string;\n depth: number;\n roots: { write: string[]; read: string[] } | null;\n created: Mark | null;\n dropped: Mark | null;\n gone: boolean;\n frozen?: string;\n removed?: boolean;\n thawedAs?: string;\n title?: string;\n titled?: number;\n};\n\nexport type FrozenRecord = {\n cone: string;\n name: string;\n conversation: number;\n title: string;\n model: string;\n messages: number;\n frozenAt: number;\n scoops: string[];\n v6?: { origin: string; key: string; suffix?: string };\n};\n\nexport type V6Import = {\n at: number;\n origin: string;\n cones: Record<string, string>;\n tails: Record<string, string>;\n};\n\nexport function live(record: ScoopRecord): boolean {\n return record.dropped === null && !record.gone && !record.frozen;\n}\n\nexport type AgentsState = {\n cones: Record<string, ConeRecord>;\n active: string;\n next: number;\n scoops: Record<string, ScoopRecord>;\n frozen?: Record<string, FrozenRecord>;\n frozenNext?: number;\n imports?: Record<string, V6Import>;\n};\n\nexport const AgentsDoc = defineDoc<AgentsState>({\n kind: 'slicc.agents',\n version: 1,\n scope: 'session',\n initial: () => ({ cones: {}, active: FIRST_CONE, next: 2, scoops: {} }),\n});\n\nexport const SCOOP_PREFIX = 'scoop:';\n\nexport function scoopId(folder: string): string {\n return `${SCOOP_PREFIX}${folder}`;\n}\n\nexport function slug(name: string): string {\n const cleaned = name\n .toLowerCase()\n .replace(/[^a-z0-9._-]+/g, '-')\n .replace(/^[-._]+|[-._]+$/g, '')\n .slice(0, 48);\n return cleaned || 'scoop';\n}\n\nexport function freeFolder(state: Pick<AgentsState, 'scoops'>, name: string): string {\n const base = slug(name);\n if (!state.scoops[scoopId(base)]) return base;\n for (let n = 2; ; n++) if (!state.scoops[scoopId(`${base}-${n}`)]) return `${base}-${n}`;\n}\n\nexport interface Agents {\n state(): Readonly<AgentsState>;\n cone(): Conversation;\n activeCone(): string;\n conversation(agentId: string, context: Context): Promise<Conversation | undefined>;\n selectCone(id: string, context: Context): Promise<void>;\n createCone(name: string, context: Context): Promise<string>;\n switchCone(conversation: Conversation, context: Context): Promise<void>;\n setConversation(coneId: string, conversation: Conversation, context: Context): Promise<void>;\n update<T>(change: (tx: Tx, state: AgentsState) => T | Promise<T>, context: Context): Promise<T>;\n onChange(listener: (state: Readonly<AgentsState>) => void): () => void;\n onCone(listener: (conversation: Conversation) => void | Promise<void>): () => void;\n}\n\nasync function initial(harness: Harness, root: Conversation, context: Context) {\n return harness.commit(async (tx) => {\n const doc = await tx.doc(AgentsDoc);\n if (!doc.cones[FIRST_CONE]) {\n const legacy = (await tx.doc(ConeDoc)).conversation;\n doc.cones[FIRST_CONE] = { name: 'sliccy', conversation: legacy ?? root.id };\n }\n if (!doc.cones[doc.active]) doc.active = FIRST_CONE;\n return JSON.parse(JSON.stringify(doc)) as AgentsState;\n }, context);\n}\n\nexport async function openAgents(\n harness: Harness,\n root: Conversation,\n context: Context\n): Promise<Agents> {\n let state = await initial(harness, root, context);\n const handles = new Map<number, Conversation>([[root.id, root]]);\n const handle = async (id: number, using: Context) => {\n const known = handles.get(id);\n if (known) return known;\n const found = await harness.conversation(id as ConversationId, using);\n if (found) handles.set(id, found);\n return found;\n };\n let cone =\n (await handle((state.cones[state.active] as ConeRecord).conversation, context)) ?? root;\n const changes = new Set<(state: Readonly<AgentsState>) => void>();\n const cones = new Set<(conversation: Conversation) => void | Promise<void>>();\n const notifyCone = async (next: Conversation) => {\n cone = next;\n await Promise.all([...cones].map((listener) => listener(next)));\n };\n const update: Agents['update'] = async (change, using) => {\n const [result, next] = await harness.commit(async (tx) => {\n const doc = await tx.doc(AgentsDoc);\n const value = await change(tx, doc as AgentsState);\n return [value, JSON.parse(JSON.stringify(doc)) as AgentsState] as const;\n }, using);\n state = next;\n for (const listener of changes) listener(state);\n return result;\n };\n const agents: Agents = {\n state: () => state,\n cone: () => cone,\n activeCone: () => state.active,\n async conversation(agentId, using) {\n const id = state.cones[agentId]?.conversation ?? state.scoops[agentId]?.conversation;\n return id === undefined ? undefined : handle(id, using);\n },\n async selectCone(id, using) {\n const record = state.cones[id];\n if (!record) throw new Error(`no cone ${id}`);\n if (state.active === id) return;\n await update((_tx, doc) => {\n doc.active = id;\n }, using);\n await notifyCone((await handle(record.conversation, using)) as Conversation);\n },\n async createCone(name, using) {\n const wanted = name.trim();\n if (!wanted) throw new Error('A cone needs a name.');\n if (Object.values(state.cones).some((record) => record.name === wanted))\n throw new Error(`A cone named ${wanted} already exists.`);\n const conversation = await harness.createConversation(\n {\n ownership: { kind: 'ownerless' },\n agent: { model: (await cone.agent(using)).model ?? null },\n },\n using\n );\n handles.set(conversation.id, conversation);\n return update((_tx, doc) => {\n const id = `cone-${doc.next}`;\n doc.next += 1;\n doc.cones[id] = { name: wanted, conversation: conversation.id };\n return id;\n }, using);\n },\n switchCone(conversation, using) {\n return agents.setConversation(state.active, conversation, using);\n },\n async setConversation(coneId, conversation, using) {\n handles.set(conversation.id, conversation);\n await update(async (tx, doc) => {\n const cone = doc.cones[coneId] as ConeRecord;\n cone.conversation = conversation.id;\n delete cone.title;\n delete cone.titled;\n if (coneId === doc.active) (await tx.doc(ConeDoc)).conversation = conversation.id;\n }, using);\n if (coneId === state.active) await notifyCone(conversation);\n },\n update,\n onChange(listener) {\n changes.add(listener);\n return () => changes.delete(listener);\n },\n onCone(listener) {\n cones.add(listener);\n return () => cones.delete(listener);\n },\n };\n return agents;\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {\n type Conversation,\n type ConversationId,\n defineDoc,\n type Harness,\n type Tx,\n} from '@earendil-works/pi-durable';\nimport { deltasOnly } from './checkpoints.ts';\nimport { ConeDoc } from './cone.ts';\nimport type { RoleMemory } from './memory/store.ts';\n\nexport const FIRST_CONE = 'cone';\n\nexport type ConeRecord = { name: string; conversation: number; title?: string; titled?: number };\n\nexport type Mark = { in: number; at: number };\n\nexport type ScoopRecord = {\n name: string;\n folder: string;\n cone: string;\n conversation: number;\n anchor: number;\n role: string | null;\n memory?: RoleMemory;\n origin?: string;\n context?: { project: boolean; global: boolean };\n kind: 'async' | 'sync';\n parent: string;\n depth: number;\n roots: { write: string[]; read: string[] } | null;\n created: Mark | null;\n dropped: Mark | null;\n gone: boolean;\n frozen?: string;\n removed?: boolean;\n thawedAs?: string;\n title?: string;\n titled?: number;\n};\n\nexport type FrozenRecord = {\n cone: string;\n name: string;\n conversation: number;\n title: string;\n model: string;\n messages: number;\n frozenAt: number;\n scoops: string[];\n v6?: { origin: string; key: string; suffix?: string };\n};\n\nexport type V6Import = {\n at: number;\n origin: string;\n cones: Record<string, string>;\n tails: Record<string, string>;\n};\n\nexport function live(record: ScoopRecord): boolean {\n return record.dropped === null && !record.gone && !record.frozen;\n}\n\nexport type AgentsState = {\n cones: Record<string, ConeRecord>;\n active: string;\n next: number;\n scoops: Record<string, ScoopRecord>;\n frozen?: Record<string, FrozenRecord>;\n frozenNext?: number;\n imports?: Record<string, V6Import>;\n};\n\nexport const AgentsDoc = defineDoc<AgentsState>({\n kind: 'slicc.agents',\n checkpointWhen: deltasOnly,\n version: 1,\n scope: 'session',\n initial: () => ({ cones: {}, active: FIRST_CONE, next: 2, scoops: {} }),\n});\n\nexport const SCOOP_PREFIX = 'scoop:';\n\nexport function scoopId(folder: string): string {\n return `${SCOOP_PREFIX}${folder}`;\n}\n\nexport function slug(name: string): string {\n const cleaned = name\n .toLowerCase()\n .replace(/[^a-z0-9._-]+/g, '-')\n .replace(/^[-._]+|[-._]+$/g, '')\n .slice(0, 48);\n return cleaned || 'scoop';\n}\n\nexport function freeFolder(state: Pick<AgentsState, 'scoops'>, name: string): string {\n const base = slug(name);\n if (!state.scoops[scoopId(base)]) return base;\n for (let n = 2; ; n++) if (!state.scoops[scoopId(`${base}-${n}`)]) return `${base}-${n}`;\n}\n\nexport interface Agents {\n state(): Readonly<AgentsState>;\n cone(): Conversation;\n activeCone(): string;\n conversation(agentId: string, context: Context): Promise<Conversation | undefined>;\n selectCone(id: string, context: Context): Promise<void>;\n createCone(name: string, context: Context): Promise<string>;\n switchCone(conversation: Conversation, context: Context): Promise<void>;\n setConversation(coneId: string, conversation: Conversation, context: Context): Promise<void>;\n update<T>(change: (tx: Tx, state: AgentsState) => T | Promise<T>, context: Context): Promise<T>;\n onChange(listener: (state: Readonly<AgentsState>) => void): () => void;\n onCone(listener: (conversation: Conversation) => void | Promise<void>): () => void;\n}\n\nasync function initial(harness: Harness, root: Conversation, context: Context) {\n return harness.commit(async (tx) => {\n const doc = await tx.doc(AgentsDoc);\n if (!doc.cones[FIRST_CONE]) {\n const legacy = (await tx.doc(ConeDoc)).conversation;\n doc.cones[FIRST_CONE] = { name: 'sliccy', conversation: legacy ?? root.id };\n }\n if (!doc.cones[doc.active]) doc.active = FIRST_CONE;\n return JSON.parse(JSON.stringify(doc)) as AgentsState;\n }, context);\n}\n\nexport async function openAgents(\n harness: Harness,\n root: Conversation,\n context: Context\n): Promise<Agents> {\n let state = await initial(harness, root, context);\n const handles = new Map<number, Conversation>([[root.id, root]]);\n const handle = async (id: number, using: Context) => {\n const known = handles.get(id);\n if (known) return known;\n const found = await harness.conversation(id as ConversationId, using);\n if (found) handles.set(id, found);\n return found;\n };\n let cone =\n (await handle((state.cones[state.active] as ConeRecord).conversation, context)) ?? root;\n const changes = new Set<(state: Readonly<AgentsState>) => void>();\n const cones = new Set<(conversation: Conversation) => void | Promise<void>>();\n const notifyCone = async (next: Conversation) => {\n cone = next;\n await Promise.all([...cones].map((listener) => listener(next)));\n };\n const update: Agents['update'] = async (change, using) => {\n const [result, next] = await harness.commit(async (tx) => {\n const doc = await tx.doc(AgentsDoc);\n const value = await change(tx, doc as AgentsState);\n return [value, JSON.parse(JSON.stringify(doc)) as AgentsState] as const;\n }, using);\n state = next;\n for (const listener of changes) listener(state);\n return result;\n };\n const agents: Agents = {\n state: () => state,\n cone: () => cone,\n activeCone: () => state.active,\n async conversation(agentId, using) {\n const id = state.cones[agentId]?.conversation ?? state.scoops[agentId]?.conversation;\n return id === undefined ? undefined : handle(id, using);\n },\n async selectCone(id, using) {\n const record = state.cones[id];\n if (!record) throw new Error(`no cone ${id}`);\n if (state.active === id) return;\n await update((_tx, doc) => {\n doc.active = id;\n }, using);\n await notifyCone((await handle(record.conversation, using)) as Conversation);\n },\n async createCone(name, using) {\n const wanted = name.trim();\n if (!wanted) throw new Error('A cone needs a name.');\n if (Object.values(state.cones).some((record) => record.name === wanted))\n throw new Error(`A cone named ${wanted} already exists.`);\n const conversation = await harness.createConversation(\n {\n ownership: { kind: 'ownerless' },\n agent: { model: (await cone.agent(using)).model ?? null },\n },\n using\n );\n handles.set(conversation.id, conversation);\n return update((_tx, doc) => {\n const id = `cone-${doc.next}`;\n doc.next += 1;\n doc.cones[id] = { name: wanted, conversation: conversation.id };\n return id;\n }, using);\n },\n switchCone(conversation, using) {\n return agents.setConversation(state.active, conversation, using);\n },\n async setConversation(coneId, conversation, using) {\n handles.set(conversation.id, conversation);\n await update(async (tx, doc) => {\n const cone = doc.cones[coneId] as ConeRecord;\n cone.conversation = conversation.id;\n delete cone.title;\n delete cone.titled;\n if (coneId === doc.active) (await tx.doc(ConeDoc)).conversation = conversation.id;\n }, using);\n if (coneId === state.active) await notifyCone(conversation);\n },\n update,\n onChange(listener) {\n changes.add(listener);\n return () => changes.delete(listener);\n },\n onCone(listener) {\n cones.add(listener);\n return () => cones.delete(listener);\n },\n };\n return agents;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["cone"]
|
|
7
7
|
}
|
package/dist/approvals/policy.js
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
} from "@earendil-works/chord";
|
|
4
4
|
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
|
5
5
|
import { defineDoc } from "@earendil-works/pi-durable";
|
|
6
|
+
import { deltasOnly } from "../checkpoints.js";
|
|
6
7
|
import {
|
|
7
8
|
changedGuests,
|
|
8
9
|
DEFAULT_GUEST_POLICY
|
|
@@ -13,6 +14,7 @@ const DEFAULT_APPROVAL_POLICY = {
|
|
|
13
14
|
};
|
|
14
15
|
const ApprovalPolicyDoc = defineDoc({
|
|
15
16
|
kind: "slicc.approval-policy",
|
|
17
|
+
checkpointWhen: deltasOnly,
|
|
16
18
|
version: 1,
|
|
17
19
|
scope: "session",
|
|
18
20
|
initial: () => ({ overrides: {} })
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/approvals/policy.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n type Context,\n type JsonValue,\n type ReplicatedState,\n replicatedState,\n} from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport { defineDoc, type Harness, type JsonObject } from '@earendil-works/pi-durable';\nimport {\n changedGuests,\n DEFAULT_GUEST_POLICY,\n type GuestPolicy,\n type GuestPolicyChange,\n} from '../guests/policy.ts';\nimport type { ApprovalKind } from './record.ts';\n\nexport type SudoPolicy = { approver: 'user' | 'agent' };\n\nexport type ApprovalPolicy = { guests: GuestPolicy; sudo: SudoPolicy };\n\nexport type ApprovalPolicyChange = { guests?: GuestPolicyChange; sudo?: Partial<SudoPolicy> };\n\nexport type ApprovalPolicySet =\n | { policy: ApprovalPolicy; error: null }\n | { policy: null; error: string };\n\nexport const DEFAULT_APPROVAL_POLICY: ApprovalPolicy = {\n guests: DEFAULT_GUEST_POLICY,\n sudo: { approver: 'user' },\n};\n\ntype Overrides = Partial<Record<keyof ApprovalPolicy, JsonObject>>;\n\nexport const ApprovalPolicyDoc = defineDoc<{ overrides: Overrides }>({\n kind: 'slicc.approval-policy',\n version: 1,\n scope: 'session',\n initial: () => ({ overrides: {} }),\n});\n\nconst copy = <T>(value: T): T => JSON.parse(JSON.stringify(value));\n\nfunction changedSudo(next: ApprovalPolicy, change: unknown): string | null {\n if (!change || typeof change !== 'object' || Array.isArray(change))\n return 'The sudo policy is an object of policy entries.';\n for (const [name, value] of Object.entries(change)) {\n if (name !== 'approver') return `There is no sudo policy entry ${name}.`;\n if (value !== 'user' && value !== 'agent')\n return 'A sudo request is approved by user or agent.';\n next.sudo.approver = value;\n }\n return null;\n}\n\nexport function changedPolicy(base: ApprovalPolicy, change: unknown): ApprovalPolicySet {\n if (!change || typeof change !== 'object' || Array.isArray(change))\n return { policy: null, error: 'An approval policy change is an object of sections.' };\n const next = copy(base);\n for (const [name, value] of Object.entries(change)) {\n const error =\n name === 'guests'\n ? changedGuests(next.guests, value)\n : name === 'sudo'\n ? changedSudo(next, value)\n : `There is no approval policy section ${name}.`;\n if (error) return { policy: null, error };\n }\n return { policy: next, error: null };\n}\n\nexport function resolvedPolicy(overrides: Overrides): ApprovalPolicy {\n const guests = (overrides.guests ?? {}) as Partial<GuestPolicy>;\n return copy({\n guests: {\n ...DEFAULT_GUEST_POLICY,\n ...guests,\n always: { ...DEFAULT_GUEST_POLICY.always, ...guests.always },\n },\n sudo: { ...DEFAULT_APPROVAL_POLICY.sudo, ...overrides.sudo },\n });\n}\n\nexport function overridden(\n overrides: Overrides,\n change: Record<string, Record<string, unknown>>,\n policy: ApprovalPolicy\n): Overrides {\n const next = copy(overrides);\n for (const [section, entries] of Object.entries(change)) {\n const key = section as keyof ApprovalPolicy;\n const into = next[key] ?? {};\n const resolved = policy[key] as unknown as JsonObject;\n for (const name of Object.keys(entries))\n into[name] = (\n name === 'always'\n ? { ...(into.always as object), ...(entries.always as object) }\n : resolved[name]\n ) as JsonValue;\n next[key] = into;\n }\n return next;\n}\n\nexport interface ApprovalPolicies {\n readonly state: ReplicatedState<ApprovalPolicy>;\n current(): ApprovalPolicy;\n set(change: unknown, context: Context): Promise<ApprovalPolicySet>;\n}\n\nexport const alwaysFor =\n (policy: Pick<ApprovalPolicies, 'current'>) =>\n (kind: ApprovalKind): boolean => {\n const { always } = policy.current().guests;\n return kind === 'guest-tool' ? always.tools : always.messages;\n };\n\nexport async function attachApprovalPolicy(\n harness: Pick<Harness, 'commit'>,\n context: Context = BACKGROUND_CONTEXT\n): Promise<ApprovalPolicies> {\n let overrides = await harness.commit(\n async (tx) => copy((await tx.doc(ApprovalPolicyDoc)).overrides ?? {}),\n context\n );\n const state = replicatedState<ApprovalPolicy>(resolvedPolicy(overrides));\n let line: Promise<unknown> = Promise.resolve();\n return {\n state,\n current: () => copy(state.value as ApprovalPolicy),\n set(change, using) {\n const run = line.then(async () => {\n const made = changedPolicy(state.value as ApprovalPolicy, change);\n if (!made.policy) return made;\n const next = overridden(\n overrides,\n change as Record<string, Record<string, unknown>>,\n made.policy\n );\n await harness.commit(async (tx) => {\n (await tx.doc(ApprovalPolicyDoc)).overrides = copy(next);\n }, using);\n overrides = next;\n const policy = resolvedPolicy(next);\n state.replace(using, copy(policy));\n return { policy: copy(policy), error: null };\n });\n line = run.catch(() => undefined);\n return run;\n },\n };\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import {\n type Context,\n type JsonValue,\n type ReplicatedState,\n replicatedState,\n} from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport { defineDoc, type Harness, type JsonObject } from '@earendil-works/pi-durable';\nimport { deltasOnly } from '../checkpoints.ts';\nimport {\n changedGuests,\n DEFAULT_GUEST_POLICY,\n type GuestPolicy,\n type GuestPolicyChange,\n} from '../guests/policy.ts';\nimport type { ApprovalKind } from './record.ts';\n\nexport type SudoPolicy = { approver: 'user' | 'agent' };\n\nexport type ApprovalPolicy = { guests: GuestPolicy; sudo: SudoPolicy };\n\nexport type ApprovalPolicyChange = { guests?: GuestPolicyChange; sudo?: Partial<SudoPolicy> };\n\nexport type ApprovalPolicySet =\n | { policy: ApprovalPolicy; error: null }\n | { policy: null; error: string };\n\nexport const DEFAULT_APPROVAL_POLICY: ApprovalPolicy = {\n guests: DEFAULT_GUEST_POLICY,\n sudo: { approver: 'user' },\n};\n\ntype Overrides = Partial<Record<keyof ApprovalPolicy, JsonObject>>;\n\nexport const ApprovalPolicyDoc = defineDoc<{ overrides: Overrides }>({\n kind: 'slicc.approval-policy',\n checkpointWhen: deltasOnly,\n version: 1,\n scope: 'session',\n initial: () => ({ overrides: {} }),\n});\n\nconst copy = <T>(value: T): T => JSON.parse(JSON.stringify(value));\n\nfunction changedSudo(next: ApprovalPolicy, change: unknown): string | null {\n if (!change || typeof change !== 'object' || Array.isArray(change))\n return 'The sudo policy is an object of policy entries.';\n for (const [name, value] of Object.entries(change)) {\n if (name !== 'approver') return `There is no sudo policy entry ${name}.`;\n if (value !== 'user' && value !== 'agent')\n return 'A sudo request is approved by user or agent.';\n next.sudo.approver = value;\n }\n return null;\n}\n\nexport function changedPolicy(base: ApprovalPolicy, change: unknown): ApprovalPolicySet {\n if (!change || typeof change !== 'object' || Array.isArray(change))\n return { policy: null, error: 'An approval policy change is an object of sections.' };\n const next = copy(base);\n for (const [name, value] of Object.entries(change)) {\n const error =\n name === 'guests'\n ? changedGuests(next.guests, value)\n : name === 'sudo'\n ? changedSudo(next, value)\n : `There is no approval policy section ${name}.`;\n if (error) return { policy: null, error };\n }\n return { policy: next, error: null };\n}\n\nexport function resolvedPolicy(overrides: Overrides): ApprovalPolicy {\n const guests = (overrides.guests ?? {}) as Partial<GuestPolicy>;\n return copy({\n guests: {\n ...DEFAULT_GUEST_POLICY,\n ...guests,\n always: { ...DEFAULT_GUEST_POLICY.always, ...guests.always },\n },\n sudo: { ...DEFAULT_APPROVAL_POLICY.sudo, ...overrides.sudo },\n });\n}\n\nexport function overridden(\n overrides: Overrides,\n change: Record<string, Record<string, unknown>>,\n policy: ApprovalPolicy\n): Overrides {\n const next = copy(overrides);\n for (const [section, entries] of Object.entries(change)) {\n const key = section as keyof ApprovalPolicy;\n const into = next[key] ?? {};\n const resolved = policy[key] as unknown as JsonObject;\n for (const name of Object.keys(entries))\n into[name] = (\n name === 'always'\n ? { ...(into.always as object), ...(entries.always as object) }\n : resolved[name]\n ) as JsonValue;\n next[key] = into;\n }\n return next;\n}\n\nexport interface ApprovalPolicies {\n readonly state: ReplicatedState<ApprovalPolicy>;\n current(): ApprovalPolicy;\n set(change: unknown, context: Context): Promise<ApprovalPolicySet>;\n}\n\nexport const alwaysFor =\n (policy: Pick<ApprovalPolicies, 'current'>) =>\n (kind: ApprovalKind): boolean => {\n const { always } = policy.current().guests;\n return kind === 'guest-tool' ? always.tools : always.messages;\n };\n\nexport async function attachApprovalPolicy(\n harness: Pick<Harness, 'commit'>,\n context: Context = BACKGROUND_CONTEXT\n): Promise<ApprovalPolicies> {\n let overrides = await harness.commit(\n async (tx) => copy((await tx.doc(ApprovalPolicyDoc)).overrides ?? {}),\n context\n );\n const state = replicatedState<ApprovalPolicy>(resolvedPolicy(overrides));\n let line: Promise<unknown> = Promise.resolve();\n return {\n state,\n current: () => copy(state.value as ApprovalPolicy),\n set(change, using) {\n const run = line.then(async () => {\n const made = changedPolicy(state.value as ApprovalPolicy, change);\n if (!made.policy) return made;\n const next = overridden(\n overrides,\n change as Record<string, Record<string, unknown>>,\n made.policy\n );\n await harness.commit(async (tx) => {\n (await tx.doc(ApprovalPolicyDoc)).overrides = copy(next);\n }, using);\n overrides = next;\n const policy = resolvedPolicy(next);\n state.replace(using, copy(policy));\n return { policy: copy(policy), error: null };\n });\n line = run.catch(() => undefined);\n return run;\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/approvals/record.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/approvals/record.ts"],
|
|
4
|
-
"sourcesContent": ["import { defineDoc } from '@earendil-works/pi-durable';\n\nexport type ApprovalKind = 'guest-message' | 'guest-tool' | 'sudo';\n\nexport type ApprovalState = 'pending' | 'approved' | 'rejected' | 'unanswered' | 'withdrawn';\n\nexport type ApprovalTier = 'user' | 'agent' | 'cone' | `scoop:${string}`;\n\nexport type ApprovalAnswer = 'once' | 'always' | 'deny';\n\nexport type DecidedBy = 'owner' | 'follower' | 'judge' | 'host' | `scoop:${string}`;\n\nexport type ApprovalRequester = {\n kind: 'guest' | 'agent' | 'user';\n name: string;\n seat?: string;\n agentId?: string;\n where?: string;\n device?: string;\n};\n\nexport type ApprovalVerdict = {\n decision: 'allow' | 'deny' | 'unsure';\n reason: string;\n risk?: number;\n aligned?: number;\n model?: string;\n};\n\nexport type ApprovalAttachment = { name: string; mimeType: string; size: number };\n\nexport type GuestMessageDetail = { text: string; attachments: ApprovalAttachment[] };\n\nexport type GuestToolDetail = { tool: string; summary: string; arguments?: string; task?: number };\n\nexport type SudoDetail = {\n command: string;\n target?: string;\n cwd?: string;\n alwaysScope?: 'cwd';\n};\n\ntype Base = {\n id: string;\n seat?: string;\n agentId: string;\n requester: ApprovalRequester;\n tier: string;\n state: ApprovalState;\n decidedBy: DecidedBy | null;\n device?: string;\n answer?: 'once' | 'always';\n reviewer?: string;\n reason?: string;\n verdict?: ApprovalVerdict;\n always: boolean;\n createdAt: number;\n expiresAt: number;\n decidedAt?: number;\n};\n\nexport type GuestMessageApproval = Base & { kind: 'guest-message'; detail: GuestMessageDetail };\nexport type GuestToolApproval = Base & { kind: 'guest-tool'; detail: GuestToolDetail };\nexport type SudoApproval = Base & { kind: 'sudo'; detail: SudoDetail };\n\nexport type Approval = GuestMessageApproval | GuestToolApproval | SudoApproval;\n\ntype DraftOf<T extends Approval> = Pick<T, 'kind' | 'detail' | 'agentId' | 'requester'> & {\n seat?: string;\n tier: string;\n always?: boolean;\n expiresAt?: number;\n};\n\nexport type ApprovalDraft =\n | DraftOf<GuestMessageApproval>\n | DraftOf<GuestToolApproval>\n | DraftOf<SudoApproval>;\n\nexport type ApprovalsState = { items: Approval[] };\n\nexport const ApprovalsDoc = defineDoc<ApprovalsState>({\n kind: 'slicc.approvals',\n version: 1,\n scope: 'session',\n initial: () => ({ items: [] }),\n});\n\nexport const APPROVAL_MS = 300_000;\nexport const APPROVALS_KEPT = 100;\nexport const MAX_DELAY = 2_147_483_647;\nexport const REASON_LENGTH = 500;\n\nexport const REASONS = {\n timeout: 'timeout',\n unknownTier: 'unknown-tier',\n unroutable: 'unroutable',\n unknownAnswer: 'unknown-answer',\n restart: 'restart',\n revoked: 'revoked',\n aborted: 'aborted',\n disconnected: 'disconnected',\n noScoop: 'no-scoop',\n scoopStopped: 'scoop-stopped',\n scoopAsked: 'scoop-asked',\n scoopUndecided: 'scoop-undecided',\n} as const;\n\nexport const guestKind = (kind: ApprovalKind): boolean => kind !== 'sudo';\n\nexport const scoopTier = (tier: unknown): tier is `scoop:${string}` =>\n typeof tier === 'string' && /^scoop:\\S+$/.test(tier);\n\nexport function knownTier(tier: unknown): tier is ApprovalTier {\n if (tier === 'user' || tier === 'agent' || tier === 'cone') return true;\n return scoopTier(tier);\n}\n\nexport function routable(kind: ApprovalKind, tier: ApprovalTier): boolean {\n return !(kind === 'guest-tool' && tier === 'cone');\n}\n\nexport function answerOf(value: unknown): ApprovalAnswer | null {\n return value === 'once' || value === 'always' || value === 'deny' ? value : null;\n}\n\nexport const personal = (by: DecidedBy): boolean => by === 'owner' || by === 'follower';\n\nexport function answerer(by: DecidedBy, item: Pick<Approval, 'tier' | 'reviewer'>): boolean {\n if (personal(by)) return item.tier === 'user';\n if (by === 'judge') return item.tier === 'agent';\n if (scoopTier(by)) return scoopTier(item.tier) && item.reviewer === by;\n return true;\n}\n\nexport type SudoApprover = 'user' | 'agent';\n\nexport const sudoApprover = (value: unknown): SudoApprover =>\n value === 'agent' ? 'agent' : 'user';\n\nexport const tierOf = (kind: ApprovalKind, tier: string, sudo: SudoApprover): string =>\n kind === 'sudo' ? sudo : tier;\n\nexport function gone(state: ApprovalState): string {\n if (state === 'unanswered') return 'This request timed out.';\n if (state === 'withdrawn') return 'This request was withdrawn.';\n return 'Someone else already answered this.';\n}\n\nexport function cleanReason(reason: unknown): string | undefined {\n if (typeof reason !== 'string') return undefined;\n const text = reason.trim().slice(0, REASON_LENGTH);\n return text || undefined;\n}\n\nconst decided = (item: Approval) => item.decidedAt ?? item.createdAt;\n\nexport function prune(items: Approval[], kept = APPROVALS_KEPT): Approval[] {\n const settled = items.filter((item) => item.state !== 'pending');\n settled.sort((a, b) => decided(a) - decided(b));\n const drop = new Set(settled.slice(0, Math.max(0, settled.length - kept)));\n return items.filter((item) => !drop.has(item));\n}\n\nexport type Settle = { state: Exclude<ApprovalState, 'pending'>; by: DecidedBy; reason?: string };\n\nexport type Ruling = { error?: string; settle?: Settle; answer?: 'once' | 'always' };\n\nconst why = (reason: string | undefined) => (reason ? { reason } : {});\n\nexport function ruled(\n item: Approval,\n answer: unknown,\n reason: unknown,\n by: DecidedBy,\n at: number\n): Ruling {\n if (item.state !== 'pending') return { error: gone(item.state) };\n if (item.expiresAt <= at)\n return {\n error: gone('unanswered'),\n settle: { state: 'unanswered', by: 'host', reason: REASONS.timeout },\n };\n if (!answerer(by, item)) return { error: \"This request isn't yours to answer.\" };\n const known = answerOf(answer);\n if (known === 'always' && scoopTier(by)) return { error: 'A scoop answers once or deny.' };\n if (known === 'always' && !item.always)\n return { error: \"Always isn't offered for this request.\" };\n if (!known) return { settle: { state: 'rejected', by, reason: REASONS.unknownAnswer } };\n const given = why(cleanReason(reason));\n if (known === 'deny') return { settle: { state: 'rejected', by, ...given } };\n return { settle: { state: 'approved', by, ...given }, answer: known };\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,iBAAiB;
|
|
4
|
+
"sourcesContent": ["import { defineDoc } from '@earendil-works/pi-durable';\nimport { deltasOnly } from '../checkpoints.ts';\n\nexport type ApprovalKind = 'guest-message' | 'guest-tool' | 'sudo';\n\nexport type ApprovalState = 'pending' | 'approved' | 'rejected' | 'unanswered' | 'withdrawn';\n\nexport type ApprovalTier = 'user' | 'agent' | 'cone' | `scoop:${string}`;\n\nexport type ApprovalAnswer = 'once' | 'always' | 'deny';\n\nexport type DecidedBy = 'owner' | 'follower' | 'judge' | 'host' | `scoop:${string}`;\n\nexport type ApprovalRequester = {\n kind: 'guest' | 'agent' | 'user';\n name: string;\n seat?: string;\n agentId?: string;\n where?: string;\n device?: string;\n};\n\nexport type ApprovalVerdict = {\n decision: 'allow' | 'deny' | 'unsure';\n reason: string;\n risk?: number;\n aligned?: number;\n model?: string;\n};\n\nexport type ApprovalAttachment = { name: string; mimeType: string; size: number };\n\nexport type GuestMessageDetail = { text: string; attachments: ApprovalAttachment[] };\n\nexport type GuestToolDetail = { tool: string; summary: string; arguments?: string; task?: number };\n\nexport type SudoDetail = {\n command: string;\n target?: string;\n cwd?: string;\n alwaysScope?: 'cwd';\n};\n\ntype Base = {\n id: string;\n seat?: string;\n agentId: string;\n requester: ApprovalRequester;\n tier: string;\n state: ApprovalState;\n decidedBy: DecidedBy | null;\n device?: string;\n answer?: 'once' | 'always';\n reviewer?: string;\n reason?: string;\n verdict?: ApprovalVerdict;\n always: boolean;\n createdAt: number;\n expiresAt: number;\n decidedAt?: number;\n};\n\nexport type GuestMessageApproval = Base & { kind: 'guest-message'; detail: GuestMessageDetail };\nexport type GuestToolApproval = Base & { kind: 'guest-tool'; detail: GuestToolDetail };\nexport type SudoApproval = Base & { kind: 'sudo'; detail: SudoDetail };\n\nexport type Approval = GuestMessageApproval | GuestToolApproval | SudoApproval;\n\ntype DraftOf<T extends Approval> = Pick<T, 'kind' | 'detail' | 'agentId' | 'requester'> & {\n seat?: string;\n tier: string;\n always?: boolean;\n expiresAt?: number;\n};\n\nexport type ApprovalDraft =\n | DraftOf<GuestMessageApproval>\n | DraftOf<GuestToolApproval>\n | DraftOf<SudoApproval>;\n\nexport type ApprovalsState = { items: Approval[] };\n\nexport const ApprovalsDoc = defineDoc<ApprovalsState>({\n kind: 'slicc.approvals',\n checkpointWhen: deltasOnly,\n version: 1,\n scope: 'session',\n initial: () => ({ items: [] }),\n});\n\nexport const APPROVAL_MS = 300_000;\nexport const APPROVALS_KEPT = 100;\nexport const MAX_DELAY = 2_147_483_647;\nexport const REASON_LENGTH = 500;\n\nexport const REASONS = {\n timeout: 'timeout',\n unknownTier: 'unknown-tier',\n unroutable: 'unroutable',\n unknownAnswer: 'unknown-answer',\n restart: 'restart',\n revoked: 'revoked',\n aborted: 'aborted',\n disconnected: 'disconnected',\n noScoop: 'no-scoop',\n scoopStopped: 'scoop-stopped',\n scoopAsked: 'scoop-asked',\n scoopUndecided: 'scoop-undecided',\n} as const;\n\nexport const guestKind = (kind: ApprovalKind): boolean => kind !== 'sudo';\n\nexport const scoopTier = (tier: unknown): tier is `scoop:${string}` =>\n typeof tier === 'string' && /^scoop:\\S+$/.test(tier);\n\nexport function knownTier(tier: unknown): tier is ApprovalTier {\n if (tier === 'user' || tier === 'agent' || tier === 'cone') return true;\n return scoopTier(tier);\n}\n\nexport function routable(kind: ApprovalKind, tier: ApprovalTier): boolean {\n return !(kind === 'guest-tool' && tier === 'cone');\n}\n\nexport function answerOf(value: unknown): ApprovalAnswer | null {\n return value === 'once' || value === 'always' || value === 'deny' ? value : null;\n}\n\nexport const personal = (by: DecidedBy): boolean => by === 'owner' || by === 'follower';\n\nexport function answerer(by: DecidedBy, item: Pick<Approval, 'tier' | 'reviewer'>): boolean {\n if (personal(by)) return item.tier === 'user';\n if (by === 'judge') return item.tier === 'agent';\n if (scoopTier(by)) return scoopTier(item.tier) && item.reviewer === by;\n return true;\n}\n\nexport type SudoApprover = 'user' | 'agent';\n\nexport const sudoApprover = (value: unknown): SudoApprover =>\n value === 'agent' ? 'agent' : 'user';\n\nexport const tierOf = (kind: ApprovalKind, tier: string, sudo: SudoApprover): string =>\n kind === 'sudo' ? sudo : tier;\n\nexport function gone(state: ApprovalState): string {\n if (state === 'unanswered') return 'This request timed out.';\n if (state === 'withdrawn') return 'This request was withdrawn.';\n return 'Someone else already answered this.';\n}\n\nexport function cleanReason(reason: unknown): string | undefined {\n if (typeof reason !== 'string') return undefined;\n const text = reason.trim().slice(0, REASON_LENGTH);\n return text || undefined;\n}\n\nconst decided = (item: Approval) => item.decidedAt ?? item.createdAt;\n\nexport function prune(items: Approval[], kept = APPROVALS_KEPT): Approval[] {\n const settled = items.filter((item) => item.state !== 'pending');\n settled.sort((a, b) => decided(a) - decided(b));\n const drop = new Set(settled.slice(0, Math.max(0, settled.length - kept)));\n return items.filter((item) => !drop.has(item));\n}\n\nexport type Settle = { state: Exclude<ApprovalState, 'pending'>; by: DecidedBy; reason?: string };\n\nexport type Ruling = { error?: string; settle?: Settle; answer?: 'once' | 'always' };\n\nconst why = (reason: string | undefined) => (reason ? { reason } : {});\n\nexport function ruled(\n item: Approval,\n answer: unknown,\n reason: unknown,\n by: DecidedBy,\n at: number\n): Ruling {\n if (item.state !== 'pending') return { error: gone(item.state) };\n if (item.expiresAt <= at)\n return {\n error: gone('unanswered'),\n settle: { state: 'unanswered', by: 'host', reason: REASONS.timeout },\n };\n if (!answerer(by, item)) return { error: \"This request isn't yours to answer.\" };\n const known = answerOf(answer);\n if (known === 'always' && scoopTier(by)) return { error: 'A scoop answers once or deny.' };\n if (known === 'always' && !item.always)\n return { error: \"Always isn't offered for this request.\" };\n if (!known) return { settle: { state: 'rejected', by, reason: REASONS.unknownAnswer } };\n const given = why(cleanReason(reason));\n if (known === 'deny') return { settle: { state: 'rejected', by, ...given } };\n return { settle: { state: 'approved', by, ...given }, answer: known };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/codemode/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
defineExtension,
|
|
12
12
|
defineTool
|
|
13
13
|
} from "@earendil-works/pi-durable";
|
|
14
|
+
import { deltasOnly } from "../checkpoints.js";
|
|
14
15
|
import { scrubItems } from "../secrets/scrub.js";
|
|
15
16
|
const CODEMODE = "codemode";
|
|
16
17
|
const DEFAULT_MAX_OUTPUT_TOKENS = 1e4;
|
|
@@ -18,6 +19,7 @@ const MEMORY_LIMIT_BYTES = 256 * 1024 * 1024;
|
|
|
18
19
|
const CHARS_PER_TOKEN = 4;
|
|
19
20
|
const CodemodeDoc = defineDoc({
|
|
20
21
|
kind: "slicc.codemode",
|
|
22
|
+
checkpointWhen: deltasOnly,
|
|
21
23
|
version: 1,
|
|
22
24
|
scope: "conversation",
|
|
23
25
|
history: "rewindable",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/codemode/index.ts"],
|
|
4
|
-
"sourcesContent": ["import type { Context, JsonValue } from '@earendil-works/chord';\nimport { withAbortSignal } from '@earendil-works/chord/context';\nimport { Type } from '@earendil-works/pi-ai';\nimport {\n type CodemodeError,\n type CodemodeJsonSchema,\n type CodemodeOutputItem,\n type CodemodeResult,\n type CodemodeRun,\n type CodemodeRunOptions,\n CodemodeSandbox,\n type CodemodeSandboxOptions,\n type CodemodeTool,\n loadQuickJSWasm,\n parseCodemodeSource,\n renderDeclarations,\n} from '@earendil-works/pi-codemode';\nimport {\n type Agent,\n defineDoc,\n defineExtension,\n defineTool,\n type Extension,\n type JsonObject,\n type NestedToolExecutionResult,\n type ToolExecutionApi,\n type ToolRegistration,\n} from '@earendil-works/pi-durable';\nimport { scrubItems } from '../secrets/scrub.ts';\n\nexport const CODEMODE = 'codemode';\nexport const DEFAULT_MAX_OUTPUT_TOKENS = 10_000;\nexport const MEMORY_LIMIT_BYTES = 256 * 1024 * 1024;\nconst CHARS_PER_TOKEN = 4;\n\nexport const CodemodeDoc = defineDoc<{ store: Record<string, JsonValue> }>({\n kind: 'slicc.codemode',\n version: 1,\n scope: 'conversation',\n history: 'rewindable',\n fork: 'asOf',\n initial: () => ({ store: {} }),\n});\n\nexport interface Sandbox {\n execute(\n code: string,\n options: { signal?: AbortSignal; store?: Record<string, unknown> }\n ): Promise<CodemodeResult>;\n start(code: string, options: CodemodeRunOptions): CodemodeRun;\n close(): Promise<void>;\n}\n\nexport type SandboxFactory = (options: CodemodeSandboxOptions) => Sandbox;\n\nexport interface CodemodeExtra {\n tools: CodemodeTool[];\n globals: CodemodeTool[];\n}\n\nexport interface CodemodeOptions {\n sandbox: SandboxFactory;\n declared: readonly ToolRegistration[];\n extra?: (api: ToolExecutionApi, agent: Agent, context: Context) => Promise<CodemodeExtra>;\n}\n\nconst INTRO = `Run JavaScript that calls other tools. The input is raw JavaScript (not JSON, no code fence), run as an async function body in a QuickJS sandbox: top-level \\`await\\` and \\`return\\` work. No Node, file system, network, or timers.\n- \\`await tools.<name>({ ...args })\\` resolves to a string, or an object if the tool's declaration says so, and rejects with an Error on failure. Calls still running when the script ends are cancelled.\n- Optional first line: \\`// @options: {\"max_output_tokens\": 10000, \"timeout_ms\": 60000}\\``;\n\nconst GLOBALS = [\n 'Globals:',\n '- `text(value)`, `image(dataUrlOrImageBlock)`, `console.log(...)`, and top-level `return` add output; `exit()` ends the script. With several text items, each starts with a `==> text N/M <==` line, and `console` lines follow the other output in one `<console_output>` block.',\n '- `store(key, value)` and `load(key)` keep JSON values across codemode calls in this conversation.',\n '- `ALL_TOOLS` lists every tool scripts can call.',\n].join('\\n');\n\nconst DISCOVERY =\n '- `await searchTools(query, { limit?, namespace? })`, `await describeTool(name)`, `await describeNamespace(name)`: find unlisted tools, such as MCP tools.';\n\nconst BASH_OUTPUT: CodemodeJsonSchema = {\n type: 'object',\n properties: {\n output: { type: 'string' },\n exit_code: { type: 'number' },\n truncated: { type: 'boolean' },\n full_output_path: { type: 'string' },\n },\n required: ['output', 'exit_code'],\n};\n\nfunction scriptTool(tool: ToolRegistration): Omit<CodemodeTool, 'execute'> {\n return {\n name: tool.name,\n description: tool.description,\n inputSchema: tool.parameters as CodemodeJsonSchema,\n ...(tool.name === 'bash' ? { outputSchema: BASH_OUTPUT } : {}),\n };\n}\n\nexport function codemodeDescription(\n declared: readonly ToolRegistration[],\n discovery = false\n): string {\n const tools = declared\n .filter((tool) => tool.name !== CODEMODE)\n .map((tool) => ({ ...scriptTool(tool), execute: () => undefined }));\n const sections = [INTRO, discovery ? `${GLOBALS}\\n${DISCOVERY}` : GLOBALS];\n if (tools.length) sections.push(`Nested tools:\\n${renderDeclarations({ tools })}`);\n return sections.join('\\n\\n');\n}\n\nfunction textOf(value: unknown): string {\n if (typeof value === 'string') return value;\n if (!Array.isArray(value)) return '';\n return value\n .filter((part) => part?.type === 'text')\n .map((part) => part.text)\n .join('\\n');\n}\n\nfunction errorOf(result: NestedToolExecutionResult): string {\n const errors = result.diagnostics\n .filter((diagnostic) => diagnostic.severity === 'error')\n .map((diagnostic) => diagnostic.message);\n return [textOf(result.structuredOutput), ...errors].filter(Boolean).join('\\n');\n}\n\ninterface ShellOutput {\n output: string;\n exitCode: number;\n truncated?: boolean;\n fullOutputPath?: string;\n}\n\nfunction shellOutput(value: unknown): ShellOutput | undefined {\n const shell = value as Partial<ShellOutput> | undefined;\n return typeof shell?.output === 'string' && typeof shell.exitCode === 'number'\n ? (shell as ShellOutput)\n : undefined;\n}\n\nexport async function callNested(\n name: string,\n args: unknown,\n api: ToolExecutionApi,\n context: Context\n): Promise<unknown> {\n const result = await api.executeTool(name, (args ?? {}) as JsonObject, context);\n const shell = name === 'bash' ? shellOutput(result.structuredOutput) : undefined;\n if (shell)\n return {\n output: shell.output,\n exit_code: shell.exitCode,\n ...(shell.truncated ? { truncated: true } : {}),\n ...(shell.fullOutputPath ? { full_output_path: shell.fullOutputPath } : {}),\n };\n if (result.isError) throw new Error(errorOf(result) || `Tool \"${name}\" failed`);\n return textOf(result.structuredOutput);\n}\n\nfunction valueText(value: unknown): string {\n return typeof value === 'string' ? value : (JSON.stringify(value) ?? String(value));\n}\n\nexport function formatOutput(output: readonly CodemodeOutputItem[]): CodemodeOutputItem[] {\n const total = output.filter((item) => item.type === 'text' && !item.console).length;\n const items: CodemodeOutputItem[] = [];\n const lines: string[] = [];\n let index = 0;\n for (const item of output) {\n if (item.type === 'image') items.push(item);\n else if (item.console) lines.push(item.text);\n else {\n index++;\n items.push({\n type: 'text',\n text: total > 1 ? `==> text ${index}/${total} <==\\n${item.text}` : item.text,\n });\n }\n }\n if (lines.length)\n items.push({ type: 'text', text: `<console_output>\\n${lines.join('\\n')}\\n</console_output>` });\n return items;\n}\n\nexport function joinText(items: readonly CodemodeOutputItem[]): CodemodeOutputItem[] {\n const joined: CodemodeOutputItem[] = [];\n for (const item of items) {\n const last = joined.at(-1);\n if (item.type === 'text' && last?.type === 'text') {\n const separator = last.text === '' || last.text.endsWith('\\n') ? '' : '\\n';\n joined[joined.length - 1] = { type: 'text', text: `${last.text}${separator}${item.text}` };\n } else joined.push(item);\n }\n return joined;\n}\n\nexport function formatError(error: CodemodeError, calls: CodemodeResult['calls']): string {\n const heads: Record<string, string> = {\n timeout: `Script timed out: ${error.message}`,\n aborted: `Script aborted: ${error.message}`,\n sandbox: `Script sandbox failed: ${error.message}`,\n };\n const head =\n error.kind === 'script'\n ? (error.stack ?? `${error.name ?? 'Error'}: ${error.message}`)\n : heads[error.kind];\n const summary = calls.length\n ? `Tool calls made before the failure (they are not undone): ${calls.map((call) => `${call.name} (${call.status})`).join(', ')}`\n : 'No tool calls were made.';\n return `${head}\\n\\n${summary}`;\n}\n\nexport function truncate(\n items: readonly CodemodeOutputItem[],\n maxTokens: number\n): { items: CodemodeOutputItem[]; full: string | null } {\n const text = items\n .filter((item) => item.type === 'text')\n .map((item) => item.text)\n .join('\\n');\n const limit = maxTokens * CHARS_PER_TOKEN;\n if (text.length <= limit) return { items: [...items], full: null };\n const half = Math.floor(limit / 2);\n const kept = `${text.slice(0, half)}\\n\\n[\u2026 ${text.length - 2 * half} characters omitted \u2026]\\n\\n${text.slice(-half)}`;\n return {\n items: [{ type: 'text', text: kept }, ...items.filter((item) => item.type === 'image')],\n full: text,\n };\n}\n\nasync function spill(api: ToolExecutionApi, text: string, context: Context): Promise<string> {\n const path = `/tmp/slicc-codemode-${api.callId.replace(/[^\\w-]/g, '_')}.txt`;\n const written = await api.env?.writeFile(path, text, context);\n return written?.ok ? path : '';\n}\n\nasync function saveStore(\n api: ToolExecutionApi,\n result: CodemodeResult,\n context: Context\n): Promise<void> {\n if (!result.ok) return;\n const { set, delete: removed } = result.storeWrites;\n if (!Object.keys(set).length && !removed.length) return;\n await api.commit(async (tx) => {\n const doc = await tx.doc(CodemodeDoc, api.conversationId);\n for (const key of removed) delete doc.store[key];\n Object.assign(doc.store, set);\n }, context);\n}\n\nexport function codemodeTool(options: CodemodeOptions): ToolRegistration {\n return defineTool({\n name: CODEMODE,\n description: codemodeDescription(options.declared, options.extra !== undefined),\n parameters: Type.Object({ code: Type.String({ description: 'Raw JavaScript source.' }) }),\n replay: 'unsafe',\n callers: ['model'],\n async execute({ code }, api, context) {\n const started = performance.now();\n const parsed = parseCodemodeSource(code);\n const agent = await api.agent(context);\n const callable = agent.tools.filter((tool) => tool.name !== CODEMODE);\n const tools: CodemodeTool[] = callable.map((tool) => ({\n ...scriptTool(tool),\n execute: (args, { signal }) =>\n callNested(tool.name, args, api, withAbortSignal(signal, context)),\n }));\n const extra = options.extra\n ? await options.extra(api, agent, context)\n : { tools: [], globals: [] };\n const replaced = new Set(extra.tools.map((tool) => tool.name));\n const scripted = [...tools.filter((tool) => !replaced.has(tool.name)), ...extra.tools];\n const saved = await api.snapshot(CodemodeDoc, api.conversationId, context);\n const sandbox = options.sandbox({\n tools: scripted,\n ...(extra.globals.length ? { globals: extra.globals } : {}),\n timeoutMs: parsed.options.timeoutMs ?? Number.POSITIVE_INFINITY,\n memoryLimitBytes: MEMORY_LIMIT_BYTES,\n });\n let result: CodemodeResult;\n try {\n result = await sandbox.execute(parsed.code, {\n signal: context.abortSignal as AbortSignal,\n store: { ...saved?.store },\n });\n } finally {\n await sandbox.close();\n }\n await saveStore(api, result, context);\n const output = [...result.output];\n if (result.ok && result.value !== undefined)\n output.push({ type: 'text', text: valueText(result.value) });\n const items = formatOutput(output);\n if (!result.ok)\n items.push({\n type: 'text',\n text: `Script error:\\n${formatError(result.error, result.calls)}`,\n });\n const cut = truncate(\n await scrubItems(joinText(items)),\n parsed.options.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS\n );\n const path = cut.full === null ? '' : await spill(api, cut.full, context);\n const note = path ? [{ type: 'text' as const, text: `Full output: ${path}` }] : [];\n const seconds = ((performance.now() - started) / 1000).toFixed(1);\n const header = `${result.ok ? 'Script completed' : 'Script failed'}\\nWall time ${seconds} seconds\\nOutput:\\n`;\n return {\n output: [{ type: 'text', text: header }, ...joinText([...cut.items, ...note])],\n ...(result.ok ? {} : { isError: true }),\n };\n },\n }) as ToolRegistration;\n}\n\nexport function codemodeExtension(tools: readonly ToolRegistration[]): Extension {\n return defineExtension({ name: 'slicc-codemode', tools });\n}\n\nexport function disabledBySettings(text: string | undefined): boolean {\n if (!text) return false;\n try {\n const settings = JSON.parse(text) as { defaultTools?: unknown };\n return Array.isArray(settings.defaultTools) && settings.defaultTools.includes(`-${CODEMODE}`);\n } catch {\n return false;\n }\n}\n\nexport type Fetcher = (url: string) => Promise<Response>;\n\nexport function wasmCandidates(from: URL): string[] {\n const out: string[] = [];\n let dir = new URL('.', from);\n for (;;) {\n out.push(new URL('node_modules/quickjs-wasi/quickjs.wasm', dir).href);\n if (dir.pathname === '/') return out;\n dir = new URL('..', dir);\n }\n}\n\nexport async function locateWasm(\n fetcher: Fetcher,\n from: URL\n): Promise<WebAssembly.Module | undefined> {\n for (const url of wasmCandidates(from)) {\n const response = await fetcher(url).catch(() => undefined);\n if (response?.ok) return WebAssembly.compile(await response.arrayBuffer());\n }\n return undefined;\n}\n\nlet shared: SandboxFactory | undefined;\n\nexport function sharedSandbox(): SandboxFactory | undefined {\n return shared;\n}\n\nexport function sandboxFactory(\n wasm: () => Promise<WebAssembly.Module | undefined>,\n workerUrl?: string | URL\n): SandboxFactory {\n let found: Promise<WebAssembly.Module | undefined> | undefined;\n const factory: SandboxFactory = (options) => {\n found ??= wasm().catch(() => undefined);\n return new CodemodeSandbox({\n ...options,\n ...(workerUrl ? { workerUrl } : {}),\n wasm: found.then((module) => module ?? loadQuickJSWasm()),\n });\n };\n shared = factory;\n return factory;\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import type { Context, JsonValue } from '@earendil-works/chord';\nimport { withAbortSignal } from '@earendil-works/chord/context';\nimport { Type } from '@earendil-works/pi-ai';\nimport {\n type CodemodeError,\n type CodemodeJsonSchema,\n type CodemodeOutputItem,\n type CodemodeResult,\n type CodemodeRun,\n type CodemodeRunOptions,\n CodemodeSandbox,\n type CodemodeSandboxOptions,\n type CodemodeTool,\n loadQuickJSWasm,\n parseCodemodeSource,\n renderDeclarations,\n} from '@earendil-works/pi-codemode';\nimport {\n type Agent,\n defineDoc,\n defineExtension,\n defineTool,\n type Extension,\n type JsonObject,\n type NestedToolExecutionResult,\n type ToolExecutionApi,\n type ToolRegistration,\n} from '@earendil-works/pi-durable';\nimport { deltasOnly } from '../checkpoints.ts';\nimport { scrubItems } from '../secrets/scrub.ts';\n\nexport const CODEMODE = 'codemode';\nexport const DEFAULT_MAX_OUTPUT_TOKENS = 10_000;\nexport const MEMORY_LIMIT_BYTES = 256 * 1024 * 1024;\nconst CHARS_PER_TOKEN = 4;\n\nexport const CodemodeDoc = defineDoc<{ store: Record<string, JsonValue> }>({\n kind: 'slicc.codemode',\n checkpointWhen: deltasOnly,\n version: 1,\n scope: 'conversation',\n history: 'rewindable',\n fork: 'asOf',\n initial: () => ({ store: {} }),\n});\n\nexport interface Sandbox {\n execute(\n code: string,\n options: { signal?: AbortSignal; store?: Record<string, unknown> }\n ): Promise<CodemodeResult>;\n start(code: string, options: CodemodeRunOptions): CodemodeRun;\n close(): Promise<void>;\n}\n\nexport type SandboxFactory = (options: CodemodeSandboxOptions) => Sandbox;\n\nexport interface CodemodeExtra {\n tools: CodemodeTool[];\n globals: CodemodeTool[];\n}\n\nexport interface CodemodeOptions {\n sandbox: SandboxFactory;\n declared: readonly ToolRegistration[];\n extra?: (api: ToolExecutionApi, agent: Agent, context: Context) => Promise<CodemodeExtra>;\n}\n\nconst INTRO = `Run JavaScript that calls other tools. The input is raw JavaScript (not JSON, no code fence), run as an async function body in a QuickJS sandbox: top-level \\`await\\` and \\`return\\` work. No Node, file system, network, or timers.\n- \\`await tools.<name>({ ...args })\\` resolves to a string, or an object if the tool's declaration says so, and rejects with an Error on failure. Calls still running when the script ends are cancelled.\n- Optional first line: \\`// @options: {\"max_output_tokens\": 10000, \"timeout_ms\": 60000}\\``;\n\nconst GLOBALS = [\n 'Globals:',\n '- `text(value)`, `image(dataUrlOrImageBlock)`, `console.log(...)`, and top-level `return` add output; `exit()` ends the script. With several text items, each starts with a `==> text N/M <==` line, and `console` lines follow the other output in one `<console_output>` block.',\n '- `store(key, value)` and `load(key)` keep JSON values across codemode calls in this conversation.',\n '- `ALL_TOOLS` lists every tool scripts can call.',\n].join('\\n');\n\nconst DISCOVERY =\n '- `await searchTools(query, { limit?, namespace? })`, `await describeTool(name)`, `await describeNamespace(name)`: find unlisted tools, such as MCP tools.';\n\nconst BASH_OUTPUT: CodemodeJsonSchema = {\n type: 'object',\n properties: {\n output: { type: 'string' },\n exit_code: { type: 'number' },\n truncated: { type: 'boolean' },\n full_output_path: { type: 'string' },\n },\n required: ['output', 'exit_code'],\n};\n\nfunction scriptTool(tool: ToolRegistration): Omit<CodemodeTool, 'execute'> {\n return {\n name: tool.name,\n description: tool.description,\n inputSchema: tool.parameters as CodemodeJsonSchema,\n ...(tool.name === 'bash' ? { outputSchema: BASH_OUTPUT } : {}),\n };\n}\n\nexport function codemodeDescription(\n declared: readonly ToolRegistration[],\n discovery = false\n): string {\n const tools = declared\n .filter((tool) => tool.name !== CODEMODE)\n .map((tool) => ({ ...scriptTool(tool), execute: () => undefined }));\n const sections = [INTRO, discovery ? `${GLOBALS}\\n${DISCOVERY}` : GLOBALS];\n if (tools.length) sections.push(`Nested tools:\\n${renderDeclarations({ tools })}`);\n return sections.join('\\n\\n');\n}\n\nfunction textOf(value: unknown): string {\n if (typeof value === 'string') return value;\n if (!Array.isArray(value)) return '';\n return value\n .filter((part) => part?.type === 'text')\n .map((part) => part.text)\n .join('\\n');\n}\n\nfunction errorOf(result: NestedToolExecutionResult): string {\n const errors = result.diagnostics\n .filter((diagnostic) => diagnostic.severity === 'error')\n .map((diagnostic) => diagnostic.message);\n return [textOf(result.structuredOutput), ...errors].filter(Boolean).join('\\n');\n}\n\ninterface ShellOutput {\n output: string;\n exitCode: number;\n truncated?: boolean;\n fullOutputPath?: string;\n}\n\nfunction shellOutput(value: unknown): ShellOutput | undefined {\n const shell = value as Partial<ShellOutput> | undefined;\n return typeof shell?.output === 'string' && typeof shell.exitCode === 'number'\n ? (shell as ShellOutput)\n : undefined;\n}\n\nexport async function callNested(\n name: string,\n args: unknown,\n api: ToolExecutionApi,\n context: Context\n): Promise<unknown> {\n const result = await api.executeTool(name, (args ?? {}) as JsonObject, context);\n const shell = name === 'bash' ? shellOutput(result.structuredOutput) : undefined;\n if (shell)\n return {\n output: shell.output,\n exit_code: shell.exitCode,\n ...(shell.truncated ? { truncated: true } : {}),\n ...(shell.fullOutputPath ? { full_output_path: shell.fullOutputPath } : {}),\n };\n if (result.isError) throw new Error(errorOf(result) || `Tool \"${name}\" failed`);\n return textOf(result.structuredOutput);\n}\n\nfunction valueText(value: unknown): string {\n return typeof value === 'string' ? value : (JSON.stringify(value) ?? String(value));\n}\n\nexport function formatOutput(output: readonly CodemodeOutputItem[]): CodemodeOutputItem[] {\n const total = output.filter((item) => item.type === 'text' && !item.console).length;\n const items: CodemodeOutputItem[] = [];\n const lines: string[] = [];\n let index = 0;\n for (const item of output) {\n if (item.type === 'image') items.push(item);\n else if (item.console) lines.push(item.text);\n else {\n index++;\n items.push({\n type: 'text',\n text: total > 1 ? `==> text ${index}/${total} <==\\n${item.text}` : item.text,\n });\n }\n }\n if (lines.length)\n items.push({ type: 'text', text: `<console_output>\\n${lines.join('\\n')}\\n</console_output>` });\n return items;\n}\n\nexport function joinText(items: readonly CodemodeOutputItem[]): CodemodeOutputItem[] {\n const joined: CodemodeOutputItem[] = [];\n for (const item of items) {\n const last = joined.at(-1);\n if (item.type === 'text' && last?.type === 'text') {\n const separator = last.text === '' || last.text.endsWith('\\n') ? '' : '\\n';\n joined[joined.length - 1] = { type: 'text', text: `${last.text}${separator}${item.text}` };\n } else joined.push(item);\n }\n return joined;\n}\n\nexport function formatError(error: CodemodeError, calls: CodemodeResult['calls']): string {\n const heads: Record<string, string> = {\n timeout: `Script timed out: ${error.message}`,\n aborted: `Script aborted: ${error.message}`,\n sandbox: `Script sandbox failed: ${error.message}`,\n };\n const head =\n error.kind === 'script'\n ? (error.stack ?? `${error.name ?? 'Error'}: ${error.message}`)\n : heads[error.kind];\n const summary = calls.length\n ? `Tool calls made before the failure (they are not undone): ${calls.map((call) => `${call.name} (${call.status})`).join(', ')}`\n : 'No tool calls were made.';\n return `${head}\\n\\n${summary}`;\n}\n\nexport function truncate(\n items: readonly CodemodeOutputItem[],\n maxTokens: number\n): { items: CodemodeOutputItem[]; full: string | null } {\n const text = items\n .filter((item) => item.type === 'text')\n .map((item) => item.text)\n .join('\\n');\n const limit = maxTokens * CHARS_PER_TOKEN;\n if (text.length <= limit) return { items: [...items], full: null };\n const half = Math.floor(limit / 2);\n const kept = `${text.slice(0, half)}\\n\\n[\u2026 ${text.length - 2 * half} characters omitted \u2026]\\n\\n${text.slice(-half)}`;\n return {\n items: [{ type: 'text', text: kept }, ...items.filter((item) => item.type === 'image')],\n full: text,\n };\n}\n\nasync function spill(api: ToolExecutionApi, text: string, context: Context): Promise<string> {\n const path = `/tmp/slicc-codemode-${api.callId.replace(/[^\\w-]/g, '_')}.txt`;\n const written = await api.env?.writeFile(path, text, context);\n return written?.ok ? path : '';\n}\n\nasync function saveStore(\n api: ToolExecutionApi,\n result: CodemodeResult,\n context: Context\n): Promise<void> {\n if (!result.ok) return;\n const { set, delete: removed } = result.storeWrites;\n if (!Object.keys(set).length && !removed.length) return;\n await api.commit(async (tx) => {\n const doc = await tx.doc(CodemodeDoc, api.conversationId);\n for (const key of removed) delete doc.store[key];\n Object.assign(doc.store, set);\n }, context);\n}\n\nexport function codemodeTool(options: CodemodeOptions): ToolRegistration {\n return defineTool({\n name: CODEMODE,\n description: codemodeDescription(options.declared, options.extra !== undefined),\n parameters: Type.Object({ code: Type.String({ description: 'Raw JavaScript source.' }) }),\n replay: 'unsafe',\n callers: ['model'],\n async execute({ code }, api, context) {\n const started = performance.now();\n const parsed = parseCodemodeSource(code);\n const agent = await api.agent(context);\n const callable = agent.tools.filter((tool) => tool.name !== CODEMODE);\n const tools: CodemodeTool[] = callable.map((tool) => ({\n ...scriptTool(tool),\n execute: (args, { signal }) =>\n callNested(tool.name, args, api, withAbortSignal(signal, context)),\n }));\n const extra = options.extra\n ? await options.extra(api, agent, context)\n : { tools: [], globals: [] };\n const replaced = new Set(extra.tools.map((tool) => tool.name));\n const scripted = [...tools.filter((tool) => !replaced.has(tool.name)), ...extra.tools];\n const saved = await api.snapshot(CodemodeDoc, api.conversationId, context);\n const sandbox = options.sandbox({\n tools: scripted,\n ...(extra.globals.length ? { globals: extra.globals } : {}),\n timeoutMs: parsed.options.timeoutMs ?? Number.POSITIVE_INFINITY,\n memoryLimitBytes: MEMORY_LIMIT_BYTES,\n });\n let result: CodemodeResult;\n try {\n result = await sandbox.execute(parsed.code, {\n signal: context.abortSignal as AbortSignal,\n store: { ...saved?.store },\n });\n } finally {\n await sandbox.close();\n }\n await saveStore(api, result, context);\n const output = [...result.output];\n if (result.ok && result.value !== undefined)\n output.push({ type: 'text', text: valueText(result.value) });\n const items = formatOutput(output);\n if (!result.ok)\n items.push({\n type: 'text',\n text: `Script error:\\n${formatError(result.error, result.calls)}`,\n });\n const cut = truncate(\n await scrubItems(joinText(items)),\n parsed.options.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS\n );\n const path = cut.full === null ? '' : await spill(api, cut.full, context);\n const note = path ? [{ type: 'text' as const, text: `Full output: ${path}` }] : [];\n const seconds = ((performance.now() - started) / 1000).toFixed(1);\n const header = `${result.ok ? 'Script completed' : 'Script failed'}\\nWall time ${seconds} seconds\\nOutput:\\n`;\n return {\n output: [{ type: 'text', text: header }, ...joinText([...cut.items, ...note])],\n ...(result.ok ? {} : { isError: true }),\n };\n },\n }) as ToolRegistration;\n}\n\nexport function codemodeExtension(tools: readonly ToolRegistration[]): Extension {\n return defineExtension({ name: 'slicc-codemode', tools });\n}\n\nexport function disabledBySettings(text: string | undefined): boolean {\n if (!text) return false;\n try {\n const settings = JSON.parse(text) as { defaultTools?: unknown };\n return Array.isArray(settings.defaultTools) && settings.defaultTools.includes(`-${CODEMODE}`);\n } catch {\n return false;\n }\n}\n\nexport type Fetcher = (url: string) => Promise<Response>;\n\nexport function wasmCandidates(from: URL): string[] {\n const out: string[] = [];\n let dir = new URL('.', from);\n for (;;) {\n out.push(new URL('node_modules/quickjs-wasi/quickjs.wasm', dir).href);\n if (dir.pathname === '/') return out;\n dir = new URL('..', dir);\n }\n}\n\nexport async function locateWasm(\n fetcher: Fetcher,\n from: URL\n): Promise<WebAssembly.Module | undefined> {\n for (const url of wasmCandidates(from)) {\n const response = await fetcher(url).catch(() => undefined);\n if (response?.ok) return WebAssembly.compile(await response.arrayBuffer());\n }\n return undefined;\n}\n\nlet shared: SandboxFactory | undefined;\n\nexport function sharedSandbox(): SandboxFactory | undefined {\n return shared;\n}\n\nexport function sandboxFactory(\n wasm: () => Promise<WebAssembly.Module | undefined>,\n workerUrl?: string | URL\n): SandboxFactory {\n let found: Promise<WebAssembly.Module | undefined> | undefined;\n const factory: SandboxFactory = (options) => {\n found ??= wasm().catch(() => undefined);\n return new CodemodeSandbox({\n ...options,\n ...(workerUrl ? { workerUrl } : {}),\n wasm: found.then((module) => module ?? loadQuickJSWasm()),\n });\n };\n shared = factory;\n return factory;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/cone.js
CHANGED
package/dist/cone.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/cone.ts"],
|
|
4
|
-
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport { type Conversation, defineDoc } from '@earendil-works/pi-durable';\n\nexport const ConeDoc = defineDoc<{ conversation: number | null }>({\n kind: 'slicc.cone',\n version: 1,\n scope: 'session',\n initial: () => ({ conversation: null }),\n});\n\nexport interface Cones {\n cone(): Conversation;\n switchCone(conversation: Conversation, context: Context): Promise<void>;\n}\n"],
|
|
5
|
-
"mappings": "AACA,SAA4B,iBAAiB;
|
|
4
|
+
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport { type Conversation, defineDoc } from '@earendil-works/pi-durable';\nimport { deltasOnly } from './checkpoints.ts';\n\nexport const ConeDoc = defineDoc<{ conversation: number | null }>({\n kind: 'slicc.cone',\n checkpointWhen: deltasOnly,\n version: 1,\n scope: 'session',\n initial: () => ({ conversation: null }),\n});\n\nexport interface Cones {\n cone(): Conversation;\n switchCone(conversation: Conversation, context: Context): Promise<void>;\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAA4B,iBAAiB;AAC7C,SAAS,kBAAkB;AAEpB,MAAM,UAAU,UAA2C;AAAA,EAChE,MAAM;AAAA,EACN,gBAAgB;AAAA,EAChB,SAAS;AAAA,EACT,OAAO;AAAA,EACP,SAAS,OAAO,EAAE,cAAc,KAAK;AACvC,CAAC;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/guests/guard.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
ToolTask
|
|
8
8
|
} from "@earendil-works/pi-durable";
|
|
9
9
|
import { REASONS, scoopTier } from "../approvals/record.js";
|
|
10
|
+
import { deltasOnly } from "../checkpoints.js";
|
|
10
11
|
import { scrubText, scrubTexts } from "../secrets/scrub.js";
|
|
11
12
|
import { gateKind, gateRefusal } from "./policy.js";
|
|
12
13
|
import { GuestsDoc, isGate, liveSeat } from "./seats.js";
|
|
@@ -104,6 +105,7 @@ function reasonOf(item) {
|
|
|
104
105
|
}
|
|
105
106
|
const GuestTaintDoc = defineDoc({
|
|
106
107
|
kind: "slicc.guest-taint",
|
|
108
|
+
checkpointWhen: deltasOnly,
|
|
107
109
|
version: 1,
|
|
108
110
|
scope: "conversation",
|
|
109
111
|
history: "rewindable",
|
|
@@ -112,6 +114,7 @@ const GuestTaintDoc = defineDoc({
|
|
|
112
114
|
});
|
|
113
115
|
const LickTaintDoc = defineDoc({
|
|
114
116
|
kind: "slicc.lick-taint",
|
|
117
|
+
checkpointWhen: deltasOnly,
|
|
115
118
|
version: 1,
|
|
116
119
|
scope: "session",
|
|
117
120
|
initial: () => ({ licks: {} })
|