@ai-ecoverse/slicc-agent 1.2.0 → 2.1.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.
Files changed (41) hide show
  1. package/README.md +33 -6
  2. package/dist/client.js +64 -0
  3. package/dist/client.js.map +7 -0
  4. package/dist/control.js +69 -0
  5. package/dist/control.js.map +7 -0
  6. package/dist/host.js +82 -0
  7. package/dist/host.js.map +7 -0
  8. package/dist/index.js +26 -4
  9. package/dist/index.js.map +2 -2
  10. package/dist/owner.js +34 -0
  11. package/dist/owner.js.map +7 -0
  12. package/dist/services.js +12 -0
  13. package/dist/services.js.map +7 -0
  14. package/dist/spectrum/agent-port.js +156 -0
  15. package/dist/spectrum/agent-port.js.map +7 -0
  16. package/dist/spectrum/emitter.js +20 -0
  17. package/dist/spectrum/emitter.js.map +7 -0
  18. package/dist/spectrum/index.js +17 -0
  19. package/dist/spectrum/index.js.map +7 -0
  20. package/dist/spectrum/messages.js +161 -0
  21. package/dist/spectrum/messages.js.map +7 -0
  22. package/dist/types/client.d.ts +14 -0
  23. package/dist/types/control.d.ts +4 -0
  24. package/dist/types/host.d.ts +13 -0
  25. package/dist/types/index.d.ts +7 -1
  26. package/dist/types/owner.d.ts +21 -0
  27. package/dist/types/services.d.ts +59 -0
  28. package/dist/types/spectrum/agent-port.d.ts +32 -0
  29. package/dist/types/spectrum/emitter.d.ts +7 -0
  30. package/dist/types/spectrum/index.d.ts +7 -0
  31. package/dist/types/spectrum/messages.d.ts +37 -0
  32. package/dist/types/wire.d.ts +27 -0
  33. package/dist/types/worker.d.ts +5 -0
  34. package/dist/wire.js +164 -0
  35. package/dist/wire.js.map +7 -0
  36. package/dist/worker.js +14 -0
  37. package/dist/worker.js.map +7 -0
  38. package/package.json +12 -1
  39. package/dist/port.js +0 -57
  40. package/dist/port.js.map +0 -7
  41. package/dist/types/port.d.ts +0 -23
package/README.md CHANGED
@@ -6,18 +6,45 @@ The package ships unbundled ESM: `dist/` holds one file per source file and impo
6
6
 
7
7
  ## Use
8
8
 
9
+ In the agent's dedicated worker:
10
+
9
11
  ```js
10
- import { connectAgent, openAgent, serveAgent } from '@ai-ecoverse/slicc-agent';
12
+ import { hostAgent, openAgent, openOpfsSqliteStorage, serveConnections } from '@ai-ecoverse/slicc-agent';
13
+
14
+ const storage = await openOpfsSqliteStorage();
15
+ serveConnections(self, openAgent({ models, model, storage }).then((agent) => hostAgent(agent)));
16
+ ```
11
17
 
12
- serveAgent(self, openAgent({ models, model: { provider: 'amazon-bedrock', modelId } }));
18
+ In the page:
13
19
 
14
- const agent = connectAgent(new Worker(workerUrl, { type: 'module' }));
20
+ ```js
21
+ import { startAgent } from '@ai-ecoverse/slicc-agent';
22
+
23
+ const owner = await startAgent({ worker: () => new Worker(workerUrl, { type: 'module' }) });
24
+ const agent = await owner.connect();
25
+ agent.transcript.subscribe((view) => render(view));
15
26
  await agent.prompt('What is in /home?');
16
27
  ```
