@ai-ecoverse/slicc-agent 3.31.0 → 3.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -31,6 +31,11 @@ await agent.prompt('What is in /home?');
31
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, plus `deliveries`: how each user entry was delivered, `run`, `steer` or `follow-up`, derived by the host from where each send it accepted was placed: after a tool result it steered, queued first it's a follow-up or a late steer per its `whenBusy`, placed at once it started a run). `serveConnections(self, host)` hands it every `MessagePort` the page sends.
32
32
  - `@ai-ecoverse/slicc-agent/page` has what the page needs (`startAgent`, `connectAgent`) without the agent's runtime: no pi-durable, pi-ai, providers or SQLite, which load only in the worker. `@ai-ecoverse/slicc-agent/spectrum` is just as light, and a test keeps both that way.
33
33
  - `startAgent({ worker })` takes the Web Lock `slicc-agent`, so one tab at a time owns the agent worker, and waits while another tab holds it. `connect()` returns an `AgentConnection` (`control`, `transcript`, `prompt()`), `restart()` replaces the worker (durable resumes from SQLite), and `release()` stops it and lets the next tab take over. `port()` returns a raw `MessagePort` into the worker instead, for another tab: hand it over (through a SharedWorker, say), and that tab calls `connectAgent(port)`. A caller that already holds the lock passes `held: true`. A connection's `closed` resolves when its port ends (with the error, if any), and from then on its states keep their last values.
34
+ - **Followers over a link** (PR 18): `port({ readOnly: true })` and `connect({ readOnly: true })` give a view-only connection.
35
+ - **What a view-only connection can do:** it sees the transcript, the agents, settings state and remote-control `watch`, and may call `wait` and `changesOpen`.
36
+ - **What it can't:** every other `AgentControl` method, settings action and remote-control `prompt` or `abort` is refused with `EACCES` ("this follower can watch, but not send, steer or abort"). Send and compact answer `accepted: false` with that code. The worker serves view-only ports from a second pi-server whose session gets these narrowed services.
37
+ - **Carrying the connection over a byte stream:** `linkEndpoint({ write, close })` is a `PortEndpoint` that `connectAgent` accepts. Feed it the stream's bytes with `receive(bytes)` and its end with `end(error?)`. On the owner's side, `bridgePort(port, { write, close })` does the same for the `MessagePort` from `port()`.
38
+ - **The bytes:** each `PortChannel` message becomes a frame: a `u32` big-endian length, then a tag (0 data, 1 hello with the server id, 2 close), then the payload. A frame is at most 16 MiB, and a malformed one ends the link.
34
39
 
35
40
  ### Bash and files on slicc-kernel
36
41
 
package/dist/host.js CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  import { createAgentControl } from "./control.js";
12
12
  import { trackDeliveries } from "./deliveries.js";
13
13
  import { PortListener } from "./listener.js";
14
+ import { viewControl, viewRemote, viewSettings } from "./readonly.js";
14
15
  import { createRemote } from "./remote/host.js";
15
16
  import {
16
17
  AGENT_SESSION,
@@ -124,7 +125,7 @@ async function hostAgent(agent, options = {}) {
124
125
  }
125
126
  });
126
127
  provider.provide(AgentRemoteService, remote);
