@ai-ecoverse/slicc-agent 3.29.0 → 3.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 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 { 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(`${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
+ "names": []
7
+ }
@@ -0,0 +1,273 @@
1
+ import { BACKGROUND_CONTEXT, withCancel } from "@earendil-works/chord/context";
2
+ import {
3
+ watchEvents
4
+ } from "@earendil-works/pi-durable";
5
+ import { idOf, Mapper, replayOf } from "./events.js";
6
+ const MAX_QUEUED = 1e4;
7
+ const IDLE_MS = 12e4;
8
+ class Stream {
9
+ queue = [];
10
+ done = false;
11
+ touched;
12
+ #wake;
13
+ #stop;
14
+ #limit;
15
+ constructor(stop, now, limit) {
16
+ this.#stop = stop;
17
+ this.touched = now;
18
+ this.#limit = limit;
19
+ }
20
+ push(events) {
21
+ if (this.done || !events.length) return;
22
+ this.queue.push(...events);
23
+ if (this.queue.length > this.#limit) {
24
+ this.queue.length = 0;
25
+ this.queue.push({ t: "end", reason: "error", message: "the stream fell too far behind" });
26
+ void this.finish();
27
+ }
28
+ this.#wake?.();
29
+ }
30
+ async finish() {
31
+ if (this.done) return;
32
+ this.done = true;
33
+ this.#wake?.();
34
+ await this.#stop().catch(() => void 0);
35
+ }
36
+ wait(ms) {
37
+ if (this.queue.length || this.done) return Promise.resolve();
38
+ return new Promise((resolve) => {
39
+ const timer = setTimeout(done, ms);
40
+ function done() {
41
+ clearTimeout(timer);
42
+ resolve();
43
+ }
44
+ this.#wake = () => {
45
+ this.#wake = void 0;
46
+ done();
47
+ };
48
+ });
49
+ }
50
+ }
51
+ function deliveredOf(status, whenBusy, busy) {
52
+ if (status?.status === "queued") return whenBusy === "followUp" ? "follow-up" : "steer";
53
+ return busy ? "steer" : "run";
54
+ }
55
+ function headOf(events) {
56
+ for (let at = events.length - 1; at >= 0; at--) {
57
+ const id = idOf(events[at]);
58
+ if (id) return id;
59
+ }
60
+ return null;
61
+ }
62
+ function followed(mapper, event) {
63
+ return event.type === "snapshot" ? resync(mapper, event) : mapper.event(event);
64
+ }
65
+ function resync(mapper, snapshot) {
66
+ const last = mapper.last();
67
+ return [
68
+ ...snapshot.entries.filter((entry) => entry.id > last).flatMap((entry) => mapper.entry(entry)),
69
+ ...mapper.partial(snapshot.generation?.message)
70
+ ];
71
+ }
72
+ function placedEntry(value) {
73
+ const entry = value?.entry;
74
+ return typeof entry === "number" ? entry : void 0;
75
+ }
76
+ class Follower {
77
+ #mapper;
78
+ #entries;
79
+ #submission;
80
+ #delivered;
81
+ #out = [];
82
+ #user;
83
+ #started = false;
84
+ constructor(mapper, entries, submission, delivered, record) {
85
+ this.#mapper = mapper;
86
+ this.#entries = new Map(entries.map((entry) => [entry.id, entry]));
87
+ this.#submission = submission;
88
+ this.#delivered = delivered;
89
+ this.#user = placedEntry(record);
90
+ }
91
+ drain() {
92
+ return this.#out.splice(0);
93
+ }
94
+ #begin() {
95
+ const entry = this.#user === void 0 ? void 0 : this.#entries.get(this.#user);
96
+ if (!entry) return;
97
+ this.#started = true;
98
+ this.#out.push(...this.#mapper.entry(entry, this.#delivered));
99
+ }
100
+ #end(reason, message) {
101
+ this.#out.push({ t: "end", reason, ...message ? { message } : {} });
102
+ return true;
103
+ }
104
+ #ours(event) {
105
+ if (event.type !== "submission" || event.record.id !== this.#submission) return false;
106
+ this.#user = placedEntry(event.record) ?? this.#user;
107
+ return this.#user === void 0 && !["queued", "placed"].includes(event.record.status);
108
+ }
109
+ take(batch) {
110
+ for (const event of batch) {
111
+ if (event.type === "message_end") this.#entries.set(event.entry.id, event.entry);
112
+ if (this.#ours(event)) return this.#end("aborted");
113
+ if (!this.#started) {
114
+ this.#begin();
115
+ continue;
116
+ }
117
+ const mapped = this.#mapper.event(event).filter((item) => item.t !== "user");
118
+ this.#out.push(...mapped);
119
+ if (mapped.some((item) => item.t === "run" && item.state === "end"))
120
+ return this.#end(this.#mapper.reason, this.#mapper.error);
121
+ }
122
+ return false;
123
+ }
124
+ }
125
+ function createRemote(host) {
126
+ const now = host.now ?? Date.now;
127
+ const streams = /* @__PURE__ */ new Map();
128
+ let serial = 0;
129
+ let timer;
130
+ const sweep = () => {
131
+ for (const [id, stream] of streams)
132
+ if (now() - stream.touched > IDLE_MS) {
133
+ streams.delete(id);
134
+ void stream.finish();
135
+ }
136
+ if (!streams.size && timer !== void 0) {
137
+ clearInterval(timer);
138
+ timer = void 0;
139
+ }
140
+ };
141
+ const track = (id, stream) => {
142
+ streams.set(id, stream);
143
+ timer ??= setInterval(sweep, host.sweepMs ?? IDLE_MS / 4);
144
+ };
145
+ const watching = async (agentId, context) => {
146
+ const conversation = await host.conversation(agentId, context);
147
+ const cancel = withCancel(BACKGROUND_CONTEXT);
148
+ const events = await watchEvents(
149
+ host.harness,
150
+ conversation.id,
151
+ cancel.context
152
+ );
153
+ const open = (onEvents) => {
154
+ const stream = new Stream(
155
+ async () => {
156
+ await events.stop();
157
+ cancel.cancel();
158
+ },
159
+ now(),
160
+ host.limit ?? MAX_QUEUED
161
+ );
162
+ const id = `s${++serial}`;
163
+ track(id, stream);
164
+ events.start(async (batch) => onEvents(batch));
165
+ return { id, stream };
166
+ };
167
+ return { conversation, snapshot: events.snapshot, open, stop: () => events.stop() };
168
+ };
169
+ return {
170
+ async prompt(request, context) {
171
+ sweep();
172
+ const watched = await watching(request.agentId, context);
173
+ const busy = Boolean(watched.snapshot.run);
174
+ const response = await host.send(
175
+ {
176
+ text: request.text,
177
+ whenBusy: request.whenBusy,
178
+ requestId: null,
179
+ ...request.agentId ? { agentId: request.agentId } : {}
180
+ },
181
+ context
182
+ );
183
+ if (!response.accepted) {
184
+ await watched.stop();
185
+ return {
186
+ ok: false,
187
+ code: response.error.code === "busy" ? "EBUSY" : "EIO",
188
+ message: response.error.message
189
+ };
190
+ }
191
+ const submission = Number(response.submissionId);
192
+ const record = await (await host.harness.submission(submission, context))?.status(context);
193
+ const delivered = deliveredOf(record, request.whenBusy, busy);
194
+ const follower = new Follower(
195
+ new Mapper(request.thinking),
196
+ watched.snapshot.entries,
197
+ submission,
198
+ delivered,
199
+ record
200
+ );
201
+ const { id, stream } = watched.open((batch) => {
202
+ const ended = follower.take(batch);
203
+ stream.push(follower.drain());
204
+ if (ended) void stream.finish();
205
+ });
206
+ return { ok: true, stream: id, delivered, head: null, gap: false };
207
+ },
208
+ async watch(request, context) {
209
+ sweep();
210
+ const watched = await watching(request.agentId, context);
211
+ const partial = watched.snapshot.generation?.message;
212
+ const all = replayOf(watched.snapshot.entries, partial, request.thinking);
213
+ const head = headOf(all);
214
+ const at = request.since === null ? -1 : all.findIndex((e) => idOf(e) === request.since);
215
+ const gap = request.since !== null && at < 0;
216
+ const mapper = new Mapper(request.thinking);
217
+ for (const entry of watched.snapshot.entries) mapper.entry(entry);
218
+ mapper.partial(partial);
219
+ const { id, stream } = watched.open(
220
+ (batch) => stream.push(batch.flatMap((event) => followed(mapper, event)))
221
+ );
222
+ if (at >= 0) {
223
+ const own = all[at];
224
+ const sinceId = idOf(own);
225
+ let last = at;
226
+ while (last + 1 < all.length && idOf(all[last + 1]) === sinceId) last++;
227
+ const replay = own.t === "text" || own.t === "thinking" ? [own] : [];
228
+ stream.push([...replay, ...all.slice(last + 1)]);
229
+ }
230
+ return { ok: true, stream: id, delivered: null, head, gap };
231
+ },
232
+ async next(id, waitMs, context) {
233
+ const stream = streams.get(id);
234
+ if (!stream) return { events: [], done: true };
235
+ stream.touched = now();
236
+ await stream.wait(Math.max(0, Math.min(waitMs, 3e4)));
237
+ context.abortSignal?.throwIfAborted();
238
+ const events = stream.queue.splice(0);
239
+ const done = stream.done && stream.queue.length === 0;
240
+ if (done) streams.delete(id);
241
+ return { events, done };
242
+ },
243
+ async close(id) {
244
+ const stream = streams.get(id);
245
+ streams.delete(id);
246
+ await stream?.finish();
247
+ },
248
+ async abort(agentId, context) {
249
+ const conversation = await host.conversation(agentId, context);
250
+ const watched = await watchEvents(host.harness, conversation.id, context);
251
+ const busy = Boolean(watched.snapshot.run);
252
+ await watched.stop();
253
+ if (busy) await conversation.abort(context);
254
+ return { aborted: busy };
255
+ },
256
+ async dispose() {
257
+ clearInterval(timer);
258
+ timer = void 0;
259
+ const all = [...streams.values()];
260
+ streams.clear();
261
+ await Promise.all(all.map((stream) => stream.finish()));
262
+ }
263
+ };
264
+ }
265
+ export {
266
+ Follower,
267
+ IDLE_MS,
268
+ MAX_QUEUED,
269
+ createRemote,
270
+ followed,
271
+ resync
272
+ };
273
+ //# sourceMappingURL=host.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/remote/host.ts"],
4
+ "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT, withCancel } from '@earendil-works/chord/context';\nimport {\n type AgentEvent,\n type AgentEventStream,\n type Conversation,\n type EntryRecord,\n type Harness,\n type SnapshotEvent,\n type SubmissionId,\n watchEvents,\n} from '@earendil-works/pi-durable';\nimport type { Delivered, SendMode, SendRequest, SendResponse } from '../services.ts';\nimport { type EndReason, idOf, Mapper, type RemoteEvent, replayOf } from './events.ts';\n\nexport const MAX_QUEUED = 10000;\nexport const IDLE_MS = 120000;\n\nexport type RemoteOpened =\n | { ok: true; stream: string; delivered: Delivered | null; head: string | null; gap: boolean }\n | { ok: false; code: string; message: string };\n\nexport interface RemoteBatch {\n events: RemoteEvent[];\n done: boolean;\n}\n\nexport interface AgentRemote {\n prompt(\n request: { agentId: string | null; text: string; whenBusy: SendMode; thinking: boolean },\n context: Context\n ): Promise<RemoteOpened>;\n watch(\n request: { agentId: string | null; since: string | null; thinking: boolean },\n context: Context\n ): Promise<RemoteOpened>;\n next(stream: string, waitMs: number, context: Context): Promise<RemoteBatch>;\n close(stream: string, context: Context): Promise<void>;\n abort(agentId: string | null, context: Context): Promise<{ aborted: boolean }>;\n}\n\nexport interface RemoteHost {\n harness: Harness;\n conversation(agentId: string | null, context: Context): Promise<Conversation>;\n send(request: SendRequest, context: Context): Promise<SendResponse>;\n now?: () => number;\n limit?: number;\n sweepMs?: number;\n}\n\nclass Stream {\n readonly queue: RemoteEvent[] = [];\n done = false;\n touched: number;\n #wake: (() => void) | undefined;\n readonly #stop: () => Promise<unknown>;\n readonly #limit: number;\n\n constructor(stop: () => Promise<unknown>, now: number, limit: number) {\n this.#stop = stop;\n this.touched = now;\n this.#limit = limit;\n }\n\n push(events: readonly RemoteEvent[]): void {\n if (this.done || !events.length) return;\n this.queue.push(...events);\n if (this.queue.length > this.#limit) {\n this.queue.length = 0;\n this.queue.push({ t: 'end', reason: 'error', message: 'the stream fell too far behind' });\n void this.finish();\n }\n this.#wake?.();\n }\n\n async finish(): Promise<void> {\n if (this.done) return;\n this.done = true;\n this.#wake?.();\n await this.#stop().catch(() => undefined);\n }\n\n wait(ms: number): Promise<void> {\n if (this.queue.length || this.done) return Promise.resolve();\n return new Promise((resolve) => {\n const timer = setTimeout(done, ms);\n function done() {\n clearTimeout(timer);\n resolve();\n }\n this.#wake = () => {\n this.#wake = undefined;\n done();\n };\n });\n }\n}\n\nfunction deliveredOf(\n status: { status: string } | undefined,\n whenBusy: SendMode,\n busy: boolean\n): Delivered {\n if (status?.status === 'queued') return whenBusy === 'followUp' ? 'follow-up' : 'steer';\n return busy ? 'steer' : 'run';\n}\n\nfunction headOf(events: readonly RemoteEvent[]): string | null {\n for (let at = events.length - 1; at >= 0; at--) {\n const id = idOf(events[at] as RemoteEvent);\n if (id) return id;\n }\n return null;\n}\n\nexport function followed(mapper: Mapper, event: AgentEvent): RemoteEvent[] {\n return event.type === 'snapshot' ? resync(mapper, event) : mapper.event(event);\n}\n\nexport function resync(mapper: Mapper, snapshot: SnapshotEvent): RemoteEvent[] {\n const last = mapper.last();\n return [\n ...snapshot.entries.filter((entry) => entry.id > last).flatMap((entry) => mapper.entry(entry)),\n ...mapper.partial(snapshot.generation?.message as never),\n ];\n}\n\nfunction placedEntry(value: unknown): number | undefined {\n const entry = (value as { entry?: unknown } | undefined)?.entry;\n return typeof entry === 'number' ? entry : undefined;\n}\n\nexport class Follower {\n readonly #mapper: Mapper;\n readonly #entries: Map<number, EntryRecord>;\n readonly #submission: SubmissionId;\n readonly #delivered: Delivered;\n readonly #out: RemoteEvent[] = [];\n #user: number | undefined;\n #started = false;\n\n constructor(\n mapper: Mapper,\n entries: readonly EntryRecord[],\n submission: SubmissionId,\n delivered: Delivered,\n record: unknown\n ) {\n this.#mapper = mapper;\n this.#entries = new Map(entries.map((entry) => [entry.id, entry]));\n this.#submission = submission;\n this.#delivered = delivered;\n this.#user = placedEntry(record);\n }\n\n drain(): RemoteEvent[] {\n return this.#out.splice(0);\n }\n\n #begin(): void {\n const entry = this.#user === undefined ? undefined : this.#entries.get(this.#user);\n if (!entry) return;\n this.#started = true;\n this.#out.push(...this.#mapper.entry(entry, this.#delivered));\n }\n\n #end(reason: EndReason, message?: string): true {\n this.#out.push({ t: 'end', reason, ...(message ? { message } : {}) });\n return true;\n }\n\n #ours(event: AgentEvent): boolean {\n if (event.type !== 'submission' || event.record.id !== this.#submission) return false;\n this.#user = placedEntry(event.record) ?? this.#user;\n return this.#user === undefined && !['queued', 'placed'].includes(event.record.status);\n }\n\n take(batch: readonly AgentEvent[]): boolean {\n for (const event of batch) {\n if (event.type === 'message_end') this.#entries.set(event.entry.id, event.entry);\n if (this.#ours(event)) return this.#end('aborted');\n if (!this.#started) {\n this.#begin();\n continue;\n }\n const mapped = this.#mapper.event(event).filter((item) => item.t !== 'user');\n this.#out.push(...mapped);\n if (mapped.some((item) => item.t === 'run' && item.state === 'end'))\n return this.#end(this.#mapper.reason, this.#mapper.error);\n }\n return false;\n }\n}\n\nexport function createRemote(host: RemoteHost): AgentRemote & { dispose(): Promise<void> } {\n const now = host.now ?? Date.now;\n const streams = new Map<string, Stream>();\n let serial = 0;\n let timer: ReturnType<typeof setInterval> | undefined;\n const sweep = () => {\n for (const [id, stream] of streams)\n if (now() - stream.touched > IDLE_MS) {\n streams.delete(id);\n void stream.finish();\n }\n if (!streams.size && timer !== undefined) {\n clearInterval(timer);\n timer = undefined;\n }\n };\n const track = (id: string, stream: Stream) => {\n streams.set(id, stream);\n timer ??= setInterval(sweep, host.sweepMs ?? IDLE_MS / 4);\n };\n const watching = async (agentId: string | null, context: Context) => {\n const conversation = await host.conversation(agentId, context);\n const cancel = withCancel(BACKGROUND_CONTEXT);\n const events: AgentEventStream = await watchEvents(\n host.harness,\n conversation.id,\n cancel.context\n );\n const open = (onEvents: (batch: readonly AgentEvent[]) => void) => {\n const stream = new Stream(\n async () => {\n await events.stop();\n cancel.cancel();\n },\n now(),\n host.limit ?? MAX_QUEUED\n );\n const id = `s${++serial}`;\n track(id, stream);\n events.start(async (batch) => onEvents(batch));\n return { id, stream };\n };\n return { conversation, snapshot: events.snapshot, open, stop: () => events.stop() };\n };\n return {\n async prompt(request, context) {\n sweep();\n const watched = await watching(request.agentId, context);\n const busy = Boolean(watched.snapshot.run);\n const response = await host.send(\n {\n text: request.text,\n whenBusy: request.whenBusy,\n requestId: null,\n ...(request.agentId ? { agentId: request.agentId } : {}),\n },\n context\n );\n if (!response.accepted) {\n await watched.stop();\n return {\n ok: false,\n code: response.error.code === 'busy' ? 'EBUSY' : 'EIO',\n message: response.error.message,\n };\n }\n const submission = Number(response.submissionId) as SubmissionId;\n const record = await (await host.harness.submission(submission, context))?.status(context);\n const delivered = deliveredOf(record, request.whenBusy, busy);\n const follower = new Follower(\n new Mapper(request.thinking),\n watched.snapshot.entries,\n submission,\n delivered,\n record\n );\n const { id, stream } = watched.open((batch) => {\n const ended = follower.take(batch);\n stream.push(follower.drain());\n if (ended) void stream.finish();\n });\n return { ok: true, stream: id, delivered, head: null, gap: false };\n },\n async watch(request, context) {\n sweep();\n const watched = await watching(request.agentId, context);\n const partial = watched.snapshot.generation?.message as never;\n const all = replayOf(watched.snapshot.entries, partial, request.thinking);\n const head = headOf(all);\n const at = request.since === null ? -1 : all.findIndex((e) => idOf(e) === request.since);\n const gap = request.since !== null && at < 0;\n const mapper = new Mapper(request.thinking);\n for (const entry of watched.snapshot.entries) mapper.entry(entry);\n mapper.partial(partial);\n const { id, stream } = watched.open((batch) =>\n stream.push(batch.flatMap((event) => followed(mapper, event)))\n );\n if (at >= 0) {\n const own = all[at] as RemoteEvent;\n const sinceId = idOf(own);\n let last = at;\n while (last + 1 < all.length && idOf(all[last + 1] as RemoteEvent) === sinceId) last++;\n const replay = own.t === 'text' || own.t === 'thinking' ? [own] : [];\n stream.push([...replay, ...all.slice(last + 1)]);\n }\n return { ok: true, stream: id, delivered: null, head, gap };\n },\n async next(id, waitMs, context) {\n const stream = streams.get(id);\n if (!stream) return { events: [], done: true };\n stream.touched = now();\n await stream.wait(Math.max(0, Math.min(waitMs, 30000)));\n context.abortSignal?.throwIfAborted();\n const events = stream.queue.splice(0);\n const done = stream.done && stream.queue.length === 0;\n if (done) streams.delete(id);\n return { events, done };\n },\n async close(id) {\n const stream = streams.get(id);\n streams.delete(id);\n await stream?.finish();\n },\n async abort(agentId, context) {\n const conversation = await host.conversation(agentId, context);\n const watched = await watchEvents(host.harness, conversation.id, context);\n const busy = Boolean(watched.snapshot.run);\n await watched.stop();\n if (busy) await conversation.abort(context);\n return { aborted: busy };\n },\n async dispose() {\n clearInterval(timer);\n timer = undefined;\n const all = [...streams.values()];\n streams.clear();\n await Promise.all(all.map((stream) => stream.finish()));\n },\n };\n}\n"],
5
+ "mappings": "AACA,SAAS,oBAAoB,kBAAkB;AAC/C;AAAA,EAQE;AAAA,OACK;AAEP,SAAyB,MAAM,QAA0B,gBAAgB;AAElE,MAAM,aAAa;AACnB,MAAM,UAAU;AAkCvB,MAAM,OAAO;AAAA,EACF,QAAuB,CAAC;AAAA,EACjC,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACS;AAAA,EACA;AAAA,EAET,YAAY,MAA8B,KAAa,OAAe;AACpE,SAAK,QAAQ;AACb,SAAK,UAAU;AACf,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,KAAK,QAAsC;AACzC,QAAI,KAAK,QAAQ,CAAC,OAAO,OAAQ;AACjC,SAAK,MAAM,KAAK,GAAG,MAAM;AACzB,QAAI,KAAK,MAAM,SAAS,KAAK,QAAQ;AACnC,WAAK,MAAM,SAAS;AACpB,WAAK,MAAM,KAAK,EAAE,GAAG,OAAO,QAAQ,SAAS,SAAS,iCAAiC,CAAC;AACxF,WAAK,KAAK,OAAO;AAAA,IACnB;AACA,SAAK,QAAQ;AAAA,EACf;AAAA,EAEA,MAAM,SAAwB;AAC5B,QAAI,KAAK,KAAM;AACf,SAAK,OAAO;AACZ,SAAK,QAAQ;AACb,UAAM,KAAK,MAAM,EAAE,MAAM,MAAM,MAAS;AAAA,EAC1C;AAAA,EAEA,KAAK,IAA2B;AAC9B,QAAI,KAAK,MAAM,UAAU,KAAK,KAAM,QAAO,QAAQ,QAAQ;AAC3D,WAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,YAAM,QAAQ,WAAW,MAAM,EAAE;AACjC,eAAS,OAAO;AACd,qBAAa,KAAK;AAClB,gBAAQ;AAAA,MACV;AACA,WAAK,QAAQ,MAAM;AACjB,aAAK,QAAQ;AACb,aAAK;AAAA,MACP;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAEA,SAAS,YACP,QACA,UACA,MACW;AACX,MAAI,QAAQ,WAAW,SAAU,QAAO,aAAa,aAAa,cAAc;AAChF,SAAO,OAAO,UAAU;AAC1B;AAEA,SAAS,OAAO,QAA+C;AAC7D,WAAS,KAAK,OAAO,SAAS,GAAG,MAAM,GAAG,MAAM;AAC9C,UAAM,KAAK,KAAK,OAAO,EAAE,CAAgB;AACzC,QAAI,GAAI,QAAO;AAAA,EACjB;AACA,SAAO;AACT;AAEO,SAAS,SAAS,QAAgB,OAAkC;AACzE,SAAO,MAAM,SAAS,aAAa,OAAO,QAAQ,KAAK,IAAI,OAAO,MAAM,KAAK;AAC/E;AAEO,SAAS,OAAO,QAAgB,UAAwC;AAC7E,QAAM,OAAO,OAAO,KAAK;AACzB,SAAO;AAAA,IACL,GAAG,SAAS,QAAQ,OAAO,CAAC,UAAU,MAAM,KAAK,IAAI,EAAE,QAAQ,CAAC,UAAU,OAAO,MAAM,KAAK,CAAC;AAAA,IAC7F,GAAG,OAAO,QAAQ,SAAS,YAAY,OAAgB;AAAA,EACzD;AACF;AAEA,SAAS,YAAY,OAAoC;AACvD,QAAM,QAAS,OAA2C;AAC1D,SAAO,OAAO,UAAU,WAAW,QAAQ;AAC7C;AAEO,MAAM,SAAS;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAsB,CAAC;AAAA,EAChC;AAAA,EACA,WAAW;AAAA,EAEX,YACE,QACA,SACA,YACA,WACA,QACA;AACA,SAAK,UAAU;AACf,SAAK,WAAW,IAAI,IAAI,QAAQ,IAAI,CAAC,UAAU,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC;AACjE,SAAK,cAAc;AACnB,SAAK,aAAa;AAClB,SAAK,QAAQ,YAAY,MAAM;AAAA,EACjC;AAAA,EAEA,QAAuB;AACrB,WAAO,KAAK,KAAK,OAAO,CAAC;AAAA,EAC3B;AAAA,EAEA,SAAe;AACb,UAAM,QAAQ,KAAK,UAAU,SAAY,SAAY,KAAK,SAAS,IAAI,KAAK,KAAK;AACjF,QAAI,CAAC,MAAO;AACZ,SAAK,WAAW;AAChB,SAAK,KAAK,KAAK,GAAG,KAAK,QAAQ,MAAM,OAAO,KAAK,UAAU,CAAC;AAAA,EAC9D;AAAA,EAEA,KAAK,QAAmB,SAAwB;AAC9C,SAAK,KAAK,KAAK,EAAE,GAAG,OAAO,QAAQ,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC,EAAG,CAAC;AACpE,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,OAA4B;AAChC,QAAI,MAAM,SAAS,gBAAgB,MAAM,OAAO,OAAO,KAAK,YAAa,QAAO;AAChF,SAAK,QAAQ,YAAY,MAAM,MAAM,KAAK,KAAK;AAC/C,WAAO,KAAK,UAAU,UAAa,CAAC,CAAC,UAAU,QAAQ,EAAE,SAAS,MAAM,OAAO,MAAM;AAAA,EACvF;AAAA,EAEA,KAAK,OAAuC;AAC1C,eAAW,SAAS,OAAO;AACzB,UAAI,MAAM,SAAS,cAAe,MAAK,SAAS,IAAI,MAAM,MAAM,IAAI,MAAM,KAAK;AAC/E,UAAI,KAAK,MAAM,KAAK,EAAG,QAAO,KAAK,KAAK,SAAS;AACjD,UAAI,CAAC,KAAK,UAAU;AAClB,aAAK,OAAO;AACZ;AAAA,MACF;AACA,YAAM,SAAS,KAAK,QAAQ,MAAM,KAAK,EAAE,OAAO,CAAC,SAAS,KAAK,MAAM,MAAM;AAC3E,WAAK,KAAK,KAAK,GAAG,MAAM;AACxB,UAAI,OAAO,KAAK,CAAC,SAAS,KAAK,MAAM,SAAS,KAAK,UAAU,KAAK;AAChE,eAAO,KAAK,KAAK,KAAK,QAAQ,QAAQ,KAAK,QAAQ,KAAK;AAAA,IAC5D;AACA,WAAO;AAAA,EACT;AACF;AAEO,SAAS,aAAa,MAA8D;AACzF,QAAM,MAAM,KAAK,OAAO,KAAK;AAC7B,QAAM,UAAU,oBAAI,IAAoB;AACxC,MAAI,SAAS;AACb,MAAI;AACJ,QAAM,QAAQ,MAAM;AAClB,eAAW,CAAC,IAAI,MAAM,KAAK;AACzB,UAAI,IAAI,IAAI,OAAO,UAAU,SAAS;AACpC,gBAAQ,OAAO,EAAE;AACjB,aAAK,OAAO,OAAO;AAAA,MACrB;AACF,QAAI,CAAC,QAAQ,QAAQ,UAAU,QAAW;AACxC,oBAAc,KAAK;AACnB,cAAQ;AAAA,IACV;AAAA,EACF;AACA,QAAM,QAAQ,CAAC,IAAY,WAAmB;AAC5C,YAAQ,IAAI,IAAI,MAAM;AACtB,cAAU,YAAY,OAAO,KAAK,WAAW,UAAU,CAAC;AAAA,EAC1D;AACA,QAAM,WAAW,OAAO,SAAwB,YAAqB;AACnE,UAAM,eAAe,MAAM,KAAK,aAAa,SAAS,OAAO;AAC7D,UAAM,SAAS,WAAW,kBAAkB;AAC5C,UAAM,SAA2B,MAAM;AAAA,MACrC,KAAK;AAAA,MACL,aAAa;AAAA,MACb,OAAO;AAAA,IACT;AACA,UAAM,OAAO,CAAC,aAAqD;AACjE,YAAM,SAAS,IAAI;AAAA,QACjB,YAAY;AACV,gBAAM,OAAO,KAAK;AAClB,iBAAO,OAAO;AAAA,QAChB;AAAA,QACA,IAAI;AAAA,QACJ,KAAK,SAAS;AAAA,MAChB;AACA,YAAM,KAAK,IAAI,EAAE,MAAM;AACvB,YAAM,IAAI,MAAM;AAChB,aAAO,MAAM,OAAO,UAAU,SAAS,KAAK,CAAC;AAC7C,aAAO,EAAE,IAAI,OAAO;AAAA,IACtB;AACA,WAAO,EAAE,cAAc,UAAU,OAAO,UAAU,MAAM,MAAM,MAAM,OAAO,KAAK,EAAE;AAAA,EACpF;AACA,SAAO;AAAA,IACL,MAAM,OAAO,SAAS,SAAS;AAC7B,YAAM;AACN,YAAM,UAAU,MAAM,SAAS,QAAQ,SAAS,OAAO;AACvD,YAAM,OAAO,QAAQ,QAAQ,SAAS,GAAG;AACzC,YAAM,WAAW,MAAM,KAAK;AAAA,QAC1B;AAAA,UACE,MAAM,QAAQ;AAAA,UACd,UAAU,QAAQ;AAAA,UAClB,WAAW;AAAA,UACX,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;AAAA,QACxD;AAAA,QACA;AAAA,MACF;AACA,UAAI,CAAC,SAAS,UAAU;AACtB,cAAM,QAAQ,KAAK;AACnB,eAAO;AAAA,UACL,IAAI;AAAA,UACJ,MAAM,SAAS,MAAM,SAAS,SAAS,UAAU;AAAA,UACjD,SAAS,SAAS,MAAM;AAAA,QAC1B;AAAA,MACF;AACA,YAAM,aAAa,OAAO,SAAS,YAAY;AAC/C,YAAM,SAAS,OAAO,MAAM,KAAK,QAAQ,WAAW,YAAY,OAAO,IAAI,OAAO,OAAO;AACzF,YAAM,YAAY,YAAY,QAAQ,QAAQ,UAAU,IAAI;AAC5D,YAAM,WAAW,IAAI;AAAA,QACnB,IAAI,OAAO,QAAQ,QAAQ;AAAA,QAC3B,QAAQ,SAAS;AAAA,QACjB;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,YAAM,EAAE,IAAI,OAAO,IAAI,QAAQ,KAAK,CAAC,UAAU;AAC7C,cAAM,QAAQ,SAAS,KAAK,KAAK;AACjC,eAAO,KAAK,SAAS,MAAM,CAAC;AAC5B,YAAI,MAAO,MAAK,OAAO,OAAO;AAAA,MAChC,CAAC;AACD,aAAO,EAAE,IAAI,MAAM,QAAQ,IAAI,WAAW,MAAM,MAAM,KAAK,MAAM;AAAA,IACnE;AAAA,IACA,MAAM,MAAM,SAAS,SAAS;AAC5B,YAAM;AACN,YAAM,UAAU,MAAM,SAAS,QAAQ,SAAS,OAAO;AACvD,YAAM,UAAU,QAAQ,SAAS,YAAY;AAC7C,YAAM,MAAM,SAAS,QAAQ,SAAS,SAAS,SAAS,QAAQ,QAAQ;AACxE,YAAM,OAAO,OAAO,GAAG;AACvB,YAAM,KAAK,QAAQ,UAAU,OAAO,KAAK,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,MAAM,QAAQ,KAAK;AACvF,YAAM,MAAM,QAAQ,UAAU,QAAQ,KAAK;AAC3C,YAAM,SAAS,IAAI,OAAO,QAAQ,QAAQ;AAC1C,iBAAW,SAAS,QAAQ,SAAS,QAAS,QAAO,MAAM,KAAK;AAChE,aAAO,QAAQ,OAAO;AACtB,YAAM,EAAE,IAAI,OAAO,IAAI,QAAQ;AAAA,QAAK,CAAC,UACnC,OAAO,KAAK,MAAM,QAAQ,CAAC,UAAU,SAAS,QAAQ,KAAK,CAAC,CAAC;AAAA,MAC/D;AACA,UAAI,MAAM,GAAG;AACX,cAAM,MAAM,IAAI,EAAE;AAClB,cAAM,UAAU,KAAK,GAAG;AACxB,YAAI,OAAO;AACX,eAAO,OAAO,IAAI,IAAI,UAAU,KAAK,IAAI,OAAO,CAAC,CAAgB,MAAM,QAAS;AAChF,cAAM,SAAS,IAAI,MAAM,UAAU,IAAI,MAAM,aAAa,CAAC,GAAG,IAAI,CAAC;AACnE,eAAO,KAAK,CAAC,GAAG,QAAQ,GAAG,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC;AAAA,MACjD;AACA,aAAO,EAAE,IAAI,MAAM,QAAQ,IAAI,WAAW,MAAM,MAAM,IAAI;AAAA,IAC5D;AAAA,IACA,MAAM,KAAK,IAAI,QAAQ,SAAS;AAC9B,YAAM,SAAS,QAAQ,IAAI,EAAE;AAC7B,UAAI,CAAC,OAAQ,QAAO,EAAE,QAAQ,CAAC,GAAG,MAAM,KAAK;AAC7C,aAAO,UAAU,IAAI;AACrB,YAAM,OAAO,KAAK,KAAK,IAAI,GAAG,KAAK,IAAI,QAAQ,GAAK,CAAC,CAAC;AACtD,cAAQ,aAAa,eAAe;AACpC,YAAM,SAAS,OAAO,MAAM,OAAO,CAAC;AACpC,YAAM,OAAO,OAAO,QAAQ,OAAO,MAAM,WAAW;AACpD,UAAI,KAAM,SAAQ,OAAO,EAAE;AAC3B,aAAO,EAAE,QAAQ,KAAK;AAAA,IACxB;AAAA,IACA,MAAM,MAAM,IAAI;AACd,YAAM,SAAS,QAAQ,IAAI,EAAE;AAC7B,cAAQ,OAAO,EAAE;AACjB,YAAM,QAAQ,OAAO;AAAA,IACvB;AAAA,IACA,MAAM,MAAM,SAAS,SAAS;AAC5B,YAAM,eAAe,MAAM,KAAK,aAAa,SAAS,OAAO;AAC7D,YAAM,UAAU,MAAM,YAAY,KAAK,SAAS,aAAa,IAAI,OAAO;AACxE,YAAM,OAAO,QAAQ,QAAQ,SAAS,GAAG;AACzC,YAAM,QAAQ,KAAK;AACnB,UAAI,KAAM,OAAM,aAAa,MAAM,OAAO;AAC1C,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB;AAAA,IACA,MAAM,UAAU;AACd,oBAAc,KAAK;AACnB,cAAQ;AACR,YAAM,MAAM,CAAC,GAAG,QAAQ,OAAO,CAAC;AAChC,cAAQ,MAAM;AACd,YAAM,QAAQ,IAAI,IAAI,IAAI,CAAC,WAAW,OAAO,OAAO,CAAC,CAAC;AAAA,IACxD;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -14,6 +14,19 @@ function packageAssets(base = new URL("../../", import.meta.url)) {
14
14
  };
15
15
  }
16
16
  const COMMANDS = ["agent", "subagent", "sprinkle", "memory", "gelatiere", "freezer"];
17
+ async function pnpmHomeOf(env, context) {
18
+ let out = "";
19
+ const ran = await env.exec(
20
+ 'printf %s "$PNPM_HOME"',
21
+ {
22
+ onOutput: (text) => {
23
+ out += text;
24
+ }
25
+ },
26
+ context
27
+ ).catch(() => void 0);
28
+ return ran?.ok && ran.value.exitCode === 0 && /^\/\S*$/.test(out) ? out : PNPM_HOME;
29
+ }
17
30
  async function install(env, script, pnpmHome, context) {
18
31
  await env.createDir(`${pnpmHome}/bin`, { recursive: true }, context);
19
32
  for (const name of COMMANDS) {
@@ -97,8 +110,10 @@ function setupScoops(registry, licks, fileTools = []) {
97
110
  };
98
111
  await roles(context).catch(() => cached);
99
112
  const script = await assets("bin/agent").catch(() => void 0);
100
- if (script)
101
- await install(env, script, options.pnpmHome ?? PNPM_HOME, context).catch(() => void 0);
113
+ if (script) {
114
+ const pnpmHome = options.pnpmHome ?? await pnpmHomeOf(env, context);
115
+ await install(env, script, pnpmHome, context).catch(() => void 0);
116
+ }
102
117
  const cli = createCli({
103
118
  env,
104
119
  harness,
@@ -126,6 +141,7 @@ export {
126
141
  COMMANDS,
127
142
  PNPM_HOME,
128
143
  packageAssets,
144
+ pnpmHomeOf,
129
145
  setupScoops
130
146
  };
131
147
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/scoops/index.ts"],
4
- "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport type { Harness, Registry, ToolRegistration } from '@earendil-works/pi-durable';\nimport type { ExecutionEnv } from '@earendil-works/pi-durable/env';\nimport { type Agents, live } from '../agents.ts';\nimport type { ProcessGroups } from '../kernel/groups.ts';\nimport type { Licks } from '../licks/licks.ts';\nimport { DEFAULT_LIMITS, loadRoles, packageDirs, type Roles } from '../roles/roles.ts';\nimport { type CliOptions, createCli, RUN_DIR } from './cli.ts';\nimport { guardExtension, scoopsExtension, scoopTasks } from './extension.ts';\nimport { type ControlPlane, controlPlane } from './requests.ts';\nimport { createScoops, type Scoops, type ScoopsHost } from './service.ts';\n\nexport const BUILTIN_ROLES = ['scout', 'worker', 'reviewer', 'oracle', 'delegate', 'gelatiere'];\nexport const PNPM_HOME = '/home/.local/share/pnpm';\n\nexport type Assets = (path: string) => Promise<string>;\n\nexport function packageAssets(base: URL = new URL('../../', import.meta.url)): Assets {\n return async (path) => {\n const response = await fetch(new URL(path, base));\n if (!response.ok) throw new Error(`${path}: ${response.status}`);\n return response.text();\n };\n}\n\nexport interface AttachOptions {\n harness: Harness;\n agents: Agents;\n groups: ProcessGroups;\n env: ExecutionEnv;\n home: string;\n assets?: Assets;\n pnpmHome?: string;\n controlDir?: string;\n sweepEvery?: number;\n alive?: (pid: number) => Promise<boolean>;\n reads: () => readonly string[];\n sprinkle?: CliOptions['sprinkle'];\n memory?: CliOptions['memory'];\n gelatiere?: CliOptions['gelatiere'];\n freezer?: CliOptions['freezer'];\n mcp?: (names: readonly string[]) => ToolRegistration[];\n}\n\nexport interface ScoopsRuntime {\n roles(context: Context): Promise<Roles>;\n plane: ControlPlane;\n close(context: Context): Promise<void>;\n}\n\nexport interface ScoopsSetup {\n scoops: Scoops;\n attach(options: AttachOptions, context: Context): Promise<ScoopsRuntime>;\n}\n\nexport const COMMANDS = ['agent', 'subagent', 'sprinkle', 'memory', 'gelatiere', 'freezer'];\n\nasync function install(env: ExecutionEnv, script: string, pnpmHome: string, context: Context) {\n await env.createDir(`${pnpmHome}/bin`, { recursive: true }, context);\n for (const name of COMMANDS) {\n const path = `${pnpmHome}/bin/${name}`;\n await env.writeFile(path, script, context);\n await env.exec(['chmod', '+x', path], undefined, context);\n }\n}\n\nfunction mirror(agents: Agents, env: ExecutionEnv) {\n return async (context: Context) => {\n for (const [id, record] of Object.entries(agents.state().scoops)) {\n const state = record.gone ? 'gone' : live(record) ? 'live' : 'stopped';\n const metadata = {\n handle: record.folder,\n id,\n name: record.name,\n role: record.role,\n cone: record.cone,\n state,\n };\n await env.writeFile(\n `${RUN_DIR}/${record.folder}/metadata.json`,\n `${JSON.stringify(metadata, null, 2)}\\n`,\n context\n );\n }\n };\n}\n\nexport function setupScoops(\n registry: Registry,\n licks: Licks,\n fileTools: readonly ToolRegistration[] = []\n): ScoopsSetup {\n let bind: (host: ScoopsHost) => void = () => undefined;\n const host = new Promise<ScoopsHost>((resolve) => {\n bind = resolve;\n });\n const lookup = () => host;\n const tasks = scoopTasks(lookup);\n const scoops = createScoops(host, () => tasks);\n let cached: Roles = { roles: [], warnings: [], limits: { ...DEFAULT_LIMITS } };\n registry.install(scoopsExtension(tasks));\n registry.install(guardExtension(lookup, fileTools));\n return {\n scoops,\n async attach(options, context) {\n const { harness, agents, groups, env, home } = options;\n const assets = options.assets ?? packageAssets();\n bind({\n harness,\n agents,\n licks,\n tools: fileTools,\n groups,\n reads: options.reads,\n files: env,\n ...(options.mcp ? { mcp: options.mcp } : {}),\n });\n const builtin = async () => {\n const out: { path: string; text: string }[] = [];\n for (const name of BUILTIN_ROLES) {\n const path = `packages/vfs-root/agents/${name}.md`;\n out.push({ path: `builtin:${name}`, text: await assets(path) });\n }\n return out;\n };\n const roles = async (using: Context) => {\n cached = await loadRoles(\n {\n files: env,\n builtin,\n dirs: [\n ...(await packageDirs(env, '/node_modules', using)).map((dir) => ({\n source: 'package' as const,\n dir,\n })),\n { source: 'user', dir: `${home}/.pi/agent/agents` },\n ],\n settingsPath: `${home}/.pi/agent/settings.json`,\n },\n using\n );\n return cached;\n };\n await roles(context).catch(() => cached);\n const script = await assets('bin/agent').catch(() => undefined);\n if (script)\n await install(env, script, options.pnpmHome ?? PNPM_HOME, context).catch(() => undefined);\n const cli = createCli({\n env,\n harness,\n agents,\n scoops,\n roles: () => roles(context),\n ...(options.alive ? { alive: options.alive } : {}),\n ...(options.sprinkle ? { sprinkle: options.sprinkle } : {}),\n ...(options.memory ? { memory: options.memory } : {}),\n ...(options.gelatiere ? { gelatiere: options.gelatiere } : {}),\n ...(options.freezer ? { freezer: options.freezer } : {}),\n });\n const plane = controlPlane(env, cli, {\n ...(options.controlDir ? { dir: options.controlDir } : {}),\n ...(options.sweepEvery ? { sweepEvery: options.sweepEvery } : {}),\n mirror: mirror(agents, env),\n });\n await plane.start(context);\n return { roles, plane, close: (using) => plane.close(using) };\n },\n };\n}\n\nexport type { Rewound, Scoops } from './service.ts';\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport type { Harness, Registry, ToolRegistration } from '@earendil-works/pi-durable';\nimport type { ExecutionEnv } from '@earendil-works/pi-durable/env';\nimport { type Agents, live } from '../agents.ts';\nimport type { ProcessGroups } from '../kernel/groups.ts';\nimport type { Licks } from '../licks/licks.ts';\nimport { DEFAULT_LIMITS, loadRoles, packageDirs, type Roles } from '../roles/roles.ts';\nimport { type CliOptions, createCli, RUN_DIR } from './cli.ts';\nimport { guardExtension, scoopsExtension, scoopTasks } from './extension.ts';\nimport { type ControlPlane, controlPlane } from './requests.ts';\nimport { createScoops, type Scoops, type ScoopsHost } from './service.ts';\n\nexport const BUILTIN_ROLES = ['scout', 'worker', 'reviewer', 'oracle', 'delegate', 'gelatiere'];\nexport const PNPM_HOME = '/home/.local/share/pnpm';\n\nexport type Assets = (path: string) => Promise<string>;\n\nexport function packageAssets(base: URL = new URL('../../', import.meta.url)): Assets {\n return async (path) => {\n const response = await fetch(new URL(path, base));\n if (!response.ok) throw new Error(`${path}: ${response.status}`);\n return response.text();\n };\n}\n\nexport interface AttachOptions {\n harness: Harness;\n agents: Agents;\n groups: ProcessGroups;\n env: ExecutionEnv;\n home: string;\n assets?: Assets;\n pnpmHome?: string;\n controlDir?: string;\n sweepEvery?: number;\n alive?: (pid: number) => Promise<boolean>;\n reads: () => readonly string[];\n sprinkle?: CliOptions['sprinkle'];\n memory?: CliOptions['memory'];\n gelatiere?: CliOptions['gelatiere'];\n freezer?: CliOptions['freezer'];\n mcp?: (names: readonly string[]) => ToolRegistration[];\n}\n\nexport interface ScoopsRuntime {\n roles(context: Context): Promise<Roles>;\n plane: ControlPlane;\n close(context: Context): Promise<void>;\n}\n\nexport interface ScoopsSetup {\n scoops: Scoops;\n attach(options: AttachOptions, context: Context): Promise<ScoopsRuntime>;\n}\n\nexport const COMMANDS = ['agent', 'subagent', 'sprinkle', 'memory', 'gelatiere', 'freezer'];\n\nexport async function pnpmHomeOf(env: ExecutionEnv, context: Context): Promise<string> {\n let out = '';\n const ran = await env\n .exec(\n 'printf %s \"$PNPM_HOME\"',\n {\n onOutput: (text) => {\n out += text;\n },\n },\n context\n )\n .catch(() => undefined);\n return ran?.ok && ran.value.exitCode === 0 && /^\\/\\S*$/.test(out) ? out : PNPM_HOME;\n}\n\nasync function install(env: ExecutionEnv, script: string, pnpmHome: string, context: Context) {\n await env.createDir(`${pnpmHome}/bin`, { recursive: true }, context);\n for (const name of COMMANDS) {\n const path = `${pnpmHome}/bin/${name}`;\n await env.writeFile(path, script, context);\n await env.exec(['chmod', '+x', path], undefined, context);\n }\n}\n\nfunction mirror(agents: Agents, env: ExecutionEnv) {\n return async (context: Context) => {\n for (const [id, record] of Object.entries(agents.state().scoops)) {\n const state = record.gone ? 'gone' : live(record) ? 'live' : 'stopped';\n const metadata = {\n handle: record.folder,\n id,\n name: record.name,\n role: record.role,\n cone: record.cone,\n state,\n };\n await env.writeFile(\n `${RUN_DIR}/${record.folder}/metadata.json`,\n `${JSON.stringify(metadata, null, 2)}\\n`,\n context\n );\n }\n };\n}\n\nexport function setupScoops(\n registry: Registry,\n licks: Licks,\n fileTools: readonly ToolRegistration[] = []\n): ScoopsSetup {\n let bind: (host: ScoopsHost) => void = () => undefined;\n const host = new Promise<ScoopsHost>((resolve) => {\n bind = resolve;\n });\n const lookup = () => host;\n const tasks = scoopTasks(lookup);\n const scoops = createScoops(host, () => tasks);\n let cached: Roles = { roles: [], warnings: [], limits: { ...DEFAULT_LIMITS } };\n registry.install(scoopsExtension(tasks));\n registry.install(guardExtension(lookup, fileTools));\n return {\n scoops,\n async attach(options, context) {\n const { harness, agents, groups, env, home } = options;\n const assets = options.assets ?? packageAssets();\n bind({\n harness,\n agents,\n licks,\n tools: fileTools,\n groups,\n reads: options.reads,\n files: env,\n ...(options.mcp ? { mcp: options.mcp } : {}),\n });\n const builtin = async () => {\n const out: { path: string; text: string }[] = [];\n for (const name of BUILTIN_ROLES) {\n const path = `packages/vfs-root/agents/${name}.md`;\n out.push({ path: `builtin:${name}`, text: await assets(path) });\n }\n return out;\n };\n const roles = async (using: Context) => {\n cached = await loadRoles(\n {\n files: env,\n builtin,\n dirs: [\n ...(await packageDirs(env, '/node_modules', using)).map((dir) => ({\n source: 'package' as const,\n dir,\n })),\n { source: 'user', dir: `${home}/.pi/agent/agents` },\n ],\n settingsPath: `${home}/.pi/agent/settings.json`,\n },\n using\n );\n return cached;\n };\n await roles(context).catch(() => cached);\n const script = await assets('bin/agent').catch(() => undefined);\n if (script) {\n const pnpmHome = options.pnpmHome ?? (await pnpmHomeOf(env, context));\n await install(env, script, pnpmHome, context).catch(() => undefined);\n }\n const cli = createCli({\n env,\n harness,\n agents,\n scoops,\n roles: () => roles(context),\n ...(options.alive ? { alive: options.alive } : {}),\n ...(options.sprinkle ? { sprinkle: options.sprinkle } : {}),\n ...(options.memory ? { memory: options.memory } : {}),\n ...(options.gelatiere ? { gelatiere: options.gelatiere } : {}),\n ...(options.freezer ? { freezer: options.freezer } : {}),\n });\n const plane = controlPlane(env, cli, {\n ...(options.controlDir ? { dir: options.controlDir } : {}),\n ...(options.sweepEvery ? { sweepEvery: options.sweepEvery } : {}),\n mirror: mirror(agents, env),\n });\n await plane.start(context);\n return { roles, plane, close: (using) => plane.close(using) };\n },\n };\n}\n\nexport type { Rewound, Scoops } from './service.ts';\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/services.js CHANGED
@@ -4,11 +4,13 @@ import {
4
4
  const AgentControl = defineService("slicc.agent.control");
5
5
  const AgentTranscript = defineService("slicc.agent.transcript");
6
6
  const AgentSessions = defineService("slicc.agent.sessions");
7
+ const AgentRemoteService = defineService("slicc.agent.remote");
7
8
  const AGENT_SESSION = "agent";
8
9
  const AgentSettings = defineService("slicc.agent.settings");
9
10
  export {
10
11
  AGENT_SESSION,
11
12
  AgentControl,
13
+ AgentRemoteService,
12
14
  AgentSessions,
13
15
  AgentSettings,
14
16
  AgentTranscript
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/services.ts"],
4
- "sourcesContent": ["import {\n type Context,\n defineService,\n type JsonValue,\n type ReplicatedState,\n} from '@earendil-works/chord';\nimport type { ConversationView } from '@earendil-works/pi-durable';\nimport type { ChangesView } from './changes/git.ts';\nimport type { FrozenCone } from './freezer/index.ts';\nimport type { MemoryEntry, MemoryScope, MemoryTag } from './memory/format.ts';\nimport type { Sprinkle } from './sprinkles/kind.ts';\n\nexport type SendMode = 'steer' | 'followUp' | 'reject';\n\nexport interface SendRequest {\n text: string;\n whenBusy: SendMode;\n requestId: string | null;\n agentId?: string | null;\n}\n\nexport interface OperationError {\n code: string;\n message: string;\n}\n\nexport type SendResponse =\n | { accepted: true; submissionId: string; error: null }\n | { accepted: false; submissionId: null; error: OperationError };\n\nexport type PromptResult =\n | { status: 'done'; text: string; reason: null }\n | { status: 'unanswered'; text: null; reason: string };\n\nexport interface AgentControl {\n send(request: SendRequest, context: Context): Promise<SendResponse>;\n wait(submissionId: string, context: Context): Promise<PromptResult>;\n withdraw(\n submissionId: string,\n context: Context\n ): Promise<{ outcome: 'withdrawn' | 'already_placed' | 'not_found' }>;\n abort(context: Context): Promise<void>;\n compact(instructions: string | null, context: Context): Promise<SendResponse>;\n compactAgent(\n agentId: string,\n instructions: string | null,\n context: Context\n ): Promise<SendResponse>;\n reset(handoff: string | null, context: Context): Promise<void>;\n configure(change: AgentSettingsChange, context: Context): Promise<void>;\n rewind(messageId: string | null, context: Context): Promise<Rewound>;\n rewindAgent(agentId: string, messageId: string | null, context: Context): Promise<Rewound>;\n resolveLick(\n lickId: string,\n state: 'confirmed' | 'dismissed',\n context: Context\n ): Promise<LickResolved>;\n resolveAgentLick(\n agentId: string,\n lickId: string,\n state: 'confirmed' | 'dismissed',\n context: Context\n ): Promise<LickResolved>;\n webhook(\n name: string,\n delivery: WebhookRequest,\n context: Context\n ): Promise<{ delivered: boolean }>;\n stopAgent(agentId: string, context: Context): Promise<void>;\n selectCone(agentId: string, context: Context): Promise<void>;\n createCone(name: string, context: Context): Promise<Created>;\n createScoop(parentId: string, name: string, context: Context): Promise<Created>;\n drop(agentId: string, context: Context): Promise<Created>;\n unqueue(\n agentId: string | null,\n submissionId: string,\n context: Context\n ): Promise<{ outcome: 'withdrawn' | 'already_placed' | 'not_found' }>;\n sprinkleSend(\n sprinkleId: string,\n payload: { action: string; data: JsonValue | null; target: string | null },\n context: Context\n ): Promise<{ delivered: boolean }>;\n sprinkleCall(\n sprinkleId: string,\n method: string,\n args: JsonValue[],\n context: Context\n ): Promise<JsonValue>;\n memorySave(draft: MemoryDraft, context: Context): Promise<MemoryEntry>;\n memoryRemove(id: string, context: Context): Promise<{ removed: boolean }>;\n freeze(agentId: string, context: Context): Promise<Created>;\n newChat(agentId: string, context: Context): Promise<Created>;\n thaw(frozenId: string, context: Context): Promise<Created>;\n discard(frozenId: string, context: Context): Promise<Created>;\n changesOpen(context: Context): Promise<void>;\n changeAccept(path: string, context: Context): Promise<Created>;\n changeRevert(path: string, context: Context): Promise<Created>;\n}\n\nexport interface MemoryDraft {\n id: string | null;\n scope: string;\n section: string;\n title: string;\n body: string;\n tag: MemoryTag | null;\n}\n\nexport type Created = { id: string; error: null } | { id: null; error: string };\n\nexport interface AgentSummary {\n id: string;\n name: string;\n kind: 'cone' | 'scoop';\n parentId: string | null;\n role: string | null;\n}\n\nexport interface AgentsSummary {\n active: string;\n agents: AgentSummary[];\n}\n\nexport type LickResolved =\n | { done: true; text: string; error: null }\n | { done: false; text: null; error: string };\n\nexport interface WebhookRequest {\n id: string | null;\n headers: Record<string, string>;\n body: string;\n}\n\nexport type Rewound =\n | { done: true; text: string; reason: null }\n | { done: false; text: null; reason: 'busy' | 'no-turn' };\n\nexport interface AgentSettingsChange {\n model: { provider: string; modelId: string } | null;\n thinkingLevel: 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max' | null;\n agentId?: string | null;\n}\n\nexport const AgentControl = defineService<AgentControl>('slicc.agent.control');\n\nexport type Delivered = 'run' | 'steer' | 'follow-up';\n\nexport interface AgentTranscript {\n readonly state: ReplicatedState<ConversationView>;\n readonly deliveries: ReplicatedState<Record<string, Delivered>>;\n readonly agents: ReplicatedState<AgentsSummary>;\n readonly views: ReplicatedState<Record<string, ConversationView>>;\n readonly commands: ReplicatedState<Command[]>;\n readonly sprinkles: ReplicatedState<Sprinkle[]>;\n readonly memories: ReplicatedState<MemoryEntry[]>;\n readonly memoryScopes: ReplicatedState<MemoryScope[]>;\n readonly frozen: ReplicatedState<FrozenCone[]>;\n readonly changes: ReplicatedState<ChangesView>;\n}\n\nexport interface Command {\n name: string;\n description: string;\n kind: 'prompt' | 'skill';\n}\n\nexport const AgentTranscript = defineService<AgentTranscript>('slicc.agent.transcript');\n\nexport interface AgentSessions {\n attach(sessionId: string, context: Context): Promise<void>;\n detach(context: Context): Promise<void>;\n}\n\nexport const AgentSessions = defineService<AgentSessions>('slicc.agent.sessions');\n\nexport const AGENT_SESSION = 'agent';\n\nexport interface ModelChoice {\n id: string;\n label: string;\n provider: string;\n kind: 'chat' | 'classifier';\n reasoning: boolean;\n images?: boolean;\n contextWindow: number;\n}\n\nexport interface AccountState {\n id: string;\n provider: string;\n identity: string;\n status: 'connected' | 'disconnected';\n auth: 'api-key' | 'oauth';\n needs: 'cors-free-transport' | null;\n}\n\nexport interface BudgetState {\n provider: string;\n percent: number;\n window: 'weekly';\n resets: string;\n}\n\nexport interface SignIn {\n clientId: string;\n scopes: string;\n imsEnvironment: string;\n}\n\nexport interface SettingsState {\n models: ModelChoice[];\n accounts: AccountState[];\n budget?: BudgetState | null;\n}\n\nexport interface AgentSettings {\n readonly state: ReplicatedState<SettingsState>;\n connect(\n providerId: string,\n secret: string,\n region: string | null,\n context: Context\n ): Promise<void>;\n disconnect(providerId: string, context: Context): Promise<void>;\n signIn(providerId: string, context: Context): Promise<SignIn | null>;\n}\n\nexport const AgentSettings = defineService<AgentSettings>('slicc.agent.settings');\n"],
5
- "mappings": "AAAA;AAAA,EAEE;AAAA,OAGK;AA2IA,MAAM,eAAe,cAA4B,qBAAqB;AAuBtE,MAAM,kBAAkB,cAA+B,wBAAwB;AAO/E,MAAM,gBAAgB,cAA6B,sBAAsB;AAEzE,MAAM,gBAAgB;AAoDtB,MAAM,gBAAgB,cAA6B,sBAAsB;",
4
+ "sourcesContent": ["import {\n type Context,\n defineService,\n type JsonValue,\n type ReplicatedState,\n} from '@earendil-works/chord';\nimport type { ConversationView } from '@earendil-works/pi-durable';\nimport type { ChangesView } from './changes/git.ts';\nimport type { FrozenCone } from './freezer/index.ts';\nimport type { MemoryEntry, MemoryScope, MemoryTag } from './memory/format.ts';\nimport type { AgentRemote } from './remote/host.ts';\nimport type { Sprinkle } from './sprinkles/kind.ts';\n\nexport type SendMode = 'steer' | 'followUp' | 'reject';\n\nexport interface SendRequest {\n text: string;\n whenBusy: SendMode;\n requestId: string | null;\n agentId?: string | null;\n}\n\nexport interface OperationError {\n code: string;\n message: string;\n}\n\nexport type SendResponse =\n | { accepted: true; submissionId: string; error: null }\n | { accepted: false; submissionId: null; error: OperationError };\n\nexport type PromptResult =\n | { status: 'done'; text: string; reason: null }\n | { status: 'unanswered'; text: null; reason: string };\n\nexport interface AgentControl {\n send(request: SendRequest, context: Context): Promise<SendResponse>;\n wait(submissionId: string, context: Context): Promise<PromptResult>;\n withdraw(\n submissionId: string,\n context: Context\n ): Promise<{ outcome: 'withdrawn' | 'already_placed' | 'not_found' }>;\n abort(context: Context): Promise<void>;\n compact(instructions: string | null, context: Context): Promise<SendResponse>;\n compactAgent(\n agentId: string,\n instructions: string | null,\n context: Context\n ): Promise<SendResponse>;\n reset(handoff: string | null, context: Context): Promise<void>;\n configure(change: AgentSettingsChange, context: Context): Promise<void>;\n rewind(messageId: string | null, context: Context): Promise<Rewound>;\n rewindAgent(agentId: string, messageId: string | null, context: Context): Promise<Rewound>;\n resolveLick(\n lickId: string,\n state: 'confirmed' | 'dismissed',\n context: Context\n ): Promise<LickResolved>;\n resolveAgentLick(\n agentId: string,\n lickId: string,\n state: 'confirmed' | 'dismissed',\n context: Context\n ): Promise<LickResolved>;\n webhook(\n name: string,\n delivery: WebhookRequest,\n context: Context\n ): Promise<{ delivered: boolean }>;\n stopAgent(agentId: string, context: Context): Promise<void>;\n selectCone(agentId: string, context: Context): Promise<void>;\n createCone(name: string, context: Context): Promise<Created>;\n createScoop(parentId: string, name: string, context: Context): Promise<Created>;\n drop(agentId: string, context: Context): Promise<Created>;\n unqueue(\n agentId: string | null,\n submissionId: string,\n context: Context\n ): Promise<{ outcome: 'withdrawn' | 'already_placed' | 'not_found' }>;\n sprinkleSend(\n sprinkleId: string,\n payload: { action: string; data: JsonValue | null; target: string | null },\n context: Context\n ): Promise<{ delivered: boolean }>;\n sprinkleCall(\n sprinkleId: string,\n method: string,\n args: JsonValue[],\n context: Context\n ): Promise<JsonValue>;\n memorySave(draft: MemoryDraft, context: Context): Promise<MemoryEntry>;\n memoryRemove(id: string, context: Context): Promise<{ removed: boolean }>;\n freeze(agentId: string, context: Context): Promise<Created>;\n newChat(agentId: string, context: Context): Promise<Created>;\n thaw(frozenId: string, context: Context): Promise<Created>;\n discard(frozenId: string, context: Context): Promise<Created>;\n changesOpen(context: Context): Promise<void>;\n changeAccept(path: string, context: Context): Promise<Created>;\n changeRevert(path: string, context: Context): Promise<Created>;\n}\n\nexport interface MemoryDraft {\n id: string | null;\n scope: string;\n section: string;\n title: string;\n body: string;\n tag: MemoryTag | null;\n}\n\nexport type Created = { id: string; error: null } | { id: null; error: string };\n\nexport interface AgentSummary {\n id: string;\n name: string;\n kind: 'cone' | 'scoop';\n parentId: string | null;\n role: string | null;\n}\n\nexport interface AgentsSummary {\n active: string;\n agents: AgentSummary[];\n}\n\nexport type LickResolved =\n | { done: true; text: string; error: null }\n | { done: false; text: null; error: string };\n\nexport interface WebhookRequest {\n id: string | null;\n headers: Record<string, string>;\n body: string;\n}\n\nexport type Rewound =\n | { done: true; text: string; reason: null }\n | { done: false; text: null; reason: 'busy' | 'no-turn' };\n\nexport interface AgentSettingsChange {\n model: { provider: string; modelId: string } | null;\n thinkingLevel: 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max' | null;\n agentId?: string | null;\n}\n\nexport const AgentControl = defineService<AgentControl>('slicc.agent.control');\n\nexport type Delivered = 'run' | 'steer' | 'follow-up';\n\nexport interface AgentTranscript {\n readonly state: ReplicatedState<ConversationView>;\n readonly deliveries: ReplicatedState<Record<string, Delivered>>;\n readonly agents: ReplicatedState<AgentsSummary>;\n readonly views: ReplicatedState<Record<string, ConversationView>>;\n readonly commands: ReplicatedState<Command[]>;\n readonly sprinkles: ReplicatedState<Sprinkle[]>;\n readonly memories: ReplicatedState<MemoryEntry[]>;\n readonly memoryScopes: ReplicatedState<MemoryScope[]>;\n readonly frozen: ReplicatedState<FrozenCone[]>;\n readonly changes: ReplicatedState<ChangesView>;\n}\n\nexport interface Command {\n name: string;\n description: string;\n kind: 'prompt' | 'skill';\n}\n\nexport const AgentTranscript = defineService<AgentTranscript>('slicc.agent.transcript');\n\nexport interface AgentSessions {\n attach(sessionId: string, context: Context): Promise<void>;\n detach(context: Context): Promise<void>;\n}\n\nexport const AgentSessions = defineService<AgentSessions>('slicc.agent.sessions');\n\nexport const AgentRemoteService = defineService<AgentRemote>('slicc.agent.remote');\n\nexport const AGENT_SESSION = 'agent';\n\nexport interface ModelChoice {\n id: string;\n label: string;\n provider: string;\n kind: 'chat' | 'classifier';\n reasoning: boolean;\n images?: boolean;\n contextWindow: number;\n}\n\nexport interface AccountState {\n id: string;\n provider: string;\n identity: string;\n status: 'connected' | 'disconnected';\n auth: 'api-key' | 'oauth';\n needs: 'cors-free-transport' | null;\n}\n\nexport interface BudgetState {\n provider: string;\n percent: number;\n window: 'weekly';\n resets: string;\n}\n\nexport interface SignIn {\n clientId: string;\n scopes: string;\n imsEnvironment: string;\n}\n\nexport interface SettingsState {\n models: ModelChoice[];\n accounts: AccountState[];\n budget?: BudgetState | null;\n}\n\nexport interface AgentSettings {\n readonly state: ReplicatedState<SettingsState>;\n connect(\n providerId: string,\n secret: string,\n region: string | null,\n context: Context\n ): Promise<void>;\n disconnect(providerId: string, context: Context): Promise<void>;\n signIn(providerId: string, context: Context): Promise<SignIn | null>;\n}\n\nexport const AgentSettings = defineService<AgentSettings>('slicc.agent.settings');\n"],
5
+ "mappings": "AAAA;AAAA,EAEE;AAAA,OAGK;AA4IA,MAAM,eAAe,cAA4B,qBAAqB;AAuBtE,MAAM,kBAAkB,cAA+B,wBAAwB;AAO/E,MAAM,gBAAgB,cAA6B,sBAAsB;AAEzE,MAAM,qBAAqB,cAA2B,oBAAoB;AAE1E,MAAM,gBAAgB;AAoDtB,MAAM,gBAAgB,cAA6B,sBAAsB;",
6
6
  "names": []
7
7
  }
@@ -3,6 +3,7 @@ import type { ConversationView } from '@earendil-works/pi-durable';
3
3
  import type { ChangesView } from './changes/git.ts';
4
4
  import type { FrozenCone } from './freezer/index.ts';
5
5
  import type { MemoryEntry, MemoryScope } from './memory/format.ts';
6
+ import type { AgentRemote } from './remote/host.ts';
6
7
  import { AgentControl, AgentSettings, type AgentsSummary, type Command, type Delivered, type SendMode } from './services.ts';
7
8
  import type { Sprinkle } from './sprinkles/kind.ts';
8
9
  import { type PortEndpoint } from './wire.ts';
@@ -20,6 +21,7 @@ export interface AgentConnection {
20
21
  readonly frozen?: ReplicatedState<FrozenCone[]>;
21
22
  readonly changes?: ReplicatedState<ChangesView>;
22
23
  readonly settings: AgentSettings;
24
+ readonly remote?: AgentRemote;
23
25
  readonly closed: Promise<Error | undefined>;
24
26
  prompt(text: string, whenBusy?: SendMode): Promise<string>;
25
27
  close(): Promise<void>;
@@ -1,2 +1,3 @@
1
1
  export { type AgentConnection, connectAgent } from './client.ts';
2
2
  export { AGENT_LOCK, type AgentOwner, type AgentWorker, type KernelSource, type OwnerOptions, startAgent, } from './owner.ts';
3
+ export { type RemoteControl, type RemoteEvent, type RemoteOptions, remoteControl, } from './remote/client.ts';
@@ -0,0 +1,30 @@
1
+ import type { AgentConnection } from '../client.ts';
2
+ import type { Delivered, SendMode } from '../services.ts';
3
+ import type { RemoteEvent } from './events.ts';
4
+ export type { RemoteEvent } from './events.ts';
5
+ export declare const POLL_MS = 25000;
6
+ export interface RemoteOptions {
7
+ agent?: () => string | null;
8
+ }
9
+ export interface RemoteControl {
10
+ prompt(request: {
11
+ text: string;
12
+ whenBusy?: SendMode;
13
+ thinking?: boolean;
14
+ }): Promise<{
15
+ delivered: Delivered;
16
+ events: AsyncIterable<RemoteEvent>;
17
+ }>;
18
+ watch(request?: {
19
+ since?: string | null;
20
+ thinking?: boolean;
21
+ }): Promise<{
22
+ head: string;
23
+ gap?: true;
24
+ events: AsyncIterable<RemoteEvent>;
25
+ }>;
26
+ abort(): Promise<{
27
+ aborted: boolean;
28
+ }>;
29
+ }
30
+ export declare function remoteControl(connection: Pick<AgentConnection, 'remote'>, options?: RemoteOptions): RemoteControl;