17
28
 
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.
29
+ - `openAgent({ models, model, storage?, registry?, settings? })` opens a durable Harness (in memory unless `storage` is given) and its root conversation, and resumes work a previous worker left unfinished.
30
+ - `openOpfsSqliteStorage({ directory?, file? })` keeps the session in wasm SQLite ([`@sqlite.org/sqlite-wasm`](https://www.npmjs.com/package/@sqlite.org/sqlite-wasm)) in an `opfs-sahpool` pool, by default `/.slicc/agent/` in OPFS. It needs a dedicated worker. Only one worker holds a pool at a time: the opener takes a Web Lock per directory and waits until every file of the pool can be opened before sqlite-wasm installs it, because a failed install deletes the pool's directory. `openMemorySqliteStorage()` keeps the same database in memory.
31
+ - `hostAgent(agent)` serves the agent with pi's protocol: a pi-server with one session, `agent`, whose Chord services are `slicc.agent.control` (send with `whenBusy` `steer`, `followUp` or `reject`, wait, withdraw, abort, compact) and `slicc.agent.transcript` (the conversation's durable view as replicated state). `serveConnections(self, host)` hands it every `MessagePort` the page sends.
32
+ - `startAgent({ worker })` takes the Web Lock `slicc-agent`, so one tab at a time owns the agent worker, and waits while another tab holds it. `connect()` returns an `AgentConnection` (`control`, `transcript`, `prompt()`), `restart()` replaces the worker (durable resumes from SQLite), and `release()` stops it and lets the next tab take over.
33
+
34
+ ### In slicc-spectrum
35
+
36
+ `@ai-ecoverse/slicc-agent/spectrum` turns an `AgentConnection` into slicc-spectrum's `AgentPort`, so `<slicc-app>` shows the real agent:
37
+
38
+ ```js
39
+ import { createAgentModel } from '@ai-ecoverse/slicc-agent/spectrum';
40
+
41
+ const agent = await owner.connect();
42
+ app.model = { ...createKernelModel({ kernel, root }), ...createAgentModel(agent) };
43
+ ```
44
+
45
+ The adapter keeps the replicated transcript and maps it on every update. User, assistant and compaction entries become messages; tool results fill in the tool calls they answer; the generation in flight shows as a streaming message; retries and running compactions show as system messages. `busy()` and `queue()` read durable's live and inbox documents. A plain send queues a follow-up, and a steer send steers, as slicc-spectrum's composer asks today. There is one cone so far; scoops, the freezer, licks and questions come later. slicc-spectrum is a peer dependency (≥ 1.7.0) used for types only.
46
+
47
+ pi-server 1.0.4 calls `unref()` on a `setTimeout` handle when it accepts a connection, which browsers don't have. `PortListener` wraps that one call so its timers get a no-op `unref`.
21
48
 
22
49
  ## Develop
23
50
 
package/dist/client.js ADDED
@@ -0,0 +1,64 @@
1
+ import {
2
+ createRemoteServiceBinding
3
+ } from "@earendil-works/chord";
4
+ import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
5
+ import { Client, createClientServiceTransport } from "@earendil-works/pi-client";
6
+ import {
7
+ AGENT_SESSION,
8
+ AgentControl,
9
+ AgentSessions,
10
+ AgentTranscript
11
+ } from "./services.js";
12
+ import { PortChannel } from "./wire.js";
13
+ function attached(client) {
14
+ return new Promise((resolve) => {
15
+ const stop = client.onAttachmentChange(() => {
16
+ stop();
17
+ resolve();
18
+ });
19
+ });
20
+ }
21
+ async function connectAgent(endpoint, options = {}) {
22
+ const context = options.context ?? BACKGROUND_CONTEXT;
23
+ const channel = new PortChannel(endpoint);
24
+ const serverId = await channel.hello;
25
+ const client = await Client.connect({ serverId, transportFactory: channel.transport });
26
+ const server = createRemoteServiceBinding({
27
+ services: [AgentSessions],
28
+ transport: createClientServiceTransport(client, () => ({ serverId })),
29
+ bound: true
30
+ });
31
+ const ready = attached(client);
32
+ await server.use(AgentSessions).attach(AGENT_SESSION, context);
33
+ await ready;
34
+ const session = createRemoteServiceBinding({
35
+ services: [AgentControl, AgentTranscript],
36
+ transport: createClientServiceTransport(client, () => client.attachment),
37
+ bound: true
38
+ });
39
+ const control = session.use(AgentControl);
40
+ const transcript = session.use(AgentTranscript).state;
41
+ await session.ready(context);
42
+ return {
43
+ serverId,
44
+ control,
45
+ transcript,
46
+ async prompt(text, whenBusy = "followUp") {
47
+ const sent = await control.send({ text, whenBusy, requestId: null }, context);
48
+ if (!sent.accepted) throw new Error(`${sent.error.code}: ${sent.error.message}`);
49
+ const settled = await control.wait(sent.submissionId, context);
50
+ if (settled.status !== "done") throw new Error(`unanswered: ${settled.reason}`);
51
+ return settled.text;
52
+ },
53
+ async close() {
54
+ await server.use(AgentSessions).detach(context);
55
+ await session.dispose(context);
56
+ await server.dispose(context);
57
+ await client.dispose();
58
+ }
59
+ };
60
+ }
61
+ export {
62
+ connectAgent
63
+ };
64
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 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 AgentTranscript,\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 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],\n transport: createClientServiceTransport(client, () => client.attachment),\n bound: true,\n });\n const control = session.use(AgentControl);\n const transcript = session.use(AgentTranscript).state;\n await session.ready(context);\n return {\n serverId,\n control,\n transcript,\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,OAEK;AACP,SAAS,mBAAsC;AAU/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,eAAe;AAAA,IACxC,WAAW,6BAA6B,QAAQ,MAAM,OAAO,UAAU;AAAA,IACvE,OAAO;AAAA,EACT,CAAC;AACD,QAAM,UAAU,QAAQ,IAAI,YAAY;AACxC,QAAM,aAAa,QAAQ,IAAI,eAAe,EAAE;AAChD,QAAM,QAAQ,MAAM,OAAO;AAC3B,SAAO;AAAA,IACL;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
+ "names": []
7
+ }
@@ -0,0 +1,69 @@
1
+ import {
2
+ AssistantEntry,
3
+ ConversationBusy
4
+ } from "@earendil-works/pi-durable";
5
+ import { answerText } from "./agent.js";
6
+ function submissionId(value) {
7
+ const id = Number(value);
8
+ return /^[1-9][0-9]*$/.test(value) && Number.isSafeInteger(id) ? id : void 0;
9
+ }
10
+ function operationError(error) {
11
+ if (error instanceof ConversationBusy) return { code: "busy", message: error.message };
12
+ return { code: "failed", message: error instanceof Error ? error.message : String(error) };
13
+ }
14
+ async function accepted(operation) {
15
+ try {
16
+ const result = await operation();
17
+ const id = typeof result === "number" ? result : result.id;
18
+ return { accepted: true, submissionId: String(id), error: null };
19
+ } catch (error) {
20
+ return { accepted: false, submissionId: null, error: operationError(error) };
21
+ }
22
+ }
23
+ function createAgentControl(harness, conversation) {
24
+ return {
25
+ send: (request, context) => accepted(
26
+ () => conversation.submit(
27
+ {
28
+ type: "input",
29
+ content: request.text,
30
+ whenBusy: request.whenBusy,
31
+ ...request.requestId ? { requestId: request.requestId } : {}
32
+ },
33
+ context
34
+ )
35
+ ),
36
+ async wait(id, context) {
37
+ const parsed = submissionId(id);
38
+ const submission = parsed === void 0 ? void 0 : await harness.submission(parsed, context);
39
+ if (!submission) throw new Error(`unknown submission ${id}`);
40
+ const settled = await submission.wait(context);
41
+ const { answer, reason } = settled;
42
+ if (answer === void 0) return { status: "unanswered", text: null, reason: String(reason) };
43
+ const entry = await harness.commit((tx) => tx.entry(AssistantEntry, answer), context);
44
+ return { status: "done", text: answerText(entry?.model?.[0]?.content), reason: null };
45
+ },
46
+ async withdraw(id, context) {
47
+ const parsed = submissionId(id);
48
+ if (parsed === void 0) return { outcome: "not_found" };
49
+ const result = await harness.abortSubmission(parsed, context, conversation.id);
50
+ if (result === "aborted") return { outcome: "withdrawn" };
51
+ return { outcome: result === "not_found" ? "not_found" : "already_placed" };
52
+ },
53
+ abort: (context) => conversation.abort(context),
54
+ compact: (instructions, context) => accepted(() => conversation.compact(instructions ?? void 0, context)),
55
+ reset: (handoff, context) => conversation.reset(handoff ?? void 0, context),
56
+ configure: (change, context) => conversation.configure(
57
+ {
58
+ ...change.model ? { model: change.model } : {},
59
+ ...change.thinkingLevel ? { thinkingLevel: change.thinkingLevel } : {}
60
+ },
61
+ context
62
+ )
63
+ };
64
+ }
65
+ export {
66
+ createAgentControl,
67
+ submissionId
68
+ };
69
+ //# sourceMappingURL=control.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/control.ts"],
4
+ "sourcesContent": ["import {\n AssistantEntry,\n type Conversation,\n ConversationBusy,\n type EntryId,\n type Harness,\n type SubmissionId,\n} from '@earendil-works/pi-durable';\nimport { answerText } from './agent.ts';\nimport type { AgentControl, OperationError, SendResponse } from './services.ts';\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\nexport function createAgentControl(harness: Harness, conversation: Conversation): AgentControl {\n return {\n send: (request, context) =>\n accepted(() =>\n conversation.submit(\n {\n type: 'input',\n content: request.text,\n whenBusy: request.whenBusy,\n ...(request.requestId ? { requestId: request.requestId } : {}),\n },\n context\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 async withdraw(id, context) {\n const parsed = submissionId(id);\n if (parsed === undefined) return { outcome: 'not_found' };\n const result = await harness.abortSubmission(parsed, context, conversation.id);\n if (result === 'aborted') return { outcome: 'withdrawn' };\n return { outcome: result === 'not_found' ? 'not_found' : 'already_placed' };\n },\n abort: (context) => conversation.abort(context),\n compact: (instructions, context) =>\n accepted(() => conversation.compact(instructions ?? undefined, context)),\n reset: (handoff, context) => conversation.reset(handoff ?? undefined, context),\n configure: (change, context) =>\n conversation.configure(\n {\n ...(change.model ? { model: change.model } : {}),\n ...(change.thinkingLevel ? { thinkingLevel: change.thinkingLevel } : {}),\n },\n context\n ),\n };\n}\n"],
5
+ "mappings": "AAAA;AAAA,EACE;AAAA,EAEA;AAAA,OAIK;AACP,SAAS,kBAAkB;AAGpB,SAAS,aAAa,OAAyC;AACpE,QAAM,KAAK,OAAO,KAAK;AACvB,SAAO,gBAAgB,KAAK,KAAK,KAAK,OAAO,cAAc,EAAE,IAAK,KAAsB;AAC1F;AAEA,SAAS,eAAe,OAAgC;AACtD,MAAI,iBAAiB,iBAAkB,QAAO,EAAE,MAAM,QAAQ,SAAS,MAAM,QAAQ;AACrF,SAAO,EAAE,MAAM,UAAU,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE;AAC3F;AAEA,eAAe,SAAS,WAA0E;AAChG,MAAI;AACF,UAAM,SAAS,MAAM,UAAU;AAC/B,UAAM,KAAK,OAAO,WAAW,WAAW,SAAS,OAAO;AACxD,WAAO,EAAE,UAAU,MAAM,cAAc,OAAO,EAAE,GAAG,OAAO,KAAK;AAAA,EACjE,SAAS,OAAO;AACd,WAAO,EAAE,UAAU,OAAO,cAAc,MAAM,OAAO,eAAe,KAAK,EAAE;AAAA,EAC7E;AACF;AAEO,SAAS,mBAAmB,SAAkB,cAA0C;AAC7F,SAAO;AAAA,IACL,MAAM,CAAC,SAAS,YACd;AAAA,MAAS,MACP,aAAa;AAAA,QACX;AAAA,UACE,MAAM;AAAA,UACN,SAAS,QAAQ;AAAA,UACjB,UAAU,QAAQ;AAAA,UAClB,GAAI,QAAQ,YAAY,EAAE,WAAW,QAAQ,UAAU,IAAI,CAAC;AAAA,QAC9D;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,IACF,MAAM,KAAK,IAAI,SAAS;AACtB,YAAM,SAAS,aAAa,EAAE;AAC9B,YAAM,aACJ,WAAW,SAAY,SAAY,MAAM,QAAQ,WAAW,QAAQ,OAAO;AAC7E,UAAI,CAAC,WAAY,OAAM,IAAI,MAAM,sBAAsB,EAAE,EAAE;AAC3D,YAAM,UAAU,MAAM,WAAW,KAAK,OAAO;AAC7C,YAAM,EAAE,QAAQ,OAAO,IAAI;AAC3B,UAAI,WAAW,OAAW,QAAO,EAAE,QAAQ,cAAc,MAAM,MAAM,QAAQ,OAAO,MAAM,EAAE;AAC5F,YAAM,QAAQ,MAAM,QAAQ,OAAO,CAAC,OAAO,GAAG,MAAM,gBAAgB,MAAM,GAAG,OAAO;AACpF,aAAO,EAAE,QAAQ,QAAQ,MAAM,WAAW,OAAO,QAAQ,CAAC,GAAG,OAAO,GAAG,QAAQ,KAAK;AAAA,IACtF;AAAA,IACA,MAAM,SAAS,IAAI,SAAS;AAC1B,YAAM,SAAS,aAAa,EAAE;AAC9B,UAAI,WAAW,OAAW,QAAO,EAAE,SAAS,YAAY;AACxD,YAAM,SAAS,MAAM,QAAQ,gBAAgB,QAAQ,SAAS,aAAa,EAAE;AAC7E,UAAI,WAAW,UAAW,QAAO,EAAE,SAAS,YAAY;AACxD,aAAO,EAAE,SAAS,WAAW,cAAc,cAAc,iBAAiB;AAAA,IAC5E;AAAA,IACA,OAAO,CAAC,YAAY,aAAa,MAAM,OAAO;AAAA,IAC9C,SAAS,CAAC,cAAc,YACtB,SAAS,MAAM,aAAa,QAAQ,gBAAgB,QAAW,OAAO,CAAC;AAAA,IACzE,OAAO,CAAC,SAAS,YAAY,aAAa,MAAM,WAAW,QAAW,OAAO;AAAA,IAC7E,WAAW,CAAC,QAAQ,YAClB,aAAa;AAAA,MACX;AAAA,QACE,GAAI,OAAO,QAAQ,EAAE,OAAO,OAAO,MAAM,IAAI,CAAC;AAAA,QAC9C,GAAI,OAAO,gBAAgB,EAAE,eAAe,OAAO,cAAc,IAAI,CAAC;AAAA,MACxE;AAAA,MACA;AAAA,IACF;AAAA,EACJ;AACF;",
6
+ "names": []
7
+ }
package/dist/host.js ADDED
@@ -0,0 +1,82 @@
1
+ import {
2
+ createRemoteServiceEndpoint,
3
+ RemoteServiceProvider
4
+ } from "@earendil-works/chord";
5
+ import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
6
+ import {
7
+ Server,
8
+ SessionNotFoundError
9
+ } from "@earendil-works/pi-server";
10
+ import { createAgentControl } from "./control.js";
11
+ import { AGENT_SESSION, AgentControl, AgentSessions, AgentTranscript } from "./services.js";
12
+ import { PortListener } from "./wire.js";
13
+ function endpointFor(provider) {
14
+ const endpoint = createRemoteServiceEndpoint(provider);
15
+ return {
16
+ invokeService: (call, publish, context) => endpoint.invoke(call, (id, update) => publish(id, update, context), context),
17
+ release: () => endpoint.dispose()
18
+ };
19
+ }
20
+ function sessionServices(provider) {
21
+ return {
22
+ attachClient: () => endpointFor(provider),
23
+ close: async () => {
24
+ }
25
+ };
26
+ }
27
+ function serverServices() {
28
+ return {
29
+ attachClient(presentation) {
30
+ const provider = new RemoteServiceProvider([{ service: AgentSessions, mode: "singleton" }]);
31
+ provider.provide(AgentSessions, {
32
+ attach: (sessionId, context) => presentation.attachSession(sessionId, context),
33
+ detach: (context) => presentation.detachSession(context)
34
+ });
35
+ const attachment = endpointFor(provider);
36
+ return {
37
+ invokeService: attachment.invokeService,
38
+ release(context) {
39
+ attachment.release(context);
40
+ provider.dispose();
41
+ }
42
+ };
43
+ }
44
+ };
45
+ }
46
+ async function hostAgent(agent, options = {}) {
47
+ const context = options.context ?? BACKGROUND_CONTEXT;
48
+ const serverId = options.serverId ?? crypto.randomUUID();
49
+ const state = await agent.root.viewState(context);
50
+ const provider = new RemoteServiceProvider([
51
+ { service: AgentControl, mode: "singleton" },
52
+ { service: AgentTranscript, mode: "singleton" }
53
+ ]);
54
+ provider.provide(AgentControl, createAgentControl(agent.harness, agent.root));
55
+ provider.provide(AgentTranscript, { state });
56
+ const host = {
57
+ serverServices: serverServices(),
58
+ async resolveSession(sessionId) {
59
+ if (sessionId !== AGENT_SESSION)
60
+ throw new SessionNotFoundError(`unknown session ${sessionId}`);
61
+ return { id: sessionId };
62
+ },
63
+ openSession: async () => sessionServices(provider)
64
+ };
65
+ const listener = new PortListener(serverId);
66
+ const server = await new Server(host, { serverId, listeners: [listener] }).start();
67
+ return {
68
+ serverId,
69
+ agent,
70
+ connect: (endpoint) => listener.connect(endpoint),
71
+ async close() {
72
+ await server.close();
73
+ provider.dispose();
74
+ state.dispose();
75
+ await agent.close();
76
+ }
77
+ };
78
+ }
79
+ export {
80
+ hostAgent
81
+ };
82
+ //# sourceMappingURL=host.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/host.ts"],
4
+ "sourcesContent": ["import {\n type Context,\n createRemoteServiceEndpoint,\n RemoteServiceProvider,\n} from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport {\n type RoutedServerServiceHost,\n type RoutedSessionAttachment,\n type RoutedSessionHandle,\n Server,\n type ServerHost,\n SessionNotFoundError,\n} from '@earendil-works/pi-server';\nimport type { Agent } from './agent.ts';\nimport { createAgentControl } from './control.ts';\nimport { AGENT_SESSION, AgentControl, AgentSessions, AgentTranscript } from './services.ts';\nimport { type PortEndpoint, PortListener } from './wire.ts';\n\nexport interface AgentHost {\n readonly serverId: string;\n readonly agent: Agent;\n connect(endpoint: PortEndpoint): void;\n close(): Promise<void>;\n}\n\nfunction endpointFor(provider: RemoteServiceProvider): RoutedSessionAttachment {\n const endpoint = createRemoteServiceEndpoint(provider);\n return {\n invokeService: (call, publish, context) =>\n endpoint.invoke(call, (id, update) => publish(id, update, context), context),\n release: () => endpoint.dispose(),\n };\n}\n\nfunction sessionServices(provider: RemoteServiceProvider): RoutedSessionHandle {\n return {\n attachClient: () => endpointFor(provider),\n close: async () => {},\n };\n}\n\nfunction serverServices(): RoutedServerServiceHost {\n return {\n attachClient(presentation) {\n const provider = new RemoteServiceProvider([{ service: AgentSessions, mode: 'singleton' }]);\n provider.provide(AgentSessions, {\n attach: (sessionId, context) => presentation.attachSession(sessionId, context),\n detach: (context) => presentation.detachSession(context),\n });\n const attachment = endpointFor(provider);\n return {\n invokeService: attachment.invokeService,\n release(context) {\n attachment.release(context);\n provider.dispose();\n },\n };\n },\n };\n}\n\nexport async function hostAgent(\n agent: Agent,\n options: { serverId?: string; context?: Context } = {}\n): Promise<AgentHost> {\n const context = options.context ?? BACKGROUND_CONTEXT;\n const serverId = options.serverId ?? crypto.randomUUID();\n const state = await agent.root.viewState(context);\n const provider = new RemoteServiceProvider([\n { service: AgentControl, mode: 'singleton' },\n { service: AgentTranscript, mode: 'singleton' },\n ]);\n provider.provide(AgentControl, createAgentControl(agent.harness, agent.root));\n provider.provide(AgentTranscript, { state });\n const host: ServerHost = {\n serverServices: serverServices(),\n async resolveSession(sessionId) {\n if (sessionId !== AGENT_SESSION)\n throw new SessionNotFoundError(`unknown session ${sessionId}`);\n return { id: sessionId };\n },\n openSession: async () => sessionServices(provider),\n };\n const listener = new PortListener(serverId);\n const server = await new Server(host, { serverId, listeners: [listener] }).start();\n return {\n serverId,\n agent,\n connect: (endpoint) => listener.connect(endpoint),\n async close() {\n await server.close();\n provider.dispose();\n state.dispose();\n await agent.close();\n },\n };\n}\n"],
5
+ "mappings": "AAAA;AAAA,EAEE;AAAA,EACA;AAAA,OACK;AACP,SAAS,0BAA0B;AACnC;AAAA,EAIE;AAAA,EAEA;AAAA,OACK;AAEP,SAAS,0BAA0B;AACnC,SAAS,eAAe,cAAc,eAAe,uBAAuB;AAC5E,SAA4B,oBAAoB;AAShD,SAAS,YAAY,UAA0D;AAC7E,QAAM,WAAW,4BAA4B,QAAQ;AACrD,SAAO;AAAA,IACL,eAAe,CAAC,MAAM,SAAS,YAC7B,SAAS,OAAO,MAAM,CAAC,IAAI,WAAW,QAAQ,IAAI,QAAQ,OAAO,GAAG,OAAO;AAAA,IAC7E,SAAS,MAAM,SAAS,QAAQ;AAAA,EAClC;AACF;AAEA,SAAS,gBAAgB,UAAsD;AAC7E,SAAO;AAAA,IACL,cAAc,MAAM,YAAY,QAAQ;AAAA,IACxC,OAAO,YAAY;AAAA,IAAC;AAAA,EACtB;AACF;AAEA,SAAS,iBAA0C;AACjD,SAAO;AAAA,IACL,aAAa,cAAc;AACzB,YAAM,WAAW,IAAI,sBAAsB,CAAC,EAAE,SAAS,eAAe,MAAM,YAAY,CAAC,CAAC;AAC1F,eAAS,QAAQ,eAAe;AAAA,QAC9B,QAAQ,CAAC,WAAW,YAAY,aAAa,cAAc,WAAW,OAAO;AAAA,QAC7E,QAAQ,CAAC,YAAY,aAAa,cAAc,OAAO;AAAA,MACzD,CAAC;AACD,YAAM,aAAa,YAAY,QAAQ;AACvC,aAAO;AAAA,QACL,eAAe,WAAW;AAAA,QAC1B,QAAQ,SAAS;AACf,qBAAW,QAAQ,OAAO;AAC1B,mBAAS,QAAQ;AAAA,QACnB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEA,eAAsB,UACpB,OACA,UAAoD,CAAC,GACjC;AACpB,QAAM,UAAU,QAAQ,WAAW;AACnC,QAAM,WAAW,QAAQ,YAAY,OAAO,WAAW;AACvD,QAAM,QAAQ,MAAM,MAAM,KAAK,UAAU,OAAO;AAChD,QAAM,WAAW,IAAI,sBAAsB;AAAA,IACzC,EAAE,SAAS,cAAc,MAAM,YAAY;AAAA,IAC3C,EAAE,SAAS,iBAAiB,MAAM,YAAY;AAAA,EAChD,CAAC;AACD,WAAS,QAAQ,cAAc,mBAAmB,MAAM,SAAS,MAAM,IAAI,CAAC;AAC5E,WAAS,QAAQ,iBAAiB,EAAE,MAAM,CAAC;AAC3C,QAAM,OAAmB;AAAA,IACvB,gBAAgB,eAAe;AAAA,IAC/B,MAAM,eAAe,WAAW;AAC9B,UAAI,cAAc;AAChB,cAAM,IAAI,qBAAqB,mBAAmB,SAAS,EAAE;AAC/D,aAAO,EAAE,IAAI,UAAU;AAAA,IACzB;AAAA,IACA,aAAa,YAAY,gBAAgB,QAAQ;AAAA,EACnD;AACA,QAAM,WAAW,IAAI,aAAa,QAAQ;AAC1C,QAAM,SAAS,MAAM,IAAI,OAAO,MAAM,EAAE,UAAU,WAAW,CAAC,QAAQ,EAAE,CAAC,EAAE,MAAM;AACjF,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,SAAS,CAAC,aAAa,SAAS,QAAQ,QAAQ;AAAA,IAChD,MAAM,QAAQ;AACZ,YAAM,OAAO,MAAM;AACnB,eAAS,QAAQ;AACjB,YAAM,QAAQ;AACd,YAAM,MAAM,MAAM;AAAA,IACpB;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
package/dist/index.js CHANGED
@@ -1,22 +1,44 @@
1
1
  import { answerText, openAgent } from "./agent.js";
