@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.
- package/README.md +33 -6
- package/dist/client.js +64 -0
- package/dist/client.js.map +7 -0
- package/dist/control.js +69 -0
- package/dist/control.js.map +7 -0
- package/dist/host.js +82 -0
- package/dist/host.js.map +7 -0
- package/dist/index.js +26 -4
- package/dist/index.js.map +2 -2
- package/dist/owner.js +34 -0
- package/dist/owner.js.map +7 -0
- package/dist/services.js +12 -0
- package/dist/services.js.map +7 -0
- package/dist/spectrum/agent-port.js +156 -0
- package/dist/spectrum/agent-port.js.map +7 -0
- package/dist/spectrum/emitter.js +20 -0
- package/dist/spectrum/emitter.js.map +7 -0
- package/dist/spectrum/index.js +17 -0
- package/dist/spectrum/index.js.map +7 -0
- package/dist/spectrum/messages.js +161 -0
- package/dist/spectrum/messages.js.map +7 -0
- package/dist/types/client.d.ts +14 -0
- package/dist/types/control.d.ts +4 -0
- package/dist/types/host.d.ts +13 -0
- package/dist/types/index.d.ts +7 -1
- package/dist/types/owner.d.ts +21 -0
- package/dist/types/services.d.ts +59 -0
- package/dist/types/spectrum/agent-port.d.ts +32 -0
- package/dist/types/spectrum/emitter.d.ts +7 -0
- package/dist/types/spectrum/index.d.ts +7 -0
- package/dist/types/spectrum/messages.d.ts +37 -0
- package/dist/types/wire.d.ts +27 -0
- package/dist/types/worker.d.ts +5 -0
- package/dist/wire.js +164 -0
- package/dist/wire.js.map +7 -0
- package/dist/worker.js +14 -0
- package/dist/worker.js.map +7 -0
- package/package.json +12 -1
- package/dist/port.js +0 -57
- package/dist/port.js.map +0 -7
- 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 {
|
|
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
|
-
|
|
18
|
+
In the page:
|
|
13
19
|
|
|
14
|
-
|
|
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
|
|
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.
|
|
20
|
-
- `
|
|
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
|
+
}
|
package/dist/control.js
ADDED
|
@@ -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
|
package/dist/host.js.map
ADDED
|
@@ -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
|
-
|
|
4
|
-
|
|
5
|
-
} from "./
|
|
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
|
-
|
|
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
|
|
5
|
-
"mappings": "AAAA,SAAwC,YAAY,iBAAiB;AACrE;AAAA,
|
|
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
|
+
}
|
package/dist/services.js
ADDED
|
@@ -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
|
+
}
|