127
- provider.provide(AgentTranscript, {
128
+ const transcriptService = {
128
129
  state,
129
130
  deliveries: deliveries.state,
130
131
  agents: mounted.agents,
@@ -135,25 +136,44 @@ async function hostAgent(agent, options = {}) {
135
136
  memoryScopes: options.memory?.scopes ?? replicatedState([]),
136
137
  frozen: options.freezer?.frozen ?? replicatedState([]),
137
138
  changes: options.changes?.view ?? replicatedState({ unavailable: null, changes: [] })
138
- });
139
- provider.provide(AgentSettings, options.settings ?? noSettings);
140
- const host = {
141
- serverServices: serverServices(),
142
- async resolveSession(sessionId) {
143
- if (sessionId !== AGENT_SESSION)
144
- throw new SessionNotFoundError(`unknown session ${sessionId}`);
145
- return { id: sessionId };
146
- },
147
- openSession: async () => sessionServices(provider)
148
139
  };
149
- const listener = new PortListener(serverId);
150
- const server = await new Server(host, { serverId, listeners: [listener] }).start();
140
+ provider.provide(AgentTranscript, transcriptService);
141
+ const settings = options.settings ?? noSettings;
142
+ provider.provide(AgentSettings, settings);
143
+ const viewer = new RemoteServiceProvider([
144
+ { service: AgentControl, mode: "singleton" },
145
+ { service: AgentTranscript, mode: "singleton" },
146
+ { service: AgentSettings, mode: "singleton" },
147
+ { service: AgentRemoteService, mode: "singleton" }
148
+ ]);
149
+ viewer.provide(AgentControl, viewControl(control));
150
+ viewer.provide(AgentTranscript, transcriptService);
151
+ viewer.provide(AgentSettings, viewSettings(settings));
152
+ viewer.provide(AgentRemoteService, viewRemote(remote));
153
+ const serve = async (id, services) => {
154
+ const host = {
155
+ serverServices: serverServices(),
156
+ async resolveSession(sessionId) {
157
+ if (sessionId !== AGENT_SESSION)
158
+ throw new SessionNotFoundError(`unknown session ${sessionId}`);
159
+ return { id: sessionId };
160
+ },
161
+ openSession: async () => sessionServices(services)
162
+ };
163
+ const listener = new PortListener(id);
164
+ const server = await new Server(host, { serverId: id, listeners: [listener] }).start();
165
+ return { listener, server };
166
+ };
167
+ const full = await serve(serverId, provider);
168
+ const view = await serve(crypto.randomUUID(), viewer);
151
169
  return {
152
170
  serverId,
153
171
  agent,
154
- connect: (endpoint) => listener.connect(endpoint),
172
+ connect: (endpoint, connectOptions) => (connectOptions?.readOnly ? view : full).listener.connect(endpoint),
155
173
  async close() {
156
- await server.close();
174
+ await full.server.close();
175
+ await view.server.close();
176
+ viewer.dispose();
157
177
  await remote.dispose();
158
178
  provider.dispose();
159
179
  deliveries.dispose();
package/dist/host.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/host.ts"],
4
- "sourcesContent": ["import {\n type Context,\n createRemoteServiceEndpoint,\n RemoteServiceProvider,\n replicatedState,\n} from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { ConversationView } from '@earendil-works/pi-durable';\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 type { ChangesView } from './changes/git.ts';\nimport type { ChangesRuntime } from './changes/index.ts';\nimport { createAgentControl, type HostLicks, type HostScoops } from './control.ts';\nimport { trackDeliveries } from './deliveries.ts';\nimport type { FreezerRuntime, FrozenCone } from './freezer/index.ts';\nimport { PortListener } from './listener.ts';\nimport type { McpRuntime } from './mcp/index.ts';\nimport type { MemoryEntry, MemoryScope } from './memory/format.ts';\nimport type { MemoryRuntime } from './memory/index.ts';\nimport { createRemote } from './remote/host.ts';\nimport {\n AGENT_SESSION,\n AgentControl,\n AgentRemoteService,\n AgentSessions,\n AgentSettings,\n AgentTranscript,\n type Command,\n} from './services.ts';\nimport type { SkillsRuntime } from './skills/index.ts';\nimport type { SprinklesRuntime } from './sprinkles/index.ts';\nimport type { Sprinkle } from './sprinkles/kind.ts';\nimport { agentViews } from './views.ts';\nimport type { PortEndpoint } 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\nconst noSettings: AgentSettings = {\n state: replicatedState({ models: [], accounts: [] }),\n connect: async () => {\n throw new Error('this agent has no accounts');\n },\n disconnect: async () => {},\n signIn: async () => null,\n};\n\nasync function follow(agent: Agent, context: Context) {\n let attached = await agent.cone().viewState(context);\n const state = replicatedState<ConversationView>(attached.value);\n const mirror = (value: ConversationView, from: Context) => state.replace(from, value);\n let off = attached.subscribe(mirror);\n const stop = agent.onCone(async (conversation) => {\n const next = await conversation.viewState(context);\n off();\n attached.dispose();\n attached = next;\n state.replace(context, next.value);\n off = next.subscribe(mirror);\n });\n return {\n state,\n dispose() {\n stop();\n off();\n attached.dispose();\n },\n };\n}\n\nexport async function hostAgent(\n agent: Agent,\n options: {\n serverId?: string;\n context?: Context;\n settings?: AgentSettings;\n licks?: HostLicks;\n scoops?: HostScoops;\n skills?: SkillsRuntime;\n sprinkles?: SprinklesRuntime;\n memory?: MemoryRuntime;\n freezer?: FreezerRuntime;\n changes?: ChangesRuntime;\n mcp?: Pick<McpRuntime, 'close'>;\n } = {}\n): Promise<AgentHost> {\n const context = options.context ?? BACKGROUND_CONTEXT;\n const serverId = options.serverId ?? crypto.randomUUID();\n const transcript = await follow(agent, context);\n const { state } = transcript;\n const mounted = await agentViews(agent, context);\n const touch = () => options.freezer?.touch();\n const untouch = [state.subscribe(touch), mounted.views.subscribe(touch)];\n const provider = new RemoteServiceProvider([\n { service: AgentControl, mode: 'singleton' },\n { service: AgentTranscript, mode: 'singleton' },\n { service: AgentSettings, mode: 'singleton' },\n { service: AgentRemoteService, mode: 'singleton' },\n ]);\n const deliveries = trackDeliveries(agent.harness, state, context);\n const control = createAgentControl(\n agent.harness,\n agent,\n deliveries,\n options.licks,\n options.scoops,\n options.skills,\n options.sprinkles,\n options.memory,\n options.freezer,\n options.changes\n );\n provider.provide(AgentControl, control);\n const remote = createRemote({\n harness: agent.harness,\n send: (request, using) => control.send(request, using),\n async conversation(agentId, using) {\n if (!agentId) return agent.cone();\n const found = await agent.agents.conversation(agentId, using);\n if (!found) throw new Error(`There is no agent ${agentId}.`);\n return found;\n },\n });\n provider.provide(AgentRemoteService, remote);\n provider.provide(AgentTranscript, {\n state,\n deliveries: deliveries.state,\n agents: mounted.agents,\n views: mounted.views,\n commands: options.skills?.commands ?? replicatedState<Command[]>([]),\n sprinkles: options.sprinkles?.sprinkles ?? replicatedState<Sprinkle[]>([]),\n memories: options.memory?.memories ?? replicatedState<MemoryEntry[]>([]),\n memoryScopes: options.memory?.scopes ?? replicatedState<MemoryScope[]>([]),\n frozen: options.freezer?.frozen ?? replicatedState<FrozenCone[]>([]),\n changes:\n options.changes?.view ?? replicatedState<ChangesView>({ unavailable: null, changes: [] }),\n });\n provider.provide(AgentSettings, options.settings ?? noSettings);\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 await remote.dispose();\n provider.dispose();\n deliveries.dispose();\n transcript.dispose();\n mounted.dispose();\n for (const off of untouch) off();\n options.freezer?.close();\n await options.changes?.close(context);\n await options.memory?.close(context);\n await options.sprinkles?.close(context);\n await options.skills?.close(context);\n await options.mcp?.close(context);\n await options.scoops?.runtime.close(context);\n await options.licks?.sources.close(context);\n await agent.close();\n },\n };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import {\n type Context,\n createRemoteServiceEndpoint,\n RemoteServiceProvider,\n replicatedState,\n} from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { ConversationView } from '@earendil-works/pi-durable';\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 type { ChangesView } from './changes/git.ts';\nimport type { ChangesRuntime } from './changes/index.ts';\nimport { createAgentControl, type HostLicks, type HostScoops } from './control.ts';\nimport { trackDeliveries } from './deliveries.ts';\nimport type { FreezerRuntime, FrozenCone } from './freezer/index.ts';\nimport { PortListener } from './listener.ts';\nimport type { McpRuntime } from './mcp/index.ts';\nimport type { MemoryEntry, MemoryScope } from './memory/format.ts';\nimport type { MemoryRuntime } from './memory/index.ts';\nimport { viewControl, viewRemote, viewSettings } from './readonly.ts';\nimport { createRemote } from './remote/host.ts';\nimport {\n AGENT_SESSION,\n AgentControl,\n AgentRemoteService,\n AgentSessions,\n AgentSettings,\n AgentTranscript,\n type Command,\n} from './services.ts';\nimport type { SkillsRuntime } from './skills/index.ts';\nimport type { SprinklesRuntime } from './sprinkles/index.ts';\nimport type { Sprinkle } from './sprinkles/kind.ts';\nimport { agentViews } from './views.ts';\nimport type { PortEndpoint } from './wire.ts';\n\nexport interface AgentHost {\n readonly serverId: string;\n readonly agent: Agent;\n connect(endpoint: PortEndpoint, options?: { readOnly?: boolean }): 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\nconst noSettings: AgentSettings = {\n state: replicatedState({ models: [], accounts: [] }),\n connect: async () => {\n throw new Error('this agent has no accounts');\n },\n disconnect: async () => {},\n signIn: async () => null,\n};\n\nasync function follow(agent: Agent, context: Context) {\n let attached = await agent.cone().viewState(context);\n const state = replicatedState<ConversationView>(attached.value);\n const mirror = (value: ConversationView, from: Context) => state.replace(from, value);\n let off = attached.subscribe(mirror);\n const stop = agent.onCone(async (conversation) => {\n const next = await conversation.viewState(context);\n off();\n attached.dispose();\n attached = next;\n state.replace(context, next.value);\n off = next.subscribe(mirror);\n });\n return {\n state,\n dispose() {\n stop();\n off();\n attached.dispose();\n },\n };\n}\n\nexport async function hostAgent(\n agent: Agent,\n options: {\n serverId?: string;\n context?: Context;\n settings?: AgentSettings;\n licks?: HostLicks;\n scoops?: HostScoops;\n skills?: SkillsRuntime;\n sprinkles?: SprinklesRuntime;\n memory?: MemoryRuntime;\n freezer?: FreezerRuntime;\n changes?: ChangesRuntime;\n mcp?: Pick<McpRuntime, 'close'>;\n } = {}\n): Promise<AgentHost> {\n const context = options.context ?? BACKGROUND_CONTEXT;\n const serverId = options.serverId ?? crypto.randomUUID();\n const transcript = await follow(agent, context);\n const { state } = transcript;\n const mounted = await agentViews(agent, context);\n const touch = () => options.freezer?.touch();\n const untouch = [state.subscribe(touch), mounted.views.subscribe(touch)];\n const provider = new RemoteServiceProvider([\n { service: AgentControl, mode: 'singleton' },\n { service: AgentTranscript, mode: 'singleton' },\n { service: AgentSettings, mode: 'singleton' },\n { service: AgentRemoteService, mode: 'singleton' },\n ]);\n const deliveries = trackDeliveries(agent.harness, state, context);\n const control = createAgentControl(\n agent.harness,\n agent,\n deliveries,\n options.licks,\n options.scoops,\n options.skills,\n options.sprinkles,\n options.memory,\n options.freezer,\n options.changes\n );\n provider.provide(AgentControl, control);\n const remote = createRemote({\n harness: agent.harness,\n send: (request, using) => control.send(request, using),\n async conversation(agentId, using) {\n if (!agentId) return agent.cone();\n const found = await agent.agents.conversation(agentId, using);\n if (!found) throw new Error(`There is no agent ${agentId}.`);\n return found;\n },\n });\n provider.provide(AgentRemoteService, remote);\n const transcriptService = {\n state,\n deliveries: deliveries.state,\n agents: mounted.agents,\n views: mounted.views,\n commands: options.skills?.commands ?? replicatedState<Command[]>([]),\n sprinkles: options.sprinkles?.sprinkles ?? replicatedState<Sprinkle[]>([]),\n memories: options.memory?.memories ?? replicatedState<MemoryEntry[]>([]),\n memoryScopes: options.memory?.scopes ?? replicatedState<MemoryScope[]>([]),\n frozen: options.freezer?.frozen ?? replicatedState<FrozenCone[]>([]),\n changes:\n options.changes?.view ?? replicatedState<ChangesView>({ unavailable: null, changes: [] }),\n };\n provider.provide(AgentTranscript, transcriptService);\n const settings = options.settings ?? noSettings;\n provider.provide(AgentSettings, settings);\n const viewer = new RemoteServiceProvider([\n { service: AgentControl, mode: 'singleton' },\n { service: AgentTranscript, mode: 'singleton' },\n { service: AgentSettings, mode: 'singleton' },\n { service: AgentRemoteService, mode: 'singleton' },\n ]);\n viewer.provide(AgentControl, viewControl(control));\n viewer.provide(AgentTranscript, transcriptService);\n viewer.provide(AgentSettings, viewSettings(settings));\n viewer.provide(AgentRemoteService, viewRemote(remote));\n const serve = async (id: string, services: RemoteServiceProvider) => {\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(services),\n };\n const listener = new PortListener(id);\n const server = await new Server(host, { serverId: id, listeners: [listener] }).start();\n return { listener, server };\n };\n const full = await serve(serverId, provider);\n const view = await serve(crypto.randomUUID(), viewer);\n return {\n serverId,\n agent,\n connect: (endpoint, connectOptions) =>\n (connectOptions?.readOnly ? view : full).listener.connect(endpoint),\n async close() {\n await full.server.close();\n await view.server.close();\n viewer.dispose();\n await remote.dispose();\n provider.dispose();\n deliveries.dispose();\n transcript.dispose();\n mounted.dispose();\n for (const off of untouch) off();\n options.freezer?.close();\n await options.changes?.close(context);\n await options.memory?.close(context);\n await options.sprinkles?.close(context);\n await options.skills?.close(context);\n await options.mcp?.close(context);\n await options.scoops?.runtime.close(context);\n await options.licks?.sources.close(context);\n await agent.close();\n },\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/link.js ADDED
@@ -0,0 +1,146 @@
1
+ const MAX_FRAME = 16 * 1024 * 1024;
2
+ const DATA = 0;
3
+ const HELLO = 1;
4
+ const CLOSE = 2;
5
+ function encodeFrame(frame) {
6
+ const body = frame instanceof Uint8Array ? frame : "hello" in frame ? new TextEncoder().encode(frame.hello) : new Uint8Array();
7
+ const tag = frame instanceof Uint8Array ? DATA : "hello" in frame ? HELLO : CLOSE;
8
+ const out = new Uint8Array(5 + body.length);
9
+ new DataView(out.buffer).setUint32(0, body.length + 1);
10
+ out[4] = tag;
11
+ out.set(body, 5);
12
+ return out;
13
+ }
14
+ class FrameReader {
15
+ #chunks = [];
16
+ #size = 0;
17
+ #take(count) {
18
+ const out = new Uint8Array(count);
19
+ let filled = 0;
20
+ while (filled < count) {
21
+ const head = this.#chunks[0];
22
+ const used = Math.min(head.length, count - filled);
23
+ out.set(head.subarray(0, used), filled);
24
+ filled += used;
25
+ if (used === head.length) this.#chunks.shift();
26
+ else this.#chunks[0] = head.subarray(used);
27
+ }
28
+ this.#size -= count;
29
+ return out;
30
+ }
31
+ #peek() {
32
+ const header = new Uint8Array(4);
33
+ let filled = 0;
34
+ for (const chunk of this.#chunks) {
35
+ const used = Math.min(chunk.length, 4 - filled);
36
+ header.set(chunk.subarray(0, used), filled);
37
+ filled += used;
38
+ if (filled === 4) break;
39
+ }
40
+ return new DataView(header.buffer).getUint32(0);
41
+ }
42
+ push(chunk) {
43
+ if (chunk.length) {
44
+ this.#chunks.push(chunk);
45
+ this.#size += chunk.length;
46
+ }
47
+ const frames = [];
48
+ while (this.#size >= 4) {
49
+ const length = this.#peek();
50
+ if (length < 1 || length > MAX_FRAME) throw new Error(`agent link frame of ${length} bytes`);
51
+ if (this.#size - 4 < length) break;
52
+ this.#take(4);
53
+ const frame = this.#take(length);
54
+ const tag = frame[0];
55
+ const body = frame.subarray(1);
56
+ if (tag === DATA) frames.push(body.slice());
57
+ else if (tag === HELLO) frames.push({ hello: new TextDecoder().decode(body) });
58
+ else if (tag === CLOSE) frames.push({ close: true });
59
+ else throw new Error(`agent link frame tag ${tag}`);
60
+ }
61
+ return frames;
62
+ }
63
+ }
64
+ function linkEndpoint(sink) {
65
+ const listeners = /* @__PURE__ */ new Map();
66
+ const reader = new FrameReader();
67
+ let ended = false;
68
+ const emit = (type, data) => {
69
+ for (const listener of listeners.get(type) ?? []) listener({ type, data });
70
+ };
71
+ const finish = (error) => {
72
+ if (ended) return;
73
+ ended = true;
74
+ emit(error ? "error" : "close");
75
+ };
76
+ return {
77
+ postMessage(message) {
78
+ if (!ended) sink.write(encodeFrame(message));
79
+ },
80
+ addEventListener(type, listener) {
81
+ listeners.set(type, [...listeners.get(type) ?? [], listener]);
82
+ },
83
+ close() {
84
+ if (ended) return;
85
+ ended = true;
86
+ sink.close();
87
+ },
88
+ receive(chunk) {
89
+ if (ended) return;
90
+ try {
91
+ for (const frame of reader.push(chunk)) emit("message", frame);
92
+ } catch (error) {
93
+ finish(error);
94
+ sink.close();
95
+ }
96
+ },
97
+ end: finish
98
+ };
99
+ }
100
+ function bridgePort(port, sink) {
101
+ const reader = new FrameReader();
102
+ let ended = false;
103
+ const stop = () => {
104
+ if (ended) return;
105
+ ended = true;
106
+ sink.close();
107
+ };
108
+ port.addEventListener("message", ({ data }) => {
109
+ if (!ended) sink.write(encodeFrame(data));
110
+ if (!(data instanceof Uint8Array) && data?.close) stop();
111
+ });
112
+ port.addEventListener("close", stop);
113
+ port.start?.();
114
+ const close = () => {
115
+ port.postMessage({ close: true });
116
+ port.close?.();
117
+ };
118
+ return {
119
+ receive(chunk) {
120
+ if (ended) return;
121
+ try {
122
+ for (const frame of reader.push(chunk)) {
123
+ if (frame instanceof Uint8Array) port.postMessage(frame, [frame.buffer]);
124
+ else port.postMessage(frame);
125
+ }
126
+ } catch {
127
+ ended = true;
128
+ close();
129
+ sink.close();
130
+ }
131
+ },
132
+ end() {
133
+ if (ended) return;
134
+ ended = true;
135
+ close();
136
+ }
137
+ };
138
+ }
139
+ export {
140
+ FrameReader,
141
+ MAX_FRAME,
142
+ bridgePort,
143
+ encodeFrame,
144
+ linkEndpoint
145
+ };
146
+ //# sourceMappingURL=link.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/link.ts"],
4
+ "sourcesContent": ["import type { Frame, PortEndpoint } from './wire.ts';\n\nexport const MAX_FRAME = 16 * 1024 * 1024;\n\nconst DATA = 0;\nconst HELLO = 1;\nconst CLOSE = 2;\n\nexport interface ByteSink {\n write(chunk: Uint8Array): void;\n close(): void;\n}\n\nexport interface LinkEnd {\n receive(chunk: Uint8Array): void;\n end(error?: Error): void;\n}\n\nexport function encodeFrame(frame: Frame): Uint8Array {\n const body =\n frame instanceof Uint8Array\n ? frame\n : 'hello' in frame\n ? new TextEncoder().encode(frame.hello)\n : new Uint8Array();\n const tag = frame instanceof Uint8Array ? DATA : 'hello' in frame ? HELLO : CLOSE;\n const out = new Uint8Array(5 + body.length);\n new DataView(out.buffer).setUint32(0, body.length + 1);\n out[4] = tag;\n out.set(body, 5);\n return out;\n}\n\nexport class FrameReader {\n readonly #chunks: Uint8Array[] = [];\n #size = 0;\n\n #take(count: number): Uint8Array {\n const out = new Uint8Array(count);\n let filled = 0;\n while (filled < count) {\n const head = this.#chunks[0] as Uint8Array;\n const used = Math.min(head.length, count - filled);\n out.set(head.subarray(0, used), filled);\n filled += used;\n if (used === head.length) this.#chunks.shift();\n else this.#chunks[0] = head.subarray(used);\n }\n this.#size -= count;\n return out;\n }\n\n #peek(): number {\n const header = new Uint8Array(4);\n let filled = 0;\n for (const chunk of this.#chunks) {\n const used = Math.min(chunk.length, 4 - filled);\n header.set(chunk.subarray(0, used), filled);\n filled += used;\n if (filled === 4) break;\n }\n return new DataView(header.buffer).getUint32(0);\n }\n\n push(chunk: Uint8Array): Frame[] {\n if (chunk.length) {\n this.#chunks.push(chunk);\n this.#size += chunk.length;\n }\n const frames: Frame[] = [];\n while (this.#size >= 4) {\n const length = this.#peek();\n if (length < 1 || length > MAX_FRAME) throw new Error(`agent link frame of ${length} bytes`);\n if (this.#size - 4 < length) break;\n this.#take(4);\n const frame = this.#take(length);\n const tag = frame[0];\n const body = frame.subarray(1);\n if (tag === DATA) frames.push(body.slice());\n else if (tag === HELLO) frames.push({ hello: new TextDecoder().decode(body) });\n else if (tag === CLOSE) frames.push({ close: true });\n else throw new Error(`agent link frame tag ${tag}`);\n }\n return frames;\n }\n}\n\ntype Listener = (event: { type: string; data?: unknown }) => void;\n\nexport function linkEndpoint(sink: ByteSink): PortEndpoint & LinkEnd {\n const listeners = new Map<string, Listener[]>();\n const reader = new FrameReader();\n let ended = false;\n const emit = (type: string, data?: unknown) => {\n for (const listener of listeners.get(type) ?? []) listener({ type, data });\n };\n const finish = (error?: Error) => {\n if (ended) return;\n ended = true;\n emit(error ? 'error' : 'close');\n };\n return {\n postMessage(message) {\n if (!ended) sink.write(encodeFrame(message as Frame));\n },\n addEventListener(type: string, listener: Listener) {\n listeners.set(type, [...(listeners.get(type) ?? []), listener]);\n },\n close() {\n if (ended) return;\n ended = true;\n sink.close();\n },\n receive(chunk) {\n if (ended) return;\n try {\n for (const frame of reader.push(chunk)) emit('message', frame);\n } catch (error) {\n finish(error as Error);\n sink.close();\n }\n },\n end: finish,\n } as PortEndpoint & LinkEnd;\n}\n\nexport function bridgePort(port: PortEndpoint, sink: ByteSink): LinkEnd {\n const reader = new FrameReader();\n let ended = false;\n const stop = () => {\n if (ended) return;\n ended = true;\n sink.close();\n };\n port.addEventListener('message', ({ data }) => {\n if (!ended) sink.write(encodeFrame(data as Frame));\n if (!(data instanceof Uint8Array) && (data as { close?: boolean } | null)?.close) stop();\n });\n port.addEventListener('close', stop);\n port.start?.();\n const close = () => {\n port.postMessage({ close: true } satisfies Frame);\n port.close?.();\n };\n return {\n receive(chunk) {\n if (ended) return;\n try {\n for (const frame of reader.push(chunk)) {\n if (frame instanceof Uint8Array) port.postMessage(frame, [frame.buffer]);\n else port.postMessage(frame);\n }\n } catch {\n ended = true;\n close();\n sink.close();\n }\n },\n end() {\n if (ended) return;\n ended = true;\n close();\n },\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
package/dist/owner.js CHANGED
@@ -24,16 +24,17 @@ async function startAgent(options) {
24
24
  release();
25
25
  throw error;
26
26
  }
27
- const open = async () => {
27
+ const open = async (portOptions = {}) => {
28
28
  const ready = await worker;
29
29
  if (!ready) throw new Error("the agent was released");
30
30
  const { port1, port2 } = channel();
31
- ready.postMessage({ connect: port2 }, [port2]);
31
+ const readOnly = portOptions.readOnly === true ? { readOnly: true } : {};
32
+ ready.postMessage({ connect: port2, ...readOnly }, [port2]);
32
33
  return port1;
33
34
  };
34
35
  return {
35
- async connect() {
36
- return connectAgent(await open());
36
+ async connect(portOptions) {
37
+ return connectAgent(await open(portOptions));
37
38
  },
38
39
  port: open,
39
40
  restart() {
package/dist/owner.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
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 port(): Promise<Transferable>;\n restart(): Promise<void>;\n release(): Promise<void>;\n}\n\nexport interface KernelSource {\n connect(): Promise<Transferable>;\n}\n\nexport interface OwnerOptions {\n worker: () => AgentWorker;\n kernel?: KernelSource;\n locks?: LockManager;\n held?: boolean;\n channel?: () => { port1: PortEndpoint & Transferable; port2: Transferable };\n}\n\nexport async function startAgent(options: OwnerOptions): Promise<AgentOwner> {\n const release = options.held\n ? () => undefined\n : await holdLock(options.locks ?? navigator.locks, AGENT_LOCK);\n const channel = options.channel ?? (() => new MessageChannel());\n const spawn = async (): Promise<AgentWorker> => {\n const created = options.worker();\n try {\n if (options.kernel) {\n const port = await options.kernel.connect();\n created.postMessage({ kernel: port }, [port]);\n }\n } catch (error) {\n created.terminate();\n throw error;\n }\n return created;\n };\n let worker: Promise<AgentWorker | undefined> = Promise.resolve().then(spawn);\n try {\n await worker;\n } catch (error) {\n release();\n throw error;\n }\n const open = async (): Promise<PortEndpoint & Transferable> => {\n const ready = await worker;\n if (!ready) throw new Error('the agent was released');\n const { port1, port2 } = channel();\n ready.postMessage({ connect: port2 }, [port2]);\n return port1;\n };\n return {\n async connect() {\n return connectAgent(await open());\n },\n port: open,\n restart() {\n worker = worker\n .catch(() => undefined)\n .then((old) => {\n old?.terminate();\n return spawn();\n });\n return worker.then(() => undefined);\n },\n async release() {\n const previous = worker;\n worker = Promise.resolve(undefined);\n try {\n (await previous.catch(() => undefined))?.terminate();\n } finally {\n release();\n }\n },\n };\n}\n"],
5
- "mappings": "AAAA,SAA+B,oBAAoB;AACnD,SAAS,gBAAgB;AAGlB,MAAM,aAAa;AA0B1B,eAAsB,WAAW,SAA4C;AAC3E,QAAM,UAAU,QAAQ,OACpB,MAAM,SACN,MAAM,SAAS,QAAQ,SAAS,UAAU,OAAO,UAAU;AAC/D,QAAM,UAAU,QAAQ,YAAY,MAAM,IAAI,eAAe;AAC7D,QAAM,QAAQ,YAAkC;AAC9C,UAAM,UAAU,QAAQ,OAAO;AAC/B,QAAI;AACF,UAAI,QAAQ,QAAQ;AAClB,cAAM,OAAO,MAAM,QAAQ,OAAO,QAAQ;AAC1C,gBAAQ,YAAY,EAAE,QAAQ,KAAK,GAAG,CAAC,IAAI,CAAC;AAAA,MAC9C;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,UAAU;AAClB,YAAM;AAAA,IACR;AACA,WAAO;AAAA,EACT;AACA,MAAI,SAA2C,QAAQ,QAAQ,EAAE,KAAK,KAAK;AAC3E,MAAI;AACF,UAAM;AAAA,EACR,SAAS,OAAO;AACd,YAAQ;AACR,UAAM;AAAA,EACR;AACA,QAAM,OAAO,YAAkD;AAC7D,UAAM,QAAQ,MAAM;AACpB,QAAI,CAAC,MAAO,OAAM,IAAI,MAAM,wBAAwB;AACpD,UAAM,EAAE,OAAO,MAAM,IAAI,QAAQ;AACjC,UAAM,YAAY,EAAE,SAAS,MAAM,GAAG,CAAC,KAAK,CAAC;AAC7C,WAAO;AAAA,EACT;AACA,SAAO;AAAA,IACL,MAAM,UAAU;AACd,aAAO,aAAa,MAAM,KAAK,CAAC;AAAA,IAClC;AAAA,IACA,MAAM;AAAA,IACN,UAAU;AACR,eAAS,OACN,MAAM,MAAM,MAAS,EACrB,KAAK,CAAC,QAAQ;AACb,aAAK,UAAU;AACf,eAAO,MAAM;AAAA,MACf,CAAC;AACH,aAAO,OAAO,KAAK,MAAM,MAAS;AAAA,IACpC;AAAA,IACA,MAAM,UAAU;AACd,YAAM,WAAW;AACjB,eAAS,QAAQ,QAAQ,MAAS;AAClC,UAAI;AACF,SAAC,MAAM,SAAS,MAAM,MAAM,MAAS,IAAI,UAAU;AAAA,MACrD,UAAE;AACA,gBAAQ;AAAA,MACV;AAAA,IACF;AAAA,EACF;AACF;",
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 PortOptions {\n readOnly?: boolean;\n}\n\nexport interface AgentOwner {\n connect(options?: PortOptions): Promise<AgentConnection>;\n port(options?: PortOptions): Promise<PortEndpoint & Transferable>;\n restart(): Promise<void>;\n release(): Promise<void>;\n}\n\nexport interface KernelSource {\n connect(): Promise<Transferable>;\n}\n\nexport interface OwnerOptions {\n worker: () => AgentWorker;\n kernel?: KernelSource;\n locks?: LockManager;\n held?: boolean;\n channel?: () => { port1: PortEndpoint & Transferable; port2: Transferable };\n}\n\nexport async function startAgent(options: OwnerOptions): Promise<AgentOwner> {\n const release = options.held\n ? () => undefined\n : await holdLock(options.locks ?? navigator.locks, AGENT_LOCK);\n const channel = options.channel ?? (() => new MessageChannel());\n const spawn = async (): Promise<AgentWorker> => {\n const created = options.worker();\n try {\n if (options.kernel) {\n const port = await options.kernel.connect();\n created.postMessage({ kernel: port }, [port]);\n }\n } catch (error) {\n created.terminate();\n throw error;\n }\n return created;\n };\n let worker: Promise<AgentWorker | undefined> = Promise.resolve().then(spawn);\n try {\n await worker;\n } catch (error) {\n release();\n throw error;\n }\n const open = async (portOptions: PortOptions = {}): Promise<PortEndpoint & Transferable> => {\n const ready = await worker;\n if (!ready) throw new Error('the agent was released');\n const { port1, port2 } = channel();\n const readOnly = portOptions.readOnly === true ? { readOnly: true } : {};\n ready.postMessage({ connect: port2, ...readOnly }, [port2]);\n return port1;\n };\n return {\n async connect(portOptions) {\n return connectAgent(await open(portOptions));\n },\n port: open,\n restart() {\n worker = worker\n .catch(() => undefined)\n .then((old) => {\n old?.terminate();\n return spawn();\n });\n return worker.then(() => undefined);\n },\n async release() {\n const previous = worker;\n worker = Promise.resolve(undefined);\n try {\n (await previous.catch(() => undefined))?.terminate();\n } finally {\n release();\n }\n },\n };\n}\n"],
5
+ "mappings": "AAAA,SAA+B,oBAAoB;AACnD,SAAS,gBAAgB;AAGlB,MAAM,aAAa;AA8B1B,eAAsB,WAAW,SAA4C;AAC3E,QAAM,UAAU,QAAQ,OACpB,MAAM,SACN,MAAM,SAAS,QAAQ,SAAS,UAAU,OAAO,UAAU;AAC/D,QAAM,UAAU,QAAQ,YAAY,MAAM,IAAI,eAAe;AAC7D,QAAM,QAAQ,YAAkC;AAC9C,UAAM,UAAU,QAAQ,OAAO;AAC/B,QAAI;AACF,UAAI,QAAQ,QAAQ;AAClB,cAAM,OAAO,MAAM,QAAQ,OAAO,QAAQ;AAC1C,gBAAQ,YAAY,EAAE,QAAQ,KAAK,GAAG,CAAC,IAAI,CAAC;AAAA,MAC9C;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,UAAU;AAClB,YAAM;AAAA,IACR;AACA,WAAO;AAAA,EACT;AACA,MAAI,SAA2C,QAAQ,QAAQ,EAAE,KAAK,KAAK;AAC3E,MAAI;AACF,UAAM;AAAA,EACR,SAAS,OAAO;AACd,YAAQ;AACR,UAAM;AAAA,EACR;AACA,QAAM,OAAO,OAAO,cAA2B,CAAC,MAA4C;AAC1F,UAAM,QAAQ,MAAM;AACpB,QAAI,CAAC,MAAO,OAAM,IAAI,MAAM,wBAAwB;AACpD,UAAM,EAAE,OAAO,MAAM,IAAI,QAAQ;AACjC,UAAM,WAAW,YAAY,aAAa,OAAO,EAAE,UAAU,KAAK,IAAI,CAAC;AACvE,UAAM,YAAY,EAAE,SAAS,OAAO,GAAG,SAAS,GAAG,CAAC,KAAK,CAAC;AAC1D,WAAO;AAAA,EACT;AACA,SAAO;AAAA,IACL,MAAM,QAAQ,aAAa;AACzB,aAAO,aAAa,MAAM,KAAK,WAAW,CAAC;AAAA,IAC7C;AAAA,IACA,MAAM;AAAA,IACN,UAAU;AACR,eAAS,OACN,MAAM,MAAM,MAAS,EACrB,KAAK,CAAC,QAAQ;AACb,aAAK,UAAU;AACf,eAAO,MAAM;AAAA,MACf,CAAC;AACH,aAAO,OAAO,KAAK,MAAM,MAAS;AAAA,IACpC;AAAA,IACA,MAAM,UAAU;AACd,YAAM,WAAW;AACjB,eAAS,QAAQ,QAAQ,MAAS;AAClC,UAAI;AACF,SAAC,MAAM,SAAS,MAAM,MAAM,MAAS,IAAI,UAAU;AAAA,MACrD,UAAE;AACA,gBAAQ;AAAA,MACV;AAAA,IACF;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
package/dist/page.js CHANGED
@@ -1,4 +1,11 @@
1
1
  import { connectAgent } from "./client.js";
2
+ import {
3
+ bridgePort,
4
+ encodeFrame,
5
+ FrameReader,
6
+ linkEndpoint,
7
+ MAX_FRAME
8
+ } from "./link.js";
2
9
  import {
3
10
  AGENT_LOCK,
4
11
  startAgent
@@ -6,9 +13,16 @@ import {
6
13
  import {
7
14
  remoteControl
8
15
  } from "./remote/client.js";
16
+ import { VIEW_ONLY } from "./view-only.js";
9
17
  export {
10
18
  AGENT_LOCK,
19
+ FrameReader,
20
+ MAX_FRAME,
21
+ VIEW_ONLY,
22
+ bridgePort,
11
23
  connectAgent,
24
+ encodeFrame,
25
+ linkEndpoint,
12
26
  remoteControl,
13
27
  startAgent
14
28
  };
package/dist/page.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/page.ts"],
4
- "sourcesContent": ["export { type AgentConnection, connectAgent } from './client.ts';\nexport {\n AGENT_LOCK,\n type AgentOwner,\n type AgentWorker,\n type KernelSource,\n type OwnerOptions,\n startAgent,\n} from './owner.ts';\nexport {\n type RemoteControl,\n type RemoteEvent,\n type RemoteOptions,\n remoteControl,\n} from './remote/client.ts';\n"],
5
- "mappings": "AAAA,SAA+B,oBAAoB;AACnD;AAAA,EACE;AAAA,EAKA;AAAA,OACK;AACP;AAAA,EAIE;AAAA,OACK;",
4
+ "sourcesContent": ["export { type AgentConnection, connectAgent } from './client.ts';\nexport {\n type ByteSink,\n bridgePort,\n encodeFrame,\n FrameReader,\n type LinkEnd,\n linkEndpoint,\n MAX_FRAME,\n} from './link.ts';\nexport {\n AGENT_LOCK,\n type AgentOwner,\n type AgentWorker,\n type KernelSource,\n type OwnerOptions,\n type PortOptions,\n startAgent,\n} from './owner.ts';\nexport {\n type RemoteControl,\n type RemoteEvent,\n type RemoteOptions,\n remoteControl,\n} from './remote/client.ts';\nexport { VIEW_ONLY } from './view-only.ts';\n"],
5
+ "mappings": "AAAA,SAA+B,oBAAoB;AACnD;AAAA,EAEE;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EAMA;AAAA,OACK;AACP;AAAA,EAIE;AAAA,OACK;AACP,SAAS,iBAAiB;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,54 @@
1
+ import { ServerError } from "@earendil-works/pi-server";
2
+ import { VIEW_ONLY } from "./view-only.js";
3
+ const READS = /* @__PURE__ */ new Set(["wait", "changesOpen"]);
4
+ function refuse() {
5
+ return Promise.reject(new ServerError("EACCES", VIEW_ONLY));
6
+ }
7
+ const refused = async () => ({
8
+ accepted: false,
9
+ submissionId: null,
10
+ error: { code: "EACCES", message: VIEW_ONLY }
11
+ });
12
+ const ANSWERS = {
13
+ send: refused,
14
+ compact: refused,
15
+ compactAgent: refused
16
+ };
17
+ function viewControl(control) {
18
+ const view = {};
19
+ for (const [key, value] of Object.entries(control))
20
+ view[key] = typeof value !== "function" || READS.has(key) ? value : ANSWERS[key] ?? refuse;
21
+ return view;
22
+ }
23
+ function viewSettings(settings) {
24
+ return { state: settings.state, connect: refuse, disconnect: refuse, signIn: refuse };
25
+ }
26
+ function viewRemote(remote) {
27
+ const mine = /* @__PURE__ */ new Set();
28
+ return {
29
+ prompt: async () => ({ ok: false, code: "EACCES", message: VIEW_ONLY }),
30
+ async watch(request, context) {
31
+ const opened = await remote.watch(request, context);
32
+ if (opened.ok) mine.add(opened.stream);
33
+ return opened;
34
+ },
35
+ async next(stream, waitMs, context) {
36
+ if (!mine.has(stream)) return { events: [], done: true };
37
+ const batch = await remote.next(stream, waitMs, context);
38
+ if (batch.done) mine.delete(stream);
39
+ return batch;
40
+ },
41
+ async close(stream, context) {
42
+ if (!mine.delete(stream)) return;
43
+ await remote.close(stream, context);
44
+ },
45
+ abort: refuse
46
+ };
47
+ }
48
+ export {
49
+ VIEW_ONLY,
50
+ viewControl,
51
+ viewRemote,
52
+ viewSettings
53
+ };
54
+ //# sourceMappingURL=readonly.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/readonly.ts"],
4
+ "sourcesContent": ["import { ServerError } from '@earendil-works/pi-server';\nimport type { AgentRemote } from './remote/host.ts';\nimport type { AgentControl, AgentSettings } from './services.ts';\nimport { VIEW_ONLY } from './view-only.ts';\n\nexport { VIEW_ONLY };\n\nconst READS = new Set<keyof AgentControl>(['wait', 'changesOpen']);\n\nfunction refuse(): Promise<never> {\n return Promise.reject(new ServerError('EACCES' as never, VIEW_ONLY));\n}\n\nconst refused = async () => ({\n accepted: false as const,\n submissionId: null,\n error: { code: 'EACCES', message: VIEW_ONLY },\n});\n\nconst ANSWERS: Partial<Record<keyof AgentControl, () => Promise<unknown>>> = {\n send: refused,\n compact: refused,\n compactAgent: refused,\n};\n\nexport function viewControl(control: AgentControl): AgentControl {\n const view: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(control))\n view[key] =\n typeof value !== 'function' || READS.has(key as keyof AgentControl)\n ? value\n : (ANSWERS[key as keyof AgentControl] ?? refuse);\n return view as unknown as AgentControl;\n}\n\nexport function viewSettings(settings: AgentSettings): AgentSettings {\n return { state: settings.state, connect: refuse, disconnect: refuse, signIn: refuse };\n}\n\nexport function viewRemote(remote: AgentRemote): AgentRemote {\n const mine = new Set<string>();\n return {\n prompt: async () => ({ ok: false, code: 'EACCES', message: VIEW_ONLY }),\n async watch(request, context) {\n const opened = await remote.watch(request, context);\n if (opened.ok) mine.add(opened.stream);\n return opened;\n },\n async next(stream, waitMs, context) {\n if (!mine.has(stream)) return { events: [], done: true };\n const batch = await remote.next(stream, waitMs, context);\n if (batch.done) mine.delete(stream);\n return batch;\n },\n async close(stream, context) {\n if (!mine.delete(stream)) return;\n await remote.close(stream, context);\n },\n abort: refuse,\n };\n}\n"],
5
+ "mappings": "AAAA,SAAS,mBAAmB;AAG5B,SAAS,iBAAiB;AAI1B,MAAM,QAAQ,oBAAI,IAAwB,CAAC,QAAQ,aAAa,CAAC;AAEjE,SAAS,SAAyB;AAChC,SAAO,QAAQ,OAAO,IAAI,YAAY,UAAmB,SAAS,CAAC;AACrE;AAEA,MAAM,UAAU,aAAa;AAAA,EAC3B,UAAU;AAAA,EACV,cAAc;AAAA,EACd,OAAO,EAAE,MAAM,UAAU,SAAS,UAAU;AAC9C;AAEA,MAAM,UAAuE;AAAA,EAC3E,MAAM;AAAA,EACN,SAAS;AAAA,EACT,cAAc;AAChB;AAEO,SAAS,YAAY,SAAqC;AAC/D,QAAM,OAAgC,CAAC;AACvC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO;AAC/C,SAAK,GAAG,IACN,OAAO,UAAU,cAAc,MAAM,IAAI,GAAyB,IAC9D,QACC,QAAQ,GAAyB,KAAK;AAC/C,SAAO;AACT;AAEO,SAAS,aAAa,UAAwC;AACnE,SAAO,EAAE,OAAO,SAAS,OAAO,SAAS,QAAQ,YAAY,QAAQ,QAAQ,OAAO;AACtF;AAEO,SAAS,WAAW,QAAkC;AAC3D,QAAM,OAAO,oBAAI,IAAY;AAC7B,SAAO;AAAA,IACL,QAAQ,aAAa,EAAE,IAAI,OAAO,MAAM,UAAU,SAAS,UAAU;AAAA,IACrE,MAAM,MAAM,SAAS,SAAS;AAC5B,YAAM,SAAS,MAAM,OAAO,MAAM,SAAS,OAAO;AAClD,UAAI,OAAO,GAAI,MAAK,IAAI,OAAO,MAAM;AACrC,aAAO;AAAA,IACT;AAAA,IACA,MAAM,KAAK,QAAQ,QAAQ,SAAS;AAClC,UAAI,CAAC,KAAK,IAAI,MAAM,EAAG,QAAO,EAAE,QAAQ,CAAC,GAAG,MAAM,KAAK;AACvD,YAAM,QAAQ,MAAM,OAAO,KAAK,QAAQ,QAAQ,OAAO;AACvD,UAAI,MAAM,KAAM,MAAK,OAAO,MAAM;AAClC,aAAO;AAAA,IACT;AAAA,IACA,MAAM,MAAM,QAAQ,SAAS;AAC3B,UAAI,CAAC,KAAK,OAAO,MAAM,EAAG;AAC1B,YAAM,OAAO,MAAM,QAAQ,OAAO;AAAA,IACpC;AAAA,IACA,OAAO;AAAA,EACT;AACF;",
6
+ "names": []
7
+ }
@@ -159,7 +159,7 @@ function createRemote(host) {
159
159
  now(),
160
160
  host.limit ?? MAX_QUEUED
161
161
  );
162
- const id = `s${++serial}`;
162
+ const id = `s${++serial}-${crypto.randomUUID()}`;
163
163
  track(id, stream);
164
164
  events.start(async (batch) => onEvents(batch));
165
165
  return { id, stream };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/remote/host.ts"],
4
- "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT, withCancel } from '@earendil-works/chord/context';\nimport {\n type AgentEvent,\n type AgentEventStream,\n type Conversation,\n type EntryRecord,\n type Harness,\n type SnapshotEvent,\n type SubmissionId,\n watchEvents,\n} from '@earendil-works/pi-durable';\nimport type { Delivered, SendMode, SendRequest, SendResponse } from '../services.ts';\nimport { type EndReason, idOf, Mapper, type RemoteEvent, replayOf } from './events.ts';\n\nexport const MAX_QUEUED = 10000;\nexport const IDLE_MS = 120000;\n\nexport type RemoteOpened =\n | { ok: true; stream: string; delivered: Delivered | null; head: string | null; gap: boolean }\n | { ok: false; code: string; message: string };\n\nexport interface RemoteBatch {\n events: RemoteEvent[];\n done: boolean;\n}\n\nexport interface AgentRemote {\n prompt(\n request: { agentId: string | null; text: string; whenBusy: SendMode; thinking: boolean },\n context: Context\n ): Promise<RemoteOpened>;\n watch(\n request: { agentId: string | null; since: string | null; thinking: boolean },\n context: Context\n ): Promise<RemoteOpened>;\n next(stream: string, waitMs: number, context: Context): Promise<RemoteBatch>;\n close(stream: string, context: Context): Promise<void>;\n abort(agentId: string | null, context: Context): Promise<{ aborted: boolean }>;\n}\n\nexport interface RemoteHost {\n harness: Harness;\n conversation(agentId: string | null, context: Context): Promise<Conversation>;\n send(request: SendRequest, context: Context): Promise<SendResponse>;\n now?: () => number;\n limit?: number;\n sweepMs?: number;\n}\n\nclass Stream {\n readonly queue: RemoteEvent[] = [];\n done = false;\n touched: number;\n #wake: (() => void) | undefined;\n readonly #stop: () => Promise<unknown>;\n readonly #limit: number;\n\n constructor(stop: () => Promise<unknown>, now: number, limit: number) {\n this.#stop = stop;\n this.touched = now;\n this.#limit = limit;\n }\n\n push(events: readonly RemoteEvent[]): void {\n if (this.done || !events.length) return;\n this.queue.push(...events);\n if (this.queue.length > this.#limit) {\n this.queue.length = 0;\n this.queue.push({ t: 'end', reason: 'error', message: 'the stream fell too far behind' });\n void this.finish();\n }\n this.#wake?.();\n }\n\n async finish(): Promise<void> {\n if (this.done) return;\n this.done = true;\n this.#wake?.();\n await this.#stop().catch(() => undefined);\n }\n\n wait(ms: number): Promise<void> {\n if (this.queue.length || this.done) return Promise.resolve();\n return new Promise((resolve) => {\n const timer = setTimeout(done, ms);\n function done() {\n clearTimeout(timer);\n resolve();\n }\n this.#wake = () => {\n this.#wake = undefined;\n done();\n };\n });\n }\n}\n\nfunction deliveredOf(\n status: { status: string } | undefined,\n whenBusy: SendMode,\n busy: boolean\n): Delivered {\n if (status?.status === 'queued') return whenBusy === 'followUp' ? 'follow-up' : 'steer';\n return busy ? 'steer' : 'run';\n}\n\nfunction headOf(events: readonly RemoteEvent[]): string | null {\n for (let at = events.length - 1; at >= 0; at--) {\n const id = idOf(events[at] as RemoteEvent);\n if (id) return id;\n }\n return null;\n}\n\nexport function followed(mapper: Mapper, event: AgentEvent): RemoteEvent[] {\n return event.type === 'snapshot' ? resync(mapper, event) : mapper.event(event);\n}\n\nexport function resync(mapper: Mapper, snapshot: SnapshotEvent): RemoteEvent[] {\n const last = mapper.last();\n return [\n ...snapshot.entries.filter((entry) => entry.id > last).flatMap((entry) => mapper.entry(entry)),\n ...mapper.partial(snapshot.generation?.message as never),\n ];\n}\n\nfunction placedEntry(value: unknown): number | undefined {\n const entry = (value as { entry?: unknown } | undefined)?.entry;\n return typeof entry === 'number' ? entry : undefined;\n}\n\nexport class Follower {\n readonly #mapper: Mapper;\n readonly #entries: Map<number, EntryRecord>;\n readonly #submission: SubmissionId;\n readonly #delivered: Delivered;\n readonly #out: RemoteEvent[] = [];\n #user: number | undefined;\n #started = false;\n\n constructor(\n mapper: Mapper,\n entries: readonly EntryRecord[],\n submission: SubmissionId,\n delivered: Delivered,\n record: unknown\n ) {\n this.#mapper = mapper;\n this.#entries = new Map(entries.map((entry) => [entry.id, entry]));\n this.#submission = submission;\n this.#delivered = delivered;\n this.#user = placedEntry(record);\n }\n\n drain(): RemoteEvent[] {\n return this.#out.splice(0);\n }\n\n #begin(): void {\n const entry = this.#user === undefined ? undefined : this.#entries.get(this.#user);\n if (!entry) return;\n this.#started = true;\n this.#out.push(...this.#mapper.entry(entry, this.#delivered));\n }\n\n #end(reason: EndReason, message?: string): true {\n this.#out.push({ t: 'end', reason, ...(message ? { message } : {}) });\n return true;\n }\n\n #ours(event: AgentEvent): boolean {\n if (event.type !== 'submission' || event.record.id !== this.#submission) return false;\n this.#user = placedEntry(event.record) ?? this.#user;\n return this.#user === undefined && !['queued', 'placed'].includes(event.record.status);\n }\n\n take(batch: readonly AgentEvent[]): boolean {\n for (const event of batch) {\n if (event.type === 'message_end') this.#entries.set(event.entry.id, event.entry);\n if (this.#ours(event)) return this.#end('aborted');\n if (!this.#started) {\n this.#begin();\n continue;\n }\n const mapped = this.#mapper.event(event).filter((item) => item.t !== 'user');\n this.#out.push(...mapped);\n if (mapped.some((item) => item.t === 'run' && item.state === 'end'))\n return this.#end(this.#mapper.reason, this.#mapper.error);\n }\n return false;\n }\n}\n\nexport function createRemote(host: RemoteHost): AgentRemote & { dispose(): Promise<void> } {\n const now = host.now ?? Date.now;\n const streams = new Map<string, Stream>();\n let serial = 0;\n let timer: ReturnType<typeof setInterval> | undefined;\n const sweep = () => {\n for (const [id, stream] of streams)\n if (now() - stream.touched > IDLE_MS) {\n streams.delete(id);\n void stream.finish();\n }\n if (!streams.size && timer !== undefined) {\n clearInterval(timer);\n timer = undefined;\n }\n };\n const track = (id: string, stream: Stream) => {\n streams.set(id, stream);\n timer ??= setInterval(sweep, host.sweepMs ?? IDLE_MS / 4);\n };\n const watching = async (agentId: string | null, context: Context) => {\n const conversation = await host.conversation(agentId, context);\n const cancel = withCancel(BACKGROUND_CONTEXT);\n const events: AgentEventStream = await watchEvents(\n host.harness,\n conversation.id,\n cancel.context\n );\n const open = (onEvents: (batch: readonly AgentEvent[]) => void) => {\n const stream = new Stream(\n async () => {\n await events.stop();\n cancel.cancel();\n },\n now(),\n host.limit ?? MAX_QUEUED\n );\n const id = `s${++serial}`;\n track(id, stream);\n events.start(async (batch) => onEvents(batch));\n return { id, stream };\n };\n return { conversation, snapshot: events.snapshot, open, stop: () => events.stop() };\n };\n return {\n async prompt(request, context) {\n sweep();\n const watched = await watching(request.agentId, context);\n const busy = Boolean(watched.snapshot.run);\n const response = await host.send(\n {\n text: request.text,\n whenBusy: request.whenBusy,\n requestId: null,\n ...(request.agentId ? { agentId: request.agentId } : {}),\n },\n context\n );\n if (!response.accepted) {\n await watched.stop();\n return {\n ok: false,\n code: response.error.code === 'busy' ? 'EBUSY' : 'EIO',\n message: response.error.message,\n };\n }\n const submission = Number(response.submissionId) as SubmissionId;\n const record = await (await host.harness.submission(submission, context))?.status(context);\n const delivered = deliveredOf(record, request.whenBusy, busy);\n const follower = new Follower(\n new Mapper(request.thinking),\n watched.snapshot.entries,\n submission,\n delivered,\n record\n );\n const { id, stream } = watched.open((batch) => {\n const ended = follower.take(batch);\n stream.push(follower.drain());\n if (ended) void stream.finish();\n });\n return { ok: true, stream: id, delivered, head: null, gap: false };\n },\n async watch(request, context) {\n sweep();\n const watched = await watching(request.agentId, context);\n const partial = watched.snapshot.generation?.message as never;\n const all = replayOf(watched.snapshot.entries, partial, request.thinking);\n const head = headOf(all);\n const at = request.since === null ? -1 : all.findIndex((e) => idOf(e) === request.since);\n const gap = request.since !== null && at < 0;\n const mapper = new Mapper(request.thinking);\n for (const entry of watched.snapshot.entries) mapper.entry(entry);\n mapper.partial(partial);\n const { id, stream } = watched.open((batch) =>\n stream.push(batch.flatMap((event) => followed(mapper, event)))\n );\n if (at >= 0) {\n const own = all[at] as RemoteEvent;\n const sinceId = idOf(own);\n let last = at;\n while (last + 1 < all.length && idOf(all[last + 1] as RemoteEvent) === sinceId) last++;\n const replay = own.t === 'text' || own.t === 'thinking' ? [own] : [];\n stream.push([...replay, ...all.slice(last + 1)]);\n }\n return { ok: true, stream: id, delivered: null, head, gap };\n },\n async next(id, waitMs, context) {\n const stream = streams.get(id);\n if (!stream) return { events: [], done: true };\n stream.touched = now();\n await stream.wait(Math.max(0, Math.min(waitMs, 30000)));\n context.abortSignal?.throwIfAborted();\n const events = stream.queue.splice(0);\n const done = stream.done && stream.queue.length === 0;\n if (done) streams.delete(id);\n return { events, done };\n },\n async close(id) {\n const stream = streams.get(id);\n streams.delete(id);\n await stream?.finish();\n },\n async abort(agentId, context) {\n const conversation = await host.conversation(agentId, context);\n const watched = await watchEvents(host.harness, conversation.id, context);\n const busy = Boolean(watched.snapshot.run);\n await watched.stop();\n if (busy) await conversation.abort(context);\n return { aborted: busy };\n },\n async dispose() {\n clearInterval(timer);\n timer = undefined;\n const all = [...streams.values()];\n streams.clear();\n await Promise.all(all.map((stream) => stream.finish()));\n },\n };\n}\n"],
5
- "mappings": "AACA,SAAS,oBAAoB,kBAAkB;AAC/C;AAAA,EAQE;AAAA,OACK;AAEP,SAAyB,MAAM,QAA0B,gBAAgB;AAElE,MAAM,aAAa;AACnB,MAAM,UAAU;AAkCvB,MAAM,OAAO;AAAA,EACF,QAAuB,CAAC;AAAA,EACjC,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACS;AAAA,EACA;AAAA,EAET,YAAY,MAA8B,KAAa,OAAe;AACpE,SAAK,QAAQ;AACb,SAAK,UAAU;AACf,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,KAAK,QAAsC;AACzC,QAAI,KAAK,QAAQ,CAAC,OAAO,OAAQ;AACjC,SAAK,MAAM,KAAK,GAAG,MAAM;AACzB,QAAI,KAAK,MAAM,SAAS,KAAK,QAAQ;AACnC,WAAK,MAAM,SAAS;AACpB,WAAK,MAAM,KAAK,EAAE,GAAG,OAAO,QAAQ,SAAS,SAAS,iCAAiC,CAAC;AACxF,WAAK,KAAK,OAAO;AAAA,IACnB;AACA,SAAK,QAAQ;AAAA,EACf;AAAA,EAEA,MAAM,SAAwB;AAC5B,QAAI,KAAK,KAAM;AACf,SAAK,OAAO;AACZ,SAAK,QAAQ;AACb,UAAM,KAAK,MAAM,EAAE,MAAM,MAAM,MAAS;AAAA,EAC1C;AAAA,EAEA,KAAK,IAA2B;AAC9B,QAAI,KAAK,MAAM,UAAU,KAAK,KAAM,QAAO,QAAQ,QAAQ;AAC3D,WAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,YAAM,QAAQ,WAAW,MAAM,EAAE;AACjC,eAAS,OAAO;AACd,qBAAa,KAAK;AAClB,gBAAQ;AAAA,MACV;AACA,WAAK,QAAQ,MAAM;AACjB,aAAK,QAAQ;AACb,aAAK;AAAA,MACP;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAEA,SAAS,YACP,QACA,UACA,MACW;AACX,MAAI,QAAQ,WAAW,SAAU,QAAO,aAAa,aAAa,cAAc;AAChF,SAAO,OAAO,UAAU;AAC1B;AAEA,SAAS,OAAO,QAA+C;AAC7D,WAAS,KAAK,OAAO,SAAS,GAAG,MAAM,GAAG,MAAM;AAC9C,UAAM,KAAK,KAAK,OAAO,EAAE,CAAgB;AACzC,QAAI,GAAI,QAAO;AAAA,EACjB;AACA,SAAO;AACT;AAEO,SAAS,SAAS,QAAgB,OAAkC;AACzE,SAAO,MAAM,SAAS,aAAa,OAAO,QAAQ,KAAK,IAAI,OAAO,MAAM,KAAK;AAC/E;AAEO,SAAS,OAAO,QAAgB,UAAwC;AAC7E,QAAM,OAAO,OAAO,KAAK;AACzB,SAAO;AAAA,IACL,GAAG,SAAS,QAAQ,OAAO,CAAC,UAAU,MAAM,KAAK,IAAI,EAAE,QAAQ,CAAC,UAAU,OAAO,MAAM,KAAK,CAAC;AAAA,IAC7F,GAAG,OAAO,QAAQ,SAAS,YAAY,OAAgB;AAAA,EACzD;AACF;AAEA,SAAS,YAAY,OAAoC;AACvD,QAAM,QAAS,OAA2C;AAC1D,SAAO,OAAO,UAAU,WAAW,QAAQ;AAC7C;AAEO,MAAM,SAAS;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAsB,CAAC;AAAA,EAChC;AAAA,EACA,WAAW;AAAA,EAEX,YACE,QACA,SACA,YACA,WACA,QACA;AACA,SAAK,UAAU;AACf,SAAK,WAAW,IAAI,IAAI,QAAQ,IAAI,CAAC,UAAU,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC;AACjE,SAAK,cAAc;AACnB,SAAK,aAAa;AAClB,SAAK,QAAQ,YAAY,MAAM;AAAA,EACjC;AAAA,EAEA,QAAuB;AACrB,WAAO,KAAK,KAAK,OAAO,CAAC;AAAA,EAC3B;AAAA,EAEA,SAAe;AACb,UAAM,QAAQ,KAAK,UAAU,SAAY,SAAY,KAAK,SAAS,IAAI,KAAK,KAAK;AACjF,QAAI,CAAC,MAAO;AACZ,SAAK,WAAW;AAChB,SAAK,KAAK,KAAK,GAAG,KAAK,QAAQ,MAAM,OAAO,KAAK,UAAU,CAAC;AAAA,EAC9D;AAAA,EAEA,KAAK,QAAmB,SAAwB;AAC9C,SAAK,KAAK,KAAK,EAAE,GAAG,OAAO,QAAQ,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC,EAAG,CAAC;AACpE,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,OAA4B;AAChC,QAAI,MAAM,SAAS,gBAAgB,MAAM,OAAO,OAAO,KAAK,YAAa,QAAO;AAChF,SAAK,QAAQ,YAAY,MAAM,MAAM,KAAK,KAAK;AAC/C,WAAO,KAAK,UAAU,UAAa,CAAC,CAAC,UAAU,QAAQ,EAAE,SAAS,MAAM,OAAO,MAAM;AAAA,EACvF;AAAA,EAEA,KAAK,OAAuC;AAC1C,eAAW,SAAS,OAAO;AACzB,UAAI,MAAM,SAAS,cAAe,MAAK,SAAS,IAAI,MAAM,MAAM,IAAI,MAAM,KAAK;AAC/E,UAAI,KAAK,MAAM,KAAK,EAAG,QAAO,KAAK,KAAK,SAAS;AACjD,UAAI,CAAC,KAAK,UAAU;AAClB,aAAK,OAAO;AACZ;AAAA,MACF;AACA,YAAM,SAAS,KAAK,QAAQ,MAAM,KAAK,EAAE,OAAO,CAAC,SAAS,KAAK,MAAM,MAAM;AAC3E,WAAK,KAAK,KAAK,GAAG,MAAM;AACxB,UAAI,OAAO,KAAK,CAAC,SAAS,KAAK,MAAM,SAAS,KAAK,UAAU,KAAK;AAChE,eAAO,KAAK,KAAK,KAAK,QAAQ,QAAQ,KAAK,QAAQ,KAAK;AAAA,IAC5D;AACA,WAAO;AAAA,EACT;AACF;AAEO,SAAS,aAAa,MAA8D;AACzF,QAAM,MAAM,KAAK,OAAO,KAAK;AAC7B,QAAM,UAAU,oBAAI,IAAoB;AACxC,MAAI,SAAS;AACb,MAAI;AACJ,QAAM,QAAQ,MAAM;AAClB,eAAW,CAAC,IAAI,MAAM,KAAK;AACzB,UAAI,IAAI,IAAI,OAAO,UAAU,SAAS;AACpC,gBAAQ,OAAO,EAAE;AACjB,aAAK,OAAO,OAAO;AAAA,MACrB;AACF,QAAI,CAAC,QAAQ,QAAQ,UAAU,QAAW;AACxC,oBAAc,KAAK;AACnB,cAAQ;AAAA,IACV;AAAA,EACF;AACA,QAAM,QAAQ,CAAC,IAAY,WAAmB;AAC5C,YAAQ,IAAI,IAAI,MAAM;AACtB,cAAU,YAAY,OAAO,KAAK,WAAW,UAAU,CAAC;AAAA,EAC1D;AACA,QAAM,WAAW,OAAO,SAAwB,YAAqB;AACnE,UAAM,eAAe,MAAM,KAAK,aAAa,SAAS,OAAO;AAC7D,UAAM,SAAS,WAAW,kBAAkB;AAC5C,UAAM,SAA2B,MAAM;AAAA,MACrC,KAAK;AAAA,MACL,aAAa;AAAA,MACb,OAAO;AAAA,IACT;AACA,UAAM,OAAO,CAAC,aAAqD;AACjE,YAAM,SAAS,IAAI;AAAA,QACjB,YAAY;AACV,gBAAM,OAAO,KAAK;AAClB,iBAAO,OAAO;AAAA,QAChB;AAAA,QACA,IAAI;AAAA,QACJ,KAAK,SAAS;AAAA,MAChB;AACA,YAAM,KAAK,IAAI,EAAE,MAAM;AACvB,YAAM,IAAI,MAAM;AAChB,aAAO,MAAM,OAAO,UAAU,SAAS,KAAK,CAAC;AAC7C,aAAO,EAAE,IAAI,OAAO;AAAA,IACtB;AACA,WAAO,EAAE,cAAc,UAAU,OAAO,UAAU,MAAM,MAAM,MAAM,OAAO,KAAK,EAAE;AAAA,EACpF;AACA,SAAO;AAAA,IACL,MAAM,OAAO,SAAS,SAAS;AAC7B,YAAM;AACN,YAAM,UAAU,MAAM,SAAS,QAAQ,SAAS,OAAO;AACvD,YAAM,OAAO,QAAQ,QAAQ,SAAS,GAAG;AACzC,YAAM,WAAW,MAAM,KAAK;AAAA,QAC1B;AAAA,UACE,MAAM,QAAQ;AAAA,UACd,UAAU,QAAQ;AAAA,UAClB,WAAW;AAAA,UACX,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;AAAA,QACxD;AAAA,QACA;AAAA,MACF;AACA,UAAI,CAAC,SAAS,UAAU;AACtB,cAAM,QAAQ,KAAK;AACnB,eAAO;AAAA,UACL,IAAI;AAAA,UACJ,MAAM,SAAS,MAAM,SAAS,SAAS,UAAU;AAAA,UACjD,SAAS,SAAS,MAAM;AAAA,QAC1B;AAAA,MACF;AACA,YAAM,aAAa,OAAO,SAAS,YAAY;AAC/C,YAAM,SAAS,OAAO,MAAM,KAAK,QAAQ,WAAW,YAAY,OAAO,IAAI,OAAO,OAAO;AACzF,YAAM,YAAY,YAAY,QAAQ,QAAQ,UAAU,IAAI;AAC5D,YAAM,WAAW,IAAI;AAAA,QACnB,IAAI,OAAO,QAAQ,QAAQ;AAAA,QAC3B,QAAQ,SAAS;AAAA,QACjB;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,YAAM,EAAE,IAAI,OAAO,IAAI,QAAQ,KAAK,CAAC,UAAU;AAC7C,cAAM,QAAQ,SAAS,KAAK,KAAK;AACjC,eAAO,KAAK,SAAS,MAAM,CAAC;AAC5B,YAAI,MAAO,MAAK,OAAO,OAAO;AAAA,MAChC,CAAC;AACD,aAAO,EAAE,IAAI,MAAM,QAAQ,IAAI,WAAW,MAAM,MAAM,KAAK,MAAM;AAAA,IACnE;AAAA,IACA,MAAM,MAAM,SAAS,SAAS;AAC5B,YAAM;AACN,YAAM,UAAU,MAAM,SAAS,QAAQ,SAAS,OAAO;AACvD,YAAM,UAAU,QAAQ,SAAS,YAAY;AAC7C,YAAM,MAAM,SAAS,QAAQ,SAAS,SAAS,SAAS,QAAQ,QAAQ;AACxE,YAAM,OAAO,OAAO,GAAG;AACvB,YAAM,KAAK,QAAQ,UAAU,OAAO,KAAK,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,MAAM,QAAQ,KAAK;AACvF,YAAM,MAAM,QAAQ,UAAU,QAAQ,KAAK;AAC3C,YAAM,SAAS,IAAI,OAAO,QAAQ,QAAQ;AAC1C,iBAAW,SAAS,QAAQ,SAAS,QAAS,QAAO,MAAM,KAAK;AAChE,aAAO,QAAQ,OAAO;AACtB,YAAM,EAAE,IAAI,OAAO,IAAI,QAAQ;AAAA,QAAK,CAAC,UACnC,OAAO,KAAK,MAAM,QAAQ,CAAC,UAAU,SAAS,QAAQ,KAAK,CAAC,CAAC;AAAA,MAC/D;AACA,UAAI,MAAM,GAAG;AACX,cAAM,MAAM,IAAI,EAAE;AAClB,cAAM,UAAU,KAAK,GAAG;AACxB,YAAI,OAAO;AACX,eAAO,OAAO,IAAI,IAAI,UAAU,KAAK,IAAI,OAAO,CAAC,CAAgB,MAAM,QAAS;AAChF,cAAM,SAAS,IAAI,MAAM,UAAU,IAAI,MAAM,aAAa,CAAC,GAAG,IAAI,CAAC;AACnE,eAAO,KAAK,CAAC,GAAG,QAAQ,GAAG,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC;AAAA,MACjD;AACA,aAAO,EAAE,IAAI,MAAM,QAAQ,IAAI,WAAW,MAAM,MAAM,IAAI;AAAA,IAC5D;AAAA,IACA,MAAM,KAAK,IAAI,QAAQ,SAAS;AAC9B,YAAM,SAAS,QAAQ,IAAI,EAAE;AAC7B,UAAI,CAAC,OAAQ,QAAO,EAAE,QAAQ,CAAC,GAAG,MAAM,KAAK;AAC7C,aAAO,UAAU,IAAI;AACrB,YAAM,OAAO,KAAK,KAAK,IAAI,GAAG,KAAK,IAAI,QAAQ,GAAK,CAAC,CAAC;AACtD,cAAQ,aAAa,eAAe;AACpC,YAAM,SAAS,OAAO,MAAM,OAAO,CAAC;AACpC,YAAM,OAAO,OAAO,QAAQ,OAAO,MAAM,WAAW;AACpD,UAAI,KAAM,SAAQ,OAAO,EAAE;AAC3B,aAAO,EAAE,QAAQ,KAAK;AAAA,IACxB;AAAA,IACA,MAAM,MAAM,IAAI;AACd,YAAM,SAAS,QAAQ,IAAI,EAAE;AAC7B,cAAQ,OAAO,EAAE;AACjB,YAAM,QAAQ,OAAO;AAAA,IACvB;AAAA,IACA,MAAM,MAAM,SAAS,SAAS;AAC5B,YAAM,eAAe,MAAM,KAAK,aAAa,SAAS,OAAO;AAC7D,YAAM,UAAU,MAAM,YAAY,KAAK,SAAS,aAAa,IAAI,OAAO;AACxE,YAAM,OAAO,QAAQ,QAAQ,SAAS,GAAG;AACzC,YAAM,QAAQ,KAAK;AACnB,UAAI,KAAM,OAAM,aAAa,MAAM,OAAO;AAC1C,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB;AAAA,IACA,MAAM,UAAU;AACd,oBAAc,KAAK;AACnB,cAAQ;AACR,YAAM,MAAM,CAAC,GAAG,QAAQ,OAAO,CAAC;AAChC,cAAQ,MAAM;AACd,YAAM,QAAQ,IAAI,IAAI,IAAI,CAAC,WAAW,OAAO,OAAO,CAAC,CAAC;AAAA,IACxD;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT, withCancel } from '@earendil-works/chord/context';\nimport {\n type AgentEvent,\n type AgentEventStream,\n type Conversation,\n type EntryRecord,\n type Harness,\n type SnapshotEvent,\n type SubmissionId,\n watchEvents,\n} from '@earendil-works/pi-durable';\nimport type { Delivered, SendMode, SendRequest, SendResponse } from '../services.ts';\nimport { type EndReason, idOf, Mapper, type RemoteEvent, replayOf } from './events.ts';\n\nexport const MAX_QUEUED = 10000;\nexport const IDLE_MS = 120000;\n\nexport type RemoteOpened =\n | { ok: true; stream: string; delivered: Delivered | null; head: string | null; gap: boolean }\n | { ok: false; code: string; message: string };\n\nexport interface RemoteBatch {\n events: RemoteEvent[];\n done: boolean;\n}\n\nexport interface AgentRemote {\n prompt(\n request: { agentId: string | null; text: string; whenBusy: SendMode; thinking: boolean },\n context: Context\n ): Promise<RemoteOpened>;\n watch(\n request: { agentId: string | null; since: string | null; thinking: boolean },\n context: Context\n ): Promise<RemoteOpened>;\n next(stream: string, waitMs: number, context: Context): Promise<RemoteBatch>;\n close(stream: string, context: Context): Promise<void>;\n abort(agentId: string | null, context: Context): Promise<{ aborted: boolean }>;\n}\n\nexport interface RemoteHost {\n harness: Harness;\n conversation(agentId: string | null, context: Context): Promise<Conversation>;\n send(request: SendRequest, context: Context): Promise<SendResponse>;\n now?: () => number;\n limit?: number;\n sweepMs?: number;\n}\n\nclass Stream {\n readonly queue: RemoteEvent[] = [];\n done = false;\n touched: number;\n #wake: (() => void) | undefined;\n readonly #stop: () => Promise<unknown>;\n readonly #limit: number;\n\n constructor(stop: () => Promise<unknown>, now: number, limit: number) {\n this.#stop = stop;\n this.touched = now;\n this.#limit = limit;\n }\n\n push(events: readonly RemoteEvent[]): void {\n if (this.done || !events.length) return;\n this.queue.push(...events);\n if (this.queue.length > this.#limit) {\n this.queue.length = 0;\n this.queue.push({ t: 'end', reason: 'error', message: 'the stream fell too far behind' });\n void this.finish();\n }\n this.#wake?.();\n }\n\n async finish(): Promise<void> {\n if (this.done) return;\n this.done = true;\n this.#wake?.();\n await this.#stop().catch(() => undefined);\n }\n\n wait(ms: number): Promise<void> {\n if (this.queue.length || this.done) return Promise.resolve();\n return new Promise((resolve) => {\n const timer = setTimeout(done, ms);\n function done() {\n clearTimeout(timer);\n resolve();\n }\n this.#wake = () => {\n this.#wake = undefined;\n done();\n };\n });\n }\n}\n\nfunction deliveredOf(\n status: { status: string } | undefined,\n whenBusy: SendMode,\n busy: boolean\n): Delivered {\n if (status?.status === 'queued') return whenBusy === 'followUp' ? 'follow-up' : 'steer';\n return busy ? 'steer' : 'run';\n}\n\nfunction headOf(events: readonly RemoteEvent[]): string | null {\n for (let at = events.length - 1; at >= 0; at--) {\n const id = idOf(events[at] as RemoteEvent);\n if (id) return id;\n }\n return null;\n}\n\nexport function followed(mapper: Mapper, event: AgentEvent): RemoteEvent[] {\n return event.type === 'snapshot' ? resync(mapper, event) : mapper.event(event);\n}\n\nexport function resync(mapper: Mapper, snapshot: SnapshotEvent): RemoteEvent[] {\n const last = mapper.last();\n return [\n ...snapshot.entries.filter((entry) => entry.id > last).flatMap((entry) => mapper.entry(entry)),\n ...mapper.partial(snapshot.generation?.message as never),\n ];\n}\n\nfunction placedEntry(value: unknown): number | undefined {\n const entry = (value as { entry?: unknown } | undefined)?.entry;\n return typeof entry === 'number' ? entry : undefined;\n}\n\nexport class Follower {\n readonly #mapper: Mapper;\n readonly #entries: Map<number, EntryRecord>;\n readonly #submission: SubmissionId;\n readonly #delivered: Delivered;\n readonly #out: RemoteEvent[] = [];\n #user: number | undefined;\n #started = false;\n\n constructor(\n mapper: Mapper,\n entries: readonly EntryRecord[],\n submission: SubmissionId,\n delivered: Delivered,\n record: unknown\n ) {\n this.#mapper = mapper;\n this.#entries = new Map(entries.map((entry) => [entry.id, entry]));\n this.#submission = submission;\n this.#delivered = delivered;\n this.#user = placedEntry(record);\n }\n\n drain(): RemoteEvent[] {\n return this.#out.splice(0);\n }\n\n #begin(): void {\n const entry = this.#user === undefined ? undefined : this.#entries.get(this.#user);\n if (!entry) return;\n this.#started = true;\n this.#out.push(...this.#mapper.entry(entry, this.#delivered));\n }\n\n #end(reason: EndReason, message?: string): true {\n this.#out.push({ t: 'end', reason, ...(message ? { message } : {}) });\n return true;\n }\n\n #ours(event: AgentEvent): boolean {\n if (event.type !== 'submission' || event.record.id !== this.#submission) return false;\n this.#user = placedEntry(event.record) ?? this.#user;\n return this.#user === undefined && !['queued', 'placed'].includes(event.record.status);\n }\n\n take(batch: readonly AgentEvent[]): boolean {\n for (const event of batch) {\n if (event.type === 'message_end') this.#entries.set(event.entry.id, event.entry);\n if (this.#ours(event)) return this.#end('aborted');\n if (!this.#started) {\n this.#begin();\n continue;\n }\n const mapped = this.#mapper.event(event).filter((item) => item.t !== 'user');\n this.#out.push(...mapped);\n if (mapped.some((item) => item.t === 'run' && item.state === 'end'))\n return this.#end(this.#mapper.reason, this.#mapper.error);\n }\n return false;\n }\n}\n\nexport function createRemote(host: RemoteHost): AgentRemote & { dispose(): Promise<void> } {\n const now = host.now ?? Date.now;\n const streams = new Map<string, Stream>();\n let serial = 0;\n let timer: ReturnType<typeof setInterval> | undefined;\n const sweep = () => {\n for (const [id, stream] of streams)\n if (now() - stream.touched > IDLE_MS) {\n streams.delete(id);\n void stream.finish();\n }\n if (!streams.size && timer !== undefined) {\n clearInterval(timer);\n timer = undefined;\n }\n };\n const track = (id: string, stream: Stream) => {\n streams.set(id, stream);\n timer ??= setInterval(sweep, host.sweepMs ?? IDLE_MS / 4);\n };\n const watching = async (agentId: string | null, context: Context) => {\n const conversation = await host.conversation(agentId, context);\n const cancel = withCancel(BACKGROUND_CONTEXT);\n const events: AgentEventStream = await watchEvents(\n host.harness,\n conversation.id,\n cancel.context\n );\n const open = (onEvents: (batch: readonly AgentEvent[]) => void) => {\n const stream = new Stream(\n async () => {\n await events.stop();\n cancel.cancel();\n },\n now(),\n host.limit ?? MAX_QUEUED\n );\n const id = `s${++serial}-${crypto.randomUUID()}`;\n track(id, stream);\n events.start(async (batch) => onEvents(batch));\n return { id, stream };\n };\n return { conversation, snapshot: events.snapshot, open, stop: () => events.stop() };\n };\n return {\n async prompt(request, context) {\n sweep();\n const watched = await watching(request.agentId, context);\n const busy = Boolean(watched.snapshot.run);\n const response = await host.send(\n {\n text: request.text,\n whenBusy: request.whenBusy,\n requestId: null,\n ...(request.agentId ? { agentId: request.agentId } : {}),\n },\n context\n );\n if (!response.accepted) {\n await watched.stop();\n return {\n ok: false,\n code: response.error.code === 'busy' ? 'EBUSY' : 'EIO',\n message: response.error.message,\n };\n }\n const submission = Number(response.submissionId) as SubmissionId;\n const record = await (await host.harness.submission(submission, context))?.status(context);\n const delivered = deliveredOf(record, request.whenBusy, busy);\n const follower = new Follower(\n new Mapper(request.thinking),\n watched.snapshot.entries,\n submission,\n delivered,\n record\n );\n const { id, stream } = watched.open((batch) => {\n const ended = follower.take(batch);\n stream.push(follower.drain());\n if (ended) void stream.finish();\n });\n return { ok: true, stream: id, delivered, head: null, gap: false };\n },\n async watch(request, context) {\n sweep();\n const watched = await watching(request.agentId, context);\n const partial = watched.snapshot.generation?.message as never;\n const all = replayOf(watched.snapshot.entries, partial, request.thinking);\n const head = headOf(all);\n const at = request.since === null ? -1 : all.findIndex((e) => idOf(e) === request.since);\n const gap = request.since !== null && at < 0;\n const mapper = new Mapper(request.thinking);\n for (const entry of watched.snapshot.entries) mapper.entry(entry);\n mapper.partial(partial);\n const { id, stream } = watched.open((batch) =>\n stream.push(batch.flatMap((event) => followed(mapper, event)))\n );\n if (at >= 0) {\n const own = all[at] as RemoteEvent;\n const sinceId = idOf(own);\n let last = at;\n while (last + 1 < all.length && idOf(all[last + 1] as RemoteEvent) === sinceId) last++;\n const replay = own.t === 'text' || own.t === 'thinking' ? [own] : [];\n stream.push([...replay, ...all.slice(last + 1)]);\n }\n return { ok: true, stream: id, delivered: null, head, gap };\n },\n async next(id, waitMs, context) {\n const stream = streams.get(id);\n if (!stream) return { events: [], done: true };\n stream.touched = now();\n await stream.wait(Math.max(0, Math.min(waitMs, 30000)));\n context.abortSignal?.throwIfAborted();\n const events = stream.queue.splice(0);\n const done = stream.done && stream.queue.length === 0;\n if (done) streams.delete(id);\n return { events, done };\n },\n async close(id) {\n const stream = streams.get(id);\n streams.delete(id);\n await stream?.finish();\n },\n async abort(agentId, context) {\n const conversation = await host.conversation(agentId, context);\n const watched = await watchEvents(host.harness, conversation.id, context);\n const busy = Boolean(watched.snapshot.run);\n await watched.stop();\n if (busy) await conversation.abort(context);\n return { aborted: busy };\n },\n async dispose() {\n clearInterval(timer);\n timer = undefined;\n const all = [...streams.values()];\n streams.clear();\n await Promise.all(all.map((stream) => stream.finish()));\n },\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -12,7 +12,9 @@ import type { PortEndpoint } from './wire.ts';
12
12
  export interface AgentHost {
13
13
  readonly serverId: string;
14
14
  readonly agent: Agent;
15
- connect(endpoint: PortEndpoint): void;
15
+ connect(endpoint: PortEndpoint, options?: {
16
+ readOnly?: boolean;
17
+ }): void;
16
18
  close(): Promise<void>;
17
19
  }
18
20
  export declare function hostAgent(agent: Agent, options?: {
@@ -0,0 +1,17 @@
1
+ import type { Frame, PortEndpoint } from './wire.ts';
2
+ export declare const MAX_FRAME: number;
3
+ export interface ByteSink {
4
+ write(chunk: Uint8Array): void;
5
+ close(): void;
6
+ }
7
+ export interface LinkEnd {
8
+ receive(chunk: Uint8Array): void;
9
+ end(error?: Error): void;
10
+ }
11
+ export declare function encodeFrame(frame: Frame): Uint8Array;
12
+ export declare class FrameReader {
13
+ #private;
14
+ push(chunk: Uint8Array): Frame[];
15
+ }
16
+ export declare function linkEndpoint(sink: ByteSink): PortEndpoint & LinkEnd;
17
+ export declare function bridgePort(port: PortEndpoint, sink: ByteSink): LinkEnd;
@@ -5,9 +5,12 @@ export interface AgentWorker {
5
5
  postMessage(message: unknown, transfer: Transferable[]): void;
6
6
  terminate(): void;
7
7
  }
8
+ export interface PortOptions {
9
+ readOnly?: boolean;
10
+ }
8
11
  export interface AgentOwner {
9
- connect(): Promise<AgentConnection>;
10
- port(): Promise<Transferable>;
12
+ connect(options?: PortOptions): Promise<AgentConnection>;
13
+ port(options?: PortOptions): Promise<PortEndpoint & Transferable>;
11
14
  restart(): Promise<void>;
12
15
  release(): Promise<void>;
13
16
  }
@@ -1,3 +1,5 @@
1
1
  export { type AgentConnection, connectAgent } from './client.ts';
2
- export { AGENT_LOCK, type AgentOwner, type AgentWorker, type KernelSource, type OwnerOptions, startAgent, } from './owner.ts';
2
+ export { type ByteSink, bridgePort, encodeFrame, FrameReader, type LinkEnd, linkEndpoint, MAX_FRAME, } from './link.ts';
3
+ export { AGENT_LOCK, type AgentOwner, type AgentWorker, type KernelSource, type OwnerOptions, type PortOptions, startAgent, } from './owner.ts';
3
4
  export { type RemoteControl, type RemoteEvent, type RemoteOptions, remoteControl, } from './remote/client.ts';
5
+ export { VIEW_ONLY } from './view-only.ts';
@@ -0,0 +1,7 @@
1
+ import type { AgentRemote } from './remote/host.ts';
2
+ import type { AgentControl, AgentSettings } from './services.ts';
3
+ import { VIEW_ONLY } from './view-only.ts';
4
+ export { VIEW_ONLY };
5
+ export declare function viewControl(control: AgentControl): AgentControl;
6
+ export declare function viewSettings(settings: AgentSettings): AgentSettings;
7
+ export declare function viewRemote(remote: AgentRemote): AgentRemote;
@@ -0,0 +1 @@
1
+ export declare const VIEW_ONLY = "this follower can watch, but not send, steer or abort";
@@ -0,0 +1,5 @@
1
+ const VIEW_ONLY = "this follower can watch, but not send, steer or abort";
2
+ export {
3
+ VIEW_ONLY
4
+ };
5
+ //# sourceMappingURL=view-only.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/view-only.ts"],
4
+ "sourcesContent": ["export const VIEW_ONLY = 'this follower can watch, but not send, steer or abort';\n"],
5
+ "mappings": "AAAO,MAAM,YAAY;",
6
+ "names": []
7
+ }
package/dist/worker.js CHANGED
@@ -1,9 +1,10 @@
1
1
  function serveConnections(scope, host) {
2
2
  scope.addEventListener("message", ({ data }) => {
3
- const port = data?.connect;
3
+ const message = data;
4
+ const port = message?.connect;
4
5
  if (!port) return;
5
6
  host.then(
6
- (ready) => ready.connect(port),
7
+ (ready) => ready.connect(port, { readOnly: message?.readOnly === true }),
7
8
  () => port.close?.()
8
9
  );
9
10
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/worker.ts"],
4
- "sourcesContent": ["import type { AgentHost } from './host.ts';\nimport type { PortEndpoint } from './wire.ts';\n\nexport interface WorkerScope {\n addEventListener(type: 'message', listener: (event: MessageEvent) => void): void;\n}\n\nexport function serveConnections(scope: WorkerScope, host: Promise<AgentHost>): void {\n scope.addEventListener('message', ({ data }) => {\n const port = (data as { connect?: PortEndpoint } | null)?.connect;\n if (!port) return;\n host.then(\n (ready) => ready.connect(port),\n () => port.close?.()\n );\n });\n}\n\nexport function kernelPort(scope: WorkerScope): Promise<MessagePort> {\n return new Promise((resolve) => {\n scope.addEventListener('message', ({ data }) => {\n const port = (data as { kernel?: MessagePort } | null)?.kernel;\n if (port) resolve(port);\n });\n });\n}\n"],
5
- "mappings": "AAOO,SAAS,iBAAiB,OAAoB,MAAgC;AACnF,QAAM,iBAAiB,WAAW,CAAC,EAAE,KAAK,MAAM;AAC9C,UAAM,OAAQ,MAA4C;AAC1D,QAAI,CAAC,KAAM;AACX,SAAK;AAAA,MACH,CAAC,UAAU,MAAM,QAAQ,IAAI;AAAA,MAC7B,MAAM,KAAK,QAAQ;AAAA,IACrB;AAAA,EACF,CAAC;AACH;AAEO,SAAS,WAAW,OAA0C;AACnE,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAM,iBAAiB,WAAW,CAAC,EAAE,KAAK,MAAM;AAC9C,YAAM,OAAQ,MAA0C;AACxD,UAAI,KAAM,SAAQ,IAAI;AAAA,IACxB,CAAC;AAAA,EACH,CAAC;AACH;",
4
+ "sourcesContent": ["import type { AgentHost } from './host.ts';\nimport type { PortEndpoint } from './wire.ts';\n\nexport interface WorkerScope {\n addEventListener(type: 'message', listener: (event: MessageEvent) => void): void;\n}\n\nexport function serveConnections(scope: WorkerScope, host: Promise<AgentHost>): void {\n scope.addEventListener('message', ({ data }) => {\n const message = data as { connect?: PortEndpoint; readOnly?: boolean } | null;\n const port = message?.connect;\n if (!port) return;\n host.then(\n (ready) => ready.connect(port, { readOnly: message?.readOnly === true }),\n () => port.close?.()\n );\n });\n}\n\nexport function kernelPort(scope: WorkerScope): Promise<MessagePort> {\n return new Promise((resolve) => {\n scope.addEventListener('message', ({ data }) => {\n const port = (data as { kernel?: MessagePort } | null)?.kernel;\n if (port) resolve(port);\n });\n });\n}\n"],
5
+ "mappings": "AAOO,SAAS,iBAAiB,OAAoB,MAAgC;AACnF,QAAM,iBAAiB,WAAW,CAAC,EAAE,KAAK,MAAM;AAC9C,UAAM,UAAU;AAChB,UAAM,OAAO,SAAS;AACtB,QAAI,CAAC,KAAM;AACX,SAAK;AAAA,MACH,CAAC,UAAU,MAAM,QAAQ,MAAM,EAAE,UAAU,SAAS,aAAa,KAAK,CAAC;AAAA,MACvE,MAAM,KAAK,QAAQ;AAAA,IACrB;AAAA,EACF,CAAC;AACH;AAEO,SAAS,WAAW,OAA0C;AACnE,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAM,iBAAiB,WAAW,CAAC,EAAE,KAAK,MAAM;AAC9C,YAAM,OAAQ,MAA0C;AACxD,UAAI,KAAM,SAAQ,IAAI;AAAA,IACxB,CAAC;AAAA,EACH,CAAC;AACH;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-ecoverse/slicc-agent",
3
- "version": "3.31.0",
3
+ "version": "3.32.0",
4
4
  "description": "SLICC's agent core on pi durable: an agent worker, session storage, remoting and pi extensions",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",