@poodle64/librarian 2026.9.28 → 2026.10.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.
Files changed (46) hide show
  1. package/README.md +66 -116
  2. package/dist/attachments.d.ts +2 -2
  3. package/dist/attachments.js +3 -3
  4. package/dist/chat.svelte.d.ts +22 -23
  5. package/dist/chat.svelte.js +41 -64
  6. package/dist/citations.d.ts +7 -7
  7. package/dist/citations.js +6 -6
  8. package/dist/client.d.ts +16 -45
  9. package/dist/client.js +15 -23
  10. package/dist/components/activity-group/activity-group.svelte +1 -1
  11. package/dist/components/agent-transcript/agent-transcript.svelte +9 -110
  12. package/dist/components/agent-transcript/agent-transcript.svelte.d.ts +4 -16
  13. package/dist/components/answer-mark/answer-mark.svelte +2 -2
  14. package/dist/components/answer-mark/answer-mark.svelte.d.ts +2 -2
  15. package/dist/components/answer-share/answer-share.svelte +2 -2
  16. package/dist/components/answer-share/answer-share.svelte.d.ts +2 -2
  17. package/dist/components/artefact-card/artefact-card.svelte +2 -2
  18. package/dist/components/artefact-card/artefact-card.svelte.d.ts +2 -2
  19. package/dist/components/artefact-pane/artefact-pane.svelte +2 -2
  20. package/dist/components/artefact-pane/artefact-pane.svelte.d.ts +2 -2
  21. package/dist/components/composer/composer.svelte +39 -112
  22. package/dist/components/composer/composer.svelte.d.ts +18 -18
  23. package/dist/components/composer/index.d.ts +1 -2
  24. package/dist/components/conversation/conversation.svelte +7 -18
  25. package/dist/components/conversation/conversation.svelte.d.ts +7 -12
  26. package/dist/components/conversation-list/conversation-list.svelte +2 -2
  27. package/dist/components/conversation-list/conversation-list.svelte.d.ts +2 -2
  28. package/dist/components/document-pane/document-pane.svelte +2 -2
  29. package/dist/components/document-pane/document-pane.svelte.d.ts +2 -2
  30. package/dist/components/fair-use-notice/fair-use-notice.svelte +2 -2
  31. package/dist/components/fair-use-notice/fair-use-notice.svelte.d.ts +2 -2
  32. package/dist/components/scope-statement/scope-statement.svelte +5 -5
  33. package/dist/components/scope-statement/scope-statement.svelte.d.ts +2 -2
  34. package/dist/components/source-list/source-list.svelte +2 -2
  35. package/dist/components/source-list/source-list.svelte.d.ts +2 -2
  36. package/dist/components/working/working.svelte +2 -2
  37. package/dist/components/working/working.svelte.d.ts +2 -2
  38. package/dist/copy.d.ts +16 -24
  39. package/dist/copy.js +10 -16
  40. package/dist/session.svelte.d.ts +1 -1
  41. package/dist/session.svelte.js +6 -7
  42. package/dist/transcript.svelte.d.ts +18 -32
  43. package/dist/transcript.svelte.js +19 -40
  44. package/package.json +1 -5
  45. package/dist/history.svelte.d.ts +0 -37
  46. package/dist/history.svelte.js +0 -59
@@ -1,8 +1,8 @@
1
1
  /**
2
- * The page's controller: one conversation with one agent, over a room's routes
3
- * or a job's watch, message and stop.
2
+ * The page's controller: one conversation with one agent, over a persona's
3
+ * chat routes or a job's watch, message and stop.
4
4
  *
5
- * A room is asked. Each question streams its own turn, the agent names the
5
+ * A persona is asked. Each question streams its own turn, the agent names the
6
6
  * conversation on its `init` event, and the conversation is read back whole
7
7
  * when it is reopened. The turn belongs to the server, not the page: a
8
8
  * refresh, a locked phone or a closed tab leaves it running, so a reopened
@@ -20,7 +20,6 @@
20
20
  * what its client returns straight through.
21
21
  */
22
22
  import { untrack } from 'svelte';
23
- import { isQueued } from './client';
24
23
  import { Session } from './session.svelte';
25
24
  import { fold, foldState } from './transcript.svelte';