2
+ import { connectAgent } from "./client.js";
3
+ import { createAgentControl } from "./control.js";
4
+ import { hostAgent } from "./host.js";
2
5
  import {
3
- connectAgent,
4
- serveAgent
5
- } from "./port.js";
6
+ AGENT_LOCK,
7
+ startAgent
8
+ } from "./owner.js";
9
+ import {
10
+ AGENT_SESSION,
11
+ AgentControl,
12
+ AgentSessions,
13
+ AgentTranscript
14
+ } from "./services.js";
6
15
  import {
7
16
  AGENT_DIRECTORY,
8
17
  openMemorySqliteStorage,
9
18
  openOpfsSqliteStorage,
10
19
  WasmSqliteDatabase
11
20
  } from "./sqlite.js";
21
+ import { PortChannel, PortListener, withUnrefTimers } from "./wire.js";
22
+ import { serveConnections } from "./worker.js";
12
23
  export {
13
24
  AGENT_DIRECTORY,
25
+ AGENT_LOCK,
26
+ AGENT_SESSION,
27
+ AgentControl,
28
+ AgentSessions,
29
+ AgentTranscript,
30
+ PortChannel,
31
+ PortListener,
14
32
  WasmSqliteDatabase,
15
33
  answerText,
16
34
  connectAgent,
35
+ createAgentControl,
36
+ hostAgent,
17
37
  openAgent,
18
38
  openMemorySqliteStorage,
19
39
  openOpfsSqliteStorage,
20
- serveAgent
40
+ serveConnections,
41
+ startAgent,
42
+ withUnrefTimers
21
43
  };
