@ai-ecoverse/slicc-agent 1.0.0 → 1.2.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 CHANGED
@@ -1,9 +1,32 @@
1
1
  # slicc-agent
2
2
 
3
- Empty package for SLICC's agent core. It will be rebuilt on pi's `packages/durable`: session storage in wasm SQLite, remoting, compaction, and pi extensions.
3
+ SLICC's agent core, built on pi's [durable](https://github.com/earendil-works/pi/tree/main/packages/durable) harness. It runs in a dedicated worker next to [slicc-kernel](https://github.com/ai-ecoverse/slicc-kernel) and is shown by [slicc-spectrum](https://github.com/ai-ecoverse/slicc-spectrum). The plan is in [ai-ecoverse/slicc-agent#2](https://github.com/ai-ecoverse/slicc-agent/issues/2).
4
+
5
+ The package ships unbundled ESM: `dist/` holds one file per source file and imports pi's packages by name, so [slicc-bios](https://github.com/ai-ecoverse/slicc-bios) installs it from its lockfile like any other package, and `pnpm upgrade @ai-ecoverse/slicc-agent` updates it in place.
6
+
7
+ ## Use
4
8
 
5
9
  ```js
6
- import {} from '@ai-ecoverse/slicc-agent';
10
+ import { connectAgent, openAgent, serveAgent } from '@ai-ecoverse/slicc-agent';
11
+
12
+ serveAgent(self, openAgent({ models, model: { provider: 'amazon-bedrock', modelId } }));
13
+
14
+ const agent = connectAgent(new Worker(workerUrl, { type: 'module' }));
15
+ await agent.prompt('What is in /home?');
16
+ ```
17
+
18
+ - `openAgent({ models, model, storage?, registry?, settings? })` opens a durable Harness (in memory unless `storage` is given) and its root conversation. `prompt(text)` submits an input and resolves with the answer's text.
19
+ - `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. Pass either as `storage`.
20
+ - `serveAgent(endpoint, agent)` answers prompts that arrive on a worker or `MessagePort`; `connectAgent(endpoint)` is the page side.
21
+
22
+ ## Develop
23
+
24
+ ```sh
25
+ npm install
26
+ npm run build
27
+ npm run test:unit
28
+ npm test
29
+ npm run lint
7
30
  ```
8
31
 
9
- Node ≥ 24. `npm run lint` runs `slicc-lint`. Releases use semantic-release on `main`.
32
+ `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.
package/dist/agent.js ADDED
@@ -0,0 +1,45 @@
1
+ import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
2
+ import {
3
+ AssistantEntry,
4
+ createRegistry,
5
+ Harness,
6
+ MemoryStorage
7
+ } from "@earendil-works/pi-durable";
8
+ function answerText(content) {
9
+ if (typeof content === "string") return content;
10
+ if (!Array.isArray(content)) return "";
11
+ return content.filter((block) => block.type === "text").map((block) => block.text ?? "").join("");
12
+ }
13
+ async function prompt(root, text, context) {
14
+ const submission = await root.submit({ type: "input", content: text }, context);
15
+ const settled = await submission.wait(context);
16
+ const { answer, reason } = settled;
17
+ if (answer === void 0) throw new Error(`unanswered: ${reason}`);
18
+ const entry = await root.commit((tx) => tx.entry(AssistantEntry, answer), context);
19
+ return answerText(entry?.model?.[0]?.content);
20
+ }
21
+ async function openAgent(options) {
22
+ const context = options.context ?? BACKGROUND_CONTEXT;
23
+ const harness = await Harness.open(
24
+ options.storage ?? new MemoryStorage(),
25
+ {
26
+ models: options.models,
27
+ registry: options.registry ?? createRegistry(),
28
+ ...options.settings ? { settings: options.settings } : {}
29
+ },
30
+ context
31
+ );
32
+ const root = await harness.root(context, { agent: { model: options.model } });
33
+ harness.resume();
34
+ return {
35
+ harness,
36
+ root,
37
+ prompt: (text) => prompt(root, text, context),
38
+ close: () => harness.close(context)
39
+ };
40
+ }
41
+ export {
42
+ answerText,
43
+ openAgent
44
+ };
45
+ //# sourceMappingURL=agent.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 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';\n\nexport interface AgentOptions {\n models: Models;\n model: ModelRef;\n storage?: Storage;\n registry?: Registry;\n settings?: HarnessOptions['settings'];\n context?: Context;\n}\n\nexport interface Agent {\n readonly harness: Harness;\n readonly root: Conversation;\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 harness = await Harness.open(\n options.storage ?? new MemoryStorage(),\n {\n models: options.models,\n registry: options.registry ?? createRegistry(),\n ...(options.settings ? { settings: options.settings } : {}),\n },\n context\n );\n const root = await harness.root(context, { agent: { model: options.model } });\n harness.resume();\n return {\n harness,\n root,\n prompt: (text) => prompt(root, 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;AAuBA,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,UAAU,MAAM,QAAQ;AAAA,IAC5B,QAAQ,WAAW,IAAI,cAAc;AAAA,IACrC;AAAA,MACE,QAAQ,QAAQ;AAAA,MAChB,UAAU,QAAQ,YAAY,eAAe;AAAA,MAC7C,GAAI,QAAQ,WAAW,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;AAAA,IAC3D;AAAA,IACA;AAAA,EACF;AACA,QAAM,OAAO,MAAM,QAAQ,KAAK,SAAS,EAAE,OAAO,EAAE,OAAO,QAAQ,MAAM,EAAE,CAAC;AAC5E,UAAQ,OAAO;AACf,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,QAAQ,CAAC,SAAS,OAAO,MAAM,MAAM,OAAO;AAAA,IAC5C,OAAO,MAAM,QAAQ,MAAM,OAAO;AAAA,EACpC;AACF;",
6
+ "names": []
7
+ }
package/dist/index.js ADDED
@@ -0,0 +1,22 @@
1
+ import { answerText, openAgent } from "./agent.js";
2
+ import {
3
+ connectAgent,
4
+ serveAgent
5
+ } from "./port.js";
6
+ import {
7
+ AGENT_DIRECTORY,
8
+ openMemorySqliteStorage,
9
+ openOpfsSqliteStorage,
10
+ WasmSqliteDatabase
11
+ } from "./sqlite.js";
12
+ export {
13
+ AGENT_DIRECTORY,
14
+ WasmSqliteDatabase,
15
+ answerText,
16
+ connectAgent,
17
+ openAgent,
18
+ openMemorySqliteStorage,
19
+ openOpfsSqliteStorage,
20
+ serveAgent
21
+ };
22
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.ts"],
4
+ "sourcesContent": ["export { type Agent, type AgentOptions, answerText, openAgent } from './agent.ts';\nexport {\n type AgentClient,\n type AgentReply,\n type AgentRequest,\n connectAgent,\n type MessageEndpoint,\n serveAgent,\n} from './port.ts';\nexport {\n AGENT_DIRECTORY,\n type OpfsSqliteOptions,\n openMemorySqliteStorage,\n openOpfsSqliteStorage,\n WasmSqliteDatabase,\n} from './sqlite.ts';\n"],
5
+ "mappings": "AAAA,SAAwC,YAAY,iBAAiB;AACrE;AAAA,EAIE;AAAA,EAEA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,OACK;",
6
+ "names": []
7
+ }
@@ -0,0 +1,55 @@
1
+ const names = /* @__PURE__ */ new Map();
2
+ function vfsName(directory) {
3
+ let name = names.get(directory);
4
+ if (!name) {
5
+ name = `slicc-agent-${names.size}`;
6
+ names.set(directory, name);
7
+ }
8
+ return name;
9
+ }
10
+ function holdLock(locks, name) {
11
+ return new Promise((acquired, failed) => {
12
+ locks.request(name, { mode: "exclusive" }, () => new Promise((release) => acquired(release))).catch(failed);
13
+ });
14
+ }
15
+ async function opaque(root, directory) {
16
+ let dir = root;
17
+ try {
18
+ for (const part of [...directory.split("/").filter(Boolean), ".opaque"]) {
19
+ dir = await dir.getDirectoryHandle(part);
20
+ }
21
+ } catch {
22
+ return void 0;
23
+ }
24
+ return dir;
25
+ }
26
+ async function acquirable(dir) {
27
+ for await (const handle of dir.values()) {
28
+ if (handle.kind !== "file") continue;
29
+ try {
30
+ const access = await handle.createSyncAccessHandle();
31
+ access.close();
32
+ } catch {
33
+ return false;
34
+ }
35
+ }
36
+ return true;
37
+ }
38
+ const pause = (ms) => new Promise((resume) => setTimeout(resume, ms));
39
+ async function settlePool(directory, access = {}) {
40
+ const root = await (access.root ?? (() => navigator.storage.getDirectory()))();
41
+ const dir = await opaque(root, directory);
42
+ if (!dir) return;
43
+ const attempts = access.attempts ?? 50;
44
+ for (let attempt = 1; attempt <= attempts; attempt++) {
45
+ if (await acquirable(dir)) return;
46
+ if (attempt < attempts) await pause(access.delayMs ?? 100);
47
+ }
48
+ throw new Error(`SQLite pool ${directory} is still in use by another worker`);
49
+ }
50
+ export {
51
+ holdLock,
52
+ settlePool,
53
+ vfsName
54
+ };
55
+ //# sourceMappingURL=opfs-pool.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/opfs-pool.ts"],
4
+ "sourcesContent": ["export interface PoolAccess {\n root?: () => Promise<FileSystemDirectoryHandle>;\n locks?: LockManager;\n attempts?: number;\n delayMs?: number;\n}\n\ninterface SyncAccessible {\n createSyncAccessHandle(): Promise<{ close(): void }>;\n}\n\nconst names = new Map<string, string>();\n\nexport function vfsName(directory: string): string {\n let name = names.get(directory);\n if (!name) {\n name = `slicc-agent-${names.size}`;\n names.set(directory, name);\n }\n return name;\n}\n\nexport function holdLock(locks: LockManager, name: string): Promise<() => void> {\n return new Promise((acquired, failed) => {\n locks\n .request(name, { mode: 'exclusive' }, () => new Promise<void>((release) => acquired(release)))\n .catch(failed);\n });\n}\n\nasync function opaque(\n root: FileSystemDirectoryHandle,\n directory: string\n): Promise<FileSystemDirectoryHandle | undefined> {\n let dir = root;\n try {\n for (const part of [...directory.split('/').filter(Boolean), '.opaque']) {\n dir = await dir.getDirectoryHandle(part);\n }\n } catch {\n return undefined;\n }\n return dir;\n}\n\nasync function acquirable(dir: FileSystemDirectoryHandle): Promise<boolean> {\n for await (const handle of dir.values()) {\n if (handle.kind !== 'file') continue;\n try {\n const access = await (handle as unknown as SyncAccessible).createSyncAccessHandle();\n access.close();\n } catch {\n return false;\n }\n }\n return true;\n}\n\nconst pause = (ms: number) => new Promise((resume) => setTimeout(resume, ms));\n\nexport async function settlePool(directory: string, access: PoolAccess = {}): Promise<void> {\n const root = await (access.root ?? (() => navigator.storage.getDirectory()))();\n const dir = await opaque(root, directory);\n if (!dir) return;\n const attempts = access.attempts ?? 50;\n for (let attempt = 1; attempt <= attempts; attempt++) {\n if (await acquirable(dir)) return;\n if (attempt < attempts) await pause(access.delayMs ?? 100);\n }\n throw new Error(`SQLite pool ${directory} is still in use by another worker`);\n}\n"],
5
+ "mappings": "AAWA,MAAM,QAAQ,oBAAI,IAAoB;AAE/B,SAAS,QAAQ,WAA2B;AACjD,MAAI,OAAO,MAAM,IAAI,SAAS;AAC9B,MAAI,CAAC,MAAM;AACT,WAAO,eAAe,MAAM,IAAI;AAChC,UAAM,IAAI,WAAW,IAAI;AAAA,EAC3B;AACA,SAAO;AACT;AAEO,SAAS,SAAS,OAAoB,MAAmC;AAC9E,SAAO,IAAI,QAAQ,CAAC,UAAU,WAAW;AACvC,UACG,QAAQ,MAAM,EAAE,MAAM,YAAY,GAAG,MAAM,IAAI,QAAc,CAAC,YAAY,SAAS,OAAO,CAAC,CAAC,EAC5F,MAAM,MAAM;AAAA,EACjB,CAAC;AACH;AAEA,eAAe,OACb,MACA,WACgD;AAChD,MAAI,MAAM;AACV,MAAI;AACF,eAAW,QAAQ,CAAC,GAAG,UAAU,MAAM,GAAG,EAAE,OAAO,OAAO,GAAG,SAAS,GAAG;AACvE,YAAM,MAAM,IAAI,mBAAmB,IAAI;AAAA,IACzC;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,eAAe,WAAW,KAAkD;AAC1E,mBAAiB,UAAU,IAAI,OAAO,GAAG;AACvC,QAAI,OAAO,SAAS,OAAQ;AAC5B,QAAI;AACF,YAAM,SAAS,MAAO,OAAqC,uBAAuB;AAClF,aAAO,MAAM;AAAA,IACf,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEA,MAAM,QAAQ,CAAC,OAAe,IAAI,QAAQ,CAAC,WAAW,WAAW,QAAQ,EAAE,CAAC;AAE5E,eAAsB,WAAW,WAAmB,SAAqB,CAAC,GAAkB;AAC1F,QAAM,OAAO,OAAO,OAAO,SAAS,MAAM,UAAU,QAAQ,aAAa,IAAI;AAC7E,QAAM,MAAM,MAAM,OAAO,MAAM,SAAS;AACxC,MAAI,CAAC,IAAK;AACV,QAAM,WAAW,OAAO,YAAY;AACpC,WAAS,UAAU,GAAG,WAAW,UAAU,WAAW;AACpD,QAAI,MAAM,WAAW,GAAG,EAAG;AAC3B,QAAI,UAAU,SAAU,OAAM,MAAM,OAAO,WAAW,GAAG;AAAA,EAC3D;AACA,QAAM,IAAI,MAAM,eAAe,SAAS,oCAAoC;AAC9E;",
6
+ "names": []
7
+ }
package/dist/port.js ADDED
@@ -0,0 +1,57 @@
1
+ function isRequest(data) {
2
+ const value = data;
3
+ return typeof value?.id === "number" && typeof value.prompt === "string";
4
+ }
5
+ function reason(error) {
6
+ return error instanceof Error ? error.message : String(error);
7
+ }
8
+ function serveAgent(endpoint, agent) {
9
+ endpoint.addEventListener("message", ({ data }) => {
10
+ if (!isRequest(data)) return;
11
+ agent.then((ready) => ready.prompt(data.prompt)).then(
12
+ (text) => endpoint.postMessage({ id: data.id, text }),
13
+ (error) => endpoint.postMessage({ id: data.id, error: reason(error) })
14
+ );
15
+ });
16
+ endpoint.start?.();
17
+ }
18
+ function failure(event) {
19
+ const message = event.message;
20
+ return new Error(message ? `agent failed: ${message}` : `agent failed: ${event.type}`);
21
+ }
22
+ function connectAgent(endpoint) {
23
+ const pending = /* @__PURE__ */ new Map();
24
+ let broken;
25
+ let next = 0;
26
+ const fail = (event) => {
27
+ broken = failure(event);
28
+ for (const call of pending.values()) call.reject(broken);
29
+ pending.clear();
30
+ };
31
+ endpoint.addEventListener("message", ({ data }) => {
32
+ const reply = data;
33
+ const call = pending.get(reply.id);
34
+ if (!call) return;
35
+ pending.delete(reply.id);
36
+ if ("error" in reply) call.reject(new Error(reply.error));
37
+ else call.resolve(reply.text);
38
+ });
39
+ endpoint.addEventListener("error", fail);
40
+ endpoint.addEventListener("messageerror", fail);
41
+ endpoint.start?.();
42
+ return {
43
+ prompt(text) {
44
+ if (broken) return Promise.reject(broken);
45
+ const id = ++next;
46
+ return new Promise((resolve, reject) => {
47
+ pending.set(id, { resolve, reject });
48
+ endpoint.postMessage({ id, prompt: text });
49
+ });
50
+ }
51
+ };
52
+ }
53
+ export {
54
+ connectAgent,
55
+ serveAgent
56
+ };
57
+ //# sourceMappingURL=port.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/port.ts"],
4
+ "sourcesContent": ["import type { Agent } from './agent.ts';\n\nexport interface AgentRequest {\n id: number;\n prompt: string;\n}\n\nexport type AgentReply = { id: number; text: string } | { id: number; error: string };\n\nexport interface MessageEndpoint {\n postMessage(message: unknown): void;\n addEventListener(type: 'message', listener: (event: MessageEvent) => void): void;\n addEventListener(type: 'error' | 'messageerror', listener: (event: Event) => void): void;\n start?(): void;\n}\n\nfunction isRequest(data: unknown): data is AgentRequest {\n const value = data as Partial<AgentRequest> | null;\n return typeof value?.id === 'number' && typeof value.prompt === 'string';\n}\n\nfunction reason(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nexport function serveAgent(endpoint: MessageEndpoint, agent: Promise<Agent>): void {\n endpoint.addEventListener('message', ({ data }) => {\n if (!isRequest(data)) return;\n agent\n .then((ready) => ready.prompt(data.prompt))\n .then(\n (text) => endpoint.postMessage({ id: data.id, text } satisfies AgentReply),\n (error: unknown) =>\n endpoint.postMessage({ id: data.id, error: reason(error) } satisfies AgentReply)\n );\n });\n endpoint.start?.();\n}\n\nexport interface AgentClient {\n prompt(text: string): Promise<string>;\n}\n\nfunction failure(event: Event): Error {\n const message = (event as ErrorEvent).message;\n return new Error(message ? `agent failed: ${message}` : `agent failed: ${event.type}`);\n}\n\nexport function connectAgent(endpoint: MessageEndpoint): AgentClient {\n const pending = new Map<number, { resolve(text: string): void; reject(error: Error): void }>();\n let broken: Error | undefined;\n let next = 0;\n const fail = (event: Event) => {\n broken = failure(event);\n for (const call of pending.values()) call.reject(broken);\n pending.clear();\n };\n endpoint.addEventListener('message', ({ data }) => {\n const reply = data as AgentReply;\n const call = pending.get(reply.id);\n if (!call) return;\n pending.delete(reply.id);\n if ('error' in reply) call.reject(new Error(reply.error));\n else call.resolve(reply.text);\n });\n endpoint.addEventListener('error', fail);\n endpoint.addEventListener('messageerror', fail);\n endpoint.start?.();\n return {\n prompt(text) {\n if (broken) return Promise.reject(broken);\n const id = ++next;\n return new Promise((resolve, reject) => {\n pending.set(id, { resolve, reject });\n endpoint.postMessage({ id, prompt: text } satisfies AgentRequest);\n });\n },\n };\n}\n"],
5
+ "mappings": "AAgBA,SAAS,UAAU,MAAqC;AACtD,QAAM,QAAQ;AACd,SAAO,OAAO,OAAO,OAAO,YAAY,OAAO,MAAM,WAAW;AAClE;AAEA,SAAS,OAAO,OAAwB;AACtC,SAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC9D;AAEO,SAAS,WAAW,UAA2B,OAA6B;AACjF,WAAS,iBAAiB,WAAW,CAAC,EAAE,KAAK,MAAM;AACjD,QAAI,CAAC,UAAU,IAAI,EAAG;AACtB,UACG,KAAK,CAAC,UAAU,MAAM,OAAO,KAAK,MAAM,CAAC,EACzC;AAAA,MACC,CAAC,SAAS,SAAS,YAAY,EAAE,IAAI,KAAK,IAAI,KAAK,CAAsB;AAAA,MACzE,CAAC,UACC,SAAS,YAAY,EAAE,IAAI,KAAK,IAAI,OAAO,OAAO,KAAK,EAAE,CAAsB;AAAA,IACnF;AAAA,EACJ,CAAC;AACD,WAAS,QAAQ;AACnB;AAMA,SAAS,QAAQ,OAAqB;AACpC,QAAM,UAAW,MAAqB;AACtC,SAAO,IAAI,MAAM,UAAU,iBAAiB,OAAO,KAAK,iBAAiB,MAAM,IAAI,EAAE;AACvF;AAEO,SAAS,aAAa,UAAwC;AACnE,QAAM,UAAU,oBAAI,IAAyE;AAC7F,MAAI;AACJ,MAAI,OAAO;AACX,QAAM,OAAO,CAAC,UAAiB;AAC7B,aAAS,QAAQ,KAAK;AACtB,eAAW,QAAQ,QAAQ,OAAO,EAAG,MAAK,OAAO,MAAM;AACvD,YAAQ,MAAM;AAAA,EAChB;AACA,WAAS,iBAAiB,WAAW,CAAC,EAAE,KAAK,MAAM;AACjD,UAAM,QAAQ;AACd,UAAM,OAAO,QAAQ,IAAI,MAAM,EAAE;AACjC,QAAI,CAAC,KAAM;AACX,YAAQ,OAAO,MAAM,EAAE;AACvB,QAAI,WAAW,MAAO,MAAK,OAAO,IAAI,MAAM,MAAM,KAAK,CAAC;AAAA,QACnD,MAAK,QAAQ,MAAM,IAAI;AAAA,EAC9B,CAAC;AACD,WAAS,iBAAiB,SAAS,IAAI;AACvC,WAAS,iBAAiB,gBAAgB,IAAI;AAC9C,WAAS,QAAQ;AACjB,SAAO;AAAA,IACL,OAAO,MAAM;AACX,UAAI,OAAQ,QAAO,QAAQ,OAAO,MAAM;AACxC,YAAM,KAAK,EAAE;AACb,aAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,gBAAQ,IAAI,IAAI,EAAE,SAAS,OAAO,CAAC;AACnC,iBAAS,YAAY,EAAE,IAAI,QAAQ,KAAK,CAAwB;AAAA,MAClE,CAAC;AAAA,IACH;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
package/dist/sqlite.js ADDED
@@ -0,0 +1,158 @@
1
+ import {
2
+ SqliteStorage
3
+ } from "@earendil-works/pi-durable/storage/sqlite";
4
+ import init, {} from "@sqlite.org/sqlite-wasm";
5
+ import { holdLock, settlePool, vfsName } from "./opfs-pool.js";
6
+ class SerialQueue {
7
+ #tail = Promise.resolve();
8
+ run(operation) {
9
+ const result = this.#tail.then(operation);
10
+ this.#tail = result.catch(() => {
11
+ });
12
+ return result;
13
+ }
14
+ }
15
+ class Statements {
16
+ #db;
17
+ #cache = /* @__PURE__ */ new Map();
18
+ constructor(db) {
19
+ this.#db = db;
20
+ }
21
+ bound(sql, params) {
22
+ let statement = this.#cache.get(sql);
23
+ if (!statement) {
24
+ statement = this.#db.prepare(sql);
25
+ this.#cache.set(sql, statement);
26
+ }
27
+ statement.reset(true);
28
+ if (params.length > 0) statement.bind([...params]);
29
+ return statement;
30
+ }
31
+ finalize() {
32
+ for (const statement of this.#cache.values()) statement.finalize();
33
+ this.#cache.clear();
34
+ }
35
+ }
36
+ class WasmSqliteExecutor {
37
+ db;
38
+ statements;
39
+ constructor(db, statements) {
40
+ this.db = db;
41
+ this.statements = statements;
42
+ }
43
+ async exec(sql) {
44
+ this.check();
45
+ this.db.exec(sql);
46
+ }
47
+ async run(sql, ...params) {
48
+ this.check();
49
+ this.statements.bound(sql, params).stepReset();
50
+ }
51
+ async get(sql, ...params) {
52
+ this.check();
53
+ const statement = this.statements.bound(sql, params);
54
+ const row = statement.step() ? statement.get({}) : void 0;
55
+ statement.reset();
56
+ return row;
57
+ }
58
+ async all(sql, ...params) {
59
+ this.check();
60
+ const statement = this.statements.bound(sql, params);
61
+ const rows = [];
62
+ while (statement.step()) rows.push(statement.get({}));
63
+ statement.reset();
64
+ return rows;
65
+ }
66
+ check() {
67
+ }
68
+ }
69
+ class WasmSqliteTransaction extends WasmSqliteExecutor {
70
+ active = true;
71
+ check() {
72
+ if (!this.active) throw new Error("SQLite transaction handle is no longer active");
73
+ }
74
+ }
75
+ class WasmSqliteDatabase extends WasmSqliteExecutor {
76
+ #queue = new SerialQueue();
77
+ #closed;
78
+ constructor(db, closed = () => {
79
+ }) {
80
+ super(db, new Statements(db));
81
+ this.#closed = closed;
82
+ }
83
+ exec(sql) {
84
+ return this.#queue.run(() => super.exec(sql));
85
+ }
86
+ run(sql, ...params) {
87
+ return this.#queue.run(() => super.run(sql, ...params));
88
+ }
89
+ get(sql, ...params) {
90
+ return this.#queue.run(() => super.get(sql, ...params));
91
+ }
92
+ all(sql, ...params) {
93
+ return this.#queue.run(() => super.all(sql, ...params));
94
+ }
95
+ transaction(callback) {
96
+ return this.#queue.run(async () => {
97
+ const transaction = new WasmSqliteTransaction(this.db, this.statements);
98
+ this.db.exec("BEGIN IMMEDIATE");
99
+ try {
100
+ const result = await callback(transaction);
101
+ this.db.exec("COMMIT");
102
+ return result;
103
+ } catch (error) {
104
+ this.#rollback(error);
105
+ throw error;
106
+ } finally {
107
+ transaction.active = false;
108
+ }
109
+ });
110
+ }
111
+ close() {
112
+ return this.#queue.run(() => {
113
+ try {
114
+ this.statements.finalize();
115
+ this.db.close();
116
+ } finally {
117
+ this.#closed();
118
+ }
119
+ });
120
+ }
121
+ #rollback(cause) {
122
+ try {
123
+ this.db.exec("ROLLBACK");
124
+ } catch (error) {
125
+ throw new AggregateError([cause, error], "SQLite rollback failed");
126
+ }
127
+ }
128
+ }
129
+ const AGENT_DIRECTORY = "/.slicc/agent";
130
+ async function openMemorySqliteStorage() {
131
+ const sqlite3 = await init();
132
+ return SqliteStorage.open(new WasmSqliteDatabase(new sqlite3.oo1.DB(":memory:", "c")));
133
+ }
134
+ async function openOpfsSqliteStorage(options = {}) {
135
+ const directory = options.directory ?? AGENT_DIRECTORY;
136
+ const release = await holdLock(
137
+ options.locks ?? navigator.locks,
138
+ `slicc-agent-sqlite:${directory}`
139
+ );
140
+ try {
141
+ await settlePool(directory, options);
142
+ const sqlite3 = await (options.load ?? init)();
143
+ const pool = await sqlite3.installOpfsSAHPoolVfs({ name: vfsName(directory), directory });
144
+ const db = new pool.OpfsSAHPoolDb(options.file ?? "/agent.sqlite");
145
+ db.exec("PRAGMA journal_mode = TRUNCATE");
146
+ return await SqliteStorage.open(new WasmSqliteDatabase(db, release));
147
+ } catch (error) {
148
+ release();
149
+ throw error;
150
+ }
151
+ }
152
+ export {
153
+ AGENT_DIRECTORY,
154
+ WasmSqliteDatabase,
155
+ openMemorySqliteStorage,
156
+ openOpfsSqliteStorage
157
+ };
158
+ //# sourceMappingURL=sqlite.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/sqlite.ts"],
4
+ "sourcesContent": ["import type { Storage } from '@earendil-works/pi-durable';\nimport {\n type SqliteDatabase,\n type SqliteExecutor,\n SqliteStorage,\n type SqliteValue,\n} from '@earendil-works/pi-durable/storage/sqlite';\nimport init, { type Database, type PreparedStatement } from '@sqlite.org/sqlite-wasm';\nimport { holdLock, type PoolAccess, settlePool, vfsName } from './opfs-pool.ts';\n\ntype Row = Record<string, unknown>;\n\nclass SerialQueue {\n #tail: Promise<unknown> = Promise.resolve();\n\n run<T>(operation: () => T | Promise<T>): Promise<T> {\n const result = this.#tail.then(operation);\n this.#tail = result.catch(() => {});\n return result;\n }\n}\n\nclass Statements {\n readonly #db: Database;\n readonly #cache = new Map<string, PreparedStatement>();\n\n constructor(db: Database) {\n this.#db = db;\n }\n\n bound(sql: string, params: readonly SqliteValue[]): PreparedStatement {\n let statement = this.#cache.get(sql);\n if (!statement) {\n statement = this.#db.prepare(sql);\n this.#cache.set(sql, statement);\n }\n statement.reset(true);\n if (params.length > 0) statement.bind([...params]);\n return statement;\n }\n\n finalize(): void {\n for (const statement of this.#cache.values()) statement.finalize();\n this.#cache.clear();\n }\n}\n\nclass WasmSqliteExecutor implements SqliteExecutor {\n protected readonly db: Database;\n protected readonly statements: Statements;\n\n constructor(db: Database, statements: Statements) {\n this.db = db;\n this.statements = statements;\n }\n\n async exec(sql: string): Promise<void> {\n this.check();\n this.db.exec(sql);\n }\n\n async run(sql: string, ...params: SqliteValue[]): Promise<void> {\n this.check();\n this.statements.bound(sql, params).stepReset();\n }\n\n async get<T extends object>(sql: string, ...params: SqliteValue[]): Promise<T | undefined> {\n this.check();\n const statement = this.statements.bound(sql, params);\n const row = statement.step() ? (statement.get({}) as Row as T) : undefined;\n statement.reset();\n return row;\n }\n\n async all<T extends object>(sql: string, ...params: SqliteValue[]): Promise<T[]> {\n this.check();\n const statement = this.statements.bound(sql, params);\n const rows: T[] = [];\n while (statement.step()) rows.push(statement.get({}) as Row as T);\n statement.reset();\n return rows;\n }\n\n protected check(): void {}\n}\n\nclass WasmSqliteTransaction extends WasmSqliteExecutor {\n active = true;\n\n protected override check(): void {\n if (!this.active) throw new Error('SQLite transaction handle is no longer active');\n }\n}\n\nexport class WasmSqliteDatabase extends WasmSqliteExecutor implements SqliteDatabase {\n readonly #queue = new SerialQueue();\n readonly #closed: () => void;\n\n constructor(db: Database, closed: () => void = () => {}) {\n super(db, new Statements(db));\n this.#closed = closed;\n }\n\n override exec(sql: string): Promise<void> {\n return this.#queue.run(() => super.exec(sql));\n }\n\n override run(sql: string, ...params: SqliteValue[]): Promise<void> {\n return this.#queue.run(() => super.run(sql, ...params));\n }\n\n override get<T extends object>(sql: string, ...params: SqliteValue[]): Promise<T | undefined> {\n return this.#queue.run(() => super.get<T>(sql, ...params));\n }\n\n override all<T extends object>(sql: string, ...params: SqliteValue[]): Promise<T[]> {\n return this.#queue.run(() => super.all<T>(sql, ...params));\n }\n\n transaction<T>(callback: (transaction: SqliteExecutor) => Promise<T>): Promise<T> {\n return this.#queue.run(async () => {\n const transaction = new WasmSqliteTransaction(this.db, this.statements);\n this.db.exec('BEGIN IMMEDIATE');\n try {\n const result = await callback(transaction);\n this.db.exec('COMMIT');\n return result;\n } catch (error) {\n this.#rollback(error);\n throw error;\n } finally {\n transaction.active = false;\n }\n });\n }\n\n close(): Promise<void> {\n return this.#queue.run(() => {\n try {\n this.statements.finalize();\n this.db.close();\n } finally {\n this.#closed();\n }\n });\n }\n\n #rollback(cause: unknown): void {\n try {\n this.db.exec('ROLLBACK');\n } catch (error) {\n throw new AggregateError([cause, error], 'SQLite rollback failed');\n }\n }\n}\n\nexport interface OpfsSqliteOptions extends PoolAccess {\n directory?: string;\n file?: string;\n load?: typeof init;\n}\n\nexport const AGENT_DIRECTORY = '/.slicc/agent';\n\nexport async function openMemorySqliteStorage(): Promise<Storage> {\n const sqlite3 = await init();\n return SqliteStorage.open(new WasmSqliteDatabase(new sqlite3.oo1.DB(':memory:', 'c')));\n}\n\nexport async function openOpfsSqliteStorage(options: OpfsSqliteOptions = {}): Promise<Storage> {\n const directory = options.directory ?? AGENT_DIRECTORY;\n const release = await holdLock(\n options.locks ?? navigator.locks,\n `slicc-agent-sqlite:${directory}`\n );\n try {\n await settlePool(directory, options);\n const sqlite3 = await (options.load ?? init)();\n const pool = await sqlite3.installOpfsSAHPoolVfs({ name: vfsName(directory), directory });\n const db = new pool.OpfsSAHPoolDb(options.file ?? '/agent.sqlite');\n db.exec('PRAGMA journal_mode = TRUNCATE');\n return await SqliteStorage.open(new WasmSqliteDatabase(db, release));\n } catch (error) {\n release();\n throw error;\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,19 @@
1
+ import type { Context } from '@earendil-works/chord';
2
+ import type { Models } from '@earendil-works/pi-ai/models';
3
+ import { type Conversation, Harness, type HarnessOptions, type ModelRef, type Registry, type Storage } from '@earendil-works/pi-durable';
4
+ export interface AgentOptions {
5
+ models: Models;
6
+ model: ModelRef;
7
+ storage?: Storage;
8
+ registry?: Registry;
9
+ settings?: HarnessOptions['settings'];
10
+ context?: Context;
11
+ }
12
+ export interface Agent {
13
+ readonly harness: Harness;
14
+ readonly root: Conversation;
15
+ prompt(text: string): Promise<string>;
16
+ close(): Promise<void>;
17
+ }
18
+ export declare function answerText(content: unknown): string;
19
+ export declare function openAgent(options: AgentOptions): Promise<Agent>;
@@ -0,0 +1,3 @@
1
+ export { type Agent, type AgentOptions, answerText, openAgent } from './agent.ts';
2
+ export { type AgentClient, type AgentReply, type AgentRequest, connectAgent, type MessageEndpoint, serveAgent, } from './port.ts';
3
+ export { AGENT_DIRECTORY, type OpfsSqliteOptions, openMemorySqliteStorage, openOpfsSqliteStorage, WasmSqliteDatabase, } from './sqlite.ts';
@@ -0,0 +1,9 @@
1
+ export interface PoolAccess {
2
+ root?: () => Promise<FileSystemDirectoryHandle>;
3
+ locks?: LockManager;
4
+ attempts?: number;
5
+ delayMs?: number;
6
+ }
7
+ export declare function vfsName(directory: string): string;
8
+ export declare function holdLock(locks: LockManager, name: string): Promise<() => void>;
9
+ export declare function settlePool(directory: string, access?: PoolAccess): Promise<void>;
@@ -0,0 +1,23 @@
1
+ import type { Agent } from './agent.ts';
2
+ export interface AgentRequest {
3
+ id: number;
4
+ prompt: string;
5
+ }
6
+ export type AgentReply = {
7
+ id: number;
8
+ text: string;
9
+ } | {
10
+ id: number;
11
+ error: string;
12
+ };
13
+ export interface MessageEndpoint {
14
+ postMessage(message: unknown): void;
15
+ addEventListener(type: 'message', listener: (event: MessageEvent) => void): void;
16
+ addEventListener(type: 'error' | 'messageerror', listener: (event: Event) => void): void;
17
+ start?(): void;
18
+ }
19
+ export declare function serveAgent(endpoint: MessageEndpoint, agent: Promise<Agent>): void;
20
+ export interface AgentClient {
21
+ prompt(text: string): Promise<string>;
22
+ }
23
+ export declare function connectAgent(endpoint: MessageEndpoint): AgentClient;
@@ -0,0 +1,39 @@
1
+ import type { Storage } from '@earendil-works/pi-durable';
2
+ import { type SqliteDatabase, type SqliteExecutor, type SqliteValue } from '@earendil-works/pi-durable/storage/sqlite';
3
+ import init, { type Database, type PreparedStatement } from '@sqlite.org/sqlite-wasm';
4
+ import { type PoolAccess } from './opfs-pool.ts';
5
+ declare class Statements {
6
+ #private;
7
+ constructor(db: Database);
8
+ bound(sql: string, params: readonly SqliteValue[]): PreparedStatement;
9
+ finalize(): void;
10
+ }
11
+ declare class WasmSqliteExecutor implements SqliteExecutor {
12
+ protected readonly db: Database;
13
+ protected readonly statements: Statements;
14
+ constructor(db: Database, statements: Statements);
15
+ exec(sql: string): Promise<void>;
16
+ run(sql: string, ...params: SqliteValue[]): Promise<void>;
17
+ get<T extends object>(sql: string, ...params: SqliteValue[]): Promise<T | undefined>;
18
+ all<T extends object>(sql: string, ...params: SqliteValue[]): Promise<T[]>;
19
+ protected check(): void;
20
+ }
21
+ export declare class WasmSqliteDatabase extends WasmSqliteExecutor implements SqliteDatabase {
22
+ #private;
23
+ constructor(db: Database, closed?: () => void);
24
+ exec(sql: string): Promise<void>;
25
+ run(sql: string, ...params: SqliteValue[]): Promise<void>;
26
+ get<T extends object>(sql: string, ...params: SqliteValue[]): Promise<T | undefined>;
27
+ all<T extends object>(sql: string, ...params: SqliteValue[]): Promise<T[]>;
28
+ transaction<T>(callback: (transaction: SqliteExecutor) => Promise<T>): Promise<T>;
29
+ close(): Promise<void>;
30
+ }
31
+ export interface OpfsSqliteOptions extends PoolAccess {
32
+ directory?: string;
33
+ file?: string;
34
+ load?: typeof init;
35
+ }
36
+ export declare const AGENT_DIRECTORY = "/.slicc/agent";
37
+ export declare function openMemorySqliteStorage(): Promise<Storage>;
38
+ export declare function openOpfsSqliteStorage(options?: OpfsSqliteOptions): Promise<Storage>;
39
+ export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ai-ecoverse/slicc-agent",
3
- "version": "1.0.0",
4
- "description": "SLICC's agent core: session storage in wasm SQLite, remoting, compaction, and pi extensions",
3
+ "version": "1.2.0",
4
+ "description": "SLICC's agent core on pi durable: an agent worker, session storage, remoting and pi extensions",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
@@ -22,24 +22,44 @@
22
22
  "provenance": true
23
23
  },
