@ai-ecoverse/slicc-agent 3.9.0 → 3.11.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 +53 -2
- package/bin/agent +1 -1
- package/dist/client.js +9 -1
- package/dist/client.js.map +2 -2
- package/dist/codemode/index.js +288 -0
- package/dist/codemode/index.js.map +7 -0
- package/dist/control.js +11 -1
- package/dist/control.js.map +2 -2
- package/dist/host.js +5 -2
- package/dist/host.js.map +2 -2
- package/dist/main.js +40 -3
- package/dist/main.js.map +2 -2
- package/dist/scoops/cli.js +2 -0
- package/dist/scoops/cli.js.map +2 -2
- package/dist/scoops/index.js +3 -2
- package/dist/scoops/index.js.map +2 -2
- package/dist/scoops/requests.js +1 -1
- package/dist/scoops/requests.js.map +2 -2
- package/dist/services.js +3 -1
- package/dist/services.js.map +2 -2
- package/dist/skills/index.js +1 -1
- package/dist/skills/index.js.map +2 -2
- package/dist/spectrum/index.js +5 -1
- package/dist/spectrum/index.js.map +3 -3
- package/dist/spectrum/messages.js +12 -0
- package/dist/spectrum/messages.js.map +2 -2
- package/dist/spectrum/sprinkle-port.js +37 -0
- package/dist/spectrum/sprinkle-port.js.map +7 -0
- package/dist/sprinkles/index.js +216 -0
- package/dist/sprinkles/index.js.map +7 -0
- package/dist/sprinkles/kind.js +5 -0
- package/dist/sprinkles/kind.js.map +7 -0
- package/dist/sprinkles/sprinkles.js +65 -0
- package/dist/sprinkles/sprinkles.js.map +7 -0
- package/dist/types/client.d.ts +2 -0
- package/dist/types/codemode/index.d.ts +37 -0
- package/dist/types/control.d.ts +2 -1
- package/dist/types/host.d.ts +2 -0
- package/dist/types/main.d.ts +2 -0
- package/dist/types/scoops/cli.d.ts +1 -0
- package/dist/types/scoops/index.d.ts +2 -0
- package/dist/types/scoops/requests.d.ts +1 -1
- package/dist/types/services.d.ts +11 -1
- package/dist/types/spectrum/index.d.ts +3 -0
- package/dist/types/spectrum/sprinkle-port.d.ts +10 -0
- package/dist/types/sprinkles/index.d.ts +38 -0
- package/dist/types/sprinkles/kind.d.ts +10 -0
- package/dist/types/sprinkles/sprinkles.d.ts +24 -0
- package/package.json +4 -3
- package/packages/vfs-root/skills/sprinkles/SKILL.md +58 -0
- package/packages/vfs-root/skills/welcome/SKILL.md +31 -0
- package/packages/vfs-root/sprinkles/suggestions.shtml +391 -0
- package/packages/vfs-root/sprinkles/welcome.shtml +552 -0
package/README.md
CHANGED
|
@@ -206,7 +206,7 @@ The worker watches the roots and reloads on change. A `skills` prompt section, g
|
|
|
206
206
|
- **Templates:** `/parallel-review` and `/review-loop`, from [pi-subagents](https://github.com/nicobailon/pi-subagents) (MIT). Their wording is kept, and they're rewritten to use `agent --async --agent reviewer|worker`, `agent wait --notify`, `agent send` and `agent stop` instead of pi-subagents' `subagent` tool. The parts that need workflow scripts or forked contexts are left out.
|
|
207
207
|
|
|
208
208
|
**v6 skills.** None of the 29 skills SLICC v6 ships runs here unchanged. They name v6 paths (`/shared`, `/workspace`) or commands this kernel doesn't have. By what they wait for:
|
|
209
|
-
- **Rewritten here:** `delegation` (now the `agent` skill), `automation` (now `licks`)
|
|
209
|
+
- **Rewritten here:** `delegation` (now the `agent` skill), `automation` (now `licks`), `skill-authoring`, `sprinkles` and `welcome`.
|
|
210
210
|
- **Memory (PR 14):** `memory`, `gelatiere`, `wiki` (needs its `wiki` CLI).
|
|
211
211
|
- **The freezer (PR 15):** `transcript-export`.
|
|
212
212
|
- **The tray hub, upskill or the Install/Update panel:** `upgrade` (`upgrade apply`, bios#70), `handoff`, `slicc`, `ssh`, `cherry`.
|
|
@@ -214,13 +214,64 @@ The worker watches the roots and reloads on change. A `skills` prompt section, g
|
|
|
214
214
|
- **v6's JS realm, `.jsh` and `ipk` (slicc-kernel#68):** `jshd`, `mcp`, `workflows`, `package-execution`, `biome`, `ffmpeg`.
|
|
215
215
|
- **Commands the kernel doesn't have:** `mount`, `theme`, `dips`, `image-processing` (ImageMagick), `dns` (`dig`), `meminfo`, `x-search` (xAI credentials).
|
|
216
216
|
|
|
217
|
+
### Codemode
|
|
218
|
+
|
|
219
|
+
The model can write a short JavaScript program that calls its tools, with the `codemode` tool from pi's [`@earendil-works/pi-codemode`](https://www.npmjs.com/package/@earendil-works/pi-codemode). Only the script's output reaches the model, so a script can run tool calls in a loop or in parallel and filter large results first. Codemode is separate from v6's `.jsh` and the node shim: it has no Node APIs, file system, network or timers, only the agent's tools.
|
|
220
|
+
|
|
221
|
+
**On by default.** Cones and scoops get `codemode` next to their other tools, which stay declared as before (pi's `codemode.mode: on`). `"defaultTools": ["-codemode"]` in `~/.pi/agent/settings.json` turns it off. The setting is read when the worker starts.
|
|
222
|
+
|
|
223
|
+
**The tool** follows coding-agent's `codemode` tool:
|
|
224
|
+
- **Input.** The input is raw JavaScript, run as the body of an async function. An optional first line, `// @options: {"max_output_tokens": …, "timeout_ms": …}`, sets an output budget and a deadline. The budget defaults to 10,000 tokens, and there's no deadline by default.
|
|
225
|
+
- **Tools.** `tools.<name>(args)` calls the agent's own tools through their registrations, so a scoop's file guard applies to nested calls too. Arguments are validated as for a model call. `bash` resolves to `{ output, exit_code }`, also for a non-zero exit; the other tools resolve to their text and reject on an error.
|
|
226
|
+
- **Globals.** `text()`, `image()`, `console.*`, `return`, `exit()`, `ALL_TOOLS`, and `store()`/`load()`.
|
|
227
|
+
- **The store** lives in the conversation document `slicc.codemode`, which is rewindable and forks as of the fork point, so each branch keeps its own values. Writes are kept only when the script succeeds.
|
|
228
|
+
- **The result** starts with `Script completed` or `Script failed` and the wall time. Several text items each start with `==> text N/M <==`, and `console` lines come last in one `<console_output>` block. A failure adds `Script error:` with the error and the tool calls made before it, which aren't undone. Output past the budget keeps its start and end, and the full text goes to `/tmp/slicc-codemode-<call>.txt`.
|
|
229
|
+
- **Not ported.** coding-agent's `models`, `searchTools`, `describeTool` and `describeNamespace`.
|
|
230
|
+
|
|
231
|
+
**In the browser.** pi-codemode runs QuickJS (`quickjs-wasi`) in a worker of its own, so a spinning script never blocks the agent worker.
|
|
232
|
+
- **The worker.** It starts through `node:worker_threads`, which slicc-bios provides as an `environment` stub (see Patched dependencies).
|
|
233
|
+
- **The wasm.** The agent compiles `quickjs.wasm` itself and hands it over: it walks up the `node_modules` folders from its own module, as bios's resolver does, and fetches the first `quickjs-wasi/quickjs.wasm` it finds. On Node, pi-codemode loads it itself.
|
|
234
|
+
- **Isolation.** Seven is cross-origin isolated, so pi-codemode's `SharedArrayBuffer` interrupt works.
|
|
235
|
+
- **Overrides.** `runAgentWorker` takes `codemodeWasm` and `codemodeWorker` to override both, for bundled hosts.
|
|
236
|
+
|
|
237
|
+
||||||| parent of d578592 (wip: sprinkles)
|
|
238
|
+
### Sprinkles
|
|
239
|
+
|
|
240
|
+
A sprinkle is a small HTML panel next to the chat. pi has nothing for this; it's SLICC's UI, rendered by slicc-spectrum (≥ 1.23) through its `SprinklePort`.
|
|
241
|
+
|
|
242
|
+
**Files.** Every `.shtml` file in `/home/sprinkles`, as `<name>.shtml` or `<name>/<name>.shtml`, is a sprinkle. `<name>` is 1–64 of `a-z 0-9 . _ -`, and `welcome` is reserved for the built-in one. Its `<title>` and `<link rel="icon" href="<lucide name>">` give its title and rail icon (default `sparkles`). The worker watches the folder: the transcript service's `sprinkles` lists every one with its HTML, so spectrum gives each a rail button, and editing a file reloads its panel. Deleting the file retires it.
|
|
243
|
+
|
|
244
|
+
**Owners.** Each sprinkle talks to one agent, its owner. The owner is whoever first showed it, or the agent named with `sprinkle own`, otherwise the active cone at the time of the send. Owners live in the session document `slicc.sprinkles`.
|
|
245
|
+
|
|
246
|
+
**Talking back.**
|
|
247
|
+
- **Sends.** `SprinklePort.send(id, { action, data, target })` becomes `AgentControl.sprinkleSend`, then a lick on the `sprinkle` channel for the owner: source `sprinkle:<name>`, the action as its text and the data as JSON in its body. It steers and coalesces per sprinkle like any lick.
|
|
248
|
+
- **Calls.** `SprinklePort.call(id, method, args)` becomes `AgentControl.sprinkleCall`:
|
|
249
|
+
- `readFile` and `exists` reach only `/home`, with `/shared/…` mapped to `/home/…` so SLICC's own sprinkles run unchanged. Paths with `.` or `..` segments are refused, and a link is followed only if its target is in `/home` too;
|
|
250
|
+
- `getState` and `setState` keep one JSON value per sprinkle in `slicc.sprinkles`;
|
|
251
|
+
- there's no `exec`: a sprinkle that asks for one gets an error telling it to send the agent a message with `slicc.lick()` instead, so every command goes through an agent and its transcript.
|
|
252
|
+
|
|
253
|
+
**The `sprinkle` command** is the same script as `agent`, installed as `sprinkle`:
|
|
254
|
+
- `sprinkle list`;
|
|
255
|
+
- `sprinkle show <name>`: posts a `slicc.sprinkle` entry in the caller's chat, which the adapter shows as an inline sprinkle;
|
|
256
|
+
- `sprinkle own <name> [<agent>]`.
|
|
257
|
+
|
|
258
|
+
v6's `open`, `reload`, `close`, `send` and `chat` answer with what to do instead.
|
|
259
|
+
|
|
260
|
+
**Shipped sprinkles.** Only `welcome` and `suggestions`, with slicc-spectrum's reviewed copy.
|
|
261
|
+
- **Welcome.** The worker posts it inline in the first cone's chat once per session. Its `onboarding-complete` send writes `/home/.welcomed`, which the card checks, and the built-in `welcome` skill answers the lick.
|
|
262
|
+
- **Suggestions.** It needs the gelatiere, which comes with memory, so it ships but isn't listed yet.
|
|
263
|
+
|
|
264
|
+
The built-in `sprinkles` skill teaches the files, the bridge and the command.
|
|
265
|
+
|
|
217
266
|
## Patched dependencies
|
|
218
267
|
|
|
219
268
|
Fixes to pi stay in this repository. [`patches/patches.json`](patches/patches.json) lists each one with its `kind`, `package`, `patchedVersion`, `reason`, `removeWhen` and `verify` command, and an optional `marker` (a file in the package and a string it must contain):
|
|
220
269
|
|
|
221
270
|
- `monkeypatch`: code that runs in users' browsers is patched at runtime. `applyPatches()` in [`src/patches.ts`](src/patches.ts) applies them once before pi is used: `PortListener.start()` calls it, so `hostAgent` and a `PortListener` used directly with pi's `Server` both get them. Each one checks that the code it replaces still looks as expected and throws, naming its manifest entry, if not. Today there is one: pi-server's `Server.accept` calls `unref()` on its handshake timer, which browsers don't have, so the patch gives numeric timer ids a no-op `unref`.
|
|
222
271
|
- `patch-package`: repository-only fixes go in `patches/<package>+<version>.patch`, applied by a `postinstall` that runs [patch-package](https://github.com/ds300/patch-package). There are none yet.
|
|
223
|
-
- `environment`: something the host must provide.
|
|
272
|
+
- `environment`: something the host must provide.
|
|
273
|
+
- pi-server imports `randomUUID` from `node:crypto`. slicc-bios serves a stub backed by `globalThis.crypto`, and the integration bundles alias it to `test/integration/shims/node-crypto.js`.
|
|
274
|
+
- pi-codemode runs QuickJS in a `node:worker_threads` Worker. slicc-bios must serve a `node:worker_threads` stub that wraps a module Web Worker; inside the worker, the stub gives `parentPort` and `workerData` from the first message. `test/integration/shims/node-worker-threads.js` is that stub, and the integration bundles alias to it.
|
|
224
275
|
|
|
225
276
|
`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, so a pi bump is always a reviewed PR: rerun each entry's `verify`, then move `patchedVersion` forward or drop the entry.
|
|
226
277
|
|
package/bin/agent
CHANGED
package/dist/client.js
CHANGED
|
@@ -38,7 +38,14 @@ async function connectAgent(endpoint, options = {}) {
|
|
|
38
38
|
bound: true
|
|
39
39
|
});
|
|
40
40
|
const control = session.use(AgentControl);
|
|
41
|
-
const {
|
|
41
|
+
const {
|
|
42
|
+
state: transcript,
|
|
43
|
+
deliveries,
|
|
44
|
+
agents,
|
|
45
|
+
views,
|
|
46
|
+
commands,
|
|
47
|
+
sprinkles
|
|
48
|
+
} = session.use(AgentTranscript);
|
|
42
49
|
const settings = session.use(AgentSettings);
|
|
43
50
|
await session.ready(context);
|
|
44
51
|
return {
|
|
@@ -49,6 +56,7 @@ async function connectAgent(endpoint, options = {}) {
|
|
|
49
56
|
agents,
|
|
50
57
|
views,
|
|
51
58
|
commands,
|
|
59
|
+
sprinkles,
|
|
52
60
|
settings,
|
|
53
61
|
async prompt(text, whenBusy = "followUp") {
|
|
54
62
|
const sent = await control.send({ text, whenBusy, requestId: null }, context);
|
package/dist/client.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/client.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n type Context,\n createRemoteServiceBinding,\n type ReplicatedState,\n} from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport { Client, createClientServiceTransport } from '@earendil-works/pi-client';\nimport type { ConversationView } from '@earendil-works/pi-durable';\nimport {\n AGENT_SESSION,\n AgentControl,\n AgentSessions,\n AgentSettings,\n type AgentsSummary,\n AgentTranscript,\n type Command,\n type Delivered,\n type SendMode,\n} from './services.ts';\nimport { PortChannel, type PortEndpoint } from './wire.ts';\n\nexport interface AgentConnection {\n readonly serverId: string;\n readonly control: AgentControl;\n readonly transcript: ReplicatedState<ConversationView>;\n readonly deliveries: ReplicatedState<Record<string, Delivered>>;\n readonly agents?: ReplicatedState<AgentsSummary>;\n readonly views?: ReplicatedState<Record<string, ConversationView>>;\n readonly commands?: ReplicatedState<Command[]>;\n readonly settings: AgentSettings;\n prompt(text: string, whenBusy?: SendMode): Promise<string>;\n close(): Promise<void>;\n}\n\nfunction attached(client: Client): Promise<void> {\n return new Promise((resolve) => {\n const stop = client.onAttachmentChange(() => {\n stop();\n resolve();\n });\n });\n}\n\nexport async function connectAgent(\n endpoint: PortEndpoint,\n options: { context?: Context } = {}\n): Promise<AgentConnection> {\n const context = options.context ?? BACKGROUND_CONTEXT;\n const channel = new PortChannel(endpoint);\n const serverId = await channel.hello;\n const client = await Client.connect({ serverId, transportFactory: channel.transport });\n const server = createRemoteServiceBinding({\n services: [AgentSessions],\n transport: createClientServiceTransport(client, () => ({ serverId })),\n bound: true,\n });\n const ready = attached(client);\n await server.use(AgentSessions).attach(AGENT_SESSION, context);\n await ready;\n const session = createRemoteServiceBinding({\n services: [AgentControl, AgentTranscript, AgentSettings],\n transport: createClientServiceTransport(client, () => client.attachment),\n bound: true,\n });\n const control = session.use(AgentControl);\n const {
|
|
5
|
-
"mappings": "AAAA;AAAA,EAEE;AAAA,OAEK;AACP,SAAS,0BAA0B;AACnC,SAAS,QAAQ,oCAAoC;AAErD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,OAIK;
|
|
4
|
+
"sourcesContent": ["import {\n type Context,\n createRemoteServiceBinding,\n type ReplicatedState,\n} from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport { Client, createClientServiceTransport } from '@earendil-works/pi-client';\nimport type { ConversationView } from '@earendil-works/pi-durable';\nimport {\n AGENT_SESSION,\n AgentControl,\n AgentSessions,\n AgentSettings,\n type AgentsSummary,\n AgentTranscript,\n type Command,\n type Delivered,\n type SendMode,\n} from './services.ts';\nimport type { Sprinkle } from './sprinkles/kind.ts';\nimport { PortChannel, type PortEndpoint } from './wire.ts';\n\nexport interface AgentConnection {\n readonly serverId: string;\n readonly control: AgentControl;\n readonly transcript: ReplicatedState<ConversationView>;\n readonly deliveries: ReplicatedState<Record<string, Delivered>>;\n readonly agents?: ReplicatedState<AgentsSummary>;\n readonly views?: ReplicatedState<Record<string, ConversationView>>;\n readonly commands?: ReplicatedState<Command[]>;\n readonly sprinkles?: ReplicatedState<Sprinkle[]>;\n readonly settings: AgentSettings;\n prompt(text: string, whenBusy?: SendMode): Promise<string>;\n close(): Promise<void>;\n}\n\nfunction attached(client: Client): Promise<void> {\n return new Promise((resolve) => {\n const stop = client.onAttachmentChange(() => {\n stop();\n resolve();\n });\n });\n}\n\nexport async function connectAgent(\n endpoint: PortEndpoint,\n options: { context?: Context } = {}\n): Promise<AgentConnection> {\n const context = options.context ?? BACKGROUND_CONTEXT;\n const channel = new PortChannel(endpoint);\n const serverId = await channel.hello;\n const client = await Client.connect({ serverId, transportFactory: channel.transport });\n const server = createRemoteServiceBinding({\n services: [AgentSessions],\n transport: createClientServiceTransport(client, () => ({ serverId })),\n bound: true,\n });\n const ready = attached(client);\n await server.use(AgentSessions).attach(AGENT_SESSION, context);\n await ready;\n const session = createRemoteServiceBinding({\n services: [AgentControl, AgentTranscript, AgentSettings],\n transport: createClientServiceTransport(client, () => client.attachment),\n bound: true,\n });\n const control = session.use(AgentControl);\n const {\n state: transcript,\n deliveries,\n agents,\n views,\n commands,\n sprinkles,\n } = session.use(AgentTranscript);\n const settings = session.use(AgentSettings);\n await session.ready(context);\n return {\n serverId,\n control,\n transcript,\n deliveries,\n agents,\n views,\n commands,\n sprinkles,\n settings,\n async prompt(text, whenBusy = 'followUp') {\n const sent = await control.send({ text, whenBusy, requestId: null }, context);\n if (!sent.accepted) throw new Error(`${sent.error.code}: ${sent.error.message}`);\n const settled = await control.wait(sent.submissionId, context);\n if (settled.status !== 'done') throw new Error(`unanswered: ${settled.reason}`);\n return settled.text;\n },\n async close() {\n await server.use(AgentSessions).detach(context);\n await session.dispose(context);\n await server.dispose(context);\n await client.dispose();\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "AAAA;AAAA,EAEE;AAAA,OAEK;AACP,SAAS,0BAA0B;AACnC,SAAS,QAAQ,oCAAoC;AAErD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,OAIK;AAEP,SAAS,mBAAsC;AAgB/C,SAAS,SAAS,QAA+B;AAC/C,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAM,OAAO,OAAO,mBAAmB,MAAM;AAC3C,WAAK;AACL,cAAQ;AAAA,IACV,CAAC;AAAA,EACH,CAAC;AACH;AAEA,eAAsB,aACpB,UACA,UAAiC,CAAC,GACR;AAC1B,QAAM,UAAU,QAAQ,WAAW;AACnC,QAAM,UAAU,IAAI,YAAY,QAAQ;AACxC,QAAM,WAAW,MAAM,QAAQ;AAC/B,QAAM,SAAS,MAAM,OAAO,QAAQ,EAAE,UAAU,kBAAkB,QAAQ,UAAU,CAAC;AACrF,QAAM,SAAS,2BAA2B;AAAA,IACxC,UAAU,CAAC,aAAa;AAAA,IACxB,WAAW,6BAA6B,QAAQ,OAAO,EAAE,SAAS,EAAE;AAAA,IACpE,OAAO;AAAA,EACT,CAAC;AACD,QAAM,QAAQ,SAAS,MAAM;AAC7B,QAAM,OAAO,IAAI,aAAa,EAAE,OAAO,eAAe,OAAO;AAC7D,QAAM;AACN,QAAM,UAAU,2BAA2B;AAAA,IACzC,UAAU,CAAC,cAAc,iBAAiB,aAAa;AAAA,IACvD,WAAW,6BAA6B,QAAQ,MAAM,OAAO,UAAU;AAAA,IACvE,OAAO;AAAA,EACT,CAAC;AACD,QAAM,UAAU,QAAQ,IAAI,YAAY;AACxC,QAAM;AAAA,IACJ,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,QAAQ,IAAI,eAAe;AAC/B,QAAM,WAAW,QAAQ,IAAI,aAAa;AAC1C,QAAM,QAAQ,MAAM,OAAO;AAC3B,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,MAAM,OAAO,MAAM,WAAW,YAAY;AACxC,YAAM,OAAO,MAAM,QAAQ,KAAK,EAAE,MAAM,UAAU,WAAW,KAAK,GAAG,OAAO;AAC5E,UAAI,CAAC,KAAK,SAAU,OAAM,IAAI,MAAM,GAAG,KAAK,MAAM,IAAI,KAAK,KAAK,MAAM,OAAO,EAAE;AAC/E,YAAM,UAAU,MAAM,QAAQ,KAAK,KAAK,cAAc,OAAO;AAC7D,UAAI,QAAQ,WAAW,OAAQ,OAAM,IAAI,MAAM,eAAe,QAAQ,MAAM,EAAE;AAC9E,aAAO,QAAQ;AAAA,IACjB;AAAA,IACA,MAAM,QAAQ;AACZ,YAAM,OAAO,IAAI,aAAa,EAAE,OAAO,OAAO;AAC9C,YAAM,QAAQ,QAAQ,OAAO;AAC7B,YAAM,OAAO,QAAQ,OAAO;AAC5B,YAAM,OAAO,QAAQ;AAAA,IACvB;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { withAbortSignal } from "@earendil-works/chord/context";
|
|
2
|
+
import { Type } from "@earendil-works/pi-ai";
|
|
3
|
+
import { validateToolArguments } from "@earendil-works/pi-ai/utils/validation";
|
|
4
|
+
import {
|
|
5
|
+
CodemodeSandbox,
|
|
6
|
+
loadQuickJSWasm,
|
|
7
|
+
parseCodemodeSource,
|
|
8
|
+
renderDeclarations
|
|
9
|
+
} from "@earendil-works/pi-codemode";
|
|
10
|
+
import {
|
|
11
|
+
defineDoc,
|
|
12
|
+
defineExtension,
|
|
13
|
+
defineTool
|
|
14
|
+
} from "@earendil-works/pi-durable";
|
|
15
|
+
const CODEMODE = "codemode";
|
|
16
|
+
const DEFAULT_MAX_OUTPUT_TOKENS = 1e4;
|
|
17
|
+
const MEMORY_LIMIT_BYTES = 256 * 1024 * 1024;
|
|
18
|
+
const CHARS_PER_TOKEN = 4;
|
|
19
|
+
const CodemodeDoc = defineDoc({
|
|
20
|
+
kind: "slicc.codemode",
|
|
21
|
+
version: 1,
|
|
22
|
+
scope: "conversation",
|
|
23
|
+
history: "rewindable",
|
|
24
|
+
fork: "asOf",
|
|
25
|
+
initial: () => ({ store: {} })
|
|
26
|
+
});
|
|
27
|
+
const 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.
|
|
28
|
+
- \`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.
|
|
29
|
+
- Optional first line: \`// @options: {"max_output_tokens": 10000, "timeout_ms": 60000}\``;
|
|
30
|
+
const GLOBALS = [
|
|
31
|
+
"Globals:",
|
|
32
|
+
"- `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.",
|
|
33
|
+
"- `store(key, value)` and `load(key)` keep JSON values across codemode calls in this conversation.",
|
|
34
|
+
"- `ALL_TOOLS` lists every tool scripts can call."
|
|
35
|
+
].join("\n");
|
|
36
|
+
const BASH_OUTPUT = {
|
|
37
|
+
type: "object",
|
|
38
|
+
properties: { output: { type: "string" }, exit_code: { type: "number" } },
|
|
39
|
+
required: ["output", "exit_code"]
|
|
40
|
+
};
|
|
41
|
+
function scriptTool(tool) {
|
|
42
|
+
return {
|
|
43
|
+
name: tool.name,
|
|
44
|
+
description: tool.description,
|
|
45
|
+
inputSchema: tool.parameters,
|
|
46
|
+
...tool.name === "bash" ? { outputSchema: BASH_OUTPUT } : {}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function codemodeDescription(declared) {
|
|
50
|
+
const tools = declared.filter((tool) => tool.name !== CODEMODE).map((tool) => ({ ...scriptTool(tool), execute: () => void 0 }));
|
|
51
|
+
const sections = [INTRO, GLOBALS];
|
|
52
|
+
if (tools.length) sections.push(`Nested tools:
|
|
53
|
+
${renderDeclarations({ tools })}`);
|
|
54
|
+
return sections.join("\n\n");
|
|
55
|
+
}
|
|
56
|
+
function textOf(content) {
|
|
57
|
+
return (content ?? []).filter((part) => part.type === "text").map((part) => part.text).join("\n");
|
|
58
|
+
}
|
|
59
|
+
async function callNested(tool, args, api, context, id) {
|
|
60
|
+
const prepared = tool.prepareArguments ? tool.prepareArguments(args) : args;
|
|
61
|
+
const valid = validateToolArguments(tool, {
|
|
62
|
+
type: "toolCall",
|
|
63
|
+
id,
|
|
64
|
+
name: tool.name,
|
|
65
|
+
arguments: prepared
|
|
66
|
+
});
|
|
67
|
+
const decoder = new TextDecoder();
|
|
68
|
+
let output = "";
|
|
69
|
+
const nested = {
|
|
70
|
+
...api,
|
|
71
|
+
callId: id,
|
|
72
|
+
outputWindow: void 0,
|
|
73
|
+
output: (chunk) => {
|
|
74
|
+
output += typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true });
|
|
75
|
+
},
|
|
76
|
+
diagnostic: () => void 0,
|
|
77
|
+
details: async () => void 0
|
|
78
|
+
};
|
|
79
|
+
const bash = tool.name === "bash";
|
|
80
|
+
try {
|
|
81
|
+
const result = await tool.execute(valid, nested, context);
|
|
82
|
+
const text = result.content ? textOf(result.content) : output;
|
|
83
|
+
if (result.isError) throw new Error(text || `Tool "${tool.name}" failed`);
|
|
84
|
+
return bash ? { output: text, exit_code: 0 } : text;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
87
|
+
const exit = /exited with code (\d+)/.exec(message);
|
|
88
|
+
if (bash && exit) return { output, exit_code: Number(exit[1]) };
|
|
89
|
+
throw new Error(message);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function valueText(value) {
|
|
93
|
+
return typeof value === "string" ? value : JSON.stringify(value) ?? String(value);
|
|
94
|
+
}
|
|
95
|
+
function formatOutput(output) {
|
|
96
|
+
const total = output.filter((item) => item.type === "text" && !item.console).length;
|
|
97
|
+
const items = [];
|
|
98
|
+
const lines = [];
|
|
99
|
+
let index = 0;
|
|
100
|
+
for (const item of output) {
|
|
101
|
+
if (item.type === "image") items.push(item);
|
|
102
|
+
else if (item.console) lines.push(item.text);
|
|
103
|
+
else {
|
|
104
|
+
index++;
|
|
105
|
+
items.push({
|
|
106
|
+
type: "text",
|
|
107
|
+
text: total > 1 ? `==> text ${index}/${total} <==
|
|
108
|
+
${item.text}` : item.text
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (lines.length)
|
|
113
|
+
items.push({ type: "text", text: `<console_output>
|
|
114
|
+
${lines.join("\n")}
|
|
115
|
+
</console_output>` });
|
|
116
|
+
return items;
|
|
117
|
+
}
|
|
118
|
+
function joinText(items) {
|
|
119
|
+
const joined = [];
|
|
120
|
+
for (const item of items) {
|
|
121
|
+
const last = joined.at(-1);
|
|
122
|
+
if (item.type === "text" && last?.type === "text") {
|
|
123
|
+
const separator = last.text === "" || last.text.endsWith("\n") ? "" : "\n";
|
|
124
|
+
joined[joined.length - 1] = { type: "text", text: `${last.text}${separator}${item.text}` };
|
|
125
|
+
} else joined.push(item);
|
|
126
|
+
}
|
|
127
|
+
return joined;
|
|
128
|
+
}
|
|
129
|
+
function formatError(error, calls) {
|
|
130
|
+
const heads = {
|
|
131
|
+
timeout: `Script timed out: ${error.message}`,
|
|
132
|
+
aborted: `Script aborted: ${error.message}`,
|
|
133
|
+
sandbox: `Script sandbox failed: ${error.message}`
|
|
134
|
+
};
|
|
135
|
+
const head = error.kind === "script" ? error.stack ?? `${error.name ?? "Error"}: ${error.message}` : heads[error.kind];
|
|
136
|
+
const summary = calls.length ? `Tool calls made before the failure (they are not undone): ${calls.map((call) => `${call.name} (${call.status})`).join(", ")}` : "No tool calls were made.";
|
|
137
|
+
return `${head}
|
|
138
|
+
|
|
139
|
+
${summary}`;
|
|
140
|
+
}
|
|
141
|
+
function truncate(items, maxTokens) {
|
|
142
|
+
const text = items.filter((item) => item.type === "text").map((item) => item.text).join("\n");
|
|
143
|
+
const limit = maxTokens * CHARS_PER_TOKEN;
|
|
144
|
+
if (text.length <= limit) return { items: [...items], full: null };
|
|
145
|
+
const half = Math.floor(limit / 2);
|
|
146
|
+
const kept = `${text.slice(0, half)}
|
|
147
|
+
|
|
148
|
+
[\u2026 ${text.length - 2 * half} characters omitted \u2026]
|
|
149
|
+
|
|
150
|
+
${text.slice(-half)}`;
|
|
151
|
+
return {
|
|
152
|
+
items: [{ type: "text", text: kept }, ...items.filter((item) => item.type === "image")],
|
|
153
|
+
full: text
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
async function spill(api, text, context) {
|
|
157
|
+
const path = `/tmp/slicc-codemode-${api.callId.replace(/[^\w-]/g, "_")}.txt`;
|
|
158
|
+
const written = await api.env?.writeFile(path, text, context);
|
|
159
|
+
return written?.ok ? path : "";
|
|
160
|
+
}
|
|
161
|
+
async function saveStore(api, result, context) {
|
|
162
|
+
if (!result.ok) return;
|
|
163
|
+
const { set, delete: removed } = result.storeWrites;
|
|
164
|
+
if (!Object.keys(set).length && !removed.length) return;
|
|
165
|
+
await api.commit(async (tx) => {
|
|
166
|
+
const doc = await tx.doc(CodemodeDoc, api.conversationId);
|
|
167
|
+
for (const key of removed) delete doc.store[key];
|
|
168
|
+
Object.assign(doc.store, set);
|
|
169
|
+
}, context);
|
|
170
|
+
}
|
|
171
|
+
function codemodeTool(options) {
|
|
172
|
+
return defineTool({
|
|
173
|
+
name: CODEMODE,
|
|
174
|
+
description: codemodeDescription(options.declared),
|
|
175
|
+
parameters: Type.Object({ code: Type.String({ description: "Raw JavaScript source." }) }),
|
|
176
|
+
replay: "unsafe",
|
|
177
|
+
async execute({ code }, api, context) {
|
|
178
|
+
const started = performance.now();
|
|
179
|
+
const parsed = parseCodemodeSource(code);
|
|
180
|
+
const agent = await api.agent(context);
|
|
181
|
+
const callable = agent.tools.filter((tool) => tool.name !== CODEMODE);
|
|
182
|
+
let count = 0;
|
|
183
|
+
const tools = callable.map((tool) => ({
|
|
184
|
+
...scriptTool(tool),
|
|
185
|
+
execute: (args, { signal }) => callNested(tool, args, api, withAbortSignal(signal, context), `${api.callId}:${++count}`)
|
|
186
|
+
}));
|
|
187
|
+
const saved = await api.snapshot(CodemodeDoc, api.conversationId, context);
|
|
188
|
+
const sandbox = options.sandbox({
|
|
189
|
+
tools,
|
|
190
|
+
timeoutMs: parsed.options.timeoutMs ?? Number.POSITIVE_INFINITY,
|
|
191
|
+
memoryLimitBytes: MEMORY_LIMIT_BYTES
|
|
192
|
+
});
|
|
193
|
+
let result;
|
|
194
|
+
try {
|
|
195
|
+
result = await sandbox.execute(parsed.code, {
|
|
196
|
+
signal: context.abortSignal,
|
|
197
|
+
store: { ...saved?.store }
|
|
198
|
+
});
|
|
199
|
+
} finally {
|
|
200
|
+
await sandbox.close();
|
|
201
|
+
}
|
|
202
|
+
await saveStore(api, result, context);
|
|
203
|
+
const output = [...result.output];
|
|
204
|
+
if (result.ok && result.value !== void 0)
|
|
205
|
+
output.push({ type: "text", text: valueText(result.value) });
|
|
206
|
+
const items = formatOutput(output);
|
|
207
|
+
if (!result.ok)
|
|
208
|
+
items.push({
|
|
209
|
+
type: "text",
|
|
210
|
+
text: `Script error:
|
|
211
|
+
${formatError(result.error, result.calls)}`
|
|
212
|
+
});
|
|
213
|
+
const cut = truncate(
|
|
214
|
+
joinText(items),
|
|
215
|
+
parsed.options.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS
|
|
216
|
+
);
|
|
217
|
+
const path = cut.full === null ? "" : await spill(api, cut.full, context);
|
|
218
|
+
const note = path ? [{ type: "text", text: `Full output: ${path}` }] : [];
|
|
219
|
+
const seconds = ((performance.now() - started) / 1e3).toFixed(1);
|
|
220
|
+
const header = `${result.ok ? "Script completed" : "Script failed"}
|
|
221
|
+
Wall time ${seconds} seconds
|
|
222
|
+
Output:
|
|
223
|
+
`;
|
|
224
|
+
return {
|
|
225
|
+
content: [{ type: "text", text: header }, ...joinText([...cut.items, ...note])],
|
|
226
|
+
...result.ok ? {} : { isError: true }
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
function codemodeExtension(tools) {
|
|
232
|
+
return defineExtension({ name: "slicc-codemode", tools });
|
|
233
|
+
}
|
|
234
|
+
function disabledBySettings(text) {
|
|
235
|
+
if (!text) return false;
|
|
236
|
+
try {
|
|
237
|
+
const settings = JSON.parse(text);
|
|
238
|
+
return Array.isArray(settings.defaultTools) && settings.defaultTools.includes(`-${CODEMODE}`);
|
|
239
|
+
} catch {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function wasmCandidates(from) {
|
|
244
|
+
const out = [];
|
|
245
|
+
let dir = new URL(".", from);
|
|
246
|
+
for (; ; ) {
|
|
247
|
+
out.push(new URL("node_modules/quickjs-wasi/quickjs.wasm", dir).href);
|
|
248
|
+
if (dir.pathname === "/") return out;
|
|
249
|
+
dir = new URL("..", dir);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
async function locateWasm(fetcher, from) {
|
|
253
|
+
for (const url of wasmCandidates(from)) {
|
|
254
|
+
const response = await fetcher(url).catch(() => void 0);
|
|
255
|
+
if (response?.ok) return WebAssembly.compile(await response.arrayBuffer());
|
|
256
|
+
}
|
|
257
|
+
return void 0;
|
|
258
|
+
}
|
|
259
|
+
function sandboxFactory(wasm, workerUrl) {
|
|
260
|
+
let found;
|
|
261
|
+
return (options) => {
|
|
262
|
+
found ??= wasm().catch(() => void 0);
|
|
263
|
+
return new CodemodeSandbox({
|
|
264
|
+
...options,
|
|
265
|
+
...workerUrl ? { workerUrl } : {},
|
|
266
|
+
wasm: found.then((module) => module ?? loadQuickJSWasm())
|
|
267
|
+
});
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
export {
|
|
271
|
+
CODEMODE,
|
|
272
|
+
CodemodeDoc,
|
|
273
|
+
DEFAULT_MAX_OUTPUT_TOKENS,
|
|
274
|
+
MEMORY_LIMIT_BYTES,
|
|
275
|
+
callNested,
|
|
276
|
+
codemodeDescription,
|
|
277
|
+
codemodeExtension,
|
|
278
|
+
codemodeTool,
|
|
279
|
+
disabledBySettings,
|
|
280
|
+
formatError,
|
|
281
|
+
formatOutput,
|
|
282
|
+
joinText,
|
|
283
|
+
locateWasm,
|
|
284
|
+
sandboxFactory,
|
|
285
|
+
truncate,
|
|
286
|
+
wasmCandidates
|
|
287
|
+
};
|
|
288
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 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 { validateToolArguments } from '@earendil-works/pi-ai/utils/validation';\nimport {\n type CodemodeError,\n type CodemodeJsonSchema,\n type CodemodeOutputItem,\n type CodemodeResult,\n CodemodeSandbox,\n type CodemodeSandboxOptions,\n type CodemodeTool,\n loadQuickJSWasm,\n parseCodemodeSource,\n renderDeclarations,\n} from '@earendil-works/pi-codemode';\nimport {\n defineDoc,\n defineExtension,\n defineTool,\n type Extension,\n type ToolExecutionApi,\n type ToolRegistration,\n} from '@earendil-works/pi-durable';\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 close(): Promise<void>;\n}\n\nexport type SandboxFactory = (options: CodemodeSandboxOptions) => Sandbox;\n\nexport interface CodemodeOptions {\n sandbox: SandboxFactory;\n declared: readonly ToolRegistration[];\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 BASH_OUTPUT: CodemodeJsonSchema = {\n type: 'object',\n properties: { output: { type: 'string' }, exit_code: { type: 'number' } },\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(declared: readonly ToolRegistration[]): string {\n const tools = declared\n .filter((tool) => tool.name !== CODEMODE)\n .map((tool) => ({ ...scriptTool(tool), execute: () => undefined }));\n const sections = [INTRO, GLOBALS];\n if (tools.length) sections.push(`Nested tools:\\n${renderDeclarations({ tools })}`);\n return sections.join('\\n\\n');\n}\n\nfunction textOf(content: readonly { type: string; text?: string }[] | undefined): string {\n return (content ?? [])\n .filter((part) => part.type === 'text')\n .map((part) => part.text)\n .join('\\n');\n}\n\nexport async function callNested(\n tool: ToolRegistration,\n args: unknown,\n api: ToolExecutionApi,\n context: Context,\n id: string\n): Promise<unknown> {\n const prepared = tool.prepareArguments ? tool.prepareArguments(args) : args;\n const valid = validateToolArguments(tool, {\n type: 'toolCall',\n id,\n name: tool.name,\n arguments: prepared as never,\n });\n const decoder = new TextDecoder();\n let output = '';\n const nested: ToolExecutionApi = {\n ...api,\n callId: id,\n outputWindow: undefined,\n output: (chunk) => {\n output += typeof chunk === 'string' ? chunk : decoder.decode(chunk, { stream: true });\n },\n diagnostic: () => undefined,\n details: async () => undefined,\n };\n const bash = tool.name === 'bash';\n try {\n const result = await tool.execute(valid, nested, context);\n const text = result.content ? textOf(result.content) : output;\n if (result.isError) throw new Error(text || `Tool \"${tool.name}\" failed`);\n return bash ? { output: text, exit_code: 0 } : text;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const exit = /exited with code (\\d+)/.exec(message);\n if (bash && exit) return { output, exit_code: Number(exit[1]) };\n throw new Error(message);\n }\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),\n parameters: Type.Object({ code: Type.String({ description: 'Raw JavaScript source.' }) }),\n replay: 'unsafe',\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 let count = 0;\n const tools: CodemodeTool[] = callable.map((tool) => ({\n ...scriptTool(tool),\n execute: (args, { signal }) =>\n callNested(tool, args, api, withAbortSignal(signal, context), `${api.callId}:${++count}`),\n }));\n const saved = await api.snapshot(CodemodeDoc, api.conversationId, context);\n const sandbox = options.sandbox({\n tools,\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 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 content: [{ 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\nexport function sandboxFactory(\n wasm: () => Promise<WebAssembly.Module | undefined>,\n workerUrl?: string | URL\n): SandboxFactory {\n let found: Promise<WebAssembly.Module | undefined> | undefined;\n return (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}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/control.js
CHANGED
|
@@ -15,6 +15,15 @@ function created(run) {
|
|
|
15
15
|
(error) => ({ id: null, error: error.message })
|
|
16
16
|
);
|
|
17
17
|
}
|
|
18
|
+
function sprinkleControl(sprinkles) {
|
|
19
|
+
return {
|
|
20
|
+
sprinkleSend: async (id, payload, context) => sprinkles ? sprinkles.send(id, payload, context) : { delivered: false },
|
|
21
|
+
async sprinkleCall(id, method, args, context) {
|
|
22
|
+
if (!sprinkles) throw new Error("This agent has no sprinkles.");
|
|
23
|
+
return sprinkles.call(id, method, args, context);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
18
27
|
function submissionId(value) {
|
|
19
28
|
const id = Number(value);
|
|
20
29
|
return /^[1-9][0-9]*$/.test(value) && Number.isSafeInteger(id) ? id : void 0;
|
|
@@ -55,7 +64,7 @@ function busy(view) {
|
|
|
55
64
|
const inbox = view.docs["pi.inbox"];
|
|
56
65
|
return Boolean(live?.run) || Boolean(inbox?.items?.length);
|
|
57
66
|
}
|
|
58
|
-
function createAgentControl(harness, target, deliveries, licks, scoops, skills) {
|
|
67
|
+
function createAgentControl(harness, target, deliveries, licks, scoops, skills, sprinkles) {
|
|
59
68
|
const cone = cones(target);
|
|
60
69
|
const current = () => cone.cone();
|
|
61
70
|
const agents = target.agents;
|
|
@@ -144,6 +153,7 @@ function createAgentControl(harness, target, deliveries, licks, scoops, skills)
|
|
|
144
153
|
return { status: "done", text: answerText(entry?.model?.[0]?.content), reason: null };
|
|
145
154
|
},
|
|
146
155
|
withdraw: (id, context) => withdraw(current(), id, context),
|
|
156
|
+
...sprinkleControl(sprinkles),
|
|
147
157
|
async unqueue(agentId, id, context) {
|
|
148
158
|
return withdraw(await conversationFor(agentId, context), id, context);
|
|
149
159
|
},
|
package/dist/control.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/control.ts"],
|
|
4
|
-
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {\n type AgentChange,\n AssistantEntry,\n type Conversation,\n ConversationBusy,\n type ConversationView,\n type EntryId,\n type EntryRecord,\n type Harness,\n type SubmissionId,\n} from '@earendil-works/pi-durable';\nimport { answerText } from './agent.ts';\nimport type { Agents } from './agents.ts';\nimport type { Cones } from './cone.ts';\nimport type { Deliveries } from './deliveries.ts';\nimport type { LickSources, Licks } from './licks/index.ts';\nimport { LICK_STATE_KIND } from './licks/state.ts';\nimport type { Scoops, ScoopsRuntime } from './scoops/index.ts';\nimport type { AgentControl, Created, OperationError, SendResponse } from './services.ts';\nimport type { SkillsRuntime } from './skills/index.ts';\n\nexport interface HostLicks {\n licks: Licks;\n sources: LickSources;\n}\n\nexport interface HostScoops {\n scoops: Scoops;\n runtime: ScoopsRuntime;\n}\n\ntype WithAgents = { agents?: Agents };\n\nexport function readable(out: string): string {\n const text = out.replace(/^(?:sub)?agent: /, '').trim();\n const sentence = text.charAt(0).toUpperCase() + text.slice(1);\n return /[.!?]$/.test(sentence) ? sentence : `${sentence}.`;\n}\n\nfunction created(run: () => Promise<string>): Promise<Created> {\n return run().then(\n (id) => ({ id, error: null }),\n (error: Error) => ({ id: null, error: error.message })\n );\n}\n\nexport function submissionId(value: string): SubmissionId | undefined {\n const id = Number(value);\n return /^[1-9][0-9]*$/.test(value) && Number.isSafeInteger(id) ? (id as SubmissionId) : undefined;\n}\n\nfunction operationError(error: unknown): OperationError {\n if (error instanceof ConversationBusy) return { code: 'busy', message: error.message };\n return { code: 'failed', message: error instanceof Error ? error.message : String(error) };\n}\n\nasync function accepted(operation: () => Promise<{ id: number } | number>): Promise<SendResponse> {\n try {\n const result = await operation();\n const id = typeof result === 'number' ? result : result.id;\n return { accepted: true, submissionId: String(id), error: null };\n } catch (error) {\n return { accepted: false, submissionId: null, error: operationError(error) };\n }\n}\n\nfunction cones(target: Conversation | Cones): Cones {\n if ('cone' in target) return target;\n return {\n cone: () => target,\n switchCone: async () => {\n throw new Error('this conversation has no cone to rewind');\n },\n };\n}\n\ninterface Turn {\n text: string;\n at: EntryId | null;\n}\n\nfunction lastTurn(view: ConversationView, messageId: string | null): Turn | null {\n const { entries } = view;\n const target = entries.findIndex((entry) => `e${entry.id}` === messageId);\n if (target < 0) return null;\n const turn = entries.slice(0, target).findLastIndex((entry) => entry.kind === 'pi.user');\n if (turn < 0) return null;\n const user = entries[turn] as EntryRecord;\n return { text: answerText(user.model?.[0]?.content), at: entries[turn - 1]?.id ?? null };\n}\n\nfunction busy(view: ConversationView): boolean {\n const live = view.docs['pi.live'] as { run?: unknown } | undefined;\n const inbox = view.docs['pi.inbox'] as { items?: unknown[] } | undefined;\n return Boolean(live?.run) || Boolean(inbox?.items?.length);\n}\n\nexport function createAgentControl(\n harness: Harness,\n target: Conversation | Cones,\n deliveries?: Deliveries,\n licks?: HostLicks,\n scoops?: HostScoops,\n skills?: Pick<SkillsRuntime, 'expand'>\n): AgentControl {\n const cone = cones(target);\n const current = () => cone.cone();\n const agents = (target as WithAgents).agents;\n const conversationFor = async (agentId: string | null | undefined, context: Context) => {\n if (!agentId || !agents) return current();\n const found = await agents.conversation(agentId, context);\n if (!found) throw new Error(`There is no agent ${agentId}.`);\n return found;\n };\n const withdraw = async (conversation: Conversation, id: string, context: Context) => {\n const parsed = submissionId(id);\n if (parsed === undefined) return { outcome: 'not_found' as const };\n const result = await harness.abortSubmission(parsed, context, conversation.id);\n if (result === 'aborted') return { outcome: 'withdrawn' as const };\n return {\n outcome: result === 'not_found' ? ('not_found' as const) : ('already_placed' as const),\n };\n };\n let lock: Promise<unknown> = Promise.resolve();\n const serial = <T>(operation: () => Promise<T>): Promise<T> => {\n const run = lock.then(operation, operation);\n lock = run.catch(() => undefined);\n return run;\n };\n return {\n send(request, context) {\n return serial(async () => {\n const text = skills ? await skills.expand(request.text, context) : request.text;\n const response = await accepted(async () =>\n (await conversationFor(request.agentId, context)).submit(\n {\n type: 'input',\n content: text,\n whenBusy: request.whenBusy,\n ...(request.requestId ? { requestId: request.requestId } : {}),\n },\n context\n )\n );\n const id = response.submissionId === null ? undefined : submissionId(response.submissionId);\n if (id !== undefined) deliveries?.expect(id, request.whenBusy);\n return response;\n });\n },\n rewind(messageId, context) {\n return serial(async () => {\n const conversation = current();\n const attached = await conversation.viewState(context);\n const view = attached.value;\n attached.dispose();\n const pending = (await harness.inspect(context)).submissions.some(\n (submission) => submission.conversationId === conversation.id\n );\n if (pending || busy(view)) return { done: false, text: null, reason: 'busy' };\n const turn = lastTurn(view, messageId);\n if (!turn) return { done: false, text: null, reason: 'no-turn' };\n const agent = view.docs['pi.agent'] as AgentChange | undefined;\n const ownership = { kind: 'ownerless' } as const;\n const fork =\n turn.at === null\n ? await harness.createConversation(\n {\n ownership,\n agent: {\n model: agent?.model ?? null,\n thinkingLevel: agent?.thinkingLevel ?? null,\n },\n },\n context\n )\n : await conversation.fork(turn.at, { ownership }, context);\n const changed =\n scoops && agents\n ? await scoops.scoops.rewound(agents.activeCone(), fork, context)\n : { stopped: [], restored: [] };\n await fork.submit(\n { type: 'write', entry: { kind: 'slicc.rewound', data: { turns: 1, ...changed } } },\n context\n );\n await cone.switchCone(fork, context);\n return { done: true, text: turn.text, reason: null };\n });\n },\n\n async wait(id, context) {\n const parsed = submissionId(id);\n const submission =\n parsed === undefined ? undefined : await harness.submission(parsed, context);\n if (!submission) throw new Error(`unknown submission ${id}`);\n const settled = await submission.wait(context);\n const { answer, reason } = settled as { answer?: EntryId; reason?: string };\n if (answer === undefined) return { status: 'unanswered', text: null, reason: String(reason) };\n const entry = await harness.commit((tx) => tx.entry(AssistantEntry, answer), context);\n return { status: 'done', text: answerText(entry?.model?.[0]?.content), reason: null };\n },\n withdraw: (id, context) => withdraw(current(), id, context),\n async unqueue(agentId, id, context) {\n return withdraw(await conversationFor(agentId, context), id, context);\n },\n abort: (context) => current().abort(context),\n compact: (instructions, context) =>\n accepted(() => current().compact(instructions ?? undefined, context)),\n reset: (handoff, context) => current().reset(handoff ?? undefined, context),\n resolveLick(lickId, state, context) {\n return serial(async () => {\n if (!licks) return { done: false, text: null, error: 'this agent has no licks' };\n const conversation = current();\n const action = state === 'confirmed' ? 'confirm' : 'dismiss';\n try {\n const text = await licks.licks.decide(conversation, lickId, action, undefined, context);\n await conversation.submit(\n {\n type: 'write',\n entry: {\n kind: LICK_STATE_KIND,\n data: { lick: lickId, state, by: 'user' },\n model: [\n {\n role: 'user',\n content: `The user ${state} lick ${lickId}: ${text}`,\n timestamp: Date.now(),\n },\n ],\n },\n },\n context\n );\n return { done: true, text, error: null };\n } catch (error) {\n return { done: false, text: null, error: (error as Error).message };\n }\n });\n },\n async webhook(name, delivery, context) {\n const delivered = licks\n ? await licks.sources.webhook(\n name,\n {\n ...(delivery.id ? { id: delivery.id } : {}),\n headers: delivery.headers,\n body: delivery.body,\n },\n context\n )\n : false;\n return { delivered };\n },\n async stopAgent(agentId, context) {\n await (await conversationFor(agentId, context)).abort(context);\n },\n async selectCone(agentId, context) {\n await agents?.selectCone(agentId, context);\n },\n createCone(name, context) {\n return created(async () => {\n if (!agents) throw new Error('This agent has a single cone.');\n return agents.createCone(name, context);\n });\n },\n createScoop(parentId, name, context) {\n return created(async () => {\n if (!scoops) throw new Error('This agent has no scoops.');\n const roles = await scoops.runtime.roles(context);\n const answer = await scoops.scoops.spawn(\n { cone: parentId, name, prompts: [], fromAgent: false, limits: roles.limits },\n context\n );\n if (answer.code !== 0) throw new Error(readable(answer.out));\n return `scoop:${answer.out.trim()}`;\n });\n },\n drop(agentId, context) {\n return created(async () => {\n if (!scoops) throw new Error('This agent has no scoops.');\n const answer = await scoops.scoops.stop(agentId, false, context);\n if (answer.code !== 0) throw new Error(readable(answer.out));\n return agentId;\n });\n },\n configure: async (change, context) =>\n (await conversationFor(change.agentId, context)).configure(\n {\n ...(change.model ? { model: change.model } : {}),\n ...(change.thinkingLevel ? { thinkingLevel: change.thinkingLevel } : {}),\n },\n context\n ),\n };\n}\n"],
|
|
5
|
-
"mappings": "AACA;AAAA,EAEE;AAAA,EAEA;AAAA,OAMK;AACP,SAAS,kBAAkB;AAK3B,SAAS,uBAAuB;
|
|
4
|
+
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {\n type AgentChange,\n AssistantEntry,\n type Conversation,\n ConversationBusy,\n type ConversationView,\n type EntryId,\n type EntryRecord,\n type Harness,\n type SubmissionId,\n} from '@earendil-works/pi-durable';\nimport { answerText } from './agent.ts';\nimport type { Agents } from './agents.ts';\nimport type { Cones } from './cone.ts';\nimport type { Deliveries } from './deliveries.ts';\nimport type { LickSources, Licks } from './licks/index.ts';\nimport { LICK_STATE_KIND } from './licks/state.ts';\nimport type { Scoops, ScoopsRuntime } from './scoops/index.ts';\nimport type { AgentControl, Created, OperationError, SendResponse } from './services.ts';\nimport type { SkillsRuntime } from './skills/index.ts';\nimport type { SprinklesRuntime } from './sprinkles/index.ts';\n\nexport interface HostLicks {\n licks: Licks;\n sources: LickSources;\n}\n\nexport interface HostScoops {\n scoops: Scoops;\n runtime: ScoopsRuntime;\n}\n\ntype WithAgents = { agents?: Agents };\n\nexport function readable(out: string): string {\n const text = out.replace(/^(?:sub)?agent: /, '').trim();\n const sentence = text.charAt(0).toUpperCase() + text.slice(1);\n return /[.!?]$/.test(sentence) ? sentence : `${sentence}.`;\n}\n\nfunction created(run: () => Promise<string>): Promise<Created> {\n return run().then(\n (id) => ({ id, error: null }),\n (error: Error) => ({ id: null, error: error.message })\n );\n}\n\nfunction sprinkleControl(\n sprinkles: Pick<SprinklesRuntime, 'send' | 'call'> | undefined\n): Pick<AgentControl, 'sprinkleSend' | 'sprinkleCall'> {\n return {\n sprinkleSend: async (id, payload, context) =>\n sprinkles ? sprinkles.send(id, payload, context) : { delivered: false },\n async sprinkleCall(id, method, args, context) {\n if (!sprinkles) throw new Error('This agent has no sprinkles.');\n return sprinkles.call(id, method, args, context);\n },\n };\n}\n\nexport function submissionId(value: string): SubmissionId | undefined {\n const id = Number(value);\n return /^[1-9][0-9]*$/.test(value) && Number.isSafeInteger(id) ? (id as SubmissionId) : undefined;\n}\n\nfunction operationError(error: unknown): OperationError {\n if (error instanceof ConversationBusy) return { code: 'busy', message: error.message };\n return { code: 'failed', message: error instanceof Error ? error.message : String(error) };\n}\n\nasync function accepted(operation: () => Promise<{ id: number } | number>): Promise<SendResponse> {\n try {\n const result = await operation();\n const id = typeof result === 'number' ? result : result.id;\n return { accepted: true, submissionId: String(id), error: null };\n } catch (error) {\n return { accepted: false, submissionId: null, error: operationError(error) };\n }\n}\n\nfunction cones(target: Conversation | Cones): Cones {\n if ('cone' in target) return target;\n return {\n cone: () => target,\n switchCone: async () => {\n throw new Error('this conversation has no cone to rewind');\n },\n };\n}\n\ninterface Turn {\n text: string;\n at: EntryId | null;\n}\n\nfunction lastTurn(view: ConversationView, messageId: string | null): Turn | null {\n const { entries } = view;\n const target = entries.findIndex((entry) => `e${entry.id}` === messageId);\n if (target < 0) return null;\n const turn = entries.slice(0, target).findLastIndex((entry) => entry.kind === 'pi.user');\n if (turn < 0) return null;\n const user = entries[turn] as EntryRecord;\n return { text: answerText(user.model?.[0]?.content), at: entries[turn - 1]?.id ?? null };\n}\n\nfunction busy(view: ConversationView): boolean {\n const live = view.docs['pi.live'] as { run?: unknown } | undefined;\n const inbox = view.docs['pi.inbox'] as { items?: unknown[] } | undefined;\n return Boolean(live?.run) || Boolean(inbox?.items?.length);\n}\n\nexport function createAgentControl(\n harness: Harness,\n target: Conversation | Cones,\n deliveries?: Deliveries,\n licks?: HostLicks,\n scoops?: HostScoops,\n skills?: Pick<SkillsRuntime, 'expand'>,\n sprinkles?: Pick<SprinklesRuntime, 'send' | 'call'>\n): AgentControl {\n const cone = cones(target);\n const current = () => cone.cone();\n const agents = (target as WithAgents).agents;\n const conversationFor = async (agentId: string | null | undefined, context: Context) => {\n if (!agentId || !agents) return current();\n const found = await agents.conversation(agentId, context);\n if (!found) throw new Error(`There is no agent ${agentId}.`);\n return found;\n };\n const withdraw = async (conversation: Conversation, id: string, context: Context) => {\n const parsed = submissionId(id);\n if (parsed === undefined) return { outcome: 'not_found' as const };\n const result = await harness.abortSubmission(parsed, context, conversation.id);\n if (result === 'aborted') return { outcome: 'withdrawn' as const };\n return {\n outcome: result === 'not_found' ? ('not_found' as const) : ('already_placed' as const),\n };\n };\n let lock: Promise<unknown> = Promise.resolve();\n const serial = <T>(operation: () => Promise<T>): Promise<T> => {\n const run = lock.then(operation, operation);\n lock = run.catch(() => undefined);\n return run;\n };\n return {\n send(request, context) {\n return serial(async () => {\n const text = skills ? await skills.expand(request.text, context) : request.text;\n const response = await accepted(async () =>\n (await conversationFor(request.agentId, context)).submit(\n {\n type: 'input',\n content: text,\n whenBusy: request.whenBusy,\n ...(request.requestId ? { requestId: request.requestId } : {}),\n },\n context\n )\n );\n const id = response.submissionId === null ? undefined : submissionId(response.submissionId);\n if (id !== undefined) deliveries?.expect(id, request.whenBusy);\n return response;\n });\n },\n rewind(messageId, context) {\n return serial(async () => {\n const conversation = current();\n const attached = await conversation.viewState(context);\n const view = attached.value;\n attached.dispose();\n const pending = (await harness.inspect(context)).submissions.some(\n (submission) => submission.conversationId === conversation.id\n );\n if (pending || busy(view)) return { done: false, text: null, reason: 'busy' };\n const turn = lastTurn(view, messageId);\n if (!turn) return { done: false, text: null, reason: 'no-turn' };\n const agent = view.docs['pi.agent'] as AgentChange | undefined;\n const ownership = { kind: 'ownerless' } as const;\n const fork =\n turn.at === null\n ? await harness.createConversation(\n {\n ownership,\n agent: {\n model: agent?.model ?? null,\n thinkingLevel: agent?.thinkingLevel ?? null,\n },\n },\n context\n )\n : await conversation.fork(turn.at, { ownership }, context);\n const changed =\n scoops && agents\n ? await scoops.scoops.rewound(agents.activeCone(), fork, context)\n : { stopped: [], restored: [] };\n await fork.submit(\n { type: 'write', entry: { kind: 'slicc.rewound', data: { turns: 1, ...changed } } },\n context\n );\n await cone.switchCone(fork, context);\n return { done: true, text: turn.text, reason: null };\n });\n },\n\n async wait(id, context) {\n const parsed = submissionId(id);\n const submission =\n parsed === undefined ? undefined : await harness.submission(parsed, context);\n if (!submission) throw new Error(`unknown submission ${id}`);\n const settled = await submission.wait(context);\n const { answer, reason } = settled as { answer?: EntryId; reason?: string };\n if (answer === undefined) return { status: 'unanswered', text: null, reason: String(reason) };\n const entry = await harness.commit((tx) => tx.entry(AssistantEntry, answer), context);\n return { status: 'done', text: answerText(entry?.model?.[0]?.content), reason: null };\n },\n withdraw: (id, context) => withdraw(current(), id, context),\n ...sprinkleControl(sprinkles),\n async unqueue(agentId, id, context) {\n return withdraw(await conversationFor(agentId, context), id, context);\n },\n abort: (context) => current().abort(context),\n compact: (instructions, context) =>\n accepted(() => current().compact(instructions ?? undefined, context)),\n reset: (handoff, context) => current().reset(handoff ?? undefined, context),\n resolveLick(lickId, state, context) {\n return serial(async () => {\n if (!licks) return { done: false, text: null, error: 'this agent has no licks' };\n const conversation = current();\n const action = state === 'confirmed' ? 'confirm' : 'dismiss';\n try {\n const text = await licks.licks.decide(conversation, lickId, action, undefined, context);\n await conversation.submit(\n {\n type: 'write',\n entry: {\n kind: LICK_STATE_KIND,\n data: { lick: lickId, state, by: 'user' },\n model: [\n {\n role: 'user',\n content: `The user ${state} lick ${lickId}: ${text}`,\n timestamp: Date.now(),\n },\n ],\n },\n },\n context\n );\n return { done: true, text, error: null };\n } catch (error) {\n return { done: false, text: null, error: (error as Error).message };\n }\n });\n },\n async webhook(name, delivery, context) {\n const delivered = licks\n ? await licks.sources.webhook(\n name,\n {\n ...(delivery.id ? { id: delivery.id } : {}),\n headers: delivery.headers,\n body: delivery.body,\n },\n context\n )\n : false;\n return { delivered };\n },\n async stopAgent(agentId, context) {\n await (await conversationFor(agentId, context)).abort(context);\n },\n async selectCone(agentId, context) {\n await agents?.selectCone(agentId, context);\n },\n createCone(name, context) {\n return created(async () => {\n if (!agents) throw new Error('This agent has a single cone.');\n return agents.createCone(name, context);\n });\n },\n createScoop(parentId, name, context) {\n return created(async () => {\n if (!scoops) throw new Error('This agent has no scoops.');\n const roles = await scoops.runtime.roles(context);\n const answer = await scoops.scoops.spawn(\n { cone: parentId, name, prompts: [], fromAgent: false, limits: roles.limits },\n context\n );\n if (answer.code !== 0) throw new Error(readable(answer.out));\n return `scoop:${answer.out.trim()}`;\n });\n },\n drop(agentId, context) {\n return created(async () => {\n if (!scoops) throw new Error('This agent has no scoops.');\n const answer = await scoops.scoops.stop(agentId, false, context);\n if (answer.code !== 0) throw new Error(readable(answer.out));\n return agentId;\n });\n },\n configure: async (change, context) =>\n (await conversationFor(change.agentId, context)).configure(\n {\n ...(change.model ? { model: change.model } : {}),\n ...(change.thinkingLevel ? { thinkingLevel: change.thinkingLevel } : {}),\n },\n context\n ),\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|