@ai-ecoverse/slicc-agent 3.29.0 → 3.30.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 +14 -2
- package/dist/client.js +4 -1
- package/dist/client.js.map +2 -2
- package/dist/host.js +28 -15
- package/dist/host.js.map +2 -2
- package/dist/page.js +4 -0
- package/dist/page.js.map +2 -2
- package/dist/remote/client.js +79 -0
- package/dist/remote/client.js.map +7 -0
- package/dist/remote/events.js +216 -0
- package/dist/remote/events.js.map +7 -0
- package/dist/remote/host.js +273 -0
- package/dist/remote/host.js.map +7 -0
- package/dist/scoops/index.js +18 -2
- package/dist/scoops/index.js.map +2 -2
- package/dist/services.js +2 -0
- package/dist/services.js.map +2 -2
- package/dist/types/client.d.ts +2 -0
- package/dist/types/page.d.ts +1 -0
- package/dist/types/remote/client.d.ts +30 -0
- package/dist/types/remote/events.d.ts +83 -0
- package/dist/types/remote/host.d.ts +58 -0
- package/dist/types/scoops/index.d.ts +1 -0
- package/dist/types/services.d.ts +2 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -160,7 +160,7 @@ A **cone** is a top-level agent the user talks to; a **scoop** is a helper agent
|
|
|
160
160
|
|
|
161
161
|
Each scoop is owned by a background durable task, its **anchor**, so Stop in its cone doesn't end it; `agent stop` and Drop do. They keep its transcript in durable storage and its reports in `/scoops/<handle>/reports/`, which report licks point at, and remove its working folder `/scoops/<handle>/workspace`. Each request handed to a scoop gets a **reporter** task that waits for the run, writes the answer to `/scoops/<handle>/reports/<task id>.md` and reports it to whoever asked. Requests from a cone's or a scoop's bash report into that agent's `pi.inbox` as a lick, through the licks outbox, so they steer and coalesce like any lick; requests from a terminal report nowhere, and a user typing in a scoop's chat gets the answer there. In a scoop's chat, a request someone else sent shows who sent it (`UserMessage.from`): the asking cone's or scoop's name, with `origin: 'agent'` so spectrum (≥ 1.26.0) shows it as that agent's request with an "Agent" badge, or `terminal` without an origin. Once the request is placed, the reporter appends a `slicc.from` entry (`{ from, entry }`) without model messages, and the adapter sets `from` on that entry's message. A request withdrawn before it was placed leaves no entry. The report lick's text is `[scoop <name> (<role>) <status>]`, and its body has the report's path and line count and a 1,000-character preview. A scoop's prompt has a `scoop` section with its name, its cone, its folders and its limits. The `agent` command is taught by the built-in `agent` skill (below), whose description says that roles exist and that `agent list --agents` lists them.
|
|
162
162
|
|
|
163
|
-
**The `agent` command.** One bash script is installed as `agent` and `subagent` in `$PNPM_HOME/bin`, so the user, a cone or a scoop runs it in any bash. It writes a request (NUL-separated fields: `slicc-agent/1`, the name it was called by, `$SLICC_AGENT`, the working directory, its pid, argc, the arguments and stdin) to `/var/lib/slicc/agent/requests/in/` and polls `out/` for the answer and exit code; the worker writes `<id>.ack` when it picks a request up, and the script gives up after 20 s without one ("the SLICC agent isn't running", exit 1). The kernel environment exports `SLICC_AGENT`, `PI_PROVIDER`, `PI_MODEL` and `PI_REASONING_LEVEL` into every agent command, so the worker knows the caller. `/var/lib/slicc/agent/scoops/<handle>/metadata.json` mirrors each scoop's state for scripts.
|
|
163
|
+
**The `agent` command.** One bash script is installed as `agent` and `subagent` in the kernel's `$PNPM_HOME/bin` (the worker asks the kernel for `PNPM_HOME`: `/home/.local/share/pnpm` before kernel users, root's `/usr/local/share/pnpm`, whose commands every user finds, from kernel 1.47.1), so the user, a cone or a scoop runs it in any bash. It writes a request (NUL-separated fields: `slicc-agent/1`, the name it was called by, `$SLICC_AGENT`, the working directory, its pid, argc, the arguments and stdin) to `/var/lib/slicc/agent/requests/in/` and polls `out/` for the answer and exit code; the worker writes `<id>.ack` when it picks a request up, and the script gives up after 20 s without one ("the SLICC agent isn't running", exit 1). The kernel environment exports `SLICC_AGENT`, `PI_PROVIDER`, `PI_MODEL` and `PI_REASONING_LEVEL` into every agent command, so the worker knows the caller. `/var/lib/slicc/agent/scoops/<handle>/metadata.json` mirrors each scoop's state for scripts.
|
|
164
164
|
- `agent [options] --prompt <text>` (or `--file <path>`, or `-` for stdin, or v6's `agent <cwd> <allowed-commands> <prompt>`) is **synchronous**: an ephemeral scoop runs, and the command prints its final answer and exits 0, or prints the reason on stderr and exits 1. Afterwards the scoop is stopped, its folder removed, its conversation marked dropped and its transcript archived as Markdown in `/tmp/agent-sessions/<handle>.md` (`/home/sessions/` with `--persist-session`, nowhere with `--no-persist-session`). The script traps TERM, INT and HUP and cancels the scoop, and the worker checks the script's pid in the kernel's process table every second for a SIGKILL. If the caller's run is still going when the bash call dies (a bash timeout), the scoop continues and its answer arrives as a `bash` lick; `--background-after <seconds>` does the same on purpose. Sync calls nest up to three deep. Allowed commands in v6's form are told to the scoop, not enforced.
|
|
165
165
|
- `agent --async …` (= `subagent spawn …`) starts a persistent scoop and prints its handle; only cones start them. `list [--agents]`, `status`, `rename`, `send [--follow-up]`, `wait [--timeout <s>] [--notify]` (30 s by default; `--notify` returns at once and brings the answers together in one `scoop-wait` lick) and `stop` manage them. From a scoop, `agent send parent "<note>"` posts a progress note to its cone as a lick, coalesced per scoop.
|
|
166
166
|
- Options: `--name`, `--agent <role>`, `--model <provider/model>`, `--thinking <level>` or `--effort low|medium|high|max`, `--tools <a,b>` (or `auto`/`full`), `--read-only <paths>`, `--system-prompt[-file]`. `--schema-b64`, `--tools output`, `--session`, `--resume`, `--image`, `--no-escalate`, `--minimal` and `--usage` answer "not supported in SLICC yet".
|
|
@@ -414,9 +414,21 @@ v6's `open`, `reload`, `close`, `send` and `chat` answer with what to do instead
|
|
|
414
414
|
|
|
415
415
|
The built-in `sprinkles` skill teaches the files, the bridge and the command.
|
|
416
416
|
|
|
417
|
+
### Remote control
|
|
418
|
+
|
|
419
|
+
`remoteControl(connection, { agent? })` from `@ai-ecoverse/slicc-agent/page` gives a linked CLI's `slicc prompt`, `slicc watch` and `slicc abort` (the `agent` stream of the slicc link protocol, §5.11) as plain objects the page writes to the link as NDJSON. `agent()` names the agent, by default the cone the worker treats as current.
|
|
420
|
+
|
|
421
|
+
- **`prompt({ text, whenBusy = 'followUp', thinking = false })`** sends the prompt and resolves `{ delivered, events }`. `delivered` is `run`, `steer` or `follow-up`. A `reject` while busy throws an `Error` with `code: 'EBUSY'`, and any other refusal `EIO`. `events` starts at the prompt's own `user` event and ends with `{ t: 'end', reason, message? }` after the run that holds it. Leaving the loop closes the stream but never aborts the run.
|
|
422
|
+
- **`watch({ since, thinking })`** resolves `{ head, gap?, events }`. Without `since` it follows from the next event; with it, it first replays `since`'s own text or thinking whole, then everything after it, or answers `gap: true` when the transcript no longer holds it.
|
|
423
|
+
- **`abort()`** resolves `{ aborted }`, `false` when nothing was running.
|
|
424
|
+
- **Events** are `user`, `text`, `thinking` (only with `thinking`), `tool`, `tool-result`, `run`, `notice` and `error`. They are mapped in the worker from pi-durable's agent events (`watchEvents`: `run_start`/`run_end` with their inputs, message deltas, tool ends, retries, compactions) and polled by the page over the `slicc.agent.remote` service.
|
|
425
|
+
- **Ids:** entry ids for committed entries, `a<timestamp>.<block>` for an assistant message's blocks (stable from the first delta to the committed entry; `~n` when timestamps collide), and unique ids for transient notices.
|
|
426
|
+
- **What leaves the worker:** tool summaries of at most 200 characters (the tool's name and main argument), result summaries (a line count, or an error's first line), secrets redacted; never tool output.
|
|
427
|
+
- **Bounds:** a stream nobody polls for two minutes is dropped, and one more than 10 000 events behind ends with `{ t: 'end', reason: 'error' }`.
|
|
428
|
+
|
|
417
429
|
## The kernel
|
|
418
430
|
|
|
419
|
-
`@ai-ecoverse/slicc-kernel` is a peer dependency (`>=1.26.3 <2`; older kernels have an IPv6-to-loopback security bug): the agent only calls `attachKernel`, the client end of the page kernel's port, so it must match the kernel the page runs. Seven chooses that version: slicc-bios pins the certified kernel next to the agent in `src/packages/agent/package.json`, and pnpm fills the peer from that pin. The devDependency is the lowest certified kernel the range allows (1.26.3), and the unit and integration tests run on it. A second CI job reruns the integration tests on the newest certified kernel (`NEWEST_CERTIFIED_KERNEL` in `.github/workflows/ci.yml`). Today the floor is 1.26.3 and the newest certified kernel 1.
|
|
431
|
+
`@ai-ecoverse/slicc-kernel` is a peer dependency (`>=1.26.3 <2`; older kernels have an IPv6-to-loopback security bug): the agent only calls `attachKernel`, the client end of the page kernel's port, so it must match the kernel the page runs. Seven chooses that version: slicc-bios pins the certified kernel next to the agent in `src/packages/agent/package.json`, and pnpm fills the peer from that pin. The devDependency is the lowest certified kernel the range allows (1.26.3), and the unit and integration tests run on it. A second CI job reruns the integration tests on the newest certified kernel (`NEWEST_CERTIFIED_KERNEL` in `.github/workflows/ci.yml`), with the wasm commands certified for it (`NEWEST_CERTIFIED_WASM`; wasm-git 2.55.0-14 and later need kernel ≥ 1.47.1, so the devDependencies stay on builds the floor runs). Today the floor is 1.26.3 and the newest certified kernel 1.47.1; the second job moves by hand to each newer certified kernel, so both ends of the range are tested. If the port protocol ever breaks within 1.x, a patch release narrows the upper bound. Renovate opens PRs for the kernel, `wasi-pnpm`, the `wasm-*` packages and `slicc-cdp` but never merges them, because they move only after the homescoop coordinator certifies them.
|
|
420
432
|
|
|
421
433
|
## Patched dependencies
|
|
422
434
|
|
package/dist/client.js
CHANGED
|
@@ -6,6 +6,7 @@ import { Client, createClientServiceTransport } from "@earendil-works/pi-client"
|
|
|
6
6
|
import {
|
|
7
7
|
AGENT_SESSION,
|
|
8
8
|
AgentControl,
|
|
9
|
+
AgentRemoteService,
|
|
9
10
|
AgentSessions,
|
|
10
11
|
AgentSettings,
|
|
11
12
|
AgentTranscript
|
|
@@ -65,7 +66,7 @@ async function connectAgent(endpoint, options = {}) {
|
|
|
65
66
|
await server.use(AgentSessions).attach(AGENT_SESSION, context);
|
|
66
67
|
await ready;
|
|
67
68
|
const session = createRemoteServiceBinding({
|
|
68
|
-
services: [AgentControl, AgentTranscript, AgentSettings],
|
|
69
|
+
services: [AgentControl, AgentTranscript, AgentSettings, AgentRemoteService],
|
|
69
70
|
transport: createClientServiceTransport(client, () => client.attachment),
|
|
70
71
|
bound: true
|
|
71
72
|
});
|
|
@@ -83,6 +84,7 @@ async function connectAgent(endpoint, options = {}) {
|
|
|
83
84
|
changes
|
|
84
85
|
} = session.use(AgentTranscript);
|
|
85
86
|
const settings = session.use(AgentSettings);
|
|
87
|
+
const remote = session.use(AgentRemoteService);
|
|
86
88
|
await session.ready(context);
|
|
87
89
|
const held = [];
|
|
88
90
|
const hold = (state) => {
|
|
@@ -111,6 +113,7 @@ async function connectAgent(endpoint, options = {}) {
|
|
|
111
113
|
frozen: hold(frozen),
|
|
112
114
|
changes: hold(changes),
|
|
113
115
|
settings: lastingSettings(settings, hold),
|
|
116
|
+
remote,
|
|
114
117
|
closed,
|
|
115
118
|
async prompt(text, whenBusy = "followUp") {
|
|
116
119
|
const sent = await control.send({ text, whenBusy, requestId: null }, context);
|
package/dist/client.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/client.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n type Context,\n createRemoteServiceBinding,\n type ReplicatedState,\n} from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport { Client, createClientServiceTransport } from '@earendil-works/pi-client';\nimport type { ConversationView } from '@earendil-works/pi-durable';\nimport type { ChangesView } from './changes/git.ts';\nimport type { FrozenCone } from './freezer/index.ts';\nimport type { MemoryEntry, MemoryScope } from './memory/format.ts';\nimport {\n AGENT_SESSION,\n AgentControl,\n AgentSessions,\n AgentSettings,\n type AgentsSummary,\n AgentTranscript,\n type Command,\n type Delivered,\n type SendMode,\n} from './services.ts';\nimport type { Sprinkle } from './sprinkles/kind.ts';\nimport { PortChannel, type PortEndpoint } from './wire.ts';\n\nexport interface AgentConnection {\n readonly serverId: string;\n readonly control: AgentControl;\n readonly transcript: ReplicatedState<ConversationView>;\n readonly deliveries: ReplicatedState<Record<string, Delivered>>;\n readonly agents?: ReplicatedState<AgentsSummary>;\n readonly views?: ReplicatedState<Record<string, ConversationView>>;\n readonly commands?: ReplicatedState<Command[]>;\n readonly sprinkles?: ReplicatedState<Sprinkle[]>;\n readonly memories?: ReplicatedState<MemoryEntry[]>;\n readonly memoryScopes?: ReplicatedState<MemoryScope[]>;\n readonly frozen?: ReplicatedState<FrozenCone[]>;\n readonly changes?: ReplicatedState<ChangesView>;\n readonly settings: AgentSettings;\n readonly closed: Promise<Error | undefined>;\n prompt(text: string, whenBusy?: SendMode): Promise<string>;\n close(): Promise<void>;\n}\n\nexport interface Lasting<T> {\n readonly state: ReplicatedState<T>;\n freeze(): void;\n}\n\nexport function lasting<T>(state: ReplicatedState<T>): Lasting<T> {\n let last: T | undefined;\n let frozen = false;\n const read = () => {\n if (frozen) return last;\n try {\n last = state.value;\n } catch {}\n return last;\n };\n return {\n state: {\n get value() {\n return read();\n },\n subscribe: (listener) => (frozen ? () => undefined : state.subscribe(listener)),\n },\n freeze() {\n read();\n frozen = true;\n },\n };\n}\n\nfunction lastingSettings(\n settings: AgentSettings,\n hold: <T>(state: ReplicatedState<T>) => ReplicatedState<T>\n): AgentSettings {\n return {\n state: hold(settings.state),\n connect: (providerId, secret, region, context) =>\n settings.connect(providerId, secret, region, context),\n disconnect: (providerId, context) => settings.disconnect(providerId, context),\n signIn: (providerId, context) => settings.signIn(providerId, context),\n };\n}\n\nfunction attached(client: Client): Promise<void> {\n return new Promise((resolve) => {\n const stop = client.onAttachmentChange(() => {\n stop();\n resolve();\n });\n });\n}\n\nexport async function connectAgent(\n endpoint: PortEndpoint,\n options: { context?: Context } = {}\n): Promise<AgentConnection> {\n const context = options.context ?? BACKGROUND_CONTEXT;\n const channel = new PortChannel(endpoint);\n const serverId = await channel.hello;\n const client = await Client.connect({ serverId, transportFactory: channel.transport });\n const server = createRemoteServiceBinding({\n services: [AgentSessions],\n transport: createClientServiceTransport(client, () => ({ serverId })),\n bound: true,\n });\n const ready = attached(client);\n await server.use(AgentSessions).attach(AGENT_SESSION, context);\n await ready;\n const session = createRemoteServiceBinding({\n services: [AgentControl, AgentTranscript, AgentSettings],\n transport: createClientServiceTransport(client, () => client.attachment),\n bound: true,\n });\n const control = session.use(AgentControl);\n const {\n state: transcript,\n deliveries,\n agents,\n views,\n commands,\n sprinkles,\n memories,\n memoryScopes,\n frozen,\n changes,\n } = session.use(AgentTranscript);\n const settings = session.use(AgentSettings);\n await session.ready(context);\n const held: Lasting<unknown>[] = [];\n const hold = <T>(state: ReplicatedState<T>): ReplicatedState<T> => {\n const kept = lasting(state);\n held.push(kept as Lasting<unknown>);\n return kept.state;\n };\n const freeze = () => {\n for (const kept of held) kept.freeze();\n };\n const closed = channel.ended.then((reason) => {\n freeze();\n return reason;\n });\n return {\n serverId,\n control,\n transcript: hold(transcript),\n deliveries: hold(deliveries),\n agents: hold(agents),\n views: hold(views),\n commands: hold(commands),\n sprinkles: hold(sprinkles),\n memories: hold(memories),\n memoryScopes: hold(memoryScopes),\n frozen: hold(frozen),\n changes: hold(changes),\n settings: lastingSettings(settings, hold),\n closed,\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 freeze();\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;
|
|
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 type { ChangesView } from './changes/git.ts';\nimport type { FrozenCone } from './freezer/index.ts';\nimport type { MemoryEntry, MemoryScope } from './memory/format.ts';\nimport type { AgentRemote } from './remote/host.ts';\nimport {\n AGENT_SESSION,\n AgentControl,\n AgentRemoteService,\n AgentSessions,\n AgentSettings,\n type AgentsSummary,\n AgentTranscript,\n type Command,\n type Delivered,\n type SendMode,\n} from './services.ts';\nimport type { Sprinkle } from './sprinkles/kind.ts';\nimport { PortChannel, type PortEndpoint } from './wire.ts';\n\nexport interface AgentConnection {\n readonly serverId: string;\n readonly control: AgentControl;\n readonly transcript: ReplicatedState<ConversationView>;\n readonly deliveries: ReplicatedState<Record<string, Delivered>>;\n readonly agents?: ReplicatedState<AgentsSummary>;\n readonly views?: ReplicatedState<Record<string, ConversationView>>;\n readonly commands?: ReplicatedState<Command[]>;\n readonly sprinkles?: ReplicatedState<Sprinkle[]>;\n readonly memories?: ReplicatedState<MemoryEntry[]>;\n readonly memoryScopes?: ReplicatedState<MemoryScope[]>;\n readonly frozen?: ReplicatedState<FrozenCone[]>;\n readonly changes?: ReplicatedState<ChangesView>;\n readonly settings: AgentSettings;\n readonly remote?: AgentRemote;\n readonly closed: Promise<Error | undefined>;\n prompt(text: string, whenBusy?: SendMode): Promise<string>;\n close(): Promise<void>;\n}\n\nexport interface Lasting<T> {\n readonly state: ReplicatedState<T>;\n freeze(): void;\n}\n\nexport function lasting<T>(state: ReplicatedState<T>): Lasting<T> {\n let last: T | undefined;\n let frozen = false;\n const read = () => {\n if (frozen) return last;\n try {\n last = state.value;\n } catch {}\n return last;\n };\n return {\n state: {\n get value() {\n return read();\n },\n subscribe: (listener) => (frozen ? () => undefined : state.subscribe(listener)),\n },\n freeze() {\n read();\n frozen = true;\n },\n };\n}\n\nfunction lastingSettings(\n settings: AgentSettings,\n hold: <T>(state: ReplicatedState<T>) => ReplicatedState<T>\n): AgentSettings {\n return {\n state: hold(settings.state),\n connect: (providerId, secret, region, context) =>\n settings.connect(providerId, secret, region, context),\n disconnect: (providerId, context) => settings.disconnect(providerId, context),\n signIn: (providerId, context) => settings.signIn(providerId, context),\n };\n}\n\nfunction attached(client: Client): Promise<void> {\n return new Promise((resolve) => {\n const stop = client.onAttachmentChange(() => {\n stop();\n resolve();\n });\n });\n}\n\nexport async function connectAgent(\n endpoint: PortEndpoint,\n options: { context?: Context } = {}\n): Promise<AgentConnection> {\n const context = options.context ?? BACKGROUND_CONTEXT;\n const channel = new PortChannel(endpoint);\n const serverId = await channel.hello;\n const client = await Client.connect({ serverId, transportFactory: channel.transport });\n const server = createRemoteServiceBinding({\n services: [AgentSessions],\n transport: createClientServiceTransport(client, () => ({ serverId })),\n bound: true,\n });\n const ready = attached(client);\n await server.use(AgentSessions).attach(AGENT_SESSION, context);\n await ready;\n const session = createRemoteServiceBinding({\n services: [AgentControl, AgentTranscript, AgentSettings, AgentRemoteService],\n transport: createClientServiceTransport(client, () => client.attachment),\n bound: true,\n });\n const control = session.use(AgentControl);\n const {\n state: transcript,\n deliveries,\n agents,\n views,\n commands,\n sprinkles,\n memories,\n memoryScopes,\n frozen,\n changes,\n } = session.use(AgentTranscript);\n const settings = session.use(AgentSettings);\n const remote = session.use(AgentRemoteService);\n await session.ready(context);\n const held: Lasting<unknown>[] = [];\n const hold = <T>(state: ReplicatedState<T>): ReplicatedState<T> => {\n const kept = lasting(state);\n held.push(kept as Lasting<unknown>);\n return kept.state;\n };\n const freeze = () => {\n for (const kept of held) kept.freeze();\n };\n const closed = channel.ended.then((reason) => {\n freeze();\n return reason;\n });\n return {\n serverId,\n control,\n transcript: hold(transcript),\n deliveries: hold(deliveries),\n agents: hold(agents),\n views: hold(views),\n commands: hold(commands),\n sprinkles: hold(sprinkles),\n memories: hold(memories),\n memoryScopes: hold(memoryScopes),\n frozen: hold(frozen),\n changes: hold(changes),\n settings: lastingSettings(settings, hold),\n remote,\n closed,\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 freeze();\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": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/host.js
CHANGED
|
@@ -11,9 +11,11 @@ import {
|
|
|
11
11
|
import { createAgentControl } from "./control.js";
|
|
12
12
|
import { trackDeliveries } from "./deliveries.js";
|
|
13
13
|
import { PortListener } from "./listener.js";
|
|
14
|
+
import { createRemote } from "./remote/host.js";
|
|
14
15
|
import {
|
|
15
16
|
AGENT_SESSION,
|
|
16
17
|
AgentControl,
|
|
18
|
+
AgentRemoteService,
|
|
17
19
|
AgentSessions,
|
|
18
20
|
AgentSettings,
|
|
19
21
|
AgentTranscript
|
|
@@ -94,24 +96,34 @@ async function hostAgent(agent, options = {}) {
|
|
|
94
96
|
const provider = new RemoteServiceProvider([
|
|
95
97
|
{ service: AgentControl, mode: "singleton" },
|
|
96
98
|
{ service: AgentTranscript, mode: "singleton" },
|
|
97
|
-
{ service: AgentSettings, mode: "singleton" }
|
|
99
|
+
{ service: AgentSettings, mode: "singleton" },
|
|
100
|
+
{ service: AgentRemoteService, mode: "singleton" }
|
|
98
101
|
]);
|
|
99
102
|
const deliveries = trackDeliveries(agent.harness, state, context);
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
options.freezer,
|
|
112
|
-
options.changes
|
|
113
|
-
)
|
|
103
|
+
const control = createAgentControl(
|
|
104
|
+
agent.harness,
|
|
105
|
+
agent,
|
|
106
|
+
deliveries,
|
|
107
|
+
options.licks,
|
|
108
|
+
options.scoops,
|
|
109
|
+
options.skills,
|
|
110
|
+
options.sprinkles,
|
|
111
|
+
options.memory,
|
|
112
|
+
options.freezer,
|
|
113
|
+
options.changes
|
|
114
114
|
);
|
|
115
|
+
provider.provide(AgentControl, control);
|
|
116
|
+
const remote = createRemote({
|
|
117
|
+
harness: agent.harness,
|
|
118
|
+
send: (request, using) => control.send(request, using),
|
|
119
|
+
async conversation(agentId, using) {
|
|
120
|
+
if (!agentId) return agent.cone();
|
|
121
|
+
const found = await agent.agents.conversation(agentId, using);
|
|
122
|
+
if (!found) throw new Error(`There is no agent ${agentId}.`);
|
|
123
|
+
return found;
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
provider.provide(AgentRemoteService, remote);
|
|
115
127
|
provider.provide(AgentTranscript, {
|
|
116
128
|
state,
|
|
117
129
|
deliveries: deliveries.state,
|
|
@@ -142,6 +154,7 @@ async function hostAgent(agent, options = {}) {
|
|
|
142
154
|
connect: (endpoint) => listener.connect(endpoint),
|
|
143
155
|
async close() {
|
|
144
156
|
await server.close();
|
|
157
|
+
await remote.dispose();
|
|
145
158
|
provider.dispose();
|
|
146
159
|
deliveries.dispose();
|
|
147
160
|
transcript.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 {\n AGENT_SESSION,\n AgentControl,\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 ]);\n const deliveries = trackDeliveries(agent.harness, state, context);\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 { 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": "AAAA;AAAA,EAEE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,0BAA0B;AAEnC;AAAA,EAIE;AAAA,EAEA;AAAA,OACK;AAIP,SAAS,0BAA2D;AACpE,SAAS,uBAAuB;AAEhC,SAAS,oBAAoB;AAI7B,SAAS,oBAAoB;AAC7B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAIP,SAAS,kBAAkB;AAU3B,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,MAAM,aAA4B;AAAA,EAChC,OAAO,gBAAgB,EAAE,QAAQ,CAAC,GAAG,UAAU,CAAC,EAAE,CAAC;AAAA,EACnD,SAAS,YAAY;AACnB,UAAM,IAAI,MAAM,4BAA4B;AAAA,EAC9C;AAAA,EACA,YAAY,YAAY;AAAA,EAAC;AAAA,EACzB,QAAQ,YAAY;AACtB;AAEA,eAAe,OAAO,OAAc,SAAkB;AACpD,MAAI,WAAW,MAAM,MAAM,KAAK,EAAE,UAAU,OAAO;AACnD,QAAM,QAAQ,gBAAkC,SAAS,KAAK;AAC9D,QAAM,SAAS,CAAC,OAAyB,SAAkB,MAAM,QAAQ,MAAM,KAAK;AACpF,MAAI,MAAM,SAAS,UAAU,MAAM;AACnC,QAAM,OAAO,MAAM,OAAO,OAAO,iBAAiB;AAChD,UAAM,OAAO,MAAM,aAAa,UAAU,OAAO;AACjD,QAAI;AACJ,aAAS,QAAQ;AACjB,eAAW;AACX,UAAM,QAAQ,SAAS,KAAK,KAAK;AACjC,UAAM,KAAK,UAAU,MAAM;AAAA,EAC7B,CAAC;AACD,SAAO;AAAA,IACL;AAAA,IACA,UAAU;AACR,WAAK;AACL,UAAI;AACJ,eAAS,QAAQ;AAAA,IACnB;AAAA,EACF;AACF;AAEA,eAAsB,UACpB,OACA,UAYI,CAAC,GACe;AACpB,QAAM,UAAU,QAAQ,WAAW;AACnC,QAAM,WAAW,QAAQ,YAAY,OAAO,WAAW;AACvD,QAAM,aAAa,MAAM,OAAO,OAAO,OAAO;AAC9C,QAAM,EAAE,MAAM,IAAI;AAClB,QAAM,UAAU,MAAM,WAAW,OAAO,OAAO;AAC/C,QAAM,QAAQ,MAAM,QAAQ,SAAS,MAAM;AAC3C,QAAM,UAAU,CAAC,MAAM,UAAU,KAAK,GAAG,QAAQ,MAAM,UAAU,KAAK,CAAC;AACvE,QAAM,WAAW,IAAI,sBAAsB;AAAA,IACzC,EAAE,SAAS,cAAc,MAAM,YAAY;AAAA,IAC3C,EAAE,SAAS,iBAAiB,MAAM,YAAY;AAAA,IAC9C,EAAE,SAAS,eAAe,MAAM,YAAY;AAAA,IAC5C,EAAE,SAAS,oBAAoB,MAAM,YAAY;AAAA,EACnD,CAAC;AACD,QAAM,aAAa,gBAAgB,MAAM,SAAS,OAAO,OAAO;AAChE,QAAM,UAAU;AAAA,IACd,MAAM;AAAA,IACN;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AACA,WAAS,QAAQ,cAAc,OAAO;AACtC,QAAM,SAAS,aAAa;AAAA,IAC1B,SAAS,MAAM;AAAA,IACf,MAAM,CAAC,SAAS,UAAU,QAAQ,KAAK,SAAS,KAAK;AAAA,IACrD,MAAM,aAAa,SAAS,OAAO;AACjC,UAAI,CAAC,QAAS,QAAO,MAAM,KAAK;AAChC,YAAM,QAAQ,MAAM,MAAM,OAAO,aAAa,SAAS,KAAK;AAC5D,UAAI,CAAC,MAAO,OAAM,IAAI,MAAM,qBAAqB,OAAO,GAAG;AAC3D,aAAO;AAAA,IACT;AAAA,EACF,CAAC;AACD,WAAS,QAAQ,oBAAoB,MAAM;AAC3C,WAAS,QAAQ,iBAAiB;AAAA,IAChC;AAAA,IACA,YAAY,WAAW;AAAA,IACvB,QAAQ,QAAQ;AAAA,IAChB,OAAO,QAAQ;AAAA,IACf,UAAU,QAAQ,QAAQ,YAAY,gBAA2B,CAAC,CAAC;AAAA,IACnE,WAAW,QAAQ,WAAW,aAAa,gBAA4B,CAAC,CAAC;AAAA,IACzE,UAAU,QAAQ,QAAQ,YAAY,gBAA+B,CAAC,CAAC;AAAA,IACvE,cAAc,QAAQ,QAAQ,UAAU,gBAA+B,CAAC,CAAC;AAAA,IACzE,QAAQ,QAAQ,SAAS,UAAU,gBAA8B,CAAC,CAAC;AAAA,IACnE,SACE,QAAQ,SAAS,QAAQ,gBAA6B,EAAE,aAAa,MAAM,SAAS,CAAC,EAAE,CAAC;AAAA,EAC5F,CAAC;AACD,WAAS,QAAQ,eAAe,QAAQ,YAAY,UAAU;AAC9D,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,YAAM,OAAO,QAAQ;AACrB,eAAS,QAAQ;AACjB,iBAAW,QAAQ;AACnB,iBAAW,QAAQ;AACnB,cAAQ,QAAQ;AAChB,iBAAW,OAAO,QAAS,KAAI;AAC/B,cAAQ,SAAS,MAAM;AACvB,YAAM,QAAQ,SAAS,MAAM,OAAO;AACpC,YAAM,QAAQ,QAAQ,MAAM,OAAO;AACnC,YAAM,QAAQ,WAAW,MAAM,OAAO;AACtC,YAAM,QAAQ,QAAQ,MAAM,OAAO;AACnC,YAAM,QAAQ,KAAK,MAAM,OAAO;AAChC,YAAM,QAAQ,QAAQ,QAAQ,MAAM,OAAO;AAC3C,YAAM,QAAQ,OAAO,QAAQ,MAAM,OAAO;AAC1C,YAAM,MAAM,MAAM;AAAA,IACpB;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/page.js
CHANGED
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';\n"],
|
|
5
|
-
"mappings": "AAAA,SAA+B,oBAAoB;AACnD;AAAA,EACE;AAAA,EAKA;AAAA,OACK;",
|
|
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;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
|
2
|
+
const POLL_MS = 25e3;
|
|
3
|
+
function failure(code, message) {
|
|
4
|
+
return Object.assign(new Error(`${code}: ${message}`), { code });
|
|
5
|
+
}
|
|
6
|
+
function opened(result) {
|
|
7
|
+
if (!result.ok) throw failure(result.code, result.message);
|
|
8
|
+
return result;
|
|
9
|
+
}
|
|
10
|
+
function events(remote, stream) {
|
|
11
|
+
return {
|
|
12
|
+
[Symbol.asyncIterator]() {
|
|
13
|
+
let buffer = [];
|
|
14
|
+
let done = false;
|
|
15
|
+
let closed = false;
|
|
16
|
+
const close = async () => {
|
|
17
|
+
if (closed) return;
|
|
18
|
+
closed = true;
|
|
19
|
+
await remote.close(stream, BACKGROUND_CONTEXT).catch(() => void 0);
|
|
20
|
+
};
|
|
21
|
+
return {
|
|
22
|
+
async next() {
|
|
23
|
+
while (!buffer.length && !done && !closed) {
|
|
24
|
+
const batch = await remote.next(stream, POLL_MS, BACKGROUND_CONTEXT);
|
|
25
|
+
buffer = batch.events;
|
|
26
|
+
done = batch.done;
|
|
27
|
+
}
|
|
28
|
+
const value = buffer.shift();
|
|
29
|
+
if (value) return { value, done: false };
|
|
30
|
+
await close();
|
|
31
|
+
return { value: void 0, done: true };
|
|
32
|
+
},
|
|
33
|
+
async return() {
|
|
34
|
+
await close();
|
|
35
|
+
return { value: void 0, done: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function remoteControl(connection, options = {}) {
|
|
42
|
+
const remote = connection.remote;
|
|
43
|
+
const agent = () => options.agent?.() ?? null;
|
|
44
|
+
const need = () => {
|
|
45
|
+
if (!remote) throw failure("ENOSYS", "this agent has no remote control");
|
|
46
|
+
return remote;
|
|
47
|
+
};
|
|
48
|
+
return {
|
|
49
|
+
async prompt({ text, whenBusy = "followUp", thinking = false }) {
|
|
50
|
+
const service = need();
|
|
51
|
+
const result = opened(
|
|
52
|
+
await service.prompt({ agentId: agent(), text, whenBusy, thinking }, BACKGROUND_CONTEXT)
|
|
53
|
+
);
|
|
54
|
+
return {
|
|
55
|
+
delivered: result.delivered,
|
|
56
|
+
events: events(service, result.stream)
|
|
57
|
+
};
|
|
58
|
+
},
|
|
59
|
+
async watch({ since = null, thinking = false } = {}) {
|
|
60
|
+
const service = need();
|
|
61
|
+
const result = opened(
|
|
62
|
+
await service.watch({ agentId: agent(), since, thinking }, BACKGROUND_CONTEXT)
|
|
63
|
+
);
|
|
64
|
+
return {
|
|
65
|
+
head: result.head ?? "",
|
|
66
|
+
...result.gap ? { gap: true } : {},
|
|
67
|
+
events: events(service, result.stream)
|
|
68
|
+
};
|
|
69
|
+
},
|
|
70
|
+
async abort() {
|
|
71
|
+
return need().abort(agent(), BACKGROUND_CONTEXT);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export {
|
|
76
|
+
POLL_MS,
|
|
77
|
+
remoteControl
|
|
78
|
+
};
|
|
79
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/remote/client.ts"],
|
|
4
|
+
"sourcesContent": ["import { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { AgentConnection } from '../client.ts';\nimport type { Delivered, SendMode } from '../services.ts';\nimport type { RemoteEvent } from './events.ts';\nimport type { AgentRemote, RemoteOpened } from './host.ts';\n\nexport type { RemoteEvent } from './events.ts';\n\nexport const POLL_MS = 25000;\n\nexport interface RemoteOptions {\n agent?: () => string | null;\n}\n\nexport interface RemoteControl {\n prompt(request: {\n text: string;\n whenBusy?: SendMode;\n thinking?: boolean;\n }): Promise<{ delivered: Delivered; events: AsyncIterable<RemoteEvent> }>;\n watch(request?: {\n since?: string | null;\n thinking?: boolean;\n }): Promise<{ head: string; gap?: true; events: AsyncIterable<RemoteEvent> }>;\n abort(): Promise<{ aborted: boolean }>;\n}\n\nfunction failure(code: string, message: string): Error {\n return Object.assign(new Error(`${code}: ${message}`), { code });\n}\n\nfunction opened(result: RemoteOpened): Extract<RemoteOpened, { ok: true }> {\n if (!result.ok) throw failure(result.code, result.message);\n return result;\n}\n\nfunction events(remote: AgentRemote, stream: string): AsyncIterable<RemoteEvent> {\n return {\n [Symbol.asyncIterator]() {\n let buffer: RemoteEvent[] = [];\n let done = false;\n let closed = false;\n const close = async () => {\n if (closed) return;\n closed = true;\n await remote.close(stream, BACKGROUND_CONTEXT).catch(() => undefined);\n };\n return {\n async next(): Promise<IteratorResult<RemoteEvent>> {\n while (!buffer.length && !done && !closed) {\n const batch = await remote.next(stream, POLL_MS, BACKGROUND_CONTEXT);\n buffer = batch.events;\n done = batch.done;\n }\n const value = buffer.shift();\n if (value) return { value, done: false };\n await close();\n return { value: undefined, done: true };\n },\n async return(): Promise<IteratorResult<RemoteEvent>> {\n await close();\n return { value: undefined, done: true };\n },\n };\n },\n };\n}\n\nexport function remoteControl(\n connection: Pick<AgentConnection, 'remote'>,\n options: RemoteOptions = {}\n): RemoteControl {\n const remote = connection.remote;\n const agent = () => options.agent?.() ?? null;\n const need = (): AgentRemote => {\n if (!remote) throw failure('ENOSYS', 'this agent has no remote control');\n return remote;\n };\n return {\n async prompt({ text, whenBusy = 'followUp', thinking = false }) {\n const service = need();\n const result = opened(\n await service.prompt({ agentId: agent(), text, whenBusy, thinking }, BACKGROUND_CONTEXT)\n );\n return {\n delivered: result.delivered as Delivered,\n events: events(service, result.stream),\n };\n },\n async watch({ since = null, thinking = false } = {}) {\n const service = need();\n const result = opened(\n await service.watch({ agentId: agent(), since, thinking }, BACKGROUND_CONTEXT)\n );\n return {\n head: result.head ?? '',\n ...(result.gap ? { gap: true as const } : {}),\n events: events(service, result.stream),\n };\n },\n async abort() {\n return need().abort(agent(), BACKGROUND_CONTEXT);\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,0BAA0B;AAQ5B,MAAM,UAAU;AAmBvB,SAAS,QAAQ,MAAc,SAAwB;AACrD,SAAO,OAAO,OAAO,IAAI,MAAM,GAAG,IAAI,KAAK,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC;AACjE;AAEA,SAAS,OAAO,QAA2D;AACzE,MAAI,CAAC,OAAO,GAAI,OAAM,QAAQ,OAAO,MAAM,OAAO,OAAO;AACzD,SAAO;AACT;AAEA,SAAS,OAAO,QAAqB,QAA4C;AAC/E,SAAO;AAAA,IACL,CAAC,OAAO,aAAa,IAAI;AACvB,UAAI,SAAwB,CAAC;AAC7B,UAAI,OAAO;AACX,UAAI,SAAS;AACb,YAAM,QAAQ,YAAY;AACxB,YAAI,OAAQ;AACZ,iBAAS;AACT,cAAM,OAAO,MAAM,QAAQ,kBAAkB,EAAE,MAAM,MAAM,MAAS;AAAA,MACtE;AACA,aAAO;AAAA,QACL,MAAM,OAA6C;AACjD,iBAAO,CAAC,OAAO,UAAU,CAAC,QAAQ,CAAC,QAAQ;AACzC,kBAAM,QAAQ,MAAM,OAAO,KAAK,QAAQ,SAAS,kBAAkB;AACnE,qBAAS,MAAM;AACf,mBAAO,MAAM;AAAA,UACf;AACA,gBAAM,QAAQ,OAAO,MAAM;AAC3B,cAAI,MAAO,QAAO,EAAE,OAAO,MAAM,MAAM;AACvC,gBAAM,MAAM;AACZ,iBAAO,EAAE,OAAO,QAAW,MAAM,KAAK;AAAA,QACxC;AAAA,QACA,MAAM,SAA+C;AACnD,gBAAM,MAAM;AACZ,iBAAO,EAAE,OAAO,QAAW,MAAM,KAAK;AAAA,QACxC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,cACd,YACA,UAAyB,CAAC,GACX;AACf,QAAM,SAAS,WAAW;AAC1B,QAAM,QAAQ,MAAM,QAAQ,QAAQ,KAAK;AACzC,QAAM,OAAO,MAAmB;AAC9B,QAAI,CAAC,OAAQ,OAAM,QAAQ,UAAU,kCAAkC;AACvE,WAAO;AAAA,EACT;AACA,SAAO;AAAA,IACL,MAAM,OAAO,EAAE,MAAM,WAAW,YAAY,WAAW,MAAM,GAAG;AAC9D,YAAM,UAAU,KAAK;AACrB,YAAM,SAAS;AAAA,QACb,MAAM,QAAQ,OAAO,EAAE,SAAS,MAAM,GAAG,MAAM,UAAU,SAAS,GAAG,kBAAkB;AAAA,MACzF;AACA,aAAO;AAAA,QACL,WAAW,OAAO;AAAA,QAClB,QAAQ,OAAO,SAAS,OAAO,MAAM;AAAA,MACvC;AAAA,IACF;AAAA,IACA,MAAM,MAAM,EAAE,QAAQ,MAAM,WAAW,MAAM,IAAI,CAAC,GAAG;AACnD,YAAM,UAAU,KAAK;AACrB,YAAM,SAAS;AAAA,QACb,MAAM,QAAQ,MAAM,EAAE,SAAS,MAAM,GAAG,OAAO,SAAS,GAAG,kBAAkB;AAAA,MAC/E;AACA,aAAO;AAAA,QACL,MAAM,OAAO,QAAQ;AAAA,QACrB,GAAI,OAAO,MAAM,EAAE,KAAK,KAAc,IAAI,CAAC;AAAA,QAC3C,QAAQ,OAAO,SAAS,OAAO,MAAM;AAAA,MACvC;AAAA,IACF;AAAA,IACA,MAAM,QAAQ;AACZ,aAAO,KAAK,EAAE,MAAM,MAAM,GAAG,kBAAkB;AAAA,IACjD;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { entryLick } from "../licks/state.js";
|
|
2
|
+
import { redactSecrets } from "../memory/format.js";
|
|
3
|
+
const SUMMARY = 200;
|
|
4
|
+
function clip(text, limit = SUMMARY) {
|
|
5
|
+
const flat = redactSecrets(text).text.replace(/\s+/g, " ").trim();
|
|
6
|
+
return flat.length > limit ? `${flat.slice(0, limit - 1)}\u2026` : flat;
|
|
7
|
+
}
|
|
8
|
+
function textOf(content) {
|
|
9
|
+
if (typeof content === "string") return content;
|
|
10
|
+
return (content ?? []).map((block) => block.type === "text" ? block.text ?? "" : "").join("");
|
|
11
|
+
}
|
|
12
|
+
const SUBJECTS = ["command", "path", "pattern", "url", "query", "code", "name"];
|
|
13
|
+
function toolSummary(name, args) {
|
|
14
|
+
const subject = SUBJECTS.map((key) => args?.[key]).find((value) => typeof value === "string");
|
|
15
|
+
const detail = typeof subject === "string" ? subject : JSON.stringify(args ?? {});
|
|
16
|
+
return clip(`${name} ${detail}`);
|
|
17
|
+
}
|
|
18
|
+
function resultSummary(message) {
|
|
19
|
+
const text = textOf(message.content);
|
|
20
|
+
if (message.isError)
|
|
21
|
+
return clip(
|
|
22
|
+
text.split("\n").find((line) => line.trim() && !/^<\/?harness>$/.test(line.trim())) ?? "failed"
|
|
23
|
+
);
|
|
24
|
+
const lines = text ? text.split("\n").length : 0;
|
|
25
|
+
return lines === 1 ? "1 line" : `${lines} lines`;
|
|
26
|
+
}
|
|
27
|
+
function keyOf(timestamp, at) {
|
|
28
|
+
return at > 0 ? `a${timestamp ?? 0}~${at}` : `a${timestamp ?? 0}`;
|
|
29
|
+
}
|
|
30
|
+
function reasonOf(stopReason) {
|
|
31
|
+
if (stopReason === "aborted") return "aborted";
|
|
32
|
+
return stopReason === "error" ? "error" : "stop";
|
|
33
|
+
}
|
|
34
|
+
class Mapper {
|
|
35
|
+
#thinking;
|
|
36
|
+
#emitted = /* @__PURE__ */ new Map();
|
|
37
|
+
#seen = /* @__PURE__ */ new Set();
|
|
38
|
+
#current;
|
|
39
|
+
#previous;
|
|
40
|
+
#ending = [];
|
|
41
|
+
#stamps = /* @__PURE__ */ new Map();
|
|
42
|
+
reason = "stop";
|
|
43
|
+
error;
|
|
44
|
+
constructor(thinking) {
|
|
45
|
+
this.#thinking = thinking;
|
|
46
|
+
}
|
|
47
|
+
#key(message, entry) {
|
|
48
|
+
const stamp = message?.timestamp ?? 0;
|
|
49
|
+
const list = this.#stamps.get(stamp) ?? [];
|
|
50
|
+
this.#stamps.set(stamp, list);
|
|
51
|
+
if (entry === void 0) return keyOf(stamp, list.length);
|
|
52
|
+
if (!list.includes(entry)) list.push(entry);
|
|
53
|
+
return keyOf(stamp, list.indexOf(entry));
|
|
54
|
+
}
|
|
55
|
+
#delta(t, id, full) {
|
|
56
|
+
if (t === "thinking" && !this.#thinking) return [];
|
|
57
|
+
const done = this.#emitted.get(id) ?? 0;
|
|
58
|
+
if (full.length <= done) return [];
|
|
59
|
+
this.#emitted.set(id, full.length);
|
|
60
|
+
return [{ t, id, delta: full.slice(done) }];
|
|
61
|
+
}
|
|
62
|
+
partial(message) {
|
|
63
|
+
if (!message || !Array.isArray(message.content)) return [];
|
|
64
|
+
const key = this.#key(message);
|
|
65
|
+
this.#current = key;
|
|
66
|
+
return message.content.flatMap((block, at) => {
|
|
67
|
+
if (block.type === "text") return this.#delta("text", `${key}.${at}`, block.text ?? "");
|
|
68
|
+
if (block.type === "thinking")
|
|
69
|
+
return this.#delta("thinking", `${key}.${at}`, block.thinking ?? "");
|
|
70
|
+
return [];
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
#assistant(message, entry) {
|
|
74
|
+
const key = this.#key(message, entry.id);
|
|
75
|
+
const blocks = Array.isArray(message.content) ? message.content : [];
|
|
76
|
+
const out = blocks.flatMap((block, at) => {
|
|
77
|
+
const id = `${key}.${at}`;
|
|
78
|
+
if (block.type === "text") return this.#delta("text", id, block.text ?? "");
|
|
79
|
+
if (block.type === "thinking") return this.#delta("thinking", id, block.thinking ?? "");
|
|
80
|
+
if (block.type !== "toolCall") return [];
|
|
81
|
+
const name = block.name ?? "tool";
|
|
82
|
+
return [
|
|
83
|
+
{ t: "tool", id, call: block.id ?? "", name, summary: toolSummary(name, block.arguments) }
|
|
84
|
+
];
|
|
85
|
+
});
|
|
86
|
+
if (typeof message.content === "string")
|
|
87
|
+
out.push(...this.#delta("text", `${key}.0`, message.content));
|
|
88
|
+
this.reason = reasonOf(message.stopReason);
|
|
89
|
+
this.error = message.stopReason === "error" ? clip(message.errorMessage ?? "error", 1e3) : void 0;
|
|
90
|
+
if (this.error) out.push({ t: "error", id: String(entry.id), message: this.error });
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
#delivered(entry) {
|
|
94
|
+
if (entry.byTaskId === void 0) return "run";
|
|
95
|
+
return this.#previous === "pi.tool-result" ? "steer" : "follow-up";
|
|
96
|
+
}
|
|
97
|
+
entry(entry, delivered = this.#delivered(entry)) {
|
|
98
|
+
if (this.#seen.has(entry.id)) return [];
|
|
99
|
+
this.#seen.add(entry.id);
|
|
100
|
+
this.#previous = entry.kind;
|
|
101
|
+
const id = String(entry.id);
|
|
102
|
+
const message = entry.model?.[0];
|
|
103
|
+
if (entry.kind === "pi.user" && message) {
|
|
104
|
+
const lick = entryLick(entry);
|
|
105
|
+
if (lick) return [{ t: "notice", id, text: clip(`lick ${lick.channel}: ${lick.title}`) }];
|
|
106
|
+
return [{ t: "user", id, text: redactSecrets(textOf(message.content)).text, delivered }];
|
|
107
|
+
}
|
|
108
|
+
if (entry.kind === "pi.assistant" && message) return this.#assistant(message, entry);
|
|
109
|
+
if (entry.kind === "pi.tool-result" && message)
|
|
110
|
+
return [
|
|
111
|
+
{
|
|
112
|
+
t: "tool-result",
|
|
113
|
+
id,
|
|
114
|
+
call: message.toolCallId ?? "",
|
|
115
|
+
ok: !message.isError,
|
|
116
|
+
summary: resultSummary(message)
|
|
117
|
+
}
|
|
118
|
+
];
|
|
119
|
+
if (entry.kind === "pi.compaction") return [{ t: "notice", id, text: "compacted" }];
|
|
120
|
+
if (entry.kind === "slicc.rewound") return [{ t: "notice", id, text: "rewound" }];
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
#ended(reason) {
|
|
124
|
+
if (!this.#ending.length) return [];
|
|
125
|
+
this.#ending = [];
|
|
126
|
+
this.reason = reason;
|
|
127
|
+
return [{ t: "run", state: "end", reason }];
|
|
128
|
+
}
|
|
129
|
+
#settled(record) {
|
|
130
|
+
if (!this.#ending.includes(record.id) || ["queued", "placed"].includes(record.status))
|
|
131
|
+
return [];
|
|
132
|
+
if (record.status !== "unanswered") return this.#ended(this.reason);
|
|
133
|
+
if (record.reason === "aborted") return this.#ended("aborted");
|
|
134
|
+
this.error ??= clip(String(record.reason ?? "error"), 1e3);
|
|
135
|
+
return this.#ended("error");
|
|
136
|
+
}
|
|
137
|
+
last() {
|
|
138
|
+
return Math.max(0, ...this.#seen);
|
|
139
|
+
}
|
|
140
|
+
event(event) {
|
|
141
|
+
switch (event.type) {
|
|
142
|
+
case "run_start": {
|
|
143
|
+
const ended = this.#ended(this.reason);
|
|
144
|
+
this.reason = "stop";
|
|
145
|
+
this.error = void 0;
|
|
146
|
+
return [...ended, { t: "run", state: "start" }];
|
|
147
|
+
}
|
|
148
|
+
case "run_end":
|
|
149
|
+
this.#ending = event.inputs;
|
|
150
|
+
return [];
|
|
151
|
+
case "submission":
|
|
152
|
+
return this.#settled(event.record);
|
|
153
|
+
case "message_start":
|
|
154
|
+
if (event.message.role === "assistant")
|
|
155
|
+
this.#current = this.#key(event.message);
|
|
156
|
+
return [];
|
|
157
|
+
case "message_update":
|
|
158
|
+
return event.changes.flatMap((change) => {
|
|
159
|
+
if (change.type !== "text_delta" && change.type !== "thinking_delta") return [];
|
|
160
|
+
const id = `${this.#current}.${change.contentIndex}`;
|
|
161
|
+
const t = change.type === "text_delta" ? "text" : "thinking";
|
|
162
|
+
const done = this.#emitted.get(id) ?? 0;
|
|
163
|
+
if (t === "thinking" && !this.#thinking) return [];
|
|
164
|
+
this.#emitted.set(id, done + change.delta.length);
|
|
165
|
+
return [{ t, id, delta: change.delta }];
|
|
166
|
+
});
|
|
167
|
+
case "message_end":
|
|
168
|
+
return this.entry(event.entry);
|
|
169
|
+
case "tool_execution_end":
|
|
170
|
+
return event.entry ? this.entry(event.entry) : [
|
|
171
|
+
{
|
|
172
|
+
t: "tool-result",
|
|
173
|
+
id: `x${event.toolCallId}`,
|
|
174
|
+
call: event.toolCallId,
|
|
175
|
+
ok: false,
|
|
176
|
+
summary: "the tool did not finish"
|
|
177
|
+
}
|
|
178
|
+
];
|
|
179
|
+
case "auto_retry_start":
|
|
180
|
+
return [
|
|
181
|
+
{
|
|
182
|
+
t: "notice",
|
|
183
|
+
id: `retry-${event.at}`,
|
|
184
|
+
text: clip(`retrying (attempt ${event.attempt}): ${event.errorMessage}`)
|
|
185
|
+
}
|
|
186
|
+
];
|
|
187
|
+
case "compaction_start":
|
|
188
|
+
return [{ t: "notice", id: `compaction-${event.taskId}`, text: "compacting" }];
|
|
189
|
+
case "task_failed":
|
|
190
|
+
return [{ t: "error", id: `task-${event.taskId}`, message: clip(event.message, 1e3) }];
|
|
191
|
+
default:
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function replayOf(entries, partial, thinking) {
|
|
197
|
+
const mapper = new Mapper(thinking);
|
|
198
|
+
const out = [];
|
|
199
|
+
for (const entry of entries) out.push(...mapper.entry(entry));
|
|
200
|
+
out.push(...mapper.partial(partial));
|
|
201
|
+
return out;
|
|
202
|
+
}
|
|
203
|
+
function idOf(event) {
|
|
204
|
+
return "id" in event ? event.id : void 0;
|
|
205
|
+
}
|
|
206
|
+
export {
|
|
207
|
+
Mapper,
|
|
208
|
+
SUMMARY,
|
|
209
|
+
clip,
|
|
210
|
+
idOf,
|
|
211
|
+
keyOf,
|
|
212
|
+
replayOf,
|
|
213
|
+
resultSummary,
|
|
214
|
+
toolSummary
|
|
215
|
+
};
|
|
216
|
+
//# sourceMappingURL=events.js.map
|