22
44
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
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;",
4
+ "sourcesContent": ["export { type Agent, type AgentOptions, answerText, openAgent } from './agent.ts';\nexport { type AgentConnection, connectAgent } from './client.ts';\nexport { createAgentControl } from './control.ts';\nexport { type AgentHost, hostAgent } from './host.ts';\nexport {\n AGENT_LOCK,\n type AgentOwner,\n type AgentWorker,\n type OwnerOptions,\n startAgent,\n} from './owner.ts';\nexport {\n AGENT_SESSION,\n AgentControl,\n AgentSessions,\n type AgentSettingsChange,\n AgentTranscript,\n type OperationError,\n type PromptResult,\n type SendMode,\n type SendRequest,\n type SendResponse,\n} from './services.ts';\nexport {\n AGENT_DIRECTORY,\n type OpfsSqliteOptions,\n openMemorySqliteStorage,\n openOpfsSqliteStorage,\n WasmSqliteDatabase,\n} from './sqlite.ts';\nexport { PortChannel, type PortEndpoint, PortListener, withUnrefTimers } from './wire.ts';\nexport { serveConnections, type WorkerScope } from './worker.ts';\n"],
5
+ "mappings": "AAAA,SAAwC,YAAY,iBAAiB;AACrE,SAA+B,oBAAoB;AACnD,SAAS,0BAA0B;AACnC,SAAyB,iBAAiB;AAC1C;AAAA,EACE;AAAA,EAIA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,OAMK;AACP;AAAA,EACE;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,aAAgC,cAAc,uBAAuB;AAC9E,SAAS,wBAA0C;",
6
6
  "names": []