24
24
  "exports": {
25
- ".": "./src/index.js"
25
+ ".": {
26
+ "types": "./dist/types/index.d.ts",
27
+ "default": "./dist/index.js"
28
+ }
26
29
  },
27
30
  "files": [
28
- "src"
31
+ "dist"
29
32
  ],
30
33
  "scripts": {
31
34
  "prepare": "lefthook install",
35
+ "build": "node build.mjs && tsc -p tsconfig.build.json",
36
+ "prepack": "npm run build",
37
+ "start": "npm run build && node test/integration/chrome.mjs",
38
+ "pretest": "npm run build && node test/integration/build.mjs && playwright-core install --no-shell chromium",
39
+ "test": "node --test --test-global-setup=node_modules/@ai-ecoverse/slicc-shared-web/harness/global.mjs 'test/integration/*.test.mjs'",
40
+ "test:unit": "NODE_NO_WARNINGS=1 mcr -c node_modules/@ai-ecoverse/slicc-shared-web/mcr.config.mjs node --test 'test/unit/**/*.test.mjs'",
41
+ "typecheck": "tsc",
32
42
  "lint": "slicc-lint",
33
43
  "format": "biome check --write .",
34
- "test": "true",
35
44
  "trust": "npx fledgling sync -y --skip-publish",
36
45
  "trust:dry": "npx fledgling sync --dry-run -y --skip-publish",
37
46
  "release": "semantic-release"
38
47
  },
39
48
  "devDependencies": {
40
- "@ai-ecoverse/slicc-shared-web": "1.1.0",
49
+ "@ai-ecoverse/slicc-shared-web": "1.5.0",
41
50
  "@biomejs/biome": "2.5.15",
51
+ "@types/node": "24.19.1",
52
+ "esbuild": "0.28.2",
42
53
  "lefthook": "2.1.16",
43
- "semantic-release": "25.0.9"
54
+ "monocart-coverage-reports": "2.13.0",
55
+ "playwright-core": "1.63.0",
56
+ "semantic-release": "25.0.9",
57
+ "typescript": "7.0.2"
58
+ },
59
+ "dependencies": {
60
+ "@earendil-works/chord": "1.0.4",
61
+ "@earendil-works/pi-ai": "1.0.4",
62
+ "@earendil-works/pi-durable": "1.0.4",
63
+ "@sqlite.org/sqlite-wasm": "3.53.4-build2"
44
64
  }
45
65
  }
package/src/index.js DELETED
@@ -1 +0,0 @@
1
- export {};