26
25
  /** The events of a run: the agent has the question. */
@@ -34,8 +33,6 @@ export class Chat {
34
33
  /** When an answer this page is not streaming began, epoch ms: a
35
34
  * conversation reopened mid-answer. */
36
35
  answering = $state(null);
37
- /** The agent is answering somebody else's question first. */
38
- waiting = $state(false);
39
36
  /** Today's allowance; null until read, and in a job. */
40
37
  quota = $state(null);
41
38
  /** The open conversation, or the job; null for a new one. The host keeps
@@ -44,7 +41,7 @@ export class Chat {
44
41
  /** This person's conversations, as `list()` last read them; null until then. */
45
42
  conversations = $state(null);
46
43
  listFailed = $state(false);
47
- #room = null;
44
+ #chat = null;
48
45
  #job = null;
49
46
  #pollMs;
50
47
  #rewatchMs;
@@ -71,7 +68,7 @@ export class Chat {
71
68
  if ('watch' in transport)
72
69
  this.#job = transport;
73
70
  else
74
- this.#room = transport;
71
+ this.#chat = transport;
75
72
  this.#pollMs = options.pollMs ?? 5000;
76
73
  this.#rewatchMs = options.rewatchMs ?? 2000;
77
74
  this.#artefact = options.artefact;
@@ -95,7 +92,7 @@ export class Chat {
95
92
  get busy() {
96
93
  return this.running || this.answering !== null;
97
94
  }
98
- /** A job reads what the reader says while it works; a room waits for its answer. */
95
+ /** A job reads what the reader says while it works; a persona waits for its answer. */
99
96
  get sendWhileRunning() {
100
97
  return this.#job !== null;
101
98
  }
@@ -119,7 +116,7 @@ export class Chat {
119
116
  void this.refreshQuota();
120
117
  const mine = this.#generation;
121
118
  try {
122
- const read = await this.#room.read(id);
119
+ const read = await this.#chat.read(id);
123
120
  if (mine === this.#generation)
124
121
  this.#show(read, mine);
125
122
  return mine === this.#generation;
@@ -147,11 +144,11 @@ export class Chat {
147
144
  }
148
145
  /**
149
146
  * Ask `question`, or what is in the box, as a `kind` of turn when it is not
150
- * a plain question, at a `depth` when the host offers the choice. Resolves
147
+ * a plain question, narrowed to one `collection` by name when given. Resolves
151
148
  * once the turn settles: true if it was asked, false if it was refused,
152
149
  * here or by the server, in which case typed words go back into the box.
153
150
  */
154
- async ask(question, kind, depth) {
151
+ async ask(question, kind, collection) {
155
152
  const typed = question === undefined;
156
153
  const text = (question ?? this.draft).trim();
157
154
  if (!text)
@@ -165,14 +162,14 @@ export class Chat {
165
162
  this.draft = '';
166
163
  this.files = [];
167
164
  }
168
- return this.#ask(text, files, typed, kind, depth);
165
+ return this.#ask(text, files, typed, kind, collection);
169
166
  }
170
167
  /** The last question again, as a new turn: the conversation is append-only,
171
- * because the agent's transcript is. Same kind, same depth. */
168
+ * because the agent's transcript is. Same kind, same collection. */
172
169
  again() {
173
170
  const last = this.turns.at(-1);
174
171
  return last?.question
175
- ? this.ask(last.question, last.kind, last.depth)
172
+ ? this.ask(last.question, last.kind, last.collection)
176
173
  : Promise.resolve(false);
177
174
  }
178
175
  /** Stop the answer being written, wherever it is being written. */
@@ -186,7 +183,6 @@ export class Chat {
186
183
  const stream = this.#stream;
187
184
  if (stream && this.#running) {
188
185
  this.#running = false;
189
- this.waiting = false;
190
186
  // Before the agent has named a new conversation there is nothing to
191
187
  // address a stop to: the stream is read on, unseen, until it does.
192
188
  if (!id) {
@@ -194,21 +190,21 @@ export class Chat {
194
190
  return;
195
191
  }
196
192
  stream.controller.abort();
197
- await this.#room.stop(id).catch(() => undefined);
193
+ await this.#chat.stop(id).catch(() => undefined);
198
194
  return;
199
195
  }
200
196
  if (this.answering !== null && id) {
201
- await this.#room.stop(id).catch(() => undefined);
197
+ await this.#chat.stop(id).catch(() => undefined);
202
198
  clearTimeout(this.#poll);
203
199
  await this.#settle(id, this.#generation);
204
200
  }
205
201
  }
206
202
  /** Read today's allowance again. One that cannot be read is left as it was. */
207
203
  async refreshQuota() {
208
- if (!this.#room)
204
+ if (!this.#chat)
209
205
  return;
210
206
  try {
211
- this.quota = await this.#room.quota();
207
+ this.quota = await this.#chat.quota();
212
208
  }
213
209
  catch {
214
210
  // A counter that cannot be read costs the counter, never the ask.
@@ -217,10 +213,10 @@ export class Chat {
217
213
  /** Read this person's conversations. Read again after every answer once
218
214
  * read at all, so the list follows what they ask. */
219
215
  async list() {
220
- if (!this.#room)
216
+ if (!this.#chat)
221
217
  return false;
222
218
  try {
223
- this.conversations = await this.#room.list();
219
+ this.conversations = await this.#chat.list();
224
220
  this.listFailed = false;
225
221
  return true;
226
222
  }
@@ -231,10 +227,10 @@ export class Chat {
231
227
  }
232
228
  async rename(id, title) {
233
229
  const name = title.trim();
234
- if (!name || !this.#room)
230
+ if (!name || !this.#chat)
235
231
  return false;
236
232
  try {
237
- await this.#room.rename(id, name);
233
+ await this.#chat.rename(id, name);
238
234
  }
239
235
  catch {
240
236
  return false;
@@ -246,10 +242,10 @@ export class Chat {
246
242
  }
247
243
  /** Forget a conversation everywhere. The open one, forgotten, leaves a new one. */
248
244
  async remove(id) {
249
- if (!this.#room)
245
+ if (!this.#chat)
250
246
  return false;
251
247
  try {
252
- await this.#room.remove(id);
248
+ await this.#chat.remove(id);
253
249
  }
254
250
  catch {
255
251
  return false;
@@ -265,10 +261,10 @@ export class Chat {
265
261
  * navigated to, so one deleted a moment ago elsewhere is a false here,
266
262
  * not the reader's page replaced by an error body. */
267
263
  async download(id) {
268
- if (!this.#room)
264
+ if (!this.#chat)
269
265
  return false;
270
266
  try {
271
- const { name, body } = await this.#room.download(id);
267
+ const { name, body } = await this.#chat.download(id);
272
268
  save(body, name);
273
269
  return true;
274
270
  }
@@ -279,10 +275,10 @@ export class Chat {
279
275
  /** Mark an answer the agent holds. False when it could not be recorded. */
280
276
  async mark(turn, verdict) {
281
277
  const id = this.conversationId;
282
- if (!id || !this.#room?.mark || !this.#held.has(turn.id))
278
+ if (!id || !this.#chat?.mark || !this.#held.has(turn.id))
283
279
  return false;
284
280
  try {
285
- await this.#room.mark(id, this.#position(turn), verdict);
281
+ await this.#chat.mark(id, this.#position(turn), verdict);
286
282
  return true;
287
283
  }
288
284
  catch {
@@ -293,10 +289,10 @@ export class Chat {
293
289
  * was not recorded. */
294
290
  async share(turn, share) {
295
291
  const id = this.conversationId;
296
- if (!id || !this.#room?.share || !this.#held.has(turn.id))
292
+ if (!id || !this.#chat?.share || !this.#held.has(turn.id))
297
293
  return false;
298
294
  try {
299
- turn.shared = (await this.#room.share(id, this.#position(turn), share)) ?? undefined;
295
+ turn.shared = (await this.#chat.share(id, this.#position(turn), share)) ?? undefined;
300
296
  this.#version += 1;
301
297
  return true;
302
298
  }
@@ -321,10 +317,9 @@ export class Chat {
321
317
  this.#watching = false;
322
318
  this.#running = false;
323
319
  this.answering = null;
324
- this.waiting = false;
325
320
  }
326
- async #ask(text, files, typed, kind, depth) {
327
- const room = this.#room;
321
+ async #ask(text, files, typed, kind, collection) {
322
+ const chat = this.#chat;
328
323
  const mine = this.#generation;
329
324
  const stream = { controller: new AbortController(), stopWanted: false };
330
325
  this.#stream = stream;
@@ -335,25 +330,23 @@ export class Chat {
335
330
  blocks: [],
336
331
  outcome: null,
337
332
  citations: [],
338
- suggestions: [],
339
333
  kind,
340
- depth,
334
+ collection,
341
335
  artefact: this.#card(kind)
342
336
  });
343
337
  const live = this.#turns[this.#turns.length - 1];
344
338
  const state = foldState();
345
339
  this.#running = true;
346
- this.waiting = false;
347
340
  this.#version += 1;
348
341
  const current = () => mine === this.#generation && this.#stream === stream;
349
342
  let ending = null;
350
- for await (const event of room.ask({
343
+ for await (const event of chat.ask({
351
344
  question: text,
352
345
  files,
353
346
  resume: this.conversationId,
354
347
  signal: stream.controller.signal,
355
348
  kind,
356
- depth
349
+ collection
357
350
  })) {
358
351
  const named = event.type === 'system' && event.subtype === 'init' ? event.session_id : undefined;
359
352
  if (stream.stopWanted) {
@@ -365,29 +358,21 @@ export class Chat {
365
358
  this.#held.add(live.id);
366
359
  }
367
360
  stream.controller.abort();
368
- await room.stop(named).catch(() => undefined);
361
+ await chat.stop(named).catch(() => undefined);
369
362
  break;
370
363
  }
371
364
  if (!current())
372
365
  break;
373
- if (isQueued(event)) {
374
- this.waiting = true;
375
- this.#version += 1;
376
- continue;
377
- }
378
- // Waiting ends when the agent starts, and no other frame says so.
379
- if (named || RUN.has(event.type))
380
- this.waiting = false;
381
366
  if (named) {
382
367
  this.conversationId = named;
383
368
  this.#held.add(live.id);
384
369
  continue;
385
370
  }
386
- if (event.type === 'library_error' && event.status === 429)
371
+ if (event.type === 'agent_error' && event.status === 429)
387
372
  ending = 'refused';
388
- else if (event.type === 'library_error' && event.status === 409)
373
+ else if (event.type === 'agent_error' && event.status === 409)
389
374
  ending = 'answering';
390
- else if (event.type === 'library_error' && event.dropped && this.conversationId)
375
+ else if (event.type === 'agent_error' && event.dropped && this.conversationId)
391
376
  ending = 'dropped';
392
377
  if (ending)
393
378
  break;
@@ -418,13 +403,13 @@ export class Chat {
418
403
  if (ending === 'dropped' && id) {
419
404
  // The page lost the stream, not the turn: read where it has got to.
420
405
  try {
421
- const read = await room.read(id);
406
+ const read = await chat.read(id);
422
407
  if (current())
423
408
  this.#show(read, mine);
424
409
  }
425
410
  catch {
426
411
  if (current())
427
- fold(live, state, { type: 'library_error' });
412
+ fold(live, state, { type: 'agent_error' });
428
413
  }
429
414
  }
430
415
  this.#finish();
@@ -438,7 +423,6 @@ export class Chat {
438
423
  #finish() {
439
424
  this.#stream = null;
440
425
  this.#running = false;
441
- this.waiting = false;
442
426
  this.#version += 1;
443
427
  void this.refreshQuota();
444
428
  if (this.conversations !== null)
@@ -477,7 +461,7 @@ export class Chat {
477
461
  async #settle(id, mine) {
478
462
  clearTimeout(this.#poll);
479
463
  try {
480
- const read = await this.#room.read(id);
464
+ const read = await this.#chat.read(id);
481
465
  if (mine !== this.#generation)
482
466
  return;
483
467
  this.#show(read, mine);
@@ -508,12 +492,6 @@ export class Chat {
508
492
  this.#watching = true;
509
493
  try {
510
494
  for await (const event of job.watch(id, signal)) {
511
- if (isQueued(event)) {
512
- this.waiting = true;
513
- continue;
514
- }
515
- if (RUN.has(event.type))
516
- this.waiting = false;
517
495
  session.apply(event);
518
496
  }
519
497
  }
@@ -523,7 +501,6 @@ export class Chat {
523
501
  if (signal.aborted)
524
502
  return;
525
503
  this.#watching = false;
526
- this.waiting = false;
527
504
  await this.#idle(signal);
528
505
  }
529
506
  }
@@ -578,7 +555,7 @@ export function storedTurn(conversation, index, stored) {
578
555
  outcome: null,
579
556
  citations: stored.citations ?? [],
580
557
  kind: stored.kind ?? undefined,
581
- depth: stored.depth ?? undefined,
558
+ collection: stored.collection ?? undefined,
582
559
  shared: stored.shared ?? undefined
583
560
  };
584
561
  }
@@ -1,14 +1,14 @@
1
1
  /**
2
2
  * Where an answer came from.
3
3
  *
4
- * Two sources, one shape. The library emits a `citations` event after the
5
- * final assistant text, and that is the real one — it carries a document id,
6
- * so a chip can open the document. Until it ships everywhere, the same chips
7
- * are DERIVED from the "## Sources" block Milton already writes, which names
8
- * a title and a section but no id: those chips render and read, and cannot
4
+ * Two sources, one shape. The app emits a `citations` event before the
5
+ * persona's answer, and that is the real one — it carries a document id,
6
+ * so a chip can open the document. Without one, the same chips are DERIVED
7
+ * from the "## Sources" block the persona writes, which names a title and a
8
+ * section but no id: those chips render and read, and cannot
9
9
  * open a pane. A derived citation is marked `derived` so the UI can tell.
10
10
  */
11
- import type { LibrarianCopy } from './copy';
11
+ import type { ChatCopy } from './copy';
12
12
  export interface Citation {
13
13
  n: number;
14
14
  document_id: string;
@@ -86,7 +86,7 @@ export declare function formatVerified(iso: string): string | null;
86
86
  * A `verified_at` that is present but not a date is treated as no date, for
87
87
  * the same reason: what is shown must be what is known.
88
88
  */
89
- export declare function trustMark(citation: Citation, copy: LibrarianCopy): string | null;
89
+ export declare function trustMark(citation: Citation, copy: ChatCopy): string | null;
90
90
  /** One addressable slice of a document — the granularity a citation names. */
91
91
  export interface DocumentSection {
92
92
  anchor: string;
package/dist/citations.js CHANGED
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * Where an answer came from.
3
3
  *
4
- * Two sources, one shape. The library emits a `citations` event after the
5
- * final assistant text, and that is the real one — it carries a document id,
6
- * so a chip can open the document. Until it ships everywhere, the same chips
7
- * are DERIVED from the "## Sources" block Milton already writes, which names
8
- * a title and a section but no id: those chips render and read, and cannot
4
+ * Two sources, one shape. The app emits a `citations` event before the
5
+ * persona's answer, and that is the real one — it carries a document id,
6
+ * so a chip can open the document. Without one, the same chips are DERIVED
7
+ * from the "## Sources" block the persona writes, which names a title and a
8
+ * section but no id: those chips render and read, and cannot
9
9
  * open a pane. A derived citation is marked `derived` so the UI can tell.
10
10
  */
11
11
  /** Inline `[n]` markers, in order of first appearance.
@@ -40,7 +40,7 @@ export function splitSources(markdown) {
40
40
  if (!heading || heading.index === undefined)
41
41
  return { body: markdown, citations: [] };
42
42
  // The block runs to the NEXT heading, not to the end of the answer. Cutting
43
- // to the end loses anything Milton wrote after his sources — a closing note,
43
+ // to the end loses anything the persona wrote after its sources — a closing note,
44
44
  // a caveat — and loses it silently, which is the worst way to lose it.
45
45
  const after = markdown.slice(heading.index + heading[0].length);
46
46
  const next = after.search(/^[ \t]{0,3}#{1,6}[ \t]/m);
package/dist/client.d.ts CHANGED
@@ -8,21 +8,12 @@
8
8
  * event type needs no change on either side.
9
9
  */
10
10
  import type { Citation } from './citations';
11
- /**
12
- * How hard the persona works on one question: a reading budget and a model,
13
- * both the operator's to set per deployment, never the colleague's concern.
14
- * `quick` is always the default; `thorough` always means something — a
15
- * bigger budget on the strongest model the room allows, even where that
16
- * model is a local one — so a host never has a state where the choice is
17
- * greyed out or explained away.
18
- */
19
- export type Depth = 'quick' | 'thorough';
20
11
  /** A Claude Code message, whole. `content` is a plain string only on a user frame. */
21
12
  export interface AgentMessage {
22
13
  id?: string;
23
14
  content?: Array<Record<string, unknown>> | string;
24
15
  }
25
- /** One Claude Code stream event, or one of the library's own frames. Typed
16
+ /** One Claude Code stream event, or one of the app's own frames. Typed
26
17
  * only where we branch on it, and `type` stays open: a new event type needs
27
18
  * no change here. */
28
19
  export interface AgentEvent {
@@ -43,7 +34,7 @@ export interface AgentEvent {
43
34
  id?: string;
44
35
  };
45
36
  };
46
- /** A whole message; on the library's `queued` frame, its own sentence. */
37
+ /** A whole message. */
47
38
  message?: AgentMessage | string;
48
39
  /** A user frame the CLI echoed back as the turn that consumes it starts
49
40
  * (`--replay-user-messages`): the reader's side of a session. */
@@ -60,57 +51,37 @@ export interface AgentEvent {
60
51
  /** What a run returned under `--json-schema`, on its `result`. */
61
52
  structured_output?: unknown;
62
53
  error?: string;
63
- /** On a `library_error` this layer raised: the HTTP status of a route that
54
+ /** On an `agent_error` this layer raised: the HTTP status of a route that
64
55
  * answered and would not stream. 429 is a spent allowance. */
65
56
  status?: number;
66
- /** On a `library_error` this layer raised: a stream that opened and died
57
+ /** On an `agent_error` this layer raised: a stream that opened and died
67
58
  * before its terminal frame. The turn may well go on without the page. */
68
59
  dropped?: boolean;
69
60
  detail?: string;
70
61
  tools?: string[];
71
62
  model?: string;
72
- /**
73
- * The payload of the library's own two trailing frames, which share a key
74
- * and not a shape: `citations` carries sources, `suggestions` carries
75
- * follow-up questions as plain strings. Narrowed on `type` by whoever
76
- * reads it (`Transcript.apply`).
77
- */
78
- items?: Citation[] | string[];
63
+ /** The sources of a `citations` frame, which the app emits before the
64
+ * persona's answer. */
65
+ items?: Citation[];
79
66
  [key: string]: unknown;
80
67
  }
81
- /**
82
- * The library's word that an ask waits behind somebody else's: repeated at
83
- * least every 30 s until the agent starts, and what keeps an idle read
84
- * timeout from firing meanwhile. `message` is the library's own sentence
85
- * ("another question is being answered; waiting for it to finish"); a page
86
- * says it in its persona's words (`copy.waiting`).
87
- */
88
- export interface QueuedEvent extends AgentEvent {
89
- type: 'queued';
90
- message: string;
91
- }
92
- /** The frame is the library's `queued`, by its type alone. */
93
- export declare function isQueued(event: AgentEvent): event is QueuedEvent;
94
68
  export interface AskOptions {
95
69
  question: string;
96
70
  resume?: string | null;
97
- subtree?: string;
98
- /** Collections this question may see. Empty means all of them. */
99
- collections?: string[];
100
- /** Files Milton reads for this question. Switches the request to multipart. */
71
+ /** The one collection this question is asked of, by name. Absent asks
72
+ * across every collection the person may read. */
73
+ collection?: string;
74
+ /** Files the persona reads for this question. Switches the request to multipart. */
101
75
  files?: File[];
102
76
  /** What kind of turn this ask should become. Absent means an ordinary
103
77
  * question; a host that offers a study artefact sends its own kind
104
78
  * (`'briefing'`) and interprets what comes back accordingly — the wire
105
79
  * vocabulary is the host's own, never a fixed set here. */
106
80
  kind?: string;
107
- /** How hard the persona should work on this one. Absent asks for
108
- * whatever the route does by default, so a host that never reads
109
- * `Composer`'s `depth` sends exactly what it always has. */
110
- depth?: Depth;
111
81
  signal?: AbortSignal;
112
- /** Where the ask lands. Each app mounts its own ask route. */
113
- endpoint?: string;
82
+ /** Where the ask lands: the persona's ask route,
83
+ * `/api/agent/{persona}/ask`. */
84
+ endpoint: string;
114
85
  /** Override for the ambient `fetch`, e.g. a caller-authenticated wrapper. */
115
86
  fetch?: typeof fetch;
116
87
  }
@@ -120,7 +91,7 @@ export interface AskOptions {
120
91
  * It never throws. Every way a stream can fail to open or die half-way through
121
92
  * — the network dropping, an expired session redirecting the POST to an
122
93
  * identity provider the browser then blocks on CORS, a server closing the
123
- * connection mid-answer — comes back as one `library_error` event and a
94
+ * connection mid-answer — comes back as one `agent_error` event and a
124
95
  * finished iteration, because a host's loop is what re-enables its Send
125
96
  * button. A thrown generator leaves that loop unfinished: Pebblestone's
126
97
  * console sat with Send disabled and no message on screen, indefinitely,
@@ -145,6 +116,6 @@ export interface WatchOptions {
145
116
  * difference: a clean close with no `result` is not a failure. A run the
146
117
  * reader stopped ends exactly that way, and so does a session that is
147
118
  * waiting between runs, so only a stream that would not open, or broke,
148
- * yields `library_error`. Fold the events with `Session`.
119
+ * yields `agent_error`. Fold the events with `Session`.
149
120
  */
150
121
  export declare function watch(options: WatchOptions): AsyncGenerator<AgentEvent>;
package/dist/client.js CHANGED
@@ -7,17 +7,13 @@
7
7
  * backend sent, so the Console renders the real thing and a new Claude Code
8
8
  * event type needs no change on either side.
9
9
  */
10
- /** The frame is the library's `queued`, by its type alone. */
11
- export function isQueued(event) {
12
- return event.type === 'queued';
13
- }
14
10
  /**
15
11
  * The request body for one ask.
16
12
  *
17
13
  * JSON when there are no files, multipart when there are — one endpoint, two
18
14
  * encodings, because a `File` cannot cross a JSON body and base64 would double
19
15
  * a 20 MB attachment on the wire for nothing. The multipart field names are
20
- * the form convention (`collections[]`, `files[]`) rather than the JSON keys.
16
+ * the form convention (`files[]`) rather than the JSON key.
21
17
  *
22
18
  * `Content-Type` is deliberately absent for multipart: setting it by hand
23
19
  * omits the boundary the browser generates, and the server then reads zero
@@ -32,10 +28,8 @@ function requestInit(options, signal) {
32
28
  body: JSON.stringify({
33
29
  question: options.question,
34
30
  resume: options.resume ?? null,
35
- subtree: options.subtree ?? '',
36
- collections: options.collections ?? [],
37
- kind: options.kind,
38
- depth: options.depth
31
+ collection: options.collection,
32
+ kind: options.kind
39
33
  }),
40
34
  signal
41
35
  };
@@ -44,14 +38,12 @@ function requestInit(options, signal) {
44
38
  form.append('question', options.question);
45
39
  if (options.resume)
46
40
  form.append('resume', options.resume);
47
- for (const collection of options.collections ?? [])
48
- form.append('collections[]', collection);
41
+ if (options.collection)
42
+ form.append('collection', options.collection);
49
43
  for (const file of options.files)
50
44
  form.append('files[]', file, file.name);
51
45
  if (options.kind)
52
46
  form.append('kind', options.kind);
53
- if (options.depth)
54
- form.append('depth', options.depth);
55
47
  return { method: 'POST', credentials: 'include', body: form, signal };
56
48
  }
57
49
  /**
@@ -60,7 +52,7 @@ function requestInit(options, signal) {
60
52
  * It never throws. Every way a stream can fail to open or die half-way through
61
53
  * — the network dropping, an expired session redirecting the POST to an
62
54
  * identity provider the browser then blocks on CORS, a server closing the
63
- * connection mid-answer — comes back as one `library_error` event and a
55
+ * connection mid-answer — comes back as one `agent_error` event and a
64
56
  * finished iteration, because a host's loop is what re-enables its Send
65
57
  * button. A thrown generator leaves that loop unfinished: Pebblestone's
66
58
  * console sat with Send disabled and no message on screen, indefinitely,
@@ -71,7 +63,7 @@ function requestInit(options, signal) {
71
63
  * where the turn is rendered.
72
64
  */
73
65
  export async function* ask(options) {
74
- yield* read(() => (options.fetch ?? fetch)(options.endpoint ?? '/api/agent/ask', requestInit(options, options.signal)), options.signal, true);
66
+ yield* read(() => (options.fetch ?? fetch)(options.endpoint, requestInit(options, options.signal)), options.signal, true);
75
67
  }
76
68
  /**
77
69
  * Async-iterate a session somebody else started: every event from the first,
@@ -81,7 +73,7 @@ export async function* ask(options) {
81
73
  * difference: a clean close with no `result` is not a failure. A run the
82
74
  * reader stopped ends exactly that way, and so does a session that is
83
75
  * waiting between runs, so only a stream that would not open, or broke,
84
- * yields `library_error`. Fold the events with `Session`.
76
+ * yields `agent_error`. Fold the events with `Session`.
85
77
  */
86
78
  export async function* watch(options) {
87
79
  yield* read(() => (options.fetch ?? fetch)(options.endpoint, {
@@ -101,7 +93,7 @@ async function* read(open, signal, terminal) {
101
93
  // A reader who pressed stop asked for this one; it is not a failure.
102
94
  if (signal?.aborted)
103
95
  return;
104
- yield { type: 'library_error' };
96
+ yield { type: 'agent_error' };
105
97
  return;
106
98
  }
107
99
  // A session that expired mid-conversation is the shape this catches: the
@@ -115,11 +107,11 @@ async function* read(open, signal, terminal) {
115
107
  // check at the end of this function catches it if it really says nothing.
116
108
  const kind = (response.headers.get('content-type') ?? '').toLowerCase();
117
109
  if (!response.ok) {
118
- yield { type: 'library_error', status: response.status };
110
+ yield { type: 'agent_error', status: response.status };
119
111
  return;
120
112
  }
121
113
  if (!response.body || (kind && !kind.includes('text/event-stream'))) {
122
- yield { type: 'library_error' };
114
+ yield { type: 'agent_error' };
123
115
  return;
124
116
  }
125
117
  const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
@@ -157,7 +149,7 @@ async function* read(open, signal, terminal) {
157
149
  catch {
158
150
  if (signal?.aborted)
159
151
  return;
160
- yield { type: 'library_error', dropped: true };
152
+ yield { type: 'agent_error', dropped: true };
161
153
  return;
162
154
  }
163
155
  if (chunk.done)
@@ -171,7 +163,7 @@ async function* read(open, signal, terminal) {
171
163
  const event = parse(frame);
172
164
  if (!event)
173
165
  continue;
174
- if (event.type === 'result' || event.type === 'library_error')
166
+ if (event.type === 'result' || event.type === 'agent_error')
175
167
  ended = true;
176
168
  yield event;
177
169
  }
@@ -182,10 +174,10 @@ async function* read(open, signal, terminal) {
182
174
  // finished turn look like one that died.
183
175
  const last = parse(buffered);
184
176
  if (last) {
185
- if (last.type === 'result' || last.type === 'library_error')
177
+ if (last.type === 'result' || last.type === 'agent_error')
186
178
  ended = true;
187
179
  yield last;
188
180
  }
189
181
  if (terminal && !ended && !signal?.aborted)
190
- yield { type: 'library_error', dropped: true };
182
+ yield { type: 'agent_error', dropped: true };
191
183
  }
@@ -2,7 +2,7 @@
2
2
  A whole investigation, as ONE line the reader can open.
3
3
 
4
4
  Not a stack. Fifteen tool rows between the question and the first word of the
5
- answer is the machine room on show, and a running commentary of "Let me
5
+ answer is the machinery on show, and a running commentary of "Let me
6
6
  check…" is worse: it narrates a mechanism the persona is under instruction
7
7
  never to mention. So there is exactly one row in every state — "Working…"
8
8
  while it runs, a count of what was done once it settles — and the steps are