7
7
  }
package/dist/owner.js ADDED
@@ -0,0 +1,34 @@
1
+ import { connectAgent } from "./client.js";
2
+ import { holdLock } from "./opfs-pool.js";
3
+ const AGENT_LOCK = "slicc-agent";
4
+ async function startAgent(options) {
5
+ const release = await holdLock(options.locks ?? navigator.locks, AGENT_LOCK);
6
+ const channel = options.channel ?? (() => new MessageChannel());
7
+ let worker;
8
+ try {
9
+ worker = options.worker();
10
+ } catch (error) {
11
+ release();
12
+ throw error;
13
+ }
14
+ return {
15
+ connect() {
16
+ const { port1, port2 } = channel();
17
+ worker.postMessage({ connect: port2 }, [port2]);
18
+ return connectAgent(port1);
19
+ },
20
+ restart() {
21
+ worker.terminate();
22
+ worker = options.worker();
23
+ },
24
+ release() {
25
+ worker.terminate();
26
+ release();
27
+ }
28
+ };
29
+ }
30
+ export {
31
+ AGENT_LOCK,
32
+ startAgent
33
+ };
34
+ //# sourceMappingURL=owner.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/owner.ts"],
4
+ "sourcesContent": ["import { type AgentConnection, connectAgent } from './client.ts';\nimport { holdLock } from './opfs-pool.ts';\nimport type { PortEndpoint } from './wire.ts';\n\nexport const AGENT_LOCK = 'slicc-agent';\n\nexport interface AgentWorker {\n postMessage(message: unknown, transfer: Transferable[]): void;\n terminate(): void;\n}\n\nexport interface AgentOwner {\n connect(): Promise<AgentConnection>;\n restart(): void;\n release(): void;\n}\n\nexport interface OwnerOptions {\n worker: () => AgentWorker;\n locks?: LockManager;\n channel?: () => { port1: PortEndpoint; port2: Transferable };\n}\n\nexport async function startAgent(options: OwnerOptions): Promise<AgentOwner> {\n const release = await holdLock(options.locks ?? navigator.locks, AGENT_LOCK);\n const channel = options.channel ?? (() => new MessageChannel());\n let worker: AgentWorker;\n try {\n worker = options.worker();\n } catch (error) {\n release();\n throw error;\n }\n return {\n connect() {\n const { port1, port2 } = channel();\n worker.postMessage({ connect: port2 }, [port2]);\n return connectAgent(port1);\n },\n restart() {\n worker.terminate();\n worker = options.worker();\n },\n release() {\n worker.terminate();\n release();\n },\n };\n}\n"],
5
+ "mappings": "AAAA,SAA+B,oBAAoB;AACnD,SAAS,gBAAgB;AAGlB,MAAM,aAAa;AAmB1B,eAAsB,WAAW,SAA4C;AAC3E,QAAM,UAAU,MAAM,SAAS,QAAQ,SAAS,UAAU,OAAO,UAAU;AAC3E,QAAM,UAAU,QAAQ,YAAY,MAAM,IAAI,eAAe;AAC7D,MAAI;AACJ,MAAI;AACF,aAAS,QAAQ,OAAO;AAAA,EAC1B,SAAS,OAAO;AACd,YAAQ;AACR,UAAM;AAAA,EACR;AACA,SAAO;AAAA,IACL,UAAU;AACR,YAAM,EAAE,OAAO,MAAM,IAAI,QAAQ;AACjC,aAAO,YAAY,EAAE,SAAS,MAAM,GAAG,CAAC,KAAK,CAAC;AAC9C,aAAO,aAAa,KAAK;AAAA,IAC3B;AAAA,IACA,UAAU;AACR,aAAO,UAAU;AACjB,eAAS,QAAQ,OAAO;AAAA,IAC1B;AAAA,IACA,UAAU;AACR,aAAO,UAAU;AACjB,cAAQ;AAAA,IACV;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,12 @@
1
+ import { defineService } from "@earendil-works/chord";
2
+ const AgentControl = defineService("slicc.agent.control");
3
+ const AgentTranscript = defineService("slicc.agent.transcript");
4
+ const AgentSessions = defineService("slicc.agent.sessions");
5
+ const AGENT_SESSION = "agent";
6
+ export {
7
+ AGENT_SESSION,
8
+ AgentControl,
9
+ AgentSessions,
10
+ AgentTranscript
11
+ };
12
+ //# sourceMappingURL=services.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/services.ts"],
4
+ "sourcesContent": ["import { type Context, defineService, type ReplicatedState } from '@earendil-works/chord';\nimport type { ConversationView } from '@earendil-works/pi-durable';\n\nexport type SendMode = 'steer' | 'followUp' | 'reject';\n\nexport interface SendRequest {\n text: string;\n whenBusy: SendMode;\n requestId: string | null;\n}\n\nexport interface OperationError {\n code: string;\n message: string;\n}\n\nexport type SendResponse =\n | { accepted: true; submissionId: string; error: null }\n | { accepted: false; submissionId: null; error: OperationError };\n\nexport type PromptResult =\n | { status: 'done'; text: string; reason: null }\n | { status: 'unanswered'; text: null; reason: string };\n\nexport interface AgentControl {\n send(request: SendRequest, context: Context): Promise<SendResponse>;\n wait(submissionId: string, context: Context): Promise<PromptResult>;\n withdraw(\n submissionId: string,\n context: Context\n ): Promise<{ outcome: 'withdrawn' | 'already_placed' | 'not_found' }>;\n abort(context: Context): Promise<void>;\n compact(instructions: string | null, context: Context): Promise<SendResponse>;\n reset(handoff: string | null, context: Context): Promise<void>;\n configure(change: AgentSettingsChange, context: Context): Promise<void>;\n}\n\nexport interface AgentSettingsChange {\n model: { provider: string; modelId: string } | null;\n thinkingLevel: 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max' | null;\n}\n\nexport const AgentControl = defineService<AgentControl>('slicc.agent.control');\n\nexport interface AgentTranscript {\n readonly state: ReplicatedState<ConversationView>;\n}\n\nexport const AgentTranscript = defineService<AgentTranscript>('slicc.agent.transcript');\n\nexport interface AgentSessions {\n attach(sessionId: string, context: Context): Promise<void>;\n detach(context: Context): Promise<void>;\n}\n\nexport const AgentSessions = defineService<AgentSessions>('slicc.agent.sessions');\n\nexport const AGENT_SESSION = 'agent';\n"],
5
+ "mappings": "AAAA,SAAuB,qBAA2C;AA0C3D,MAAM,eAAe,cAA4B,qBAAqB;AAMtE,MAAM,kBAAkB,cAA+B,wBAAwB;AAO/E,MAAM,gBAAgB,cAA6B,sBAAsB;AAEzE,MAAM,gBAAgB;",
6
+ "names": []
7
+ }
@@ -0,0 +1,156 @@
1
+ import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
2
+ import { Emitter } from "./emitter.js";
3
+ import { isBusy, queued, toMessages } from "./messages.js";
4
+ const CONE = "cone";
5
+ const thinking = {
6
+ off: "off",
7
+ minimal: "low",
8
+ low: "low",
9
+ medium: "medium",
10
+ high: "high",
11
+ xhigh: "high",
12
+ max: "high"
13
+ };
14
+ function status(view, messages) {
15
+ if (isBusy(view)) {
16
+ const live = view?.docs["pi.live"];
17
+ return live?.tools?.some((slot) => slot.status === "running") ? "working" : "thinking";
18
+ }
19
+ const last = messages.at(-1);
20
+ return last?.role === "assistant" && last.status === "error" ? "error" : "idle";
21
+ }
22
+ class AgentAdapter extends Emitter {
23
+ #connection;
24
+ #ready;
25
+ #messages = [];
26
+ constructor(connection) {
27
+ super();
28
+ this.#connection = connection;
29
+ this.#ready = new Promise((resolve) => {
30
+ connection.transcript.subscribe((view) => {
31
+ this.#update(view);
32
+ resolve();
33
+ });
34
+ });
35
+ }
36
+ #view() {
37
+ return this.#connection.transcript.value;
38
+ }
39
+ #update(view) {
40
+ const before = this.#messages;
41
+ this.#messages = toMessages(view);
42
+ this.emit("agents", this.list());
43
+ if (before.length !== this.#messages.length) {
44
+ this.emit("messages", CONE);
45
+ return;
46
+ }
47
+ const changed = this.#messages.filter(
48
+ (message, index) => JSON.stringify(message) !== JSON.stringify(before[index])
49
+ );
50
+ if (changed.length === 1)
51
+ this.emit("message", { agentId: CONE, message: changed[0] });
52
+ else if (changed.length > 1) this.emit("messages", CONE);
53
+ }
54
+ ready() {
55
+ return this.#ready;
56
+ }
57
+ list() {
58
+ const doc = this.#view()?.docs["pi.agent"];
59
+ return [
60
+ {
61
+ id: CONE,
62
+ name: "sliccy",
63
+ kind: "cone",
64
+ parentId: null,
65
+ status: status(this.#view(), this.#messages),
66
+ model: doc?.model ? `${doc.model.provider}/${doc.model.modelId}` : "",
67
+ contextFill: 0,
68
+ unread: 0,
69
+ thinking: thinking[doc?.thinkingLevel ?? "off"] ?? "off"
70
+ }
71
+ ];
72
+ }
73
+ active() {
74
+ return CONE;
75
+ }
76
+ select() {
77
+ }
78
+ messages() {
79
+ return this.#messages;
80
+ }
81
+ async send(_agentId, input) {
82
+ const outgoing = typeof input === "string" ? { text: input } : input;
83
+ const sent = await this.#connection.control.send(
84
+ {
85
+ text: outgoing.text,
86
+ whenBusy: outgoing.mode === "steer" ? "steer" : "followUp",
87
+ requestId: crypto.randomUUID()
88
+ },
89
+ BACKGROUND_CONTEXT
90
+ );
91
+ if (!sent.accepted) throw new Error(sent.error.message);
92
+ }
93
+ stop() {
94
+ void this.#connection.control.abort(BACKGROUND_CONTEXT);
95
+ }
96
+ busy() {
97
+ return isBusy(this.#view());
98
+ }
99
+ queue() {
100
+ return queued(this.#view());
101
+ }
102
+ unqueue(_agentId, messageId) {
103
+ void this.#connection.control.withdraw(messageId.replace(/^q/, ""), BACKGROUND_CONTEXT);
104
+ }
105
+ suggestion() {
106
+ return null;
107
+ }
108
+ answer() {
109
+ }
110
+ resolveLick() {
111
+ }
112
+ compact() {
113
+ void this.#connection.control.compact(null, BACKGROUND_CONTEXT);
114
+ }
115
+ clear() {
116
+ void this.#connection.control.reset(null, BACKGROUND_CONTEXT);
117
+ }
118
+ #configure(change) {
119
+ void this.#connection.control.configure(
120
+ { model: change.model ?? null, thinkingLevel: change.thinkingLevel ?? null },
121
+ BACKGROUND_CONTEXT
122
+ );
123
+ }
124
+ setModel(_agentId, model) {
125
+ const at = model.indexOf("/");
126
+ if (at <= 0) return;
127
+ this.#configure({ model: { provider: model.slice(0, at), modelId: model.slice(at + 1) } });
128
+ }
129
+ setThinking(_agentId, level) {
130
+ this.#configure({ thinkingLevel: level });
131
+ }
132
+ async older() {
133
+ return [];
134
+ }
135
+ commands() {
136
+ return [];
137
+ }
138
+ createScoop() {
139
+ throw new Error("Scoops come with a later slicc-agent");
140
+ }
141
+ frozen() {
142
+ return [];
143
+ }
144
+ freeze() {
145
+ }
146
+ thaw() {
147
+ return null;
148
+ }
149
+ discard() {
150
+ }
151
+ }
152
+ export {
153
+ AgentAdapter,
154
+ CONE
155
+ };
156
+ //# sourceMappingURL=agent-port.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/spectrum/agent-port.ts"],
4
+ "sourcesContent": ["import type {\n Agent,\n AgentEvents,\n AgentPort,\n AgentStatus,\n FrozenCone,\n Message,\n Outgoing,\n SlashCommand,\n Thinking,\n UserMessage,\n} from '@ai-ecoverse/slicc-spectrum/ui';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { ConversationView } from '@earendil-works/pi-durable';\nimport type { AgentConnection } from '../client.ts';\nimport type { AgentSettingsChange } from '../services.ts';\nimport { Emitter } from './emitter.ts';\nimport { isBusy, queued, toMessages } from './messages.ts';\n\nexport const CONE = 'cone';\n\nconst thinking: Record<string, Thinking> = {\n off: 'off',\n minimal: 'low',\n low: 'low',\n medium: 'medium',\n high: 'high',\n xhigh: 'high',\n max: 'high',\n};\n\ninterface AgentDoc {\n model?: { provider: string; modelId: string };\n thinkingLevel?: string;\n}\n\nfunction status(view: ConversationView | undefined, messages: readonly Message[]): AgentStatus {\n if (isBusy(view)) {\n const live = view?.docs['pi.live'] as { tools?: { status: string }[] } | undefined;\n return live?.tools?.some((slot) => slot.status === 'running') ? 'working' : 'thinking';\n }\n const last = messages.at(-1);\n return last?.role === 'assistant' && last.status === 'error' ? 'error' : 'idle';\n}\n\nexport class AgentAdapter extends Emitter<AgentEvents> implements AgentPort {\n readonly #connection: AgentConnection;\n readonly #ready: Promise<void>;\n #messages: Message[] = [];\n\n constructor(connection: AgentConnection) {\n super();\n this.#connection = connection;\n this.#ready = new Promise((resolve) => {\n connection.transcript.subscribe((view) => {\n this.#update(view);\n resolve();\n });\n });\n }\n\n #view(): ConversationView | undefined {\n return this.#connection.transcript.value;\n }\n\n #update(view: ConversationView): void {\n const before = this.#messages;\n this.#messages = toMessages(view);\n this.emit('agents', this.list());\n if (before.length !== this.#messages.length) {\n this.emit('messages', CONE);\n return;\n }\n const changed = this.#messages.filter(\n (message, index) => JSON.stringify(message) !== JSON.stringify(before[index])\n );\n if (changed.length === 1)\n this.emit('message', { agentId: CONE, message: changed[0] as Message });\n else if (changed.length > 1) this.emit('messages', CONE);\n }\n\n ready(): Promise<void> {\n return this.#ready;\n }\n\n list(): readonly Agent[] {\n const doc = this.#view()?.docs['pi.agent'] as AgentDoc | undefined;\n return [\n {\n id: CONE,\n name: 'sliccy',\n kind: 'cone',\n parentId: null,\n status: status(this.#view(), this.#messages),\n model: doc?.model ? `${doc.model.provider}/${doc.model.modelId}` : '',\n contextFill: 0,\n unread: 0,\n thinking: thinking[doc?.thinkingLevel ?? 'off'] ?? 'off',\n },\n ];\n }\n\n active(): string {\n return CONE;\n }\n\n select(): void {}\n\n messages(): readonly Message[] {\n return this.#messages;\n }\n\n async send(_agentId: string, input: string | Outgoing): Promise<void> {\n const outgoing = typeof input === 'string' ? { text: input } : input;\n const sent = await this.#connection.control.send(\n {\n text: outgoing.text,\n whenBusy: outgoing.mode === 'steer' ? 'steer' : 'followUp',\n requestId: crypto.randomUUID(),\n },\n BACKGROUND_CONTEXT\n );\n if (!sent.accepted) throw new Error(sent.error.message);\n }\n\n stop(): void {\n void this.#connection.control.abort(BACKGROUND_CONTEXT);\n }\n\n busy(): boolean {\n return isBusy(this.#view());\n }\n\n queue(): readonly UserMessage[] {\n return queued(this.#view());\n }\n\n unqueue(_agentId: string, messageId: string): void {\n void this.#connection.control.withdraw(messageId.replace(/^q/, ''), BACKGROUND_CONTEXT);\n }\n\n suggestion(): string | null {\n return null;\n }\n\n answer(): void {}\n\n resolveLick(): void {}\n\n compact(): void {\n void this.#connection.control.compact(null, BACKGROUND_CONTEXT);\n }\n\n clear(): void {\n void this.#connection.control.reset(null, BACKGROUND_CONTEXT);\n }\n\n #configure(change: Partial<AgentSettingsChange>): void {\n void this.#connection.control.configure(\n { model: change.model ?? null, thinkingLevel: change.thinkingLevel ?? null },\n BACKGROUND_CONTEXT\n );\n }\n\n setModel(_agentId: string, model: string): void {\n const at = model.indexOf('/');\n if (at <= 0) return;\n this.#configure({ model: { provider: model.slice(0, at), modelId: model.slice(at + 1) } });\n }\n\n setThinking(_agentId: string, level: Thinking): void {\n this.#configure({ thinkingLevel: level });\n }\n\n async older(): Promise<readonly Message[]> {\n return [];\n }\n\n commands(): readonly SlashCommand[] {\n return [];\n }\n\n createScoop(): Agent {\n throw new Error('Scoops come with a later slicc-agent');\n }\n\n frozen(): readonly FrozenCone[] {\n return [];\n }\n\n freeze(): void {}\n\n thaw(): Agent | null {\n return null;\n }\n\n discard(): void {}\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }