@ai-ecoverse/slicc-agent 6.14.3 → 6.15.1
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 +9 -3
- package/dist/remote/busy.js +226 -0
- package/dist/remote/busy.js.map +7 -0
- package/dist/remote/client.js +4 -4
- package/dist/remote/client.js.map +2 -2
- package/dist/remote/events.js.map +2 -2
- package/dist/remote/host.js +16 -5
- package/dist/remote/host.js.map +2 -2
- package/dist/types/remote/busy.d.ts +41 -0
- package/dist/types/remote/client.d.ts +2 -0
- package/dist/types/remote/events.d.ts +4 -0
- package/dist/types/remote/host.d.ts +2 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -723,12 +723,18 @@ All the limits on approvals and guests live in one owner-changeable policy, the
|
|
|
723
723
|
|
|
724
724
|
### Remote control
|
|
725
725
|
|
|
726
|
-
`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.
|
|
726
|
+
`remoteControl(connection, { agent? })` from `@ai-ecoverse/slicc-agent/page` gives a linked CLI's `slicc prompt`, `slicc watch`, `slicc wait` 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.
|
|
727
727
|
|
|
728
728
|
- **`prompt({ text, whenBusy = 'followUp', thinking = false, attachments? })`** sends the prompt, with attachments as on `SendRequest` (see Attachments), 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.
|
|
729
|
-
- **`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.
|
|
729
|
+
- **`watch({ since, thinking, busy })`** resolves `{ head, gap?, busy?, 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.
|
|
730
|
+
- **`busy`** (the OPEN's `"busy": true`, for slicc-cli's `prompt --allsettled` and `wait`) tells when the agent has nothing left to do. The cone can end a run while its scoops still work, and run again on their `scoop-notify` licks.
|
|
731
|
+
- **The answer** carries `busy: true | false`, the state after any replay. Without the option it has no `busy`, and neither does an older agent's, so the page's OPENED leaves it out and the node can tell.
|
|
732
|
+
- **Then** a `{ t: 'busy', busy, why? }` line follows each flip, in order with the other events. It has no id and is never replayed. `why` (`cone`, `scoop:<name>`, `pending-lick`) comes only with `true`.
|
|
733
|
+
- **Busy** means, for the cone the stream belongs to (a scoop's own cone when `agent()` names a scoop): the cone has a run or a queued input (`pi.live`, `pi.inbox`); a live scoop of that cone has a run, a queued input or a feed (`ScoopWorkDoc.feeds`); or a lick is still to reach the cone or one of those scoops: a scoop `wait` still to deliver (`ScoopWorkDoc.waits`), or a lick pending in the licks outbox, a lick held there until its conversation's model is known (#475) included. A cron or file-watch lick counts once it is queued, not before. If the model never comes, `busy` stays true.
|
|
734
|
+
- **Computed** in the worker from the Session's commits (`subscribeCommits`, one consistent state per commit: the cone's and its scoops' `pi.live` and `pi.inbox`, `slicc.agents`, `slicc.scoops.work` and the licks outbox `slicc.licks`), never polled, and a line goes out only on a flip.
|
|
735
|
+
- **Ordering:** a `run` `start` never comes before its `busy: true` (one is sent first when needed), and a `busy: false` goes out only after the stream has delivered every event of the commits before it, so the last `busy: false` follows the last `run` `end`. A `busy: true` sent ahead of a run whose work had already ended by then is followed by its `false` the same way.
|
|
730
736
|
- **`abort()`** resolves `{ aborted }`, `false` when nothing was running.
|
|
731
|
-
- **Events** are `user`, `text`, `thinking` (only with `thinking`), `tool`, `tool-result`, `run`, `notice` and `
|
|
737
|
+
- **Events** are `user`, `text`, `thinking` (only with `thinking`), `tool`, `tool-result`, `run`, `notice`, `error` and `busy` (only on a `watch` with `busy`). 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.
|
|
732
738
|
- **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.
|
|
733
739
|
- **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.
|
|
734
740
|
- **Bounds:** a stream nobody polls for two minutes is dropped, and one more than 10 000 events behind ends with `{ t: 'end', reason: 'error' }`.
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import {
|
|
2
|
+
InboxDoc,
|
|
3
|
+
LiveDoc
|
|
4
|
+
} from "@earendil-works/pi-durable";
|
|
5
|
+
import { AgentsDoc, live } from "../agents.js";
|
|
6
|
+
import { LicksOutbox } from "../licks/licks.js";
|
|
7
|
+
import { coneOf, ScoopWorkDoc } from "../scoops/service.js";
|
|
8
|
+
const KINDS = {
|
|
9
|
+
live: LiveDoc.definition.kind,
|
|
10
|
+
inbox: InboxDoc.definition.kind,
|
|
11
|
+
agents: AgentsDoc.definition.kind,
|
|
12
|
+
work: ScoopWorkDoc.definition.kind,
|
|
13
|
+
outbox: LicksOutbox.definition.kind
|
|
14
|
+
};
|
|
15
|
+
function running(value) {
|
|
16
|
+
return Boolean(value?.run);
|
|
17
|
+
}
|
|
18
|
+
function waiting(value) {
|
|
19
|
+
return (value?.items ?? []).some((item) => item.mode !== "write");
|
|
20
|
+
}
|
|
21
|
+
function mark(set, id, on) {
|
|
22
|
+
if (on) set.add(id);
|
|
23
|
+
else set.delete(id);
|
|
24
|
+
}
|
|
25
|
+
function snapshotBusy(snapshot) {
|
|
26
|
+
return Boolean(snapshot.run) || snapshot.inbox.some((item) => item.mode !== "write");
|
|
27
|
+
}
|
|
28
|
+
class BusyTracker {
|
|
29
|
+
#conversation;
|
|
30
|
+
#home;
|
|
31
|
+
#running = /* @__PURE__ */ new Set();
|
|
32
|
+
#waiting = /* @__PURE__ */ new Set();
|
|
33
|
+
#agents;
|
|
34
|
+
#work;
|
|
35
|
+
#outbox;
|
|
36
|
+
#cone;
|
|
37
|
+
constructor(conversation) {
|
|
38
|
+
this.#conversation = conversation;
|
|
39
|
+
this.#home = conversation;
|
|
40
|
+
}
|
|
41
|
+
seed(agents, work, outbox) {
|
|
42
|
+
this.#agents = agents;
|
|
43
|
+
this.#work = work;
|
|
44
|
+
this.#outbox = outbox;
|
|
45
|
+
const state = this.#agents;
|
|
46
|
+
if (!state) return;
|
|
47
|
+
const own = Object.values(state.scoops).find(
|
|
48
|
+
(record) => record.conversation === this.#conversation
|
|
49
|
+
);
|
|
50
|
+
this.#cone = coneOf(state, this.#conversation) ?? own?.cone;
|
|
51
|
+
this.#home = own ? state.cones[own.cone]?.conversation ?? this.#conversation : this.#conversation;
|
|
52
|
+
}
|
|
53
|
+
conversations() {
|
|
54
|
+
const scoops = this.#scoops().map(([, record]) => record.conversation);
|
|
55
|
+
return [.../* @__PURE__ */ new Set([this.#home, this.#conversation, ...scoops])];
|
|
56
|
+
}
|
|
57
|
+
conversation(id, live2, inbox) {
|
|
58
|
+
mark(this.#running, id, running(live2));
|
|
59
|
+
mark(this.#waiting, id, waiting(inbox));
|
|
60
|
+
}
|
|
61
|
+
apply(publication) {
|
|
62
|
+
let touched = false;
|
|
63
|
+
for (const change of publication.changes) {
|
|
64
|
+
if (change.type !== "document" || change.record.key !== void 0) continue;
|
|
65
|
+
const { kind } = change.record;
|
|
66
|
+
const id = change.conversationId;
|
|
67
|
+
if (kind === KINDS.live && id !== void 0) mark(this.#running, id, running(change.value));
|
|
68
|
+
else if (kind === KINDS.inbox && id !== void 0)
|
|
69
|
+
mark(this.#waiting, id, waiting(change.value));
|
|
70
|
+
else if (kind === KINDS.agents && id === void 0)
|
|
71
|
+
this.#agents = change.value ?? void 0;
|
|
72
|
+
else if (kind === KINDS.work && id === void 0)
|
|
73
|
+
this.#work = change.value ?? void 0;
|
|
74
|
+
else if (kind === KINDS.outbox && id === void 0)
|
|
75
|
+
this.#outbox = change.value ?? void 0;
|
|
76
|
+
else continue;
|
|
77
|
+
touched = true;
|
|
78
|
+
}
|
|
79
|
+
return touched;
|
|
80
|
+
}
|
|
81
|
+
#scoops() {
|
|
82
|
+
const cone = this.#cone;
|
|
83
|
+
if (cone === void 0) return [];
|
|
84
|
+
return Object.entries(this.#agents?.scoops ?? {}).filter(
|
|
85
|
+
([, record]) => record.cone === cone && live(record)
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
#busy(id) {
|
|
89
|
+
return this.#running.has(id) || this.#waiting.has(id);
|
|
90
|
+
}
|
|
91
|
+
#lands(target, channel) {
|
|
92
|
+
const state = this.#agents;
|
|
93
|
+
if (!state) return void 0;
|
|
94
|
+
const active = state.cones[state.active]?.conversation;
|
|
95
|
+
if (target === "cone") return active;
|
|
96
|
+
if (target.startsWith("cone:"))
|
|
97
|
+
return state.cones[target.slice("cone:".length)]?.conversation ?? active;
|
|
98
|
+
if (channel === "webhook") return active;
|
|
99
|
+
const scoop = state.scoops[target];
|
|
100
|
+
return scoop && live(scoop) ? scoop.conversation : active;
|
|
101
|
+
}
|
|
102
|
+
#pending(ours) {
|
|
103
|
+
const cone = this.#cone;
|
|
104
|
+
if (cone !== void 0 && Object.values(this.#work?.waits ?? {}).some((w) => w.cone === cone))
|
|
105
|
+
return true;
|
|
106
|
+
return Object.entries(this.#outbox?.queues ?? {}).some(([key, queue]) => {
|
|
107
|
+
if (!queue.pending) return false;
|
|
108
|
+
const lands = this.#lands(key.split("|")[0], queue.pending.lick.channel);
|
|
109
|
+
return lands !== void 0 && ours.has(lands);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
value() {
|
|
113
|
+
const why = [];
|
|
114
|
+
if (this.#busy(this.#home)) why.push("cone");
|
|
115
|
+
const scoops = this.#scoops();
|
|
116
|
+
const feeds = Object.values(this.#work?.feeds ?? {});
|
|
117
|
+
for (const [id, record] of scoops)
|
|
118
|
+
if (this.#busy(record.conversation) || feeds.some((feed) => feed.scoop === id))
|
|
119
|
+
why.push(`scoop:${record.name}`);
|
|
120
|
+
const ours = /* @__PURE__ */ new Set([this.#home, ...scoops.map(([, record]) => record.conversation)]);
|
|
121
|
+
if (this.#pending(ours)) why.push("pending-lick");
|
|
122
|
+
return { busy: why.length > 0, why };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async function trackBusy(harness, conversation, context) {
|
|
126
|
+
const tracker = new BusyTracker(conversation);
|
|
127
|
+
const buffer = [];
|
|
128
|
+
let ready = false;
|
|
129
|
+
let listener = () => void 0;
|
|
130
|
+
const stop = harness.subscribeCommits((publication) => {
|
|
131
|
+
if (!ready) buffer.push(publication);
|
|
132
|
+
else if (tracker.apply(publication)) listener();
|
|
133
|
+
});
|
|
134
|
+
try {
|
|
135
|
+
const [agents, work, outbox] = await Promise.all([
|
|
136
|
+
harness.snapshot(AgentsDoc, context),
|
|
137
|
+
harness.snapshot(ScoopWorkDoc, context),
|
|
138
|
+
harness.snapshot(LicksOutbox, context)
|
|
139
|
+
]);
|
|
140
|
+
tracker.seed(agents, work, outbox);
|
|
141
|
+
await Promise.all(
|
|
142
|
+
tracker.conversations().map(async (id) => {
|
|
143
|
+
const [liveDoc, inboxDoc] = await Promise.all([
|
|
144
|
+
harness.snapshot(LiveDoc, id, context),
|
|
145
|
+
harness.snapshot(InboxDoc, id, context)
|
|
146
|
+
]);
|
|
147
|
+
tracker.conversation(id, liveDoc, inboxDoc);
|
|
148
|
+
})
|
|
149
|
+
);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
stop();
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
for (const publication of buffer.splice(0)) tracker.apply(publication);
|
|
155
|
+
ready = true;
|
|
156
|
+
return {
|
|
157
|
+
tracker,
|
|
158
|
+
listen(next) {
|
|
159
|
+
listener = next;
|
|
160
|
+
},
|
|
161
|
+
stop
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function busyLine(state) {
|
|
165
|
+
return state.busy ? { t: "busy", busy: true, why: state.why } : { t: "busy", busy: false };
|
|
166
|
+
}
|
|
167
|
+
class BusyGate {
|
|
168
|
+
#tracker;
|
|
169
|
+
#push;
|
|
170
|
+
#sent;
|
|
171
|
+
#timer;
|
|
172
|
+
constructor(tracker, push, sent) {
|
|
173
|
+
this.#tracker = tracker;
|
|
174
|
+
this.#push = push;
|
|
175
|
+
this.#sent = sent;
|
|
176
|
+
}
|
|
177
|
+
before(events) {
|
|
178
|
+
if (this.#sent || !events.some((event) => event.t === "run" && event.state === "start"))
|
|
179
|
+
return events;
|
|
180
|
+
this.#sent = true;
|
|
181
|
+
const now = this.#tracker.value();
|
|
182
|
+
this.changed();
|
|
183
|
+
return [busyLine(now.busy ? now : { busy: true, why: ["cone"] }), ...events];
|
|
184
|
+
}
|
|
185
|
+
changed() {
|
|
186
|
+
if (this.#timer !== void 0 || this.#tracker.value().busy === this.#sent) return;
|
|
187
|
+
this.#timer = setTimeout(() => {
|
|
188
|
+
this.#timer = void 0;
|
|
189
|
+
const now = this.#tracker.value();
|
|
190
|
+
if (now.busy === this.#sent) return;
|
|
191
|
+
this.#sent = now.busy;
|
|
192
|
+
this.#push([busyLine(now)]);
|
|
193
|
+
}, 0);
|
|
194
|
+
}
|
|
195
|
+
stop() {
|
|
196
|
+
clearTimeout(this.#timer);
|
|
197
|
+
this.#timer = void 0;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
async function watchBusy(harness, watched, push, context) {
|
|
201
|
+
const busy = await trackBusy(harness, watched.conversation.id, context).catch(async (error) => {
|
|
202
|
+
await watched.stop();
|
|
203
|
+
throw error;
|
|
204
|
+
});
|
|
205
|
+
const opened = busy.tracker.value().busy || snapshotBusy(watched.snapshot);
|
|
206
|
+
const gate = new BusyGate(busy.tracker, push, opened);
|
|
207
|
+
busy.listen(() => gate.changed());
|
|
208
|
+
gate.changed();
|
|
209
|
+
return {
|
|
210
|
+
opened,
|
|
211
|
+
gate,
|
|
212
|
+
stop() {
|
|
213
|
+
busy.stop();
|
|
214
|
+
gate.stop();
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
export {
|
|
219
|
+
BusyGate,
|
|
220
|
+
BusyTracker,
|
|
221
|
+
busyLine,
|
|
222
|
+
snapshotBusy,
|
|
223
|
+
trackBusy,
|
|
224
|
+
watchBusy
|
|
225
|
+
};
|
|
226
|
+
//# sourceMappingURL=busy.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/remote/busy.ts"],
|
|
4
|
+
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {\n type CommitPublication,\n type ConversationId,\n type Harness,\n InboxDoc,\n type InboxState,\n LiveDoc,\n type LiveState,\n type SnapshotEvent,\n} from '@earendil-works/pi-durable';\nimport { AgentsDoc, type AgentsState, live } from '../agents.ts';\nimport { LicksOutbox } from '../licks/licks.ts';\nimport { coneOf, ScoopWorkDoc } from '../scoops/service.ts';\nimport type { RemoteEvent } from './events.ts';\n\nexport interface BusyState {\n busy: boolean;\n why: string[];\n}\n\ntype Work = {\n feeds: Record<string, { scoop: string }>;\n waits: Record<string, { cone: string }>;\n};\n\ntype Outbox = {\n queues: Record<string, { pending: { lick: { channel: string } } | null }>;\n};\n\nconst KINDS = {\n live: LiveDoc.definition.kind,\n inbox: InboxDoc.definition.kind,\n agents: AgentsDoc.definition.kind,\n work: ScoopWorkDoc.definition.kind,\n outbox: LicksOutbox.definition.kind,\n};\n\nfunction running(value: unknown): boolean {\n return Boolean((value as LiveState | null)?.run);\n}\n\nfunction waiting(value: unknown): boolean {\n return ((value as InboxState | null)?.items ?? []).some((item) => item.mode !== 'write');\n}\n\nfunction mark(set: Set<number>, id: number, on: boolean): void {\n if (on) set.add(id);\n else set.delete(id);\n}\n\nexport function snapshotBusy(snapshot: SnapshotEvent): boolean {\n return Boolean(snapshot.run) || snapshot.inbox.some((item) => item.mode !== 'write');\n}\n\nexport class BusyTracker {\n readonly #conversation: number;\n #home: number;\n readonly #running = new Set<number>();\n readonly #waiting = new Set<number>();\n #agents: Readonly<AgentsState> | undefined;\n #work: Readonly<Work> | undefined;\n #outbox: Readonly<Outbox> | undefined;\n #cone: string | undefined;\n\n constructor(conversation: number) {\n this.#conversation = conversation;\n this.#home = conversation;\n }\n\n seed(agents: unknown, work: unknown, outbox: unknown): void {\n this.#agents = agents as AgentsState | undefined;\n this.#work = work as Work | undefined;\n this.#outbox = outbox as Outbox | undefined;\n const state = this.#agents;\n if (!state) return;\n const own = Object.values(state.scoops).find(\n (record) => record.conversation === this.#conversation\n );\n this.#cone = coneOf(state, this.#conversation) ?? own?.cone;\n this.#home = own\n ? (state.cones[own.cone]?.conversation ?? this.#conversation)\n : this.#conversation;\n }\n\n conversations(): number[] {\n const scoops = this.#scoops().map(([, record]) => record.conversation);\n return [...new Set([this.#home, this.#conversation, ...scoops])];\n }\n\n conversation(id: number, live: unknown, inbox: unknown): void {\n mark(this.#running, id, running(live));\n mark(this.#waiting, id, waiting(inbox));\n }\n\n apply(publication: CommitPublication): boolean {\n let touched = false;\n for (const change of publication.changes) {\n if (change.type !== 'document' || change.record.key !== undefined) continue;\n const { kind } = change.record;\n const id = change.conversationId;\n if (kind === KINDS.live && id !== undefined) mark(this.#running, id, running(change.value));\n else if (kind === KINDS.inbox && id !== undefined)\n mark(this.#waiting, id, waiting(change.value));\n else if (kind === KINDS.agents && id === undefined)\n this.#agents = (change.value ?? undefined) as AgentsState | undefined;\n else if (kind === KINDS.work && id === undefined)\n this.#work = (change.value ?? undefined) as Work | undefined;\n else if (kind === KINDS.outbox && id === undefined)\n this.#outbox = (change.value ?? undefined) as Outbox | undefined;\n else continue;\n touched = true;\n }\n return touched;\n }\n\n #scoops() {\n const cone = this.#cone;\n if (cone === undefined) return [];\n return Object.entries(this.#agents?.scoops ?? {}).filter(\n ([, record]) => record.cone === cone && live(record)\n );\n }\n\n #busy(id: number): boolean {\n return this.#running.has(id) || this.#waiting.has(id);\n }\n\n #lands(target: string, channel: string): number | undefined {\n const state = this.#agents;\n if (!state) return undefined;\n const active = state.cones[state.active]?.conversation;\n if (target === 'cone') return active;\n if (target.startsWith('cone:'))\n return state.cones[target.slice('cone:'.length)]?.conversation ?? active;\n if (channel === 'webhook') return active;\n const scoop = state.scoops[target];\n return scoop && live(scoop) ? scoop.conversation : active;\n }\n\n #pending(ours: Set<number>): boolean {\n const cone = this.#cone;\n if (cone !== undefined && Object.values(this.#work?.waits ?? {}).some((w) => w.cone === cone))\n return true;\n return Object.entries(this.#outbox?.queues ?? {}).some(([key, queue]) => {\n if (!queue.pending) return false;\n const lands = this.#lands(key.split('|')[0] as string, queue.pending.lick.channel);\n return lands !== undefined && ours.has(lands);\n });\n }\n\n value(): BusyState {\n const why: string[] = [];\n if (this.#busy(this.#home)) why.push('cone');\n const scoops = this.#scoops();\n const feeds = Object.values(this.#work?.feeds ?? {});\n for (const [id, record] of scoops)\n if (this.#busy(record.conversation) || feeds.some((feed) => feed.scoop === id))\n why.push(`scoop:${record.name}`);\n const ours = new Set([this.#home, ...scoops.map(([, record]) => record.conversation)]);\n if (this.#pending(ours)) why.push('pending-lick');\n return { busy: why.length > 0, why };\n }\n}\n\nexport async function trackBusy(\n harness: Harness,\n conversation: number,\n context: Context\n): Promise<{ tracker: BusyTracker; listen(listener: () => void): void; stop(): void }> {\n const tracker = new BusyTracker(conversation);\n const buffer: CommitPublication[] = [];\n let ready = false;\n let listener: () => void = () => undefined;\n const stop = harness.subscribeCommits((publication) => {\n if (!ready) buffer.push(publication);\n else if (tracker.apply(publication)) listener();\n });\n try {\n const [agents, work, outbox] = await Promise.all([\n harness.snapshot(AgentsDoc, context),\n harness.snapshot(ScoopWorkDoc, context),\n harness.snapshot(LicksOutbox, context),\n ]);\n tracker.seed(agents, work, outbox);\n await Promise.all(\n tracker.conversations().map(async (id) => {\n const [liveDoc, inboxDoc] = await Promise.all([\n harness.snapshot(LiveDoc, id as ConversationId, context),\n harness.snapshot(InboxDoc, id as ConversationId, context),\n ]);\n tracker.conversation(id, liveDoc, inboxDoc);\n })\n );\n } catch (error) {\n stop();\n throw error;\n }\n for (const publication of buffer.splice(0)) tracker.apply(publication);\n ready = true;\n return {\n tracker,\n listen(next) {\n listener = next;\n },\n stop,\n };\n}\n\nexport function busyLine(state: BusyState): RemoteEvent {\n return state.busy ? { t: 'busy', busy: true, why: state.why } : { t: 'busy', busy: false };\n}\n\nexport class BusyGate {\n readonly #tracker: BusyTracker;\n readonly #push: (events: RemoteEvent[]) => void;\n #sent: boolean;\n #timer: ReturnType<typeof setTimeout> | undefined;\n\n constructor(tracker: BusyTracker, push: (events: RemoteEvent[]) => void, sent: boolean) {\n this.#tracker = tracker;\n this.#push = push;\n this.#sent = sent;\n }\n\n before(events: RemoteEvent[]): RemoteEvent[] {\n if (this.#sent || !events.some((event) => event.t === 'run' && event.state === 'start'))\n return events;\n this.#sent = true;\n const now = this.#tracker.value();\n this.changed();\n return [busyLine(now.busy ? now : { busy: true, why: ['cone'] }), ...events];\n }\n\n changed(): void {\n if (this.#timer !== undefined || this.#tracker.value().busy === this.#sent) return;\n this.#timer = setTimeout(() => {\n this.#timer = undefined;\n const now = this.#tracker.value();\n if (now.busy === this.#sent) return;\n this.#sent = now.busy;\n this.#push([busyLine(now)]);\n }, 0);\n }\n\n stop(): void {\n clearTimeout(this.#timer);\n this.#timer = undefined;\n }\n}\n\nexport async function watchBusy(\n harness: Harness,\n watched: { conversation: { id: number }; snapshot: SnapshotEvent; stop(): Promise<unknown> },\n push: (events: RemoteEvent[]) => void,\n context: Context\n): Promise<{ opened: boolean; gate: BusyGate; stop(): void }> {\n const busy = await trackBusy(harness, watched.conversation.id, context).catch(async (error) => {\n await watched.stop();\n throw error;\n });\n const opened = busy.tracker.value().busy || snapshotBusy(watched.snapshot);\n const gate = new BusyGate(busy.tracker, push, opened);\n busy.listen(() => gate.changed());\n gate.changed();\n return {\n opened,\n gate,\n stop() {\n busy.stop();\n gate.stop();\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["live"]
|
|
7
|
+
}
|
package/dist/remote/client.js
CHANGED
|
@@ -61,14 +61,14 @@ function remoteControl(connection, options = {}) {
|
|
|
61
61
|
events: events(service, result.stream)
|
|
62
62
|
};
|
|
63
63
|
},
|
|
64
|
-
async watch({ since = null, thinking = false } = {}) {
|
|
64
|
+
async watch({ since = null, thinking = false, busy = false } = {}) {
|
|
65
65
|
const service = need();
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
);
|
|
66
|
+
const request = { agentId: agent(), since, thinking, ...busy ? { busy: true } : {} };
|
|
67
|
+
const result = opened(await service.watch(request, BACKGROUND_CONTEXT));
|
|
69
68
|
return {
|
|
70
69
|
head: result.head ?? "",
|
|
71
70
|
...result.gap ? { gap: true } : {},
|
|
71
|
+
...typeof result.busy === "boolean" ? { busy: result.busy } : {},
|
|
72
72
|
events: events(service, result.stream)
|
|
73
73
|
};
|
|
74
74
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/remote/client.ts"],
|
|
4
|
-
"sourcesContent": ["import { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { SendAttachment } from '../attachments.ts';\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 attachments?: SendAttachment[];\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, attachments }) {\n const service = need();\n const request = {\n agentId: agent(),\n text,\n whenBusy,\n thinking,\n ...(attachments?.length ? { attachments } : {}),\n };\n const result = opened(await service.prompt(request, BACKGROUND_CONTEXT));\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
|
|
5
|
-
"mappings": "AAAA,SAAS,0BAA0B;AAS5B,MAAM,UAAU;
|
|
4
|
+
"sourcesContent": ["import { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { SendAttachment } from '../attachments.ts';\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 attachments?: SendAttachment[];\n }): Promise<{ delivered: Delivered; events: AsyncIterable<RemoteEvent> }>;\n watch(request?: {\n since?: string | null;\n thinking?: boolean;\n busy?: boolean;\n }): Promise<{ head: string; gap?: true; busy?: boolean; 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, attachments }) {\n const service = need();\n const request = {\n agentId: agent(),\n text,\n whenBusy,\n thinking,\n ...(attachments?.length ? { attachments } : {}),\n };\n const result = opened(await service.prompt(request, BACKGROUND_CONTEXT));\n return {\n delivered: result.delivered as Delivered,\n events: events(service, result.stream),\n };\n },\n async watch({ since = null, thinking = false, busy = false } = {}) {\n const service = need();\n const request = { agentId: agent(), since, thinking, ...(busy ? { busy: true } : {}) };\n const result = opened(await service.watch(request, BACKGROUND_CONTEXT));\n return {\n head: result.head ?? '',\n ...(result.gap ? { gap: true as const } : {}),\n ...(typeof result.busy === 'boolean' ? { busy: result.busy } : {}),\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;AAS5B,MAAM,UAAU;AAqBvB,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,OAAO,YAAY,GAAG;AAC3E,YAAM,UAAU,KAAK;AACrB,YAAM,UAAU;AAAA,QACd,SAAS,MAAM;AAAA,QACf;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAI,aAAa,SAAS,EAAE,YAAY,IAAI,CAAC;AAAA,MAC/C;AACA,YAAM,SAAS,OAAO,MAAM,QAAQ,OAAO,SAAS,kBAAkB,CAAC;AACvE,aAAO;AAAA,QACL,WAAW,OAAO;AAAA,QAClB,QAAQ,OAAO,SAAS,OAAO,MAAM;AAAA,MACvC;AAAA,IACF;AAAA,IACA,MAAM,MAAM,EAAE,QAAQ,MAAM,WAAW,OAAO,OAAO,MAAM,IAAI,CAAC,GAAG;AACjE,YAAM,UAAU,KAAK;AACrB,YAAM,UAAU,EAAE,SAAS,MAAM,GAAG,OAAO,UAAU,GAAI,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC,EAAG;AACrF,YAAM,SAAS,OAAO,MAAM,QAAQ,MAAM,SAAS,kBAAkB,CAAC;AACtE,aAAO;AAAA,QACL,MAAM,OAAO,QAAQ;AAAA,QACrB,GAAI,OAAO,MAAM,EAAE,KAAK,KAAc,IAAI,CAAC;AAAA,QAC3C,GAAI,OAAO,OAAO,SAAS,YAAY,EAAE,MAAM,OAAO,KAAK,IAAI,CAAC;AAAA,QAChE,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
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/remote/events.ts"],
|
|
4
|
-
"sourcesContent": ["import type { AgentEvent, EntryRecord } from '@earendil-works/pi-durable';\nimport { entryLick } from '../licks/state.ts';\nimport { mcpLabel } from '../mcp/label.ts';\nimport { redactSecrets } from '../memory/format.ts';\nimport type { Delivered } from '../services.ts';\n\nexport const SUMMARY = 200;\n\nexport type EndReason = 'stop' | 'aborted' | 'error';\n\nexport type RemoteEvent =\n | { t: 'user'; id: string; text: string; delivered: Delivered }\n | { t: 'text'; id: string; delta: string }\n | { t: 'thinking'; id: string; delta: string }\n | { t: 'tool'; id: string; call: string; name: string; summary: string }\n | { t: 'tool-result'; id: string; call: string; ok: boolean; summary: string }\n | { t: 'run'; state: 'start' | 'end'; reason?: EndReason }\n | { t: 'notice'; id: string; text: string }\n | { t: 'error'; id: string; message: string; detail?: string }\n | { t: 'end'; reason: EndReason; message?: string };\n\ntype Json = Record<string, unknown>;\n\ninterface Block {\n type: string;\n text?: string;\n thinking?: string;\n id?: string;\n name?: string;\n arguments?: Json;\n}\n\ninterface ModelMessage {\n role?: string;\n content?: string | Block[];\n timestamp?: number;\n stopReason?: string;\n errorMessage?: string;\n toolCallId?: string;\n toolName?: string;\n isError?: boolean;\n}\n\nexport function clip(text: string, limit = SUMMARY): string {\n const flat = redactSecrets(text).text.replace(/\\s+/g, ' ').trim();\n return flat.length > limit ? `${flat.slice(0, limit - 1)}\u2026` : flat;\n}\n\nfunction textOf(content: ModelMessage['content']): string {\n if (typeof content === 'string') return content;\n return (content ?? []).map((block) => (block.type === 'text' ? (block.text ?? '') : '')).join('');\n}\n\nconst SUBJECTS = ['command', 'path', 'pattern', 'url', 'query', 'code', 'name'];\n\nexport function toolSummary(name: string, args: Json | undefined): string {\n const subject = SUBJECTS.map((key) => args?.[key]).find((value) => typeof value === 'string');\n const detail = typeof subject === 'string' ? subject : JSON.stringify(args ?? {});\n return clip(`${mcpLabel(name) ?? name} ${detail}`);\n}\n\nexport function resultSummary(message: ModelMessage): string {\n const text = textOf(message.content);\n if (message.isError)\n return clip(\n text.split('\\n').find((line) => line.trim() && !/^<\\/?harness>$/.test(line.trim())) ??\n 'failed'\n );\n const lines = text ? text.split('\\n').length : 0;\n return lines === 1 ? '1 line' : `${lines} lines`;\n}\n\nexport function keyOf(timestamp: number | undefined, at: number): string {\n return at > 0 ? `a${timestamp ?? 0}~${at}` : `a${timestamp ?? 0}`;\n}\n\nfunction reasonOf(stopReason: string | undefined): EndReason {\n if (stopReason === 'aborted') return 'aborted';\n return stopReason === 'error' ? 'error' : 'stop';\n}\n\nexport class Mapper {\n readonly #thinking: boolean;\n readonly #emitted = new Map<string, number>();\n readonly #seen = new Set<number>();\n #current: string | undefined;\n #previous: string | undefined;\n #ending: readonly number[] = [];\n readonly #stamps = new Map<number, number[]>();\n reason: EndReason = 'stop';\n error: string | undefined;\n\n constructor(thinking: boolean) {\n this.#thinking = thinking;\n }\n\n #key(message: ModelMessage | undefined, entry?: number): string {\n const stamp = message?.timestamp ?? 0;\n const list = this.#stamps.get(stamp) ?? [];\n this.#stamps.set(stamp, list);\n if (entry === undefined) return keyOf(stamp, list.length);\n if (!list.includes(entry)) list.push(entry);\n return keyOf(stamp, list.indexOf(entry));\n }\n\n #delta(t: 'text' | 'thinking', id: string, full: string): RemoteEvent[] {\n if (t === 'thinking' && !this.#thinking) return [];\n const done = this.#emitted.get(id) ?? 0;\n if (full.length <= done) return [];\n this.#emitted.set(id, full.length);\n return [{ t, id, delta: full.slice(done) }];\n }\n\n partial(message: ModelMessage | undefined): RemoteEvent[] {\n if (!message || !Array.isArray(message.content)) return [];\n const key = this.#key(message);\n this.#current = key;\n return message.content.flatMap((block, at) => {\n if (block.type === 'text') return this.#delta('text', `${key}.${at}`, block.text ?? '');\n if (block.type === 'thinking')\n return this.#delta('thinking', `${key}.${at}`, block.thinking ?? '');\n return [];\n });\n }\n\n #assistant(message: ModelMessage, entry: EntryRecord): RemoteEvent[] {\n const key = this.#key(message, entry.id);\n const blocks = Array.isArray(message.content) ? message.content : [];\n const out = blocks.flatMap((block, at): RemoteEvent[] => {\n const id = `${key}.${at}`;\n if (block.type === 'text') return this.#delta('text', id, block.text ?? '');\n if (block.type === 'thinking') return this.#delta('thinking', id, block.thinking ?? '');\n if (block.type !== 'toolCall') return [];\n const name = block.name ?? 'tool';\n return [\n { t: 'tool', id, call: block.id ?? '', name, summary: toolSummary(name, block.arguments) },\n ];\n });\n if (typeof message.content === 'string')\n out.push(...this.#delta('text', `${key}.0`, message.content));\n this.reason = reasonOf(message.stopReason);\n this.error =\n message.stopReason === 'error' ? clip(message.errorMessage ?? 'error', 1000) : undefined;\n if (this.error) out.push({ t: 'error', id: String(entry.id), message: this.error });\n return out;\n }\n\n #delivered(entry: EntryRecord): Delivered {\n if (entry.byTaskId === undefined) return 'run';\n return this.#previous === 'pi.tool-result' ? 'steer' : 'follow-up';\n }\n\n entry(entry: EntryRecord, delivered: Delivered = this.#delivered(entry)): RemoteEvent[] {\n if (this.#seen.has(entry.id)) return [];\n this.#seen.add(entry.id);\n this.#previous = entry.kind;\n const id = String(entry.id);\n const message = entry.model?.[0] as ModelMessage | undefined;\n if (entry.kind === 'pi.user' && message) {\n const lick = entryLick(entry);\n if (lick) return [{ t: 'notice', id, text: clip(`lick ${lick.channel}: ${lick.title}`) }];\n return [{ t: 'user', id, text: redactSecrets(textOf(message.content)).text, delivered }];\n }\n if (entry.kind === 'pi.assistant' && message) return this.#assistant(message, entry);\n if (entry.kind === 'pi.tool-result' && message)\n return [\n {\n t: 'tool-result',\n id,\n call: message.toolCallId ?? '',\n ok: !message.isError,\n summary: resultSummary(message),\n },\n ];\n if (entry.kind === 'pi.compaction') return [{ t: 'notice', id, text: 'compacted' }];\n if (entry.kind === 'slicc.rewound') return [{ t: 'notice', id, text: 'rewound' }];\n return [];\n }\n\n #ended(reason: EndReason): RemoteEvent[] {\n if (!this.#ending.length) return [];\n this.#ending = [];\n this.reason = reason;\n return [{ t: 'run', state: 'end', reason }];\n }\n\n #settled(record: { id: number; status: string; reason?: unknown }): RemoteEvent[] {\n if (!this.#ending.includes(record.id) || ['queued', 'placed'].includes(record.status))\n return [];\n if (record.status !== 'unanswered') return this.#ended(this.reason);\n if (record.reason === 'aborted') return this.#ended('aborted');\n this.error ??= clip(String(record.reason ?? 'error'), 1000);\n return this.#ended('error');\n }\n\n last(): number {\n return Math.max(0, ...this.#seen);\n }\n\n event(event: AgentEvent): RemoteEvent[] {\n switch (event.type) {\n case 'run_start': {\n const ended = this.#ended(this.reason);\n this.reason = 'stop';\n this.error = undefined;\n return [...ended, { t: 'run', state: 'start' }];\n }\n case 'run_end':\n this.#ending = event.inputs;\n return [];\n case 'submission':\n return this.#settled(event.record);\n case 'message_start':\n if ((event.message as ModelMessage).role === 'assistant')\n this.#current = this.#key(event.message as ModelMessage);\n return [];\n case 'message_update':\n return event.changes.flatMap((change) => {\n if (change.type !== 'text_delta' && change.type !== 'thinking_delta') return [];\n const id = `${this.#current}.${change.contentIndex}`;\n const t = change.type === 'text_delta' ? 'text' : 'thinking';\n const done = this.#emitted.get(id) ?? 0;\n if (t === 'thinking' && !this.#thinking) return [];\n this.#emitted.set(id, done + change.delta.length);\n return [{ t, id, delta: change.delta }];\n });\n case 'message_end':\n return this.entry(event.entry);\n case 'tool_execution_end':\n return event.entry\n ? this.entry(event.entry)\n : [\n {\n t: 'tool-result',\n id: `x${event.toolCallId}`,\n call: event.toolCallId,\n ok: false,\n summary: 'the tool did not finish',\n },\n ];\n case 'auto_retry_start':\n return [\n {\n t: 'notice',\n id: `retry-${event.at}`,\n text: clip(`retrying (attempt ${event.attempt}): ${event.errorMessage}`),\n },\n ];\n case 'compaction_start':\n return [{ t: 'notice', id: `compaction-${event.taskId}`, text: 'compacting' }];\n case 'task_failed':\n return [{ t: 'error', id: `task-${event.taskId}`, message: clip(event.message, 1000) }];\n default:\n return [];\n }\n }\n}\n\nexport function replayOf(\n entries: readonly EntryRecord[],\n partial: ModelMessage | undefined,\n thinking: boolean\n): RemoteEvent[] {\n const mapper = new Mapper(thinking);\n const out: RemoteEvent[] = [];\n for (const entry of entries) out.push(...mapper.entry(entry));\n out.push(...mapper.partial(partial));\n return out;\n}\n\nexport function idOf(event: RemoteEvent): string | undefined {\n return 'id' in event ? event.id : undefined;\n}\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,iBAAiB;AAC1B,SAAS,gBAAgB;AACzB,SAAS,qBAAqB;AAGvB,MAAM,UAAU;
|
|
4
|
+
"sourcesContent": ["import type { AgentEvent, EntryRecord } from '@earendil-works/pi-durable';\nimport { entryLick } from '../licks/state.ts';\nimport { mcpLabel } from '../mcp/label.ts';\nimport { redactSecrets } from '../memory/format.ts';\nimport type { Delivered } from '../services.ts';\n\nexport const SUMMARY = 200;\n\nexport type EndReason = 'stop' | 'aborted' | 'error';\n\nexport type RemoteEvent =\n | { t: 'user'; id: string; text: string; delivered: Delivered }\n | { t: 'text'; id: string; delta: string }\n | { t: 'thinking'; id: string; delta: string }\n | { t: 'tool'; id: string; call: string; name: string; summary: string }\n | { t: 'tool-result'; id: string; call: string; ok: boolean; summary: string }\n | { t: 'run'; state: 'start' | 'end'; reason?: EndReason }\n | { t: 'notice'; id: string; text: string }\n | { t: 'error'; id: string; message: string; detail?: string }\n | { t: 'busy'; busy: boolean; why?: string[] }\n | { t: 'end'; reason: EndReason; message?: string };\n\ntype Json = Record<string, unknown>;\n\ninterface Block {\n type: string;\n text?: string;\n thinking?: string;\n id?: string;\n name?: string;\n arguments?: Json;\n}\n\ninterface ModelMessage {\n role?: string;\n content?: string | Block[];\n timestamp?: number;\n stopReason?: string;\n errorMessage?: string;\n toolCallId?: string;\n toolName?: string;\n isError?: boolean;\n}\n\nexport function clip(text: string, limit = SUMMARY): string {\n const flat = redactSecrets(text).text.replace(/\\s+/g, ' ').trim();\n return flat.length > limit ? `${flat.slice(0, limit - 1)}\u2026` : flat;\n}\n\nfunction textOf(content: ModelMessage['content']): string {\n if (typeof content === 'string') return content;\n return (content ?? []).map((block) => (block.type === 'text' ? (block.text ?? '') : '')).join('');\n}\n\nconst SUBJECTS = ['command', 'path', 'pattern', 'url', 'query', 'code', 'name'];\n\nexport function toolSummary(name: string, args: Json | undefined): string {\n const subject = SUBJECTS.map((key) => args?.[key]).find((value) => typeof value === 'string');\n const detail = typeof subject === 'string' ? subject : JSON.stringify(args ?? {});\n return clip(`${mcpLabel(name) ?? name} ${detail}`);\n}\n\nexport function resultSummary(message: ModelMessage): string {\n const text = textOf(message.content);\n if (message.isError)\n return clip(\n text.split('\\n').find((line) => line.trim() && !/^<\\/?harness>$/.test(line.trim())) ??\n 'failed'\n );\n const lines = text ? text.split('\\n').length : 0;\n return lines === 1 ? '1 line' : `${lines} lines`;\n}\n\nexport function keyOf(timestamp: number | undefined, at: number): string {\n return at > 0 ? `a${timestamp ?? 0}~${at}` : `a${timestamp ?? 0}`;\n}\n\nfunction reasonOf(stopReason: string | undefined): EndReason {\n if (stopReason === 'aborted') return 'aborted';\n return stopReason === 'error' ? 'error' : 'stop';\n}\n\nexport class Mapper {\n readonly #thinking: boolean;\n readonly #emitted = new Map<string, number>();\n readonly #seen = new Set<number>();\n #current: string | undefined;\n #previous: string | undefined;\n #ending: readonly number[] = [];\n readonly #stamps = new Map<number, number[]>();\n reason: EndReason = 'stop';\n error: string | undefined;\n\n constructor(thinking: boolean) {\n this.#thinking = thinking;\n }\n\n #key(message: ModelMessage | undefined, entry?: number): string {\n const stamp = message?.timestamp ?? 0;\n const list = this.#stamps.get(stamp) ?? [];\n this.#stamps.set(stamp, list);\n if (entry === undefined) return keyOf(stamp, list.length);\n if (!list.includes(entry)) list.push(entry);\n return keyOf(stamp, list.indexOf(entry));\n }\n\n #delta(t: 'text' | 'thinking', id: string, full: string): RemoteEvent[] {\n if (t === 'thinking' && !this.#thinking) return [];\n const done = this.#emitted.get(id) ?? 0;\n if (full.length <= done) return [];\n this.#emitted.set(id, full.length);\n return [{ t, id, delta: full.slice(done) }];\n }\n\n partial(message: ModelMessage | undefined): RemoteEvent[] {\n if (!message || !Array.isArray(message.content)) return [];\n const key = this.#key(message);\n this.#current = key;\n return message.content.flatMap((block, at) => {\n if (block.type === 'text') return this.#delta('text', `${key}.${at}`, block.text ?? '');\n if (block.type === 'thinking')\n return this.#delta('thinking', `${key}.${at}`, block.thinking ?? '');\n return [];\n });\n }\n\n #assistant(message: ModelMessage, entry: EntryRecord): RemoteEvent[] {\n const key = this.#key(message, entry.id);\n const blocks = Array.isArray(message.content) ? message.content : [];\n const out = blocks.flatMap((block, at): RemoteEvent[] => {\n const id = `${key}.${at}`;\n if (block.type === 'text') return this.#delta('text', id, block.text ?? '');\n if (block.type === 'thinking') return this.#delta('thinking', id, block.thinking ?? '');\n if (block.type !== 'toolCall') return [];\n const name = block.name ?? 'tool';\n return [\n { t: 'tool', id, call: block.id ?? '', name, summary: toolSummary(name, block.arguments) },\n ];\n });\n if (typeof message.content === 'string')\n out.push(...this.#delta('text', `${key}.0`, message.content));\n this.reason = reasonOf(message.stopReason);\n this.error =\n message.stopReason === 'error' ? clip(message.errorMessage ?? 'error', 1000) : undefined;\n if (this.error) out.push({ t: 'error', id: String(entry.id), message: this.error });\n return out;\n }\n\n #delivered(entry: EntryRecord): Delivered {\n if (entry.byTaskId === undefined) return 'run';\n return this.#previous === 'pi.tool-result' ? 'steer' : 'follow-up';\n }\n\n entry(entry: EntryRecord, delivered: Delivered = this.#delivered(entry)): RemoteEvent[] {\n if (this.#seen.has(entry.id)) return [];\n this.#seen.add(entry.id);\n this.#previous = entry.kind;\n const id = String(entry.id);\n const message = entry.model?.[0] as ModelMessage | undefined;\n if (entry.kind === 'pi.user' && message) {\n const lick = entryLick(entry);\n if (lick) return [{ t: 'notice', id, text: clip(`lick ${lick.channel}: ${lick.title}`) }];\n return [{ t: 'user', id, text: redactSecrets(textOf(message.content)).text, delivered }];\n }\n if (entry.kind === 'pi.assistant' && message) return this.#assistant(message, entry);\n if (entry.kind === 'pi.tool-result' && message)\n return [\n {\n t: 'tool-result',\n id,\n call: message.toolCallId ?? '',\n ok: !message.isError,\n summary: resultSummary(message),\n },\n ];\n if (entry.kind === 'pi.compaction') return [{ t: 'notice', id, text: 'compacted' }];\n if (entry.kind === 'slicc.rewound') return [{ t: 'notice', id, text: 'rewound' }];\n return [];\n }\n\n #ended(reason: EndReason): RemoteEvent[] {\n if (!this.#ending.length) return [];\n this.#ending = [];\n this.reason = reason;\n return [{ t: 'run', state: 'end', reason }];\n }\n\n #settled(record: { id: number; status: string; reason?: unknown }): RemoteEvent[] {\n if (!this.#ending.includes(record.id) || ['queued', 'placed'].includes(record.status))\n return [];\n if (record.status !== 'unanswered') return this.#ended(this.reason);\n if (record.reason === 'aborted') return this.#ended('aborted');\n this.error ??= clip(String(record.reason ?? 'error'), 1000);\n return this.#ended('error');\n }\n\n last(): number {\n return Math.max(0, ...this.#seen);\n }\n\n event(event: AgentEvent): RemoteEvent[] {\n switch (event.type) {\n case 'run_start': {\n const ended = this.#ended(this.reason);\n this.reason = 'stop';\n this.error = undefined;\n return [...ended, { t: 'run', state: 'start' }];\n }\n case 'run_end':\n this.#ending = event.inputs;\n return [];\n case 'submission':\n return this.#settled(event.record);\n case 'message_start':\n if ((event.message as ModelMessage).role === 'assistant')\n this.#current = this.#key(event.message as ModelMessage);\n return [];\n case 'message_update':\n return event.changes.flatMap((change) => {\n if (change.type !== 'text_delta' && change.type !== 'thinking_delta') return [];\n const id = `${this.#current}.${change.contentIndex}`;\n const t = change.type === 'text_delta' ? 'text' : 'thinking';\n const done = this.#emitted.get(id) ?? 0;\n if (t === 'thinking' && !this.#thinking) return [];\n this.#emitted.set(id, done + change.delta.length);\n return [{ t, id, delta: change.delta }];\n });\n case 'message_end':\n return this.entry(event.entry);\n case 'tool_execution_end':\n return event.entry\n ? this.entry(event.entry)\n : [\n {\n t: 'tool-result',\n id: `x${event.toolCallId}`,\n call: event.toolCallId,\n ok: false,\n summary: 'the tool did not finish',\n },\n ];\n case 'auto_retry_start':\n return [\n {\n t: 'notice',\n id: `retry-${event.at}`,\n text: clip(`retrying (attempt ${event.attempt}): ${event.errorMessage}`),\n },\n ];\n case 'compaction_start':\n return [{ t: 'notice', id: `compaction-${event.taskId}`, text: 'compacting' }];\n case 'task_failed':\n return [{ t: 'error', id: `task-${event.taskId}`, message: clip(event.message, 1000) }];\n default:\n return [];\n }\n }\n}\n\nexport function replayOf(\n entries: readonly EntryRecord[],\n partial: ModelMessage | undefined,\n thinking: boolean\n): RemoteEvent[] {\n const mapper = new Mapper(thinking);\n const out: RemoteEvent[] = [];\n for (const entry of entries) out.push(...mapper.entry(entry));\n out.push(...mapper.partial(partial));\n return out;\n}\n\nexport function idOf(event: RemoteEvent): string | undefined {\n return 'id' in event ? event.id : undefined;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/remote/host.js
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
watchEvents
|
|
4
4
|
} from "@earendil-works/pi-durable";
|
|
5
5
|
import { holdText } from "../secrets/vault.js";
|
|
6
|
+
import { watchBusy } from "./busy.js";
|
|
6
7
|
import { idOf, Mapper, replayOf } from "./events.js";
|
|
7
8
|
const VAULT_BACK = "the vault is answering again";
|
|
8
9
|
function vaultNotices(vault, push) {
|
|
@@ -169,11 +170,12 @@ function createRemote(host) {
|
|
|
169
170
|
conversation.id,
|
|
170
171
|
cancel.context
|
|
171
172
|
);
|
|
172
|
-
const open = (onEvents) => {
|
|
173
|
+
const open = (onEvents, ended) => {
|
|
173
174
|
let off = () => void 0;
|
|
174
175
|
const stream = new Stream(
|
|
175
176
|
async () => {
|
|
176
177
|
off();
|
|
178
|
+
ended?.();
|
|
177
179
|
await events.stop();
|
|
178
180
|
cancel.cancel();
|
|
179
181
|
},
|
|
@@ -240,9 +242,11 @@ function createRemote(host) {
|
|
|
240
242
|
const mapper = new Mapper(request.thinking);
|
|
241
243
|
for (const entry of watched.snapshot.entries) mapper.entry(entry);
|
|
242
244
|
mapper.partial(partial);
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
245
|
+
const busy = request.busy ? await watchBusy(host.harness, watched, (events) => stream.push(events), context) : void 0;
|
|
246
|
+
const { id, stream } = watched.open((batch) => {
|
|
247
|
+
const mapped = batch.flatMap((event) => followed(mapper, event));
|
|
248
|
+
stream.push(busy ? busy.gate.before(mapped) : mapped);
|
|
249
|
+
}, busy?.stop);
|
|
246
250
|
if (at >= 0) {
|
|
247
251
|
const own = all[at];
|
|
248
252
|
const sinceId = idOf(own);
|
|
@@ -251,7 +255,14 @@ function createRemote(host) {
|
|
|
251
255
|
const replay = own.t === "text" || own.t === "thinking" ? [own] : [];
|
|
252
256
|
stream.push([...replay, ...all.slice(last + 1)]);
|
|
253
257
|
}
|
|
254
|
-
return {
|
|
258
|
+
return {
|
|
259
|
+
ok: true,
|
|
260
|
+
stream: id,
|
|
261
|
+
delivered: null,
|
|
262
|
+
head,
|
|
263
|
+
gap,
|
|
264
|
+
...busy ? { busy: busy.opened } : {}
|
|
265
|
+
};
|
|
255
266
|
},
|
|
256
267
|
async next(id, waitMs, context) {
|
|
257
268
|
const stream = streams.get(id);
|
package/dist/remote/host.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/remote/host.ts"],
|
|
4
|
-
"sourcesContent": ["import type { Context, ReplicatedState } from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT, withCancel } from '@earendil-works/chord/context';\nimport {\n type AgentEvent,\n type AgentEventStream,\n type Conversation,\n type EntryRecord,\n type Harness,\n type SnapshotEvent,\n type SubmissionId,\n watchEvents,\n} from '@earendil-works/pi-durable';\nimport type { SendAttachment } from '../attachments.ts';\nimport { holdText, type VaultState } from '../secrets/vault.ts';\nimport type { Delivered, SendMode, SendRequest, SendResponse } from '../services.ts';\nimport { type EndReason, idOf, Mapper, type RemoteEvent, replayOf } from './events.ts';\n\nexport const VAULT_BACK = 'the vault is answering again';\n\nexport function vaultNotices(\n vault: ReplicatedState<VaultState> | undefined,\n push: (events: RemoteEvent[]) => void\n): () => void {\n if (!vault) return () => undefined;\n let shown = vault.value ? holdText(vault.value) : undefined;\n let serial = 0;\n const notice = (text: string) => push([{ t: 'notice', id: `vault-${++serial}`, text }]);\n if (shown) notice(shown);\n return vault.subscribe((value) => {\n const next = holdText(value);\n if (next === shown) return;\n shown = next;\n notice(next ?? VAULT_BACK);\n });\n}\n\nexport const MAX_QUEUED = 10000;\nexport const IDLE_MS = 120000;\n\nexport type RemoteOpened =\n | { ok: true; stream: string; delivered: Delivered | null; head: string | null; gap: boolean }\n | { ok: false; code: string; message: string };\n\nexport interface RemoteBatch {\n events: RemoteEvent[];\n done: boolean;\n}\n\nexport interface AgentRemote {\n prompt(\n request: {\n agentId: string | null;\n text: string;\n whenBusy: SendMode;\n thinking: boolean;\n attachments?: SendAttachment[];\n },\n context: Context\n ): Promise<RemoteOpened>;\n watch(\n request: { agentId: string | null; since: string | null; thinking: boolean },\n context: Context\n ): Promise<RemoteOpened>;\n next(stream: string, waitMs: number, context: Context): Promise<RemoteBatch>;\n close(stream: string, context: Context): Promise<void>;\n abort(agentId: string | null, context: Context): Promise<{ aborted: boolean }>;\n}\n\nexport interface RemoteHost {\n harness: Harness;\n conversation(agentId: string | null, context: Context): Promise<Conversation>;\n send(request: SendRequest, context: Context): Promise<SendResponse>;\n vault?: ReplicatedState<VaultState>;\n now?: () => number;\n limit?: number;\n sweepMs?: number;\n}\n\nexport class Stream<T = RemoteEvent> {\n readonly queue: T[] = [];\n done = false;\n touched: number;\n #wake: (() => void) | undefined;\n readonly #stop: () => Promise<unknown>;\n readonly #limit: number;\n readonly #overflow: readonly T[];\n\n constructor(stop: () => Promise<unknown>, now: number, limit: number, overflow: readonly T[]) {\n this.#stop = stop;\n this.touched = now;\n this.#limit = limit;\n this.#overflow = overflow;\n }\n\n push(events: readonly T[]): void {\n if (this.done || !events.length) return;\n this.queue.push(...events);\n if (this.queue.length > this.#limit) {\n this.queue.length = 0;\n this.queue.push(...this.#overflow);\n void this.finish();\n }\n this.#wake?.();\n }\n\n async finish(): Promise<void> {\n if (this.done) return;\n this.done = true;\n this.#wake?.();\n await this.#stop().catch(() => undefined);\n }\n\n wait(ms: number, signal?: AbortSignal): Promise<void> {\n if (this.queue.length || this.done) return Promise.resolve();\n return new Promise((resolve) => {\n const timer = setTimeout(done, ms);\n signal?.addEventListener('abort', done, { once: true });\n function done() {\n clearTimeout(timer);\n signal?.removeEventListener('abort', done);\n resolve();\n }\n this.#wake = () => {\n this.#wake = undefined;\n done();\n };\n });\n }\n}\n\nfunction deliveredOf(\n status: { status: string } | undefined,\n whenBusy: SendMode,\n busy: boolean\n): Delivered {\n if (status?.status === 'queued') return whenBusy === 'followUp' ? 'follow-up' : 'steer';\n return busy ? 'steer' : 'run';\n}\n\nfunction headOf(events: readonly RemoteEvent[]): string | null {\n for (let at = events.length - 1; at >= 0; at--) {\n const id = idOf(events[at] as RemoteEvent);\n if (id) return id;\n }\n return null;\n}\n\nexport function followed(mapper: Mapper, event: AgentEvent): RemoteEvent[] {\n return event.type === 'snapshot' ? resync(mapper, event) : mapper.event(event);\n}\n\nexport function resync(mapper: Mapper, snapshot: SnapshotEvent): RemoteEvent[] {\n const last = mapper.last();\n return [\n ...snapshot.entries.filter((entry) => entry.id > last).flatMap((entry) => mapper.entry(entry)),\n ...mapper.partial(snapshot.generation?.message as never),\n ];\n}\n\nfunction placedEntry(value: unknown): number | undefined {\n const entry = (value as { entry?: unknown } | undefined)?.entry;\n return typeof entry === 'number' ? entry : undefined;\n}\n\nexport class Follower {\n readonly #mapper: Mapper;\n readonly #entries: Map<number, EntryRecord>;\n readonly #submission: SubmissionId;\n readonly #delivered: Delivered;\n readonly #out: RemoteEvent[] = [];\n #user: number | undefined;\n #started = false;\n\n constructor(\n mapper: Mapper,\n entries: readonly EntryRecord[],\n submission: SubmissionId,\n delivered: Delivered,\n record: unknown\n ) {\n this.#mapper = mapper;\n this.#entries = new Map(entries.map((entry) => [entry.id, entry]));\n this.#submission = submission;\n this.#delivered = delivered;\n this.#user = placedEntry(record);\n }\n\n drain(): RemoteEvent[] {\n return this.#out.splice(0);\n }\n\n #begin(): void {\n const entry = this.#user === undefined ? undefined : this.#entries.get(this.#user);\n if (!entry) return;\n this.#started = true;\n this.#out.push(...this.#mapper.entry(entry, this.#delivered));\n }\n\n #end(reason: EndReason, message?: string): true {\n this.#out.push({ t: 'end', reason, ...(message ? { message } : {}) });\n return true;\n }\n\n #ours(event: AgentEvent): boolean {\n if (event.type !== 'submission' || event.record.id !== this.#submission) return false;\n this.#user = placedEntry(event.record) ?? this.#user;\n return this.#user === undefined && !['queued', 'placed'].includes(event.record.status);\n }\n\n take(batch: readonly AgentEvent[]): boolean {\n for (const event of batch) {\n if (event.type === 'message_end') this.#entries.set(event.entry.id, event.entry);\n if (this.#ours(event)) return this.#end('aborted');\n if (!this.#started) {\n this.#begin();\n continue;\n }\n const mapped = this.#mapper.event(event).filter((item) => item.t !== 'user');\n this.#out.push(...mapped);\n if (mapped.some((item) => item.t === 'run' && item.state === 'end'))\n return this.#end(this.#mapper.reason, this.#mapper.error);\n }\n return false;\n }\n}\n\nexport function createRemote(host: RemoteHost): AgentRemote & { dispose(): Promise<void> } {\n const now = host.now ?? Date.now;\n const streams = new Map<string, Stream>();\n let serial = 0;\n let timer: ReturnType<typeof setInterval> | undefined;\n const sweep = () => {\n for (const [id, stream] of streams)\n if (now() - stream.touched > IDLE_MS) {\n streams.delete(id);\n void stream.finish();\n }\n if (!streams.size && timer !== undefined) {\n clearInterval(timer);\n timer = undefined;\n }\n };\n const track = (id: string, stream: Stream) => {\n streams.set(id, stream);\n timer ??= setInterval(sweep, host.sweepMs ?? IDLE_MS / 4);\n };\n const watching = async (agentId: string | null, context: Context) => {\n const conversation = await host.conversation(agentId, context);\n const cancel = withCancel(BACKGROUND_CONTEXT);\n const events: AgentEventStream = await watchEvents(\n host.harness,\n conversation.id,\n cancel.context\n );\n const open = (onEvents: (batch: readonly AgentEvent[]) => void) => {\n let off = () => undefined as void;\n const stream = new Stream<RemoteEvent>(\n async () => {\n off();\n await events.stop();\n cancel.cancel();\n },\n now(),\n host.limit ?? MAX_QUEUED,\n [{ t: 'end', reason: 'error', message: 'the stream fell too far behind' }]\n );\n const id = `s${++serial}-${crypto.randomUUID()}`;\n track(id, stream);\n off = vaultNotices(host.vault, (notices) => stream.push(notices));\n events.start(async (batch) => onEvents(batch));\n return { id, stream };\n };\n return { conversation, snapshot: events.snapshot, open, stop: () => events.stop() };\n };\n return {\n async prompt(request, context) {\n sweep();\n const watched = await watching(request.agentId, context);\n const busy = Boolean(watched.snapshot.run);\n const response = await host.send(\n {\n text: request.text,\n whenBusy: request.whenBusy,\n requestId: null,\n ...(request.attachments?.length ? { attachments: request.attachments } : {}),\n ...(request.agentId ? { agentId: request.agentId } : {}),\n },\n context\n );\n if (!response.accepted) {\n await watched.stop();\n return {\n ok: false,\n code: response.error.code === 'busy' ? 'EBUSY' : 'EIO',\n message: response.error.message,\n };\n }\n const submission = Number(response.submissionId) as SubmissionId;\n const record = await (await host.harness.submission(submission, context))?.status(context);\n const delivered = deliveredOf(record, request.whenBusy, busy);\n const follower = new Follower(\n new Mapper(request.thinking),\n watched.snapshot.entries,\n submission,\n delivered,\n record\n );\n const { id, stream } = watched.open((batch) => {\n const ended = follower.take(batch);\n stream.push(follower.drain());\n if (ended) void stream.finish();\n });\n return { ok: true, stream: id, delivered, head: null, gap: false };\n },\n async watch(request, context) {\n sweep();\n const watched = await watching(request.agentId, context);\n const partial = watched.snapshot.generation?.message as never;\n const all = replayOf(watched.snapshot.entries, partial, request.thinking);\n const head = headOf(all);\n const at = request.since === null ? -1 : all.findIndex((e) => idOf(e) === request.since);\n const gap = request.since !== null && at < 0;\n const mapper = new Mapper(request.thinking);\n for (const entry of watched.snapshot.entries) mapper.entry(entry);\n mapper.partial(partial);\n const { id, stream } = watched.open((batch) =>\n stream.push(batch.flatMap((event) => followed(mapper, event)))\n );\n if (at >= 0) {\n const own = all[at] as RemoteEvent;\n const sinceId = idOf(own);\n let last = at;\n while (last + 1 < all.length && idOf(all[last + 1] as RemoteEvent) === sinceId) last++;\n const replay = own.t === 'text' || own.t === 'thinking' ? [own] : [];\n stream.push([...replay, ...all.slice(last + 1)]);\n }\n return { ok: true, stream: id, delivered: null, head, gap };\n },\n async next(id, waitMs, context) {\n const stream = streams.get(id);\n if (!stream) return { events: [], done: true };\n stream.touched = now();\n await stream.wait(Math.max(0, Math.min(waitMs, 30000)));\n context.abortSignal?.throwIfAborted();\n const events = stream.queue.splice(0);\n const done = stream.done && stream.queue.length === 0;\n if (done) streams.delete(id);\n return { events, done };\n },\n async close(id) {\n const stream = streams.get(id);\n streams.delete(id);\n await stream?.finish();\n },\n async abort(agentId, context) {\n const conversation = await host.conversation(agentId, context);\n const watched = await watchEvents(host.harness, conversation.id, context);\n const busy = Boolean(watched.snapshot.run);\n await watched.stop();\n if (busy) await conversation.abort(context);\n return { aborted: busy };\n },\n async dispose() {\n clearInterval(timer);\n timer = undefined;\n const all = [...streams.values()];\n streams.clear();\n await Promise.all(all.map((stream) => stream.finish()));\n },\n };\n}\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,oBAAoB,kBAAkB;AAC/C;AAAA,EAQE;AAAA,OACK;AAEP,SAAS,gBAAiC;AAE1C,SAAyB,MAAM,QAA0B,gBAAgB;AAElE,MAAM,aAAa;AAEnB,SAAS,aACd,OACA,MACY;AACZ,MAAI,CAAC,MAAO,QAAO,MAAM;AACzB,MAAI,QAAQ,MAAM,QAAQ,SAAS,MAAM,KAAK,IAAI;AAClD,MAAI,SAAS;AACb,QAAM,SAAS,CAAC,SAAiB,KAAK,CAAC,EAAE,GAAG,UAAU,IAAI,SAAS,EAAE,MAAM,IAAI,KAAK,CAAC,CAAC;AACtF,MAAI,MAAO,QAAO,KAAK;AACvB,SAAO,MAAM,UAAU,CAAC,UAAU;AAChC,UAAM,OAAO,SAAS,KAAK;AAC3B,QAAI,SAAS,MAAO;AACpB,YAAQ;AACR,WAAO,QAAQ,UAAU;AAAA,EAC3B,CAAC;AACH;AAEO,MAAM,aAAa;AACnB,MAAM,UAAU;
|
|
4
|
+
"sourcesContent": ["import type { Context, ReplicatedState } from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT, withCancel } from '@earendil-works/chord/context';\nimport {\n type AgentEvent,\n type AgentEventStream,\n type Conversation,\n type EntryRecord,\n type Harness,\n type SnapshotEvent,\n type SubmissionId,\n watchEvents,\n} from '@earendil-works/pi-durable';\nimport type { SendAttachment } from '../attachments.ts';\nimport { holdText, type VaultState } from '../secrets/vault.ts';\nimport type { Delivered, SendMode, SendRequest, SendResponse } from '../services.ts';\nimport { watchBusy } from './busy.ts';\nimport { type EndReason, idOf, Mapper, type RemoteEvent, replayOf } from './events.ts';\n\nexport const VAULT_BACK = 'the vault is answering again';\n\nexport function vaultNotices(\n vault: ReplicatedState<VaultState> | undefined,\n push: (events: RemoteEvent[]) => void\n): () => void {\n if (!vault) return () => undefined;\n let shown = vault.value ? holdText(vault.value) : undefined;\n let serial = 0;\n const notice = (text: string) => push([{ t: 'notice', id: `vault-${++serial}`, text }]);\n if (shown) notice(shown);\n return vault.subscribe((value) => {\n const next = holdText(value);\n if (next === shown) return;\n shown = next;\n notice(next ?? VAULT_BACK);\n });\n}\n\nexport const MAX_QUEUED = 10000;\nexport const IDLE_MS = 120000;\n\nexport type RemoteOpened =\n | {\n ok: true;\n stream: string;\n delivered: Delivered | null;\n head: string | null;\n gap: boolean;\n busy?: boolean;\n }\n | { ok: false; code: string; message: string };\n\nexport interface RemoteBatch {\n events: RemoteEvent[];\n done: boolean;\n}\n\nexport interface AgentRemote {\n prompt(\n request: {\n agentId: string | null;\n text: string;\n whenBusy: SendMode;\n thinking: boolean;\n attachments?: SendAttachment[];\n },\n context: Context\n ): Promise<RemoteOpened>;\n watch(\n request: { agentId: string | null; since: string | null; thinking: boolean; busy?: boolean },\n context: Context\n ): Promise<RemoteOpened>;\n next(stream: string, waitMs: number, context: Context): Promise<RemoteBatch>;\n close(stream: string, context: Context): Promise<void>;\n abort(agentId: string | null, context: Context): Promise<{ aborted: boolean }>;\n}\n\nexport interface RemoteHost {\n harness: Harness;\n conversation(agentId: string | null, context: Context): Promise<Conversation>;\n send(request: SendRequest, context: Context): Promise<SendResponse>;\n vault?: ReplicatedState<VaultState>;\n now?: () => number;\n limit?: number;\n sweepMs?: number;\n}\n\nexport class Stream<T = RemoteEvent> {\n readonly queue: T[] = [];\n done = false;\n touched: number;\n #wake: (() => void) | undefined;\n readonly #stop: () => Promise<unknown>;\n readonly #limit: number;\n readonly #overflow: readonly T[];\n\n constructor(stop: () => Promise<unknown>, now: number, limit: number, overflow: readonly T[]) {\n this.#stop = stop;\n this.touched = now;\n this.#limit = limit;\n this.#overflow = overflow;\n }\n\n push(events: readonly T[]): void {\n if (this.done || !events.length) return;\n this.queue.push(...events);\n if (this.queue.length > this.#limit) {\n this.queue.length = 0;\n this.queue.push(...this.#overflow);\n void this.finish();\n }\n this.#wake?.();\n }\n\n async finish(): Promise<void> {\n if (this.done) return;\n this.done = true;\n this.#wake?.();\n await this.#stop().catch(() => undefined);\n }\n\n wait(ms: number, signal?: AbortSignal): Promise<void> {\n if (this.queue.length || this.done) return Promise.resolve();\n return new Promise((resolve) => {\n const timer = setTimeout(done, ms);\n signal?.addEventListener('abort', done, { once: true });\n function done() {\n clearTimeout(timer);\n signal?.removeEventListener('abort', done);\n resolve();\n }\n this.#wake = () => {\n this.#wake = undefined;\n done();\n };\n });\n }\n}\n\nfunction deliveredOf(\n status: { status: string } | undefined,\n whenBusy: SendMode,\n busy: boolean\n): Delivered {\n if (status?.status === 'queued') return whenBusy === 'followUp' ? 'follow-up' : 'steer';\n return busy ? 'steer' : 'run';\n}\n\nfunction headOf(events: readonly RemoteEvent[]): string | null {\n for (let at = events.length - 1; at >= 0; at--) {\n const id = idOf(events[at] as RemoteEvent);\n if (id) return id;\n }\n return null;\n}\n\nexport function followed(mapper: Mapper, event: AgentEvent): RemoteEvent[] {\n return event.type === 'snapshot' ? resync(mapper, event) : mapper.event(event);\n}\n\nexport function resync(mapper: Mapper, snapshot: SnapshotEvent): RemoteEvent[] {\n const last = mapper.last();\n return [\n ...snapshot.entries.filter((entry) => entry.id > last).flatMap((entry) => mapper.entry(entry)),\n ...mapper.partial(snapshot.generation?.message as never),\n ];\n}\n\nfunction placedEntry(value: unknown): number | undefined {\n const entry = (value as { entry?: unknown } | undefined)?.entry;\n return typeof entry === 'number' ? entry : undefined;\n}\n\nexport class Follower {\n readonly #mapper: Mapper;\n readonly #entries: Map<number, EntryRecord>;\n readonly #submission: SubmissionId;\n readonly #delivered: Delivered;\n readonly #out: RemoteEvent[] = [];\n #user: number | undefined;\n #started = false;\n\n constructor(\n mapper: Mapper,\n entries: readonly EntryRecord[],\n submission: SubmissionId,\n delivered: Delivered,\n record: unknown\n ) {\n this.#mapper = mapper;\n this.#entries = new Map(entries.map((entry) => [entry.id, entry]));\n this.#submission = submission;\n this.#delivered = delivered;\n this.#user = placedEntry(record);\n }\n\n drain(): RemoteEvent[] {\n return this.#out.splice(0);\n }\n\n #begin(): void {\n const entry = this.#user === undefined ? undefined : this.#entries.get(this.#user);\n if (!entry) return;\n this.#started = true;\n this.#out.push(...this.#mapper.entry(entry, this.#delivered));\n }\n\n #end(reason: EndReason, message?: string): true {\n this.#out.push({ t: 'end', reason, ...(message ? { message } : {}) });\n return true;\n }\n\n #ours(event: AgentEvent): boolean {\n if (event.type !== 'submission' || event.record.id !== this.#submission) return false;\n this.#user = placedEntry(event.record) ?? this.#user;\n return this.#user === undefined && !['queued', 'placed'].includes(event.record.status);\n }\n\n take(batch: readonly AgentEvent[]): boolean {\n for (const event of batch) {\n if (event.type === 'message_end') this.#entries.set(event.entry.id, event.entry);\n if (this.#ours(event)) return this.#end('aborted');\n if (!this.#started) {\n this.#begin();\n continue;\n }\n const mapped = this.#mapper.event(event).filter((item) => item.t !== 'user');\n this.#out.push(...mapped);\n if (mapped.some((item) => item.t === 'run' && item.state === 'end'))\n return this.#end(this.#mapper.reason, this.#mapper.error);\n }\n return false;\n }\n}\n\nexport function createRemote(host: RemoteHost): AgentRemote & { dispose(): Promise<void> } {\n const now = host.now ?? Date.now;\n const streams = new Map<string, Stream>();\n let serial = 0;\n let timer: ReturnType<typeof setInterval> | undefined;\n const sweep = () => {\n for (const [id, stream] of streams)\n if (now() - stream.touched > IDLE_MS) {\n streams.delete(id);\n void stream.finish();\n }\n if (!streams.size && timer !== undefined) {\n clearInterval(timer);\n timer = undefined;\n }\n };\n const track = (id: string, stream: Stream) => {\n streams.set(id, stream);\n timer ??= setInterval(sweep, host.sweepMs ?? IDLE_MS / 4);\n };\n const watching = async (agentId: string | null, context: Context) => {\n const conversation = await host.conversation(agentId, context);\n const cancel = withCancel(BACKGROUND_CONTEXT);\n const events: AgentEventStream = await watchEvents(\n host.harness,\n conversation.id,\n cancel.context\n );\n const open = (onEvents: (batch: readonly AgentEvent[]) => void, ended?: () => void) => {\n let off = () => undefined as void;\n const stream = new Stream<RemoteEvent>(\n async () => {\n off();\n ended?.();\n await events.stop();\n cancel.cancel();\n },\n now(),\n host.limit ?? MAX_QUEUED,\n [{ t: 'end', reason: 'error', message: 'the stream fell too far behind' }]\n );\n const id = `s${++serial}-${crypto.randomUUID()}`;\n track(id, stream);\n off = vaultNotices(host.vault, (notices) => stream.push(notices));\n events.start(async (batch) => onEvents(batch));\n return { id, stream };\n };\n return { conversation, snapshot: events.snapshot, open, stop: () => events.stop() };\n };\n return {\n async prompt(request, context) {\n sweep();\n const watched = await watching(request.agentId, context);\n const busy = Boolean(watched.snapshot.run);\n const response = await host.send(\n {\n text: request.text,\n whenBusy: request.whenBusy,\n requestId: null,\n ...(request.attachments?.length ? { attachments: request.attachments } : {}),\n ...(request.agentId ? { agentId: request.agentId } : {}),\n },\n context\n );\n if (!response.accepted) {\n await watched.stop();\n return {\n ok: false,\n code: response.error.code === 'busy' ? 'EBUSY' : 'EIO',\n message: response.error.message,\n };\n }\n const submission = Number(response.submissionId) as SubmissionId;\n const record = await (await host.harness.submission(submission, context))?.status(context);\n const delivered = deliveredOf(record, request.whenBusy, busy);\n const follower = new Follower(\n new Mapper(request.thinking),\n watched.snapshot.entries,\n submission,\n delivered,\n record\n );\n const { id, stream } = watched.open((batch) => {\n const ended = follower.take(batch);\n stream.push(follower.drain());\n if (ended) void stream.finish();\n });\n return { ok: true, stream: id, delivered, head: null, gap: false };\n },\n async watch(request, context) {\n sweep();\n const watched = await watching(request.agentId, context);\n const partial = watched.snapshot.generation?.message as never;\n const all = replayOf(watched.snapshot.entries, partial, request.thinking);\n const head = headOf(all);\n const at = request.since === null ? -1 : all.findIndex((e) => idOf(e) === request.since);\n const gap = request.since !== null && at < 0;\n const mapper = new Mapper(request.thinking);\n for (const entry of watched.snapshot.entries) mapper.entry(entry);\n mapper.partial(partial);\n const busy = request.busy\n ? await watchBusy(host.harness, watched, (events) => stream.push(events), context)\n : undefined;\n const { id, stream } = watched.open((batch) => {\n const mapped = batch.flatMap((event) => followed(mapper, event));\n stream.push(busy ? busy.gate.before(mapped) : mapped);\n }, busy?.stop);\n if (at >= 0) {\n const own = all[at] as RemoteEvent;\n const sinceId = idOf(own);\n let last = at;\n while (last + 1 < all.length && idOf(all[last + 1] as RemoteEvent) === sinceId) last++;\n const replay = own.t === 'text' || own.t === 'thinking' ? [own] : [];\n stream.push([...replay, ...all.slice(last + 1)]);\n }\n return {\n ok: true,\n stream: id,\n delivered: null,\n head,\n gap,\n ...(busy ? { busy: busy.opened } : {}),\n };\n },\n async next(id, waitMs, context) {\n const stream = streams.get(id);\n if (!stream) return { events: [], done: true };\n stream.touched = now();\n await stream.wait(Math.max(0, Math.min(waitMs, 30000)));\n context.abortSignal?.throwIfAborted();\n const events = stream.queue.splice(0);\n const done = stream.done && stream.queue.length === 0;\n if (done) streams.delete(id);\n return { events, done };\n },\n async close(id) {\n const stream = streams.get(id);\n streams.delete(id);\n await stream?.finish();\n },\n async abort(agentId, context) {\n const conversation = await host.conversation(agentId, context);\n const watched = await watchEvents(host.harness, conversation.id, context);\n const busy = Boolean(watched.snapshot.run);\n await watched.stop();\n if (busy) await conversation.abort(context);\n return { aborted: busy };\n },\n async dispose() {\n clearInterval(timer);\n timer = undefined;\n const all = [...streams.values()];\n streams.clear();\n await Promise.all(all.map((stream) => stream.finish()));\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Context } from '@earendil-works/chord';
|
|
2
|
+
import { type CommitPublication, type Harness, type SnapshotEvent } from '@earendil-works/pi-durable';
|
|
3
|
+
import type { RemoteEvent } from './events.ts';
|
|
4
|
+
export interface BusyState {
|
|
5
|
+
busy: boolean;
|
|
6
|
+
why: string[];
|
|
7
|
+
}
|
|
8
|
+
export declare function snapshotBusy(snapshot: SnapshotEvent): boolean;
|
|
9
|
+
export declare class BusyTracker {
|
|
10
|
+
#private;
|
|
11
|
+
constructor(conversation: number);
|
|
12
|
+
seed(agents: unknown, work: unknown, outbox: unknown): void;
|
|
13
|
+
conversations(): number[];
|
|
14
|
+
conversation(id: number, live: unknown, inbox: unknown): void;
|
|
15
|
+
apply(publication: CommitPublication): boolean;
|
|
16
|
+
value(): BusyState;
|
|
17
|
+
}
|
|
18
|
+
export declare function trackBusy(harness: Harness, conversation: number, context: Context): Promise<{
|
|
19
|
+
tracker: BusyTracker;
|
|
20
|
+
listen(listener: () => void): void;
|
|
21
|
+
stop(): void;
|
|
22
|
+
}>;
|
|
23
|
+
export declare function busyLine(state: BusyState): RemoteEvent;
|
|
24
|
+
export declare class BusyGate {
|
|
25
|
+
#private;
|
|
26
|
+
constructor(tracker: BusyTracker, push: (events: RemoteEvent[]) => void, sent: boolean);
|
|
27
|
+
before(events: RemoteEvent[]): RemoteEvent[];
|
|
28
|
+
changed(): void;
|
|
29
|
+
stop(): void;
|
|
30
|
+
}
|
|
31
|
+
export declare function watchBusy(harness: Harness, watched: {
|
|
32
|
+
conversation: {
|
|
33
|
+
id: number;
|
|
34
|
+
};
|
|
35
|
+
snapshot: SnapshotEvent;
|
|
36
|
+
stop(): Promise<unknown>;
|
|
37
|
+
}, push: (events: RemoteEvent[]) => void, context: Context): Promise<{
|
|
38
|
+
opened: boolean;
|
|
39
|
+
gate: BusyGate;
|
|
40
|
+
stop(): void;
|
|
41
|
+
}>;
|
|
@@ -20,9 +20,11 @@ export interface RemoteControl {
|
|
|
20
20
|
watch(request?: {
|
|
21
21
|
since?: string | null;
|
|
22
22
|
thinking?: boolean;
|
|
23
|
+
busy?: boolean;
|
|
23
24
|
}): Promise<{
|
|
24
25
|
head: string;
|
|
25
26
|
gap?: true;
|
|
27
|
+
busy?: boolean;
|
|
26
28
|
events: AsyncIterable<RemoteEvent>;
|
|
27
29
|
}>;
|
|
28
30
|
abort(): Promise<{
|
|
@@ -14,6 +14,7 @@ export type RemoteOpened = {
|
|
|
14
14
|
delivered: Delivered | null;
|
|
15
15
|
head: string | null;
|
|
16
16
|
gap: boolean;
|
|
17
|
+
busy?: boolean;
|
|
17
18
|
} | {
|
|
18
19
|
ok: false;
|
|
19
20
|
code: string;
|
|
@@ -35,6 +36,7 @@ export interface AgentRemote {
|
|
|
35
36
|
agentId: string | null;
|
|
36
37
|
since: string | null;
|
|
37
38
|
thinking: boolean;
|
|
39
|
+
busy?: boolean;
|
|
38
40
|
}, context: Context): Promise<RemoteOpened>;
|
|
39
41
|
next(stream: string, waitMs: number, context: Context): Promise<RemoteBatch>;
|
|
40
42
|
close(stream: string, context: Context): Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/slicc-agent",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.15.1",
|
|
4
4
|
"description": "SLICC's agent core on pi durable: an agent worker, session storage, remoting and pi extensions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -65,10 +65,10 @@
|
|
|
65
65
|
"release": "semantic-release"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
|
-
"@ai-ecoverse/slicc-kernel": "1.
|
|
68
|
+
"@ai-ecoverse/slicc-kernel": "1.133.0",
|
|
69
69
|
"@ai-ecoverse/slicc-scrub": "1.0.0",
|
|
70
70
|
"@ai-ecoverse/slicc-shared-web": "1.25.1",
|
|
71
|
-
"@ai-ecoverse/slicc-spectrum": "1.99.
|
|
71
|
+
"@ai-ecoverse/slicc-spectrum": "1.99.2",
|
|
72
72
|
"@ai-ecoverse/wasi-fnox": "1.39.0-5",
|
|
73
73
|
"@ai-ecoverse/wasm-bash": "5.3.0-21",
|
|
74
74
|
"@ai-ecoverse/wasm-coreutils": "9.12.0-10",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"typescript": "7.0.2"
|
|
86
86
|
},
|
|
87
87
|
"dependencies": {
|
|
88
|
-
"@ai-ecoverse/slicc-browsers": "1.9.
|
|
88
|
+
"@ai-ecoverse/slicc-browsers": "1.9.2",
|
|
89
89
|
"@ai-ecoverse/slicc-vault": "1.14.0",
|
|
90
90
|
"@aws-sdk/middleware-websocket": "^3.972.54",
|
|
91
91
|
"@earendil-works/chord": "1.3.0",
|