@ai-ecoverse/slicc-agent 3.6.0 → 3.8.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 +20 -3
- package/bin/agent +76 -0
- package/dist/agent.js +7 -14
- package/dist/agent.js.map +2 -2
- package/dist/agents.js +133 -0
- package/dist/agents.js.map +7 -0
- package/dist/client.js +3 -1
- package/dist/client.js.map +2 -2
- package/dist/cone.js +2 -16
- package/dist/cone.js.map +2 -2
- package/dist/control.js +68 -10
- package/dist/control.js.map +2 -2
- package/dist/host.js +11 -2
- package/dist/host.js.map +2 -2
- package/dist/kernel/env.js +30 -3
- package/dist/kernel/env.js.map +2 -2
- package/dist/kernel/exec.js +2 -1
- package/dist/kernel/exec.js.map +2 -2
- package/dist/kernel/groups.js +31 -0
- package/dist/kernel/groups.js.map +7 -0
- package/dist/licks/index.js +16 -1
- package/dist/licks/index.js.map +2 -2
- package/dist/licks/lick.js.map +1 -1
- package/dist/licks/licks.js +12 -1
- package/dist/licks/licks.js.map +2 -2
- package/dist/main.js +38 -9
- package/dist/main.js.map +2 -2
- package/dist/roles/frontmatter.js +59 -0
- package/dist/roles/frontmatter.js.map +7 -0
- package/dist/roles/roles.js +204 -0
- package/dist/roles/roles.js.map +7 -0
- package/dist/scoops/cli.js +391 -0
- package/dist/scoops/cli.js.map +7 -0
- package/dist/scoops/extension.js +330 -0
- package/dist/scoops/extension.js.map +7 -0
- package/dist/scoops/from.js +5 -0
- package/dist/scoops/from.js.map +7 -0
- package/dist/scoops/identity.js +33 -0
- package/dist/scoops/identity.js.map +7 -0
- package/dist/scoops/index.js +120 -0
- package/dist/scoops/index.js.map +7 -0
- package/dist/scoops/requests.js +136 -0
- package/dist/scoops/requests.js.map +7 -0
- package/dist/scoops/service.js +419 -0
- package/dist/scoops/service.js.map +7 -0
- package/dist/scoops/sync.js +253 -0
- package/dist/scoops/sync.js.map +7 -0
- package/dist/scoops/visible.js +15 -0
- package/dist/scoops/visible.js.map +7 -0
- package/dist/services.js.map +2 -2
- package/dist/spectrum/agent-port.js +137 -54
- package/dist/spectrum/agent-port.js.map +2 -2
- package/dist/spectrum/messages.js +39 -16
- package/dist/spectrum/messages.js.map +2 -2
- package/dist/types/agent.d.ts +3 -1
- package/dist/types/agents.d.ts +54 -0
- package/dist/types/client.d.ts +3 -1
- package/dist/types/cone.d.ts +1 -3
- package/dist/types/control.d.ts +7 -1
- package/dist/types/host.d.ts +2 -1
- package/dist/types/kernel/client.d.ts +4 -0
- package/dist/types/kernel/env.d.ts +13 -2
- package/dist/types/kernel/exec.d.ts +2 -2
- package/dist/types/kernel/groups.d.ts +7 -0
- package/dist/types/licks/index.d.ts +5 -0
- package/dist/types/licks/lick.d.ts +1 -1
- package/dist/types/licks/licks.d.ts +2 -1
- package/dist/types/main.d.ts +3 -0
- package/dist/types/roles/frontmatter.d.ts +7 -0
- package/dist/types/roles/roles.d.ts +48 -0
- package/dist/types/scoops/cli.d.ts +66 -0
- package/dist/types/scoops/extension.d.ts +79 -0
- package/dist/types/scoops/from.d.ts +5 -0
- package/dist/types/scoops/identity.d.ts +6 -0
- package/dist/types/scoops/index.d.ts +37 -0
- package/dist/types/scoops/requests.d.ts +32 -0
- package/dist/types/scoops/service.d.ts +119 -0
- package/dist/types/scoops/sync.d.ts +22 -0
- package/dist/types/scoops/visible.d.ts +4 -0
- package/dist/types/services.d.ts +30 -0
- package/dist/types/spectrum/agent-port.d.ts +13 -11
- package/dist/types/spectrum/messages.d.ts +2 -1
- package/dist/types/views.d.ts +12 -0
- package/dist/views.js +89 -0
- package/dist/views.js.map +7 -0
- package/package.json +5 -3
- package/packages/vfs-root/agents/delegate.md +8 -0
- package/packages/vfs-root/agents/oracle.md +25 -0
- package/packages/vfs-root/agents/reviewer.md +23 -0
- package/packages/vfs-root/agents/scout.md +30 -0
- package/packages/vfs-root/agents/worker.md +23 -0
- package/packages/vfs-root/skills/agent/SKILL.md +72 -0
package/README.md
CHANGED
|
@@ -106,7 +106,7 @@ app.model = { ...createKernelModel({ kernel, root }), ...createAgentModel(agent,
|
|
|
106
106
|
|
|
107
107
|
The adapter keeps the replicated transcript and maps it on every update. User, assistant and compaction entries become messages; tool results fill in the tool calls they answer; the generation in flight shows as a streaming message; retries and running compactions show as system messages. A failed generation ends in an error card whose action fits the error: credential errors (an invalid or missing key, 401/403, not authorized) open settings, a model the account can't use offers the model picker, and anything else offers Retry. A content-filter stop is different: every later request resends the content that tripped the filter, so Retry, and any new message, would be stopped again. Content-filter stops are Bedrock's `content_filtered` and `guardrail_intervened`, Anthropic's `sensitive` and refusals, and OpenAI's `content_filter`. Their card leads with "The model's content filter stopped this reply." and offers **Drop the last turn** (spectrum ≥ 1.12.0). `AgentControl.rewind(messageId)` finds the user turn before that message and forks the cone's conversation at the entry before it (`Conversation.fork`, so `pi.agent` comes back as it was there). It points the cone at the fork and returns the turn's prompt, which spectrum puts back into the composer. Forking the very first turn creates a fresh conversation with the same model and thinking level. The filtered branch stays in storage, off the active path. The fork starts with a `slicc.rewound` entry that shows as a "Rewound 1 turn" notice, and clicking the card of an earlier filtered turn rewinds that turn.
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
Each cone's active conversation is in the session document `slicc.agents` (the root until the first rewind). The host's transcript follows it across forks. Rewinding is refused while the cone itself is running or has queued input, because a fork starts with an empty inbox, but never because its scoops are busy (see Cones and scoops). A background compaction still running on the old branch finishes there. A fork also starts with fresh `pi.usage`. Credential and model errors lead with a plain sentence ("Bedrock rejected the API key.", "This model isn't available with the current account.") and keep the provider's raw error as `detail`, which spectrum (≥ 1.11.0) shows underneath. `busy()` and `queue()` read durable's live and inbox documents. Steering is the default (#4): a send or a steer goes out with `whenBusy: 'steer'`, so while a run is going it joins after the current tool round, and a `queue` send is a follow-up. User messages carry `delivered` from the host's deliveries; entries the host didn't record (from before a reload) count as steered when they follow a tool result. Cones and scoops are below; the freezer and questions come later. Licks show as spectrum's lick cards (below). slicc-spectrum is an optional peer dependency (≥ 1.13.0), used for types only, so package managers don't install it next to the agent.
|
|
110
110
|
|
|
111
111
|
Each tool call shows its input in the way that suits it:
|
|
112
112
|
- `bash`: its command, with a timeout as the card's `meta`;
|
|
@@ -127,13 +127,13 @@ gone /home/notes/old.md
|
|
|
127
127
|
</lick>
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
-
`&`, `<` and `>` are escaped everywhere inside it, and `"` and line breaks in attributes too, so no payload (a webhook body, a file name, a crontab message) can close the tag, open another one or add an attribute. A `licks` prompt section tells the model that lick text comes from events, not from the user, and that webhook payloads may be hostile. The adapter maps these inputs to spectrum's `LickMessage` (channel, title, text, body, `count` when more than one). A message
|
|
130
|
+
`&`, `<` and `>` are escaped everywhere inside it, and `"` and line breaks in attributes too, so no payload (a webhook body, a file name, a crontab message) can close the tag, open another one or add an attribute. A `licks` prompt section tells the model that lick text comes from events, not from the user, and that webhook payloads may be hostile. The adapter maps these inputs to spectrum's `LickMessage` (channel, title, text, body, `count` when more than one, and `severity`, shown by spectrum ≥ 1.23.0). A user message shows as a card only when it has exactly that shape: the `<lick …>` line with an `id` and a known `channel`, the text on its own line, and `</lick>` alone on the last line. Anything else, such as a one-line `<lick …>…</lick>`, stays an ordinary message.
|
|
131
131
|
|
|
132
132
|
**Coalescing.** Licks with the same target, channel and source share an outbox (the session document `slicc.licks`). At most one of them is undelivered (queued in `pi.inbox`) at a time. Events that arrive while it waits are merged into the next one: the count adds up and the paths are unioned (capped at 50 shown). Once the queued lick is placed, the merged one is submitted. Nothing is ever withdrawn, so a lick that already reached the model is never touched, and each lick's `count` is exactly the events it carries. Webhook requests are never merged. A lick's `requestId` is `lick:<id>`, and events with an id (cron fires, webhook deliveries, boot facts) are remembered per source, so a crash between admitting a lick and recording it doesn't deliver it twice.
|
|
133
133
|
|
|
134
134
|
**Stop.** pi's `Conversation.abort()` withdraws every queued steer and follow-up, so a lick that wasn't placed yet is withdrawn with them, and the events merged behind it are dropped too: Stop leaves the cone quiet. Events that arrive after Stop are delivered as usual, and an idle cone starts a run for them.
|
|
135
135
|
|
|
136
|
-
**Configuration** lives in files under `~/.slicc` (`/home/.slicc`), which the user and the agent edit with any tool. The worker watches the folder and reconciles on every change. Each malformed line or file becomes one lick naming the file, the line and the problem, once per distinct problem, with `severity="error"`; the entry is ignored until it's fixed. Names are 1–64 of `a-z 0-9 . _ -`, starting with a letter or digit. A target is `cone` or `scoop:<
|
|
136
|
+
**Configuration** lives in files under `~/.slicc` (`/home/.slicc`), which the user and the agent edit with any tool. The worker watches the folder and reconciles on every change. Each malformed line or file becomes one lick naming the file, the line and the problem, once per distinct problem, with `severity="error"`; the entry is ignored until it's fixed. Names are 1–64 of `a-z 0-9 . _ -`, starting with a letter or digit. A target is `cone` (the active cone) or `scoop:<handle>`; a lick for a scoop that is stopped or gone goes to the cone, and webhooks always go to the cone. Files starting with `.` or ending in `~` are skipped; any other file that isn't `<name>.json` in `watches/` or `webhooks/` is an error.
|
|
137
137
|
|
|
138
138
|
- `~/.slicc/crontab`, one job per line: `<schedule> <name> [<target>] [<message>]`. The schedule is five fields (minute, hour, day of month, month, day of week, with `*`, ranges, steps, lists and `jan`–`dec`/`sun`–`sat` names; 7 is Sunday, and a job whose day of month and day of week are both set runs on either) or `@yearly`, `@monthly`, `@weekly`, `@daily`, `@hourly`. Times are the browser's local time. Blank lines and `#` comments are ignored.
|
|
139
139
|
```
|
|
@@ -152,6 +152,23 @@ gone /home/notes/old.md
|
|
|
152
152
|
|
|
153
153
|
**Decisions.** A lick channel with a handler (`licks.handle(channel, { confirm, dismiss })`) gets `actions="confirm dismiss"` (or one of them), and its card is pending until decided. The agent decides with the tools `lick_confirm` and `lick_dismiss` (`{ lick_id, reason? }`); the user decides from the card (`AgentPort.resolveLick` → `AgentControl.resolveLick`), which runs the handler and writes a `slicc.lick-state` entry that tells the agent. The state comes from these entries in the transcript, so rewinds and forks keep it consistent. Deciding twice, an unknown id, or an action the channel doesn't have is an error result. No channel in this release has a handler yet; scoop permission requests, upskill links and mount recovery will. The two tools' cards are folded into the lick card.
|
|
154
154
|
|
|
155
|
+
### Cones and scoops
|
|
156
|
+
|
|
157
|
+
A **cone** is a top-level agent the user talks to; a **scoop** is a helper agent with its own conversation and its own folder, `/scoops/<handle>/`. The session document `slicc.agents` is the registry. The first cone's id is `cone`, later ones `cone-<n>`, counting up only. A scoop's id is `scoop:<handle>`, where the handle is its name made safe for a folder; a handle that is or was ever taken gets `-2`, `-3` and so on, so ids and folders are never reused. `AgentControl` adds `selectCone`, `createCone`, `createScoop`, `stopAgent`, `drop` (stop a scoop and keep its folder) and `unqueue(agentId, submissionId)`; `send` and `configure` take an `agentId`. Its errors are sentences the UI can show as they are, such as "A cone named harbor already exists." The transcript service adds `agents`, a summary of every cone and scoop, and `views`, one durable view per live agent, which the spectrum adapter shows in the agents rail (the header picker lists cones only). With spectrum ≥ 1.23.0 the rail drops scoops and the picker creates cones. A scoop created from the rail has a provisional id until the worker answers; the adapter then moves the selection, and sends, to the id the worker gave it.
|
|
158
|
+
|
|
159
|
+
Each scoop is owned by a background durable task, its **anchor**, so Stop in its cone doesn't end it; `agent stop` does, and keeps its transcript and folder. 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, or `terminal`. 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; a cone's has a `subagent` section with the `agent` skill and up to 16 roles.
|
|
160
|
+
|
|
161
|
+
**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.
|
|
162
|
+
- `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.
|
|
163
|
+
- `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.
|
|
164
|
+
- 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".
|
|
165
|
+
|
|
166
|
+
**Roles** are pi agent files: Markdown with a frontmatter subset (`name`, `description`, `tools`, `model`, `thinking`, …) and the role's prompt as the body, added to the scoop's instructions. The built-ins are `scout`, `worker`, `reviewer`, `oracle` and `delegate`. Later sources replace earlier ones by name: built-ins, then packages in the kernel's `node_modules` that name a folder in `pi-subagents.agents` or `pi.subagents.agents`, then `~/.pi/agent/agents/`. A project's `.pi/agents/` will count only in trusted folders, and folders can't be trusted yet. `~/.pi/agent/settings.json` can override roles (`subagents.agentOverrides`) and set the limits `subagents.maxLiveScoops` (16 live scoops per cone) and `subagents.maxPerTurn` (64 scoops started per cone turn), which the worker counts for both kinds.
|
|
167
|
+
|
|
168
|
+
**Isolation, for now.** The file tools of a scoop write only under its folder and `/tmp`, plus the cwd of a v6-form call, and read those, their cone's working folder (`/home` by default) and `--read-only` paths; paths are normalized, `..` included, before the check. bash isn't confined, and the scoop is told so: the guard is a guardrail, not a boundary. Every process group a scoop starts is recorded, and stopping the scoop signals them all.
|
|
169
|
+
|
|
170
|
+
**Rewind.** Dropping a turn never waits for scoops. Scoops the dropped turn created are stopped and marked gone; requests it handed to older scoops are aborted and their reports withdrawn; scoops it stopped come back idle. The `slicc.rewound` entry lists them, and its notice says "Stopped scoop X. Restored scoop Y."
|
|
171
|
+
|
|
155
172
|
## Patched dependencies
|
|
156
173
|
|
|
157
174
|
Fixes to pi stay in this repository. [`patches/patches.json`](patches/patches.json) lists each one with its `kind`, `package`, `patchedVersion`, `reason`, `removeWhen` and `verify` command, and an optional `marker` (a file in the package and a string it must contain):
|
package/bin/agent
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
dir=/var/lib/slicc/agent/requests
|
|
3
|
+
as=$(basename "$0")
|
|
4
|
+
if [ "$as" != "subagent" ]; then as=agent; fi
|
|
5
|
+
mkdir -p "$dir/in" "$dir/out" 2>/dev/null
|
|
6
|
+
|
|
7
|
+
submit() {
|
|
8
|
+
local id="$1"
|
|
9
|
+
shift
|
|
10
|
+
local tmp="$dir/in/.tmp-$id"
|
|
11
|
+
{
|
|
12
|
+
printf '%s\0' "slicc-agent/1" "$as" "${SLICC_AGENT:-}" "$PWD" "$$" "$#"
|
|
13
|
+
if [ $# -gt 0 ]; then printf '%s\0' "$@"; fi
|
|
14
|
+
if [ "$stdin" = 1 ]; then cat; fi
|
|
15
|
+
} > "$tmp" && mv "$tmp" "$dir/in/$id"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
stdin=0
|
|
19
|
+
for arg in "$@"; do
|
|
20
|
+
if [ "$arg" = "-" ]; then stdin=1; fi
|
|
21
|
+
done
|
|
22
|
+
|
|
23
|
+
id="$(date +%s%N)-$$-$RANDOM"
|
|
24
|
+
submit "$id" "$@" || exit 1
|
|
25
|
+
stdin=0
|
|
26
|
+
|
|
27
|
+
sync=0
|
|
28
|
+
case "${1:-}" in
|
|
29
|
+
''|list|status|rename|send|wait|stop|help|--help|-h|spawn) ;;
|
|
30
|
+
*) case " $* " in *" --async "*) ;; *) [ "$as" = agent ] && sync=1 ;; esac ;;
|
|
31
|
+
esac
|
|
32
|
+
|
|
33
|
+
cancel() {
|
|
34
|
+
submit "$(date +%s%N)-$$-$RANDOM" __cancel "$id"
|
|
35
|
+
exit 130
|
|
36
|
+
}
|
|
37
|
+
if [ $sync = 1 ]; then trap cancel TERM INT HUP; fi
|
|
38
|
+
|
|
39
|
+
limit=0
|
|
40
|
+
if [ "${1:-}" = wait ]; then limit=350; fi
|
|
41
|
+
after=""
|
|
42
|
+
previous=""
|
|
43
|
+
for arg in "$@"; do
|
|
44
|
+
if [ "$previous" = "--timeout" ]; then limit=$(( (${arg//[!0-9]/} + 5) * 10 )); fi
|
|
45
|
+
if [ "$previous" = "--background-after" ]; then after="${arg//[!0-9]/}"; fi
|
|
46
|
+
previous="$arg"
|
|
47
|
+
done
|
|
48
|
+
|
|
49
|
+
count=0
|
|
50
|
+
seen=0
|
|
51
|
+
while [ ! -f "$dir/out/$id.code" ]; do
|
|
52
|
+
sleep 0.1
|
|
53
|
+
count=$((count + 1))
|
|
54
|
+
if [ $seen = 0 ] && [ -f "$dir/out/$id.ack" ]; then seen=1; fi
|
|
55
|
+
if [ $seen = 0 ] && [ $count -ge 200 ]; then
|
|
56
|
+
rm -f "$dir/in/$id"
|
|
57
|
+
echo "$as: the SLICC agent isn't running: $as needs it" >&2
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
if [ $limit -gt 0 ] && [ $count -ge $limit ]; then
|
|
61
|
+
echo "$as: no answer from the SLICC agent" >&2
|
|
62
|
+
exit 1
|
|
63
|
+
fi
|
|
64
|
+
if [ -n "$after" ] && [ $count -ge $((after * 10)) ]; then
|
|
65
|
+
submit "$(date +%s%N)-$$-$RANDOM" __detach "$id"
|
|
66
|
+
after=""
|
|
67
|
+
fi
|
|
68
|
+
done
|
|
69
|
+
code=$(cat "$dir/out/$id.code")
|
|
70
|
+
if [ "$code" = 0 ]; then
|
|
71
|
+
cat "$dir/out/$id.out"
|
|
72
|
+
else
|
|
73
|
+
cat "$dir/out/$id.out" >&2
|
|
74
|
+
fi
|
|
75
|
+
rm -f "$dir/out/$id.out" "$dir/out/$id.code" "$dir/out/$id.ack"
|
|
76
|
+
exit "$code"
|
package/dist/agent.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
Harness,
|
|
6
6
|
MemoryStorage
|
|
7
7
|
} from "@earendil-works/pi-durable";
|
|
8
|
-
import {
|
|
8
|
+
import { openAgents } from "./agents.js";
|
|
9
9
|
function answerText(content) {
|
|
10
10
|
if (typeof content === "string") return content;
|
|
11
11
|
if (!Array.isArray(content)) return "";
|
|
@@ -32,23 +32,16 @@ async function openAgent(options) {
|
|
|
32
32
|
context
|
|
33
33
|
);
|
|
34
34
|
const root = await harness.root(context, { agent: { model: options.model } });
|
|
35
|
-
|
|
36
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
35
|
+
const agents = await openAgents(harness, root, context);
|
|
37
36
|
harness.resume();
|
|
38
37
|
return {
|
|
39
38
|
harness,
|
|
40
39
|
root,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
},
|
|
47
|
-
onCone(listener) {
|
|
48
|
-
listeners.add(listener);
|
|
49
|
-
return () => listeners.delete(listener);
|
|
50
|
-
},
|
|
51
|
-
prompt: (text) => prompt(cone, text, context),
|
|
40
|
+
agents,
|
|
41
|
+
cone: () => agents.cone(),
|
|
42
|
+
switchCone: (conversation, switching) => agents.switchCone(conversation, switching),
|
|
43
|
+
onCone: (listener) => agents.onCone(listener),
|
|
44
|
+
prompt: (text) => prompt(agents.cone(), text, context),
|
|
52
45
|
close: () => harness.close(context)
|
|
53
46
|
};
|
|
54
47
|
}
|
package/dist/agent.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/agent.ts"],
|
|
4
|
-
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { Models } from '@earendil-works/pi-ai/models';\nimport {\n AssistantEntry,\n type Conversation,\n createRegistry,\n type EntryId,\n Harness,\n type HarnessOptions,\n MemoryStorage,\n type ModelRef,\n type Registry,\n type Storage,\n} from '@earendil-works/pi-durable';\nimport {
|
|
5
|
-
"mappings": "AACA,SAAS,0BAA0B;AAEnC;AAAA,EACE;AAAA,EAEA;AAAA,EAEA;AAAA,EAEA;AAAA,OAIK;AACP,
|
|
4
|
+
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { Models } from '@earendil-works/pi-ai/models';\nimport {\n AssistantEntry,\n type Conversation,\n createRegistry,\n type EntryId,\n Harness,\n type HarnessOptions,\n MemoryStorage,\n type ModelRef,\n type Registry,\n type Storage,\n} from '@earendil-works/pi-durable';\nimport { type Agents, openAgents } from './agents.ts';\nimport type { Cones } from './cone.ts';\n\nexport interface AgentOptions {\n models: Models;\n model: ModelRef;\n storage?: Storage;\n registry?: Registry;\n settings?: HarnessOptions['settings'];\n env?: HarnessOptions['env'];\n context?: Context;\n}\n\nexport interface Agent extends Cones {\n readonly harness: Harness;\n readonly root: Conversation;\n readonly agents: Agents;\n onCone(listener: (conversation: Conversation) => void | Promise<void>): () => void;\n prompt(text: string): Promise<string>;\n close(): Promise<void>;\n}\n\ninterface Block {\n type: string;\n text?: string;\n}\n\nexport function answerText(content: unknown): string {\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return '';\n return (content as Block[])\n .filter((block) => block.type === 'text')\n .map((block) => block.text ?? '')\n .join('');\n}\n\nasync function prompt(root: Conversation, text: string, context: Context): Promise<string> {\n const submission = await root.submit({ type: 'input', content: text }, context);\n const settled = await submission.wait(context);\n const { answer, reason } = settled as { answer?: EntryId; reason?: string };\n if (answer === undefined) throw new Error(`unanswered: ${reason}`);\n const entry = await root.commit((tx) => tx.entry(AssistantEntry, answer), context);\n return answerText(entry?.model?.[0]?.content);\n}\n\nexport async function openAgent(options: AgentOptions): Promise<Agent> {\n const context = options.context ?? BACKGROUND_CONTEXT;\n const harness = await Harness.open(\n options.storage ?? new MemoryStorage(),\n {\n models: options.models,\n registry: options.registry ?? createRegistry(),\n ...(options.settings ? { settings: options.settings } : {}),\n ...(options.env ? { env: options.env } : {}),\n },\n context\n );\n const root = await harness.root(context, { agent: { model: options.model } });\n const agents = await openAgents(harness, root, context);\n harness.resume();\n return {\n harness,\n root,\n agents,\n cone: () => agents.cone(),\n switchCone: (conversation, switching) => agents.switchCone(conversation, switching),\n onCone: (listener) => agents.onCone(listener),\n prompt: (text) => prompt(agents.cone(), text, context),\n close: () => harness.close(context),\n };\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,0BAA0B;AAEnC;AAAA,EACE;AAAA,EAEA;AAAA,EAEA;AAAA,EAEA;AAAA,OAIK;AACP,SAAsB,kBAAkB;AA2BjC,SAAS,WAAW,SAA0B;AACnD,MAAI,OAAO,YAAY,SAAU,QAAO;AACxC,MAAI,CAAC,MAAM,QAAQ,OAAO,EAAG,QAAO;AACpC,SAAQ,QACL,OAAO,CAAC,UAAU,MAAM,SAAS,MAAM,EACvC,IAAI,CAAC,UAAU,MAAM,QAAQ,EAAE,EAC/B,KAAK,EAAE;AACZ;AAEA,eAAe,OAAO,MAAoB,MAAc,SAAmC;AACzF,QAAM,aAAa,MAAM,KAAK,OAAO,EAAE,MAAM,SAAS,SAAS,KAAK,GAAG,OAAO;AAC9E,QAAM,UAAU,MAAM,WAAW,KAAK,OAAO;AAC7C,QAAM,EAAE,QAAQ,OAAO,IAAI;AAC3B,MAAI,WAAW,OAAW,OAAM,IAAI,MAAM,eAAe,MAAM,EAAE;AACjE,QAAM,QAAQ,MAAM,KAAK,OAAO,CAAC,OAAO,GAAG,MAAM,gBAAgB,MAAM,GAAG,OAAO;AACjF,SAAO,WAAW,OAAO,QAAQ,CAAC,GAAG,OAAO;AAC9C;AAEA,eAAsB,UAAU,SAAuC;AACrE,QAAM,UAAU,QAAQ,WAAW;AACnC,QAAM,UAAU,MAAM,QAAQ;AAAA,IAC5B,QAAQ,WAAW,IAAI,cAAc;AAAA,IACrC;AAAA,MACE,QAAQ,QAAQ;AAAA,MAChB,UAAU,QAAQ,YAAY,eAAe;AAAA,MAC7C,GAAI,QAAQ,WAAW,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;AAAA,MACzD,GAAI,QAAQ,MAAM,EAAE,KAAK,QAAQ,IAAI,IAAI,CAAC;AAAA,IAC5C;AAAA,IACA;AAAA,EACF;AACA,QAAM,OAAO,MAAM,QAAQ,KAAK,SAAS,EAAE,OAAO,EAAE,OAAO,QAAQ,MAAM,EAAE,CAAC;AAC5E,QAAM,SAAS,MAAM,WAAW,SAAS,MAAM,OAAO;AACtD,UAAQ,OAAO;AACf,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,MAAM,MAAM,OAAO,KAAK;AAAA,IACxB,YAAY,CAAC,cAAc,cAAc,OAAO,WAAW,cAAc,SAAS;AAAA,IAClF,QAAQ,CAAC,aAAa,OAAO,OAAO,QAAQ;AAAA,IAC5C,QAAQ,CAAC,SAAS,OAAO,OAAO,KAAK,GAAG,MAAM,OAAO;AAAA,IACrD,OAAO,MAAM,QAAQ,MAAM,OAAO;AAAA,EACpC;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/agents.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineDoc
|
|
3
|
+
} from "@earendil-works/pi-durable";
|
|
4
|
+
import { ConeDoc } from "./cone.js";
|
|
5
|
+
const FIRST_CONE = "cone";
|
|
6
|
+
function live(record) {
|
|
7
|
+
return record.dropped === null && !record.gone;
|
|
8
|
+
}
|
|
9
|
+
const AgentsDoc = defineDoc({
|
|
10
|
+
kind: "slicc.agents",
|
|
11
|
+
version: 1,
|
|
12
|
+
scope: "session",
|
|
13
|
+
initial: () => ({ cones: {}, active: FIRST_CONE, next: 2, scoops: {} })
|
|
14
|
+
});
|
|
15
|
+
const SCOOP_PREFIX = "scoop:";
|
|
16
|
+
function scoopId(folder) {
|
|
17
|
+
return `${SCOOP_PREFIX}${folder}`;
|
|
18
|
+
}
|
|
19
|
+
function slug(name) {
|
|
20
|
+
const cleaned = name.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[-._]+|[-._]+$/g, "").slice(0, 48);
|
|
21
|
+
return cleaned || "scoop";
|
|
22
|
+
}
|
|
23
|
+
function freeFolder(state, name) {
|
|
24
|
+
const base = slug(name);
|
|
25
|
+
if (!state.scoops[scoopId(base)]) return base;
|
|
26
|
+
for (let n = 2; ; n++) if (!state.scoops[scoopId(`${base}-${n}`)]) return `${base}-${n}`;
|
|
27
|
+
}
|
|
28
|
+
async function initial(harness, root, context) {
|
|
29
|
+
return harness.commit(async (tx) => {
|
|
30
|
+
const doc = await tx.doc(AgentsDoc);
|
|
31
|
+
if (!doc.cones[FIRST_CONE]) {
|
|
32
|
+
const legacy = (await tx.doc(ConeDoc)).conversation;
|
|
33
|
+
doc.cones[FIRST_CONE] = { name: "sliccy", conversation: legacy ?? root.id };
|
|
34
|
+
}
|
|
35
|
+
if (!doc.cones[doc.active]) doc.active = FIRST_CONE;
|
|
36
|
+
return JSON.parse(JSON.stringify(doc));
|
|
37
|
+
}, context);
|
|
38
|
+
}
|
|
39
|
+
async function openAgents(harness, root, context) {
|
|
40
|
+
let state = await initial(harness, root, context);
|
|
41
|
+
const handles = /* @__PURE__ */ new Map([[root.id, root]]);
|
|
42
|
+
const handle = async (id, using) => {
|
|
43
|
+
const known = handles.get(id);
|
|
44
|
+
if (known) return known;
|
|
45
|
+
const found = await harness.conversation(id, using);
|
|
46
|
+
if (found) handles.set(id, found);
|
|
47
|
+
return found;
|
|
48
|
+
};
|
|
49
|
+
let cone = await handle(state.cones[state.active].conversation, context) ?? root;
|
|
50
|
+
const changes = /* @__PURE__ */ new Set();
|
|
51
|
+
const cones = /* @__PURE__ */ new Set();
|
|
52
|
+
const notifyCone = async (next) => {
|
|
53
|
+
cone = next;
|
|
54
|
+
await Promise.all([...cones].map((listener) => listener(next)));
|
|
55
|
+
};
|
|
56
|
+
const update = async (change, using) => {
|
|
57
|
+
const [result, next] = await harness.commit(async (tx) => {
|
|
58
|
+
const doc = await tx.doc(AgentsDoc);
|
|
59
|
+
const value = await change(tx, doc);
|
|
60
|
+
return [value, JSON.parse(JSON.stringify(doc))];
|
|
61
|
+
}, using);
|
|
62
|
+
state = next;
|
|
63
|
+
for (const listener of changes) listener(state);
|
|
64
|
+
return result;
|
|
65
|
+
};
|
|
66
|
+
const agents = {
|
|
67
|
+
state: () => state,
|
|
68
|
+
cone: () => cone,
|
|
69
|
+
activeCone: () => state.active,
|
|
70
|
+
async conversation(agentId, using) {
|
|
71
|
+
const id = state.cones[agentId]?.conversation ?? state.scoops[agentId]?.conversation;
|
|
72
|
+
return id === void 0 ? void 0 : handle(id, using);
|
|
73
|
+
},
|
|
74
|
+
async selectCone(id, using) {
|
|
75
|
+
const record = state.cones[id];
|
|
76
|
+
if (!record) throw new Error(`no cone ${id}`);
|
|
77
|
+
if (state.active === id) return;
|
|
78
|
+
await update((_tx, doc) => {
|
|
79
|
+
doc.active = id;
|
|
80
|
+
}, using);
|
|
81
|
+
await notifyCone(await handle(record.conversation, using));
|
|
82
|
+
},
|
|
83
|
+
async createCone(name, using) {
|
|
84
|
+
const wanted = name.trim();
|
|
85
|
+
if (!wanted) throw new Error("A cone needs a name.");
|
|
86
|
+
if (Object.values(state.cones).some((record) => record.name === wanted))
|
|
87
|
+
throw new Error(`A cone named ${wanted} already exists.`);
|
|
88
|
+
const conversation = await harness.createConversation(
|
|
89
|
+
{
|
|
90
|
+
ownership: { kind: "ownerless" },
|
|
91
|
+
agent: { model: (await cone.agent(using)).model ?? null }
|
|
92
|
+
},
|
|
93
|
+
using
|
|
94
|
+
);
|
|
95
|
+
handles.set(conversation.id, conversation);
|
|
96
|
+
return update((_tx, doc) => {
|
|
97
|
+
const id = `cone-${doc.next}`;
|
|
98
|
+
doc.next += 1;
|
|
99
|
+
doc.cones[id] = { name: wanted, conversation: conversation.id };
|
|
100
|
+
return id;
|
|
101
|
+
}, using);
|
|
102
|
+
},
|
|
103
|
+
async switchCone(conversation, using) {
|
|
104
|
+
handles.set(conversation.id, conversation);
|
|
105
|
+
await update(async (tx, doc) => {
|
|
106
|
+
doc.cones[doc.active].conversation = conversation.id;
|
|
107
|
+
(await tx.doc(ConeDoc)).conversation = conversation.id;
|
|
108
|
+
}, using);
|
|
109
|
+
await notifyCone(conversation);
|
|
110
|
+
},
|
|
111
|
+
update,
|
|
112
|
+
onChange(listener) {
|
|
113
|
+
changes.add(listener);
|
|
114
|
+
return () => changes.delete(listener);
|
|
115
|
+
},
|
|
116
|
+
onCone(listener) {
|
|
117
|
+
cones.add(listener);
|
|
118
|
+
return () => cones.delete(listener);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
return agents;
|
|
122
|
+
}
|
|
123
|
+
export {
|
|
124
|
+
AgentsDoc,
|
|
125
|
+
FIRST_CONE,
|
|
126
|
+
SCOOP_PREFIX,
|
|
127
|
+
freeFolder,
|
|
128
|
+
live,
|
|
129
|
+
openAgents,
|
|
130
|
+
scoopId,
|
|
131
|
+
slug
|
|
132
|
+
};
|
|
133
|
+
//# sourceMappingURL=agents.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/agents.ts"],
|
|
4
|
+
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {\n type Conversation,\n type ConversationId,\n defineDoc,\n type Harness,\n type Tx,\n} from '@earendil-works/pi-durable';\nimport { ConeDoc } from './cone.ts';\n\nexport const FIRST_CONE = 'cone';\n\nexport type ConeRecord = { name: string; conversation: number };\n\nexport type Mark = { in: number; at: number };\n\nexport type ScoopRecord = {\n name: string;\n folder: string;\n cone: string;\n conversation: number;\n anchor: number;\n role: string | null;\n kind: 'async' | 'sync';\n parent: string;\n depth: number;\n roots: { write: string[]; read: string[] } | null;\n created: Mark | null;\n dropped: Mark | null;\n gone: boolean;\n};\n\nexport function live(record: ScoopRecord): boolean {\n return record.dropped === null && !record.gone;\n}\n\nexport type AgentsState = {\n cones: Record<string, ConeRecord>;\n active: string;\n next: number;\n scoops: Record<string, ScoopRecord>;\n};\n\nexport const AgentsDoc = defineDoc<AgentsState>({\n kind: 'slicc.agents',\n version: 1,\n scope: 'session',\n initial: () => ({ cones: {}, active: FIRST_CONE, next: 2, scoops: {} }),\n});\n\nexport const SCOOP_PREFIX = 'scoop:';\n\nexport function scoopId(folder: string): string {\n return `${SCOOP_PREFIX}${folder}`;\n}\n\nexport function slug(name: string): string {\n const cleaned = name\n .toLowerCase()\n .replace(/[^a-z0-9._-]+/g, '-')\n .replace(/^[-._]+|[-._]+$/g, '')\n .slice(0, 48);\n return cleaned || 'scoop';\n}\n\nexport function freeFolder(state: Pick<AgentsState, 'scoops'>, name: string): string {\n const base = slug(name);\n if (!state.scoops[scoopId(base)]) return base;\n for (let n = 2; ; n++) if (!state.scoops[scoopId(`${base}-${n}`)]) return `${base}-${n}`;\n}\n\nexport interface Agents {\n state(): Readonly<AgentsState>;\n cone(): Conversation;\n activeCone(): string;\n conversation(agentId: string, context: Context): Promise<Conversation | undefined>;\n selectCone(id: string, context: Context): Promise<void>;\n createCone(name: string, context: Context): Promise<string>;\n switchCone(conversation: Conversation, context: Context): Promise<void>;\n update<T>(change: (tx: Tx, state: AgentsState) => T | Promise<T>, context: Context): Promise<T>;\n onChange(listener: (state: Readonly<AgentsState>) => void): () => void;\n onCone(listener: (conversation: Conversation) => void | Promise<void>): () => void;\n}\n\nasync function initial(harness: Harness, root: Conversation, context: Context) {\n return harness.commit(async (tx) => {\n const doc = await tx.doc(AgentsDoc);\n if (!doc.cones[FIRST_CONE]) {\n const legacy = (await tx.doc(ConeDoc)).conversation;\n doc.cones[FIRST_CONE] = { name: 'sliccy', conversation: legacy ?? root.id };\n }\n if (!doc.cones[doc.active]) doc.active = FIRST_CONE;\n return JSON.parse(JSON.stringify(doc)) as AgentsState;\n }, context);\n}\n\nexport async function openAgents(\n harness: Harness,\n root: Conversation,\n context: Context\n): Promise<Agents> {\n let state = await initial(harness, root, context);\n const handles = new Map<number, Conversation>([[root.id, root]]);\n const handle = async (id: number, using: Context) => {\n const known = handles.get(id);\n if (known) return known;\n const found = await harness.conversation(id as ConversationId, using);\n if (found) handles.set(id, found);\n return found;\n };\n let cone =\n (await handle((state.cones[state.active] as ConeRecord).conversation, context)) ?? root;\n const changes = new Set<(state: Readonly<AgentsState>) => void>();\n const cones = new Set<(conversation: Conversation) => void | Promise<void>>();\n const notifyCone = async (next: Conversation) => {\n cone = next;\n await Promise.all([...cones].map((listener) => listener(next)));\n };\n const update: Agents['update'] = async (change, using) => {\n const [result, next] = await harness.commit(async (tx) => {\n const doc = await tx.doc(AgentsDoc);\n const value = await change(tx, doc as AgentsState);\n return [value, JSON.parse(JSON.stringify(doc)) as AgentsState] as const;\n }, using);\n state = next;\n for (const listener of changes) listener(state);\n return result;\n };\n const agents: Agents = {\n state: () => state,\n cone: () => cone,\n activeCone: () => state.active,\n async conversation(agentId, using) {\n const id = state.cones[agentId]?.conversation ?? state.scoops[agentId]?.conversation;\n return id === undefined ? undefined : handle(id, using);\n },\n async selectCone(id, using) {\n const record = state.cones[id];\n if (!record) throw new Error(`no cone ${id}`);\n if (state.active === id) return;\n await update((_tx, doc) => {\n doc.active = id;\n }, using);\n await notifyCone((await handle(record.conversation, using)) as Conversation);\n },\n async createCone(name, using) {\n const wanted = name.trim();\n if (!wanted) throw new Error('A cone needs a name.');\n if (Object.values(state.cones).some((record) => record.name === wanted))\n throw new Error(`A cone named ${wanted} already exists.`);\n const conversation = await harness.createConversation(\n {\n ownership: { kind: 'ownerless' },\n agent: { model: (await cone.agent(using)).model ?? null },\n },\n using\n );\n handles.set(conversation.id, conversation);\n return update((_tx, doc) => {\n const id = `cone-${doc.next}`;\n doc.next += 1;\n doc.cones[id] = { name: wanted, conversation: conversation.id };\n return id;\n }, using);\n },\n async switchCone(conversation, using) {\n handles.set(conversation.id, conversation);\n await update(async (tx, doc) => {\n (doc.cones[doc.active] as ConeRecord).conversation = conversation.id;\n (await tx.doc(ConeDoc)).conversation = conversation.id;\n }, using);\n await notifyCone(conversation);\n },\n update,\n onChange(listener) {\n changes.add(listener);\n return () => changes.delete(listener);\n },\n onCone(listener) {\n cones.add(listener);\n return () => cones.delete(listener);\n },\n };\n return agents;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/client.js
CHANGED
|
@@ -38,7 +38,7 @@ async function connectAgent(endpoint, options = {}) {
|
|
|
38
38
|
bound: true
|
|
39
39
|
});
|
|
40
40
|
const control = session.use(AgentControl);
|
|
41
|
-
const { state: transcript, deliveries } = session.use(AgentTranscript);
|
|
41
|
+
const { state: transcript, deliveries, agents, views } = session.use(AgentTranscript);
|
|
42
42
|
const settings = session.use(AgentSettings);
|
|
43
43
|
await session.ready(context);
|
|
44
44
|
return {
|
|
@@ -46,6 +46,8 @@ async function connectAgent(endpoint, options = {}) {
|
|
|
46
46
|
control,
|
|
47
47
|
transcript,
|
|
48
48
|
deliveries,
|
|
49
|
+
agents,
|
|
50
|
+
views,
|
|
49
51
|
settings,
|
|
50
52
|
async prompt(text, whenBusy = "followUp") {
|
|
51
53
|
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 {\n AGENT_SESSION,\n AgentControl,\n AgentSessions,\n AgentSettings,\n AgentTranscript,\n type Delivered,\n type SendMode,\n} from './services.ts';\nimport { PortChannel, type PortEndpoint } from './wire.ts';\n\nexport interface AgentConnection {\n readonly serverId: string;\n readonly control: AgentControl;\n readonly transcript: ReplicatedState<ConversationView>;\n readonly deliveries: ReplicatedState<Record<string, Delivered>>;\n readonly settings: AgentSettings;\n prompt(text: string, whenBusy?: SendMode): Promise<string>;\n close(): Promise<void>;\n}\n\nfunction attached(client: Client): Promise<void> {\n return new Promise((resolve) => {\n const stop = client.onAttachmentChange(() => {\n stop();\n resolve();\n });\n });\n}\n\nexport async function connectAgent(\n endpoint: PortEndpoint,\n options: { context?: Context } = {}\n): Promise<AgentConnection> {\n const context = options.context ?? BACKGROUND_CONTEXT;\n const channel = new PortChannel(endpoint);\n const serverId = await channel.hello;\n const client = await Client.connect({ serverId, transportFactory: channel.transport });\n const server = createRemoteServiceBinding({\n services: [AgentSessions],\n transport: createClientServiceTransport(client, () => ({ serverId })),\n bound: true,\n });\n const ready = attached(client);\n await server.use(AgentSessions).attach(AGENT_SESSION, context);\n await ready;\n const session = createRemoteServiceBinding({\n services: [AgentControl, AgentTranscript, AgentSettings],\n transport: createClientServiceTransport(client, () => client.attachment),\n bound: true,\n });\n const control = session.use(AgentControl);\n const { state: transcript, deliveries } = session.use(AgentTranscript);\n const settings = session.use(AgentSettings);\n await session.ready(context);\n return {\n serverId,\n control,\n transcript,\n deliveries,\n settings,\n async prompt(text, whenBusy = 'followUp') {\n const sent = await control.send({ text, whenBusy, requestId: null }, context);\n if (!sent.accepted) throw new Error(`${sent.error.code}: ${sent.error.message}`);\n const settled = await control.wait(sent.submissionId, context);\n if (settled.status !== 'done') throw new Error(`unanswered: ${settled.reason}`);\n return settled.text;\n },\n async close() {\n await server.use(AgentSessions).detach(context);\n await session.dispose(context);\n await server.dispose(context);\n await client.dispose();\n },\n };\n}\n"],
|
|
5
|
-
"mappings": "AAAA;AAAA,EAEE;AAAA,OAEK;AACP,SAAS,0BAA0B;AACnC,SAAS,QAAQ,oCAAoC;AAErD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,
|
|
4
|
+
"sourcesContent": ["import {\n type Context,\n createRemoteServiceBinding,\n type ReplicatedState,\n} from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport { Client, createClientServiceTransport } from '@earendil-works/pi-client';\nimport type { ConversationView } from '@earendil-works/pi-durable';\nimport {\n AGENT_SESSION,\n AgentControl,\n AgentSessions,\n AgentSettings,\n type AgentsSummary,\n AgentTranscript,\n type Delivered,\n type SendMode,\n} from './services.ts';\nimport { PortChannel, type PortEndpoint } from './wire.ts';\n\nexport interface AgentConnection {\n readonly serverId: string;\n readonly control: AgentControl;\n readonly transcript: ReplicatedState<ConversationView>;\n readonly deliveries: ReplicatedState<Record<string, Delivered>>;\n readonly agents?: ReplicatedState<AgentsSummary>;\n readonly views?: ReplicatedState<Record<string, ConversationView>>;\n readonly settings: AgentSettings;\n prompt(text: string, whenBusy?: SendMode): Promise<string>;\n close(): Promise<void>;\n}\n\nfunction attached(client: Client): Promise<void> {\n return new Promise((resolve) => {\n const stop = client.onAttachmentChange(() => {\n stop();\n resolve();\n });\n });\n}\n\nexport async function connectAgent(\n endpoint: PortEndpoint,\n options: { context?: Context } = {}\n): Promise<AgentConnection> {\n const context = options.context ?? BACKGROUND_CONTEXT;\n const channel = new PortChannel(endpoint);\n const serverId = await channel.hello;\n const client = await Client.connect({ serverId, transportFactory: channel.transport });\n const server = createRemoteServiceBinding({\n services: [AgentSessions],\n transport: createClientServiceTransport(client, () => ({ serverId })),\n bound: true,\n });\n const ready = attached(client);\n await server.use(AgentSessions).attach(AGENT_SESSION, context);\n await ready;\n const session = createRemoteServiceBinding({\n services: [AgentControl, AgentTranscript, AgentSettings],\n transport: createClientServiceTransport(client, () => client.attachment),\n bound: true,\n });\n const control = session.use(AgentControl);\n const { state: transcript, deliveries, agents, views } = session.use(AgentTranscript);\n const settings = session.use(AgentSettings);\n await session.ready(context);\n return {\n serverId,\n control,\n transcript,\n deliveries,\n agents,\n views,\n settings,\n async prompt(text, whenBusy = 'followUp') {\n const sent = await control.send({ text, whenBusy, requestId: null }, context);\n if (!sent.accepted) throw new Error(`${sent.error.code}: ${sent.error.message}`);\n const settled = await control.wait(sent.submissionId, context);\n if (settled.status !== 'done') throw new Error(`unanswered: ${settled.reason}`);\n return settled.text;\n },\n async close() {\n await server.use(AgentSessions).detach(context);\n await session.dispose(context);\n await server.dispose(context);\n await client.dispose();\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "AAAA;AAAA,EAEE;AAAA,OAEK;AACP,SAAS,0BAA0B;AACnC,SAAS,QAAQ,oCAAoC;AAErD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,OAGK;AACP,SAAS,mBAAsC;AAc/C,SAAS,SAAS,QAA+B;AAC/C,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAM,OAAO,OAAO,mBAAmB,MAAM;AAC3C,WAAK;AACL,cAAQ;AAAA,IACV,CAAC;AAAA,EACH,CAAC;AACH;AAEA,eAAsB,aACpB,UACA,UAAiC,CAAC,GACR;AAC1B,QAAM,UAAU,QAAQ,WAAW;AACnC,QAAM,UAAU,IAAI,YAAY,QAAQ;AACxC,QAAM,WAAW,MAAM,QAAQ;AAC/B,QAAM,SAAS,MAAM,OAAO,QAAQ,EAAE,UAAU,kBAAkB,QAAQ,UAAU,CAAC;AACrF,QAAM,SAAS,2BAA2B;AAAA,IACxC,UAAU,CAAC,aAAa;AAAA,IACxB,WAAW,6BAA6B,QAAQ,OAAO,EAAE,SAAS,EAAE;AAAA,IACpE,OAAO;AAAA,EACT,CAAC;AACD,QAAM,QAAQ,SAAS,MAAM;AAC7B,QAAM,OAAO,IAAI,aAAa,EAAE,OAAO,eAAe,OAAO;AAC7D,QAAM;AACN,QAAM,UAAU,2BAA2B;AAAA,IACzC,UAAU,CAAC,cAAc,iBAAiB,aAAa;AAAA,IACvD,WAAW,6BAA6B,QAAQ,MAAM,OAAO,UAAU;AAAA,IACvE,OAAO;AAAA,EACT,CAAC;AACD,QAAM,UAAU,QAAQ,IAAI,YAAY;AACxC,QAAM,EAAE,OAAO,YAAY,YAAY,QAAQ,MAAM,IAAI,QAAQ,IAAI,eAAe;AACpF,QAAM,WAAW,QAAQ,IAAI,aAAa;AAC1C,QAAM,QAAQ,MAAM,OAAO;AAC3B,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,MAAM,OAAO,MAAM,WAAW,YAAY;AACxC,YAAM,OAAO,MAAM,QAAQ,KAAK,EAAE,MAAM,UAAU,WAAW,KAAK,GAAG,OAAO;AAC5E,UAAI,CAAC,KAAK,SAAU,OAAM,IAAI,MAAM,GAAG,KAAK,MAAM,IAAI,KAAK,KAAK,MAAM,OAAO,EAAE;AAC/E,YAAM,UAAU,MAAM,QAAQ,KAAK,KAAK,cAAc,OAAO;AAC7D,UAAI,QAAQ,WAAW,OAAQ,OAAM,IAAI,MAAM,eAAe,QAAQ,MAAM,EAAE;AAC9E,aAAO,QAAQ;AAAA,IACjB;AAAA,IACA,MAAM,QAAQ;AACZ,YAAM,OAAO,IAAI,aAAa,EAAE,OAAO,OAAO;AAC9C,YAAM,QAAQ,QAAQ,OAAO;AAC7B,YAAM,OAAO,QAAQ,OAAO;AAC5B,YAAM,OAAO,QAAQ;AAAA,IACvB;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/cone.js
CHANGED
|
@@ -1,25 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
defineDoc
|
|
3
|
-
} from "@earendil-works/pi-durable";
|
|
1
|
+
import { defineDoc } from "@earendil-works/pi-durable";
|
|
4
2
|
const ConeDoc = defineDoc({
|
|
5
3
|
kind: "slicc.cone",
|
|
6
4
|
version: 1,
|
|
7
5
|
scope: "session",
|
|
8
6
|
initial: () => ({ conversation: null })
|
|
9
7
|
});
|
|
10
|
-
async function activeCone(harness, root, context) {
|
|
11
|
-
const id = await harness.commit(async (tx) => (await tx.doc(ConeDoc)).conversation, context);
|
|
12
|
-
const found = id === null ? void 0 : await harness.conversation(id, context);
|
|
13
|
-
return found ?? root;
|
|
14
|
-
}
|
|
15
|
-
async function pointCone(harness, conversation, context) {
|
|
16
|
-
await harness.commit(async (tx) => {
|
|
17
|
-
(await tx.doc(ConeDoc)).conversation = conversation.id;
|
|
18
|
-
}, context);
|
|
19
|
-
}
|
|
20
8
|
export {
|
|
21
|
-
ConeDoc
|
|
22
|
-
activeCone,
|
|
23
|
-
pointCone
|
|
9
|
+
ConeDoc
|
|
24
10
|
};
|
|
25
11
|
//# sourceMappingURL=cone.js.map
|
package/dist/cone.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/cone.ts"],
|
|
4
|
-
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {
|
|
5
|
-
"mappings": "AACA
|
|
4
|
+
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport { type Conversation, defineDoc } from '@earendil-works/pi-durable';\n\nexport const ConeDoc = defineDoc<{ conversation: number | null }>({\n kind: 'slicc.cone',\n version: 1,\n scope: 'session',\n initial: () => ({ conversation: null }),\n});\n\nexport interface Cones {\n cone(): Conversation;\n switchCone(conversation: Conversation, context: Context): Promise<void>;\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAA4B,iBAAiB;AAEtC,MAAM,UAAU,UAA2C;AAAA,EAChE,MAAM;AAAA,EACN,SAAS;AAAA,EACT,OAAO;AAAA,EACP,SAAS,OAAO,EAAE,cAAc,KAAK;AACvC,CAAC;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/control.js
CHANGED
|
@@ -4,6 +4,17 @@ import {
|
|
|
4
4
|
} from "@earendil-works/pi-durable";
|
|
5
5
|
import { answerText } from "./agent.js";
|
|
6
6
|
import { LICK_STATE_KIND } from "./licks/state.js";
|
|
7
|
+
function readable(out) {
|
|
8
|
+
const text = out.replace(/^(?:sub)?agent: /, "").trim();
|
|
9
|
+
const sentence = text.charAt(0).toUpperCase() + text.slice(1);
|
|
10
|
+
return /[.!?]$/.test(sentence) ? sentence : `${sentence}.`;
|
|
11
|
+
}
|
|
12
|
+
function created(run) {
|
|
13
|
+
return run().then(
|
|
14
|
+
(id) => ({ id, error: null }),
|
|
15
|
+
(error) => ({ id: null, error: error.message })
|
|
16
|
+
);
|
|
17
|
+
}
|
|
7
18
|
function submissionId(value) {
|
|
8
19
|
const id = Number(value);
|
|
9
20
|
return /^[1-9][0-9]*$/.test(value) && Number.isSafeInteger(id) ? id : void 0;
|
|
@@ -44,9 +55,25 @@ function busy(view) {
|
|
|
44
55
|
const inbox = view.docs["pi.inbox"];
|
|
45
56
|
return Boolean(live?.run) || Boolean(inbox?.items?.length);
|
|
46
57
|
}
|
|
47
|
-
function createAgentControl(harness, target, deliveries, licks) {
|
|
58
|
+
function createAgentControl(harness, target, deliveries, licks, scoops) {
|
|
48
59
|
const cone = cones(target);
|
|
49
60
|
const current = () => cone.cone();
|
|
61
|
+
const agents = target.agents;
|
|
62
|
+
const conversationFor = async (agentId, context) => {
|
|
63
|
+
if (!agentId || !agents) return current();
|
|
64
|
+
const found = await agents.conversation(agentId, context);
|
|
65
|
+
if (!found) throw new Error(`There is no agent ${agentId}.`);
|
|
66
|
+
return found;
|
|
67
|
+
};
|
|
68
|
+
const withdraw = async (conversation, id, context) => {
|
|
69
|
+
const parsed = submissionId(id);
|
|
70
|
+
if (parsed === void 0) return { outcome: "not_found" };
|
|
71
|
+
const result = await harness.abortSubmission(parsed, context, conversation.id);
|
|
72
|
+
if (result === "aborted") return { outcome: "withdrawn" };
|
|
73
|
+
return {
|
|
74
|
+
outcome: result === "not_found" ? "not_found" : "already_placed"
|
|
75
|
+
};
|
|
76
|
+
};
|
|
50
77
|
let lock = Promise.resolve();
|
|
51
78
|
const serial = (operation) => {
|
|
52
79
|
const run = lock.then(operation, operation);
|
|
@@ -57,7 +84,7 @@ function createAgentControl(harness, target, deliveries, licks) {
|
|
|
57
84
|
send(request, context) {
|
|
58
85
|
return serial(async () => {
|
|
59
86
|
const response = await accepted(
|
|
60
|
-
() =>
|
|
87
|
+
async () => (await conversationFor(request.agentId, context)).submit(
|
|
61
88
|
{
|
|
62
89
|
type: "input",
|
|
63
90
|
content: request.text,
|
|
@@ -96,8 +123,9 @@ function createAgentControl(harness, target, deliveries, licks) {
|
|
|
96
123
|
},
|
|
97
124
|
context
|
|
98
125
|
) : await conversation.fork(turn.at, { ownership }, context);
|
|
126
|
+
const changed = scoops && agents ? await scoops.scoops.rewound(agents.activeCone(), fork, context) : { stopped: [], restored: [] };
|
|
99
127
|
await fork.submit(
|
|
100
|
-
{ type: "write", entry: { kind: "slicc.rewound", data: { turns: 1 } } },
|
|
128
|
+
{ type: "write", entry: { kind: "slicc.rewound", data: { turns: 1, ...changed } } },
|
|
101
129
|
context
|
|
102
130
|
);
|
|
103
131
|
await cone.switchCone(fork, context);
|
|
@@ -114,12 +142,9 @@ function createAgentControl(harness, target, deliveries, licks) {
|
|
|
114
142
|
const entry = await harness.commit((tx) => tx.entry(AssistantEntry, answer), context);
|
|
115
143
|
return { status: "done", text: answerText(entry?.model?.[0]?.content), reason: null };
|
|
116
144
|
},
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const result = await harness.abortSubmission(parsed, context, current().id);
|
|
121
|
-
if (result === "aborted") return { outcome: "withdrawn" };
|
|
122
|
-
return { outcome: result === "not_found" ? "not_found" : "already_placed" };
|
|
145
|
+
withdraw: (id, context) => withdraw(current(), id, context),
|
|
146
|
+
async unqueue(agentId, id, context) {
|
|
147
|
+
return withdraw(await conversationFor(agentId, context), id, context);
|
|
123
148
|
},
|
|
124
149
|
abort: (context) => current().abort(context),
|
|
125
150
|
compact: (instructions, context) => accepted(() => current().compact(instructions ?? void 0, context)),
|
|
@@ -166,7 +191,39 @@ function createAgentControl(harness, target, deliveries, licks) {
|
|
|
166
191
|
) : false;
|
|
167
192
|
return { delivered };
|
|
168
193
|
},
|
|
169
|
-
|
|
194
|
+
async stopAgent(agentId, context) {
|
|
195
|
+
await (await conversationFor(agentId, context)).abort(context);
|
|
196
|
+
},
|
|
197
|
+
async selectCone(agentId, context) {
|
|
198
|
+
await agents?.selectCone(agentId, context);
|
|
199
|
+
},
|
|
200
|
+
createCone(name, context) {
|
|
201
|
+
return created(async () => {
|
|
202
|
+
if (!agents) throw new Error("This agent has a single cone.");
|
|
203
|
+
return agents.createCone(name, context);
|
|
204
|
+
});
|
|
205
|
+
},
|
|
206
|
+
createScoop(parentId, name, context) {
|
|
207
|
+
return created(async () => {
|
|
208
|
+
if (!scoops) throw new Error("This agent has no scoops.");
|
|
209
|
+
const roles = await scoops.runtime.roles(context);
|
|
210
|
+
const answer = await scoops.scoops.spawn(
|
|
211
|
+
{ cone: parentId, name, prompts: [], fromAgent: false, limits: roles.limits },
|
|
212
|
+
context
|
|
213
|
+
);
|
|
214
|
+
if (answer.code !== 0) throw new Error(readable(answer.out));
|
|
215
|
+
return `scoop:${answer.out.trim()}`;
|
|
216
|
+
});
|
|
217
|
+
},
|
|
218
|
+
drop(agentId, context) {
|
|
219
|
+
return created(async () => {
|
|
220
|
+
if (!scoops) throw new Error("This agent has no scoops.");
|
|
221
|
+
const answer = await scoops.scoops.stop(agentId, false, context);
|
|
222
|
+
if (answer.code !== 0) throw new Error(readable(answer.out));
|
|
223
|
+
return agentId;
|
|
224
|
+
});
|
|
225
|
+
},
|
|
226
|
+
configure: async (change, context) => (await conversationFor(change.agentId, context)).configure(
|
|
170
227
|
{
|
|
171
228
|
...change.model ? { model: change.model } : {},
|
|
172
229
|
...change.thinkingLevel ? { thinkingLevel: change.thinkingLevel } : {}
|
|
@@ -177,6 +234,7 @@ function createAgentControl(harness, target, deliveries, licks) {
|
|
|
177
234
|
}
|
|
178
235
|
export {
|
|
179
236
|
createAgentControl,
|
|
237
|
+
readable,
|
|
180
238
|
submissionId
|
|
181
239
|
};
|
|
182
240
|
//# sourceMappingURL=control.js.map
|