@poodle64/librarian 2026.9.5 → 2026.9.7

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 (34) hide show
  1. package/README.md +143 -44
  2. package/dist/attachments.d.ts +30 -0
  3. package/dist/attachments.js +80 -0
  4. package/dist/citations.d.ts +63 -0
  5. package/dist/citations.js +89 -0
  6. package/dist/client.d.ts +5 -0
  7. package/dist/client.js +39 -13
  8. package/dist/components/activity-group/activity-group.svelte +36 -44
  9. package/dist/components/activity-group/activity-group.svelte.d.ts +1 -1
  10. package/dist/components/agent-transcript/agent-transcript.svelte +166 -26
  11. package/dist/components/agent-transcript/agent-transcript.svelte.d.ts +8 -0
  12. package/dist/components/composer/composer.svelte +212 -59
  13. package/dist/components/composer/composer.svelte.d.ts +5 -1
  14. package/dist/components/conversation/conversation.svelte +179 -0
  15. package/dist/components/conversation/conversation.svelte.d.ts +26 -0
  16. package/dist/components/conversation/index.d.ts +2 -0
  17. package/dist/components/conversation/index.js +2 -0
  18. package/dist/components/document-pane/document-pane.svelte +177 -0
  19. package/dist/components/document-pane/document-pane.svelte.d.ts +9 -0
  20. package/dist/components/document-pane/index.d.ts +2 -0
  21. package/dist/components/document-pane/index.js +2 -0
  22. package/dist/components/markdown/index.d.ts +1 -1
  23. package/dist/components/markdown/index.js +1 -1
  24. package/dist/components/markdown/markdown.d.ts +13 -0
  25. package/dist/components/markdown/markdown.js +49 -1
  26. package/dist/components/markdown/markdown.svelte +85 -3
  27. package/dist/components/markdown/markdown.svelte.d.ts +6 -0
  28. package/dist/components/tool-row/tool-row.svelte +5 -11
  29. package/dist/components/working/working.svelte +1 -9
  30. package/dist/follow-scroll.svelte.d.ts +34 -0
  31. package/dist/follow-scroll.svelte.js +46 -0
  32. package/dist/transcript.svelte.d.ts +36 -4
  33. package/dist/transcript.svelte.js +50 -26
  34. package/package.json +16 -2
@@ -6,43 +6,62 @@
6
6
  * deltas that belong to each. An event type this does not know about is kept
7
7
  * verbatim under `other`, so nothing is silently dropped.
8
8
  */
9
- /** What the agent is DOING, in words a reader who has never seen a shell knows.
9
+ /**
10
+ * The question as the READER asked it.
11
+ *
12
+ * A host prepends a system preamble to every question before it goes to the
13
+ * library — cadmus sends `{room.preamble}\n\n{question}` — and echoing that
14
+ * back into the transcript shows a colleague the machine room. Leading
15
+ * blank-line-separated paragraphs addressed to the model are dropped.
16
+ *
17
+ * The LAST paragraph is never dropped, whatever it starts with: someone whose
18
+ * entire question is "You are wrong about the leave rule" must still see it.
19
+ */
20
+ export function readerQuestion(question) {
21
+ const parts = question.split(/\n{2,}/);
22
+ let start = 0;
23
+ while (start < parts.length - 1 && /^(you are|you're|your role|act as|system:)\b/i.test(parts[start].trim()))
24
+ start += 1;
25
+ return parts.slice(start).join('\n\n').trim();
26
+ }
27
+ /** What Milton DID, in a reader's own words — never the tool's name or the
28
+ * raw command it ran.
10
29
  *
11
30
  * The collapsed row is read by someone asking a question about documents, not
12
31
  * by an engineer: `Bash ls -1 .` tells them nothing and looks like a leak from
13
- * the machine room. The raw command is still one click away on expand, so this
14
- * hides nothing — it just stops the transcript opening with jargon.
32
+ * the machine room. Milton never narrates how he searched, so a tool this
33
+ * does not recognise falls back to something that names no mechanism at all.
15
34
  */
16
35
  export function describe(block) {
17
36
  const input = parseInput(block);
18
37
  const command = typeof input.command === 'string' ? input.command : '';
19
38
  if (block.name === 'Read') {
20
39
  const path = str(input.file_path ?? input.path);
21
- return { verb: 'Reading', object: documentName(path) };
40
+ return { verb: 'Read', object: documentName(path) };
22
41
  }
23
42
  if (block.name === 'Grep') {
24
- return { verb: 'Searching for', object: str(input.pattern) };
43
+ return { verb: 'Looked for', object: str(input.pattern) };
25
44
  }
26
45
  if (block.name === 'Glob') {
27
- return { verb: 'Looking for files', object: str(input.pattern) };
46
+ return { verb: 'Looked for documents', object: '' };
28
47
  }
29
48
  if (block.name === 'Bash') {
30
49
  if (/\bgrep\b|\brg\b/.test(command)) {
31
50
  const quoted = command.match(/["']([^"']{2,60})["']/);
32
- return { verb: 'Searching for', object: quoted?.[1] ?? 'a phrase' };
51
+ return { verb: 'Looked for', object: quoted?.[1] ?? 'a phrase' };
33
52
  }
34
53
  if (/\bls\b/.test(command))
35
- return { verb: 'Listing', object: listTarget(command) };
54
+ return { verb: 'Looked through the library', object: '' };
36
55
  if (/\bfind\b/.test(command))
37
- return { verb: 'Looking for files', object: '' };
56
+ return { verb: 'Looked for documents', object: '' };
38
57
  if (/\bcat\b|\bhead\b|\bsed\b/.test(command)) {
39
- return { verb: 'Reading', object: documentName(lastPath(command)) };
58
+ return { verb: 'Read', object: documentName(lastPath(command)) };
40
59
  }
41
60
  if (/\bwc\b/.test(command))
42
- return { verb: 'Counting', object: '' };
43
- return { verb: 'Running a command', object: '' };
61
+ return { verb: 'Checked', object: '' };
62
+ return { verb: 'Looked into it', object: '' };
44
63
  }
45
- return { verb: block.name, object: summarise(block) };
64
+ return { verb: 'Looked into it', object: '' };
46
65
  }
47
66
  function parseInput(block) {
48
67
  try {
@@ -64,12 +83,6 @@ function documentName(path) {
64
83
  return prettify(segments.at(-2) ?? '');
65
84
  return prettify(page.replace(/\.md$/, ''));
66
85
  }
67
- function listTarget(command) {
68
- const target = command.trim().split(/\s+/).at(-1) ?? '';
69
- if (!target || target === '.' || target.startsWith('-'))
70
- return 'the collections';
71
- return prettify(target.replace(/\/$/, ''));
72
- }
73
86
  function lastPath(command) {
74
87
  return (command
75
88
  .split(/\s+/)
@@ -123,6 +136,8 @@ export class Transcript {
123
136
  sessionId = $state(null);
124
137
  model = $state(null);
125
138
  outcome = $state(null);
139
+ /** Sources for the answer, from the library's own `citations` frame. */
140
+ citations = $state([]);
126
141
  other = $state([]);
127
142
  /** Content-block index is per MESSAGE, so it repeats across turns; this
128
143
  * maps the live index onto a position in the flat list. Cleared whenever a
@@ -135,6 +150,7 @@ export class Transcript {
135
150
  reset() {
136
151
  this.blocks = [];
137
152
  this.outcome = null;
153
+ this.citations = [];
138
154
  this.other = [];
139
155
  this.#open.clear();
140
156
  }
@@ -154,8 +170,14 @@ export class Transcript {
154
170
  };
155
171
  return;
156
172
  }
173
+ // Emitted after the final assistant text, so it lands on a turn that is
174
+ // otherwise complete.
175
+ if (event.type === 'citations') {
176
+ this.citations = event.items ?? [];
177
+ return;
178
+ }
157
179
  if (event.type === 'library_error') {
158
- this.outcome = { isError: true, error: event.error ?? 'the agent failed' };
180
+ this.outcome = { isError: true, error: event.error ?? "Milton can't be reached right now." };
159
181
  return;
160
182
  }
161
183
  // A tool RESULT arrives as a user message carrying tool_result blocks.
@@ -289,9 +311,9 @@ function sameStep(a, b) {
289
311
  }
290
312
  function tally(group, block) {
291
313
  const { verb } = describe(block);
292
- if (verb === 'Searching for')
314
+ if (verb === 'Looked for')
293
315
  group.searches += 1;
294
- if (verb === 'Reading')
316
+ if (verb === 'Read')
295
317
  group.documents += 1;
296
318
  const collection = collectionOf(block);
297
319
  if (collection && !group.collections.includes(collection))
@@ -303,14 +325,16 @@ function collectionOf(block) {
303
325
  const match = raw.match(/(?:^|["'\s/])([a-z0-9]+(?:-[a-z0-9]+)+)\/local\//);
304
326
  return match?.[1] ?? '';
305
327
  }
306
- /** One line describing a whole investigation, for the collapsed state. */
328
+ /** One line describing a whole investigation, for the collapsed state.
329
+ *
330
+ * Counts of what Milton did are fine ("1 search · 2 documents read"); how he
331
+ * organises what he knows is not — no collection count, no shelf, no corpus.
332
+ */
307
333
  export function summariseActivity(group) {
308
334
  const parts = [];
309
335
  if (group.searches)
310
336
  parts.push(`${group.searches} search${group.searches === 1 ? '' : 'es'}`);
311
337
  if (group.documents)
312
338
  parts.push(`${group.documents} document${group.documents === 1 ? '' : 's'} read`);
313
- if (group.collections.length)
314
- parts.push(`${group.collections.length} collection${group.collections.length === 1 ? '' : 's'}`);
315
- return parts.length ? parts.join(' · ') : 'Worked on it';
339
+ return parts.length ? parts.join(' · ') : 'Looked into it';
316
340
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poodle64/librarian",
3
- "version": "2026.9.5",
3
+ "version": "2026.9.7",
4
4
  "description": "Milton's conversation surface as a consumable Svelte 5 package: the stream client, transcript state and chat components (transcript, composer, markdown) every household app renders instead of rebuilding.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -30,6 +30,18 @@
30
30
  "types": "./dist/history.svelte.d.ts",
31
31
  "svelte": "./dist/history.svelte.js"
32
32
  },
33
+ "./citations": {
34
+ "types": "./dist/citations.d.ts",
35
+ "svelte": "./dist/citations.js"
36
+ },
37
+ "./attachments": {
38
+ "types": "./dist/attachments.d.ts",
39
+ "svelte": "./dist/attachments.js"
40
+ },
41
+ "./follow-scroll": {
42
+ "types": "./dist/follow-scroll.svelte.d.ts",
43
+ "svelte": "./dist/follow-scroll.svelte.js"
44
+ },
33
45
  "./*": {
34
46
  "types": "./dist/components/*/index.d.ts",
35
47
  "svelte": "./dist/components/*/index.js"
@@ -55,6 +67,7 @@
55
67
  "isomorphic-dompurify": "^3.23.0",
56
68
  "jsdom": "^29.1.1",
57
69
  "marked": "^18.0.11",
70
+ "playwright": "^1.62.0",
58
71
  "publint": "^0.3.15",
59
72
  "shiki": "^4.4.3",
60
73
  "svelte": "^5.56.2",
@@ -67,7 +80,8 @@
67
80
  "build": "svelte-kit sync && svelte-package && publint",
68
81
  "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
69
82
  "test": "pnpm run build && vitest run",
70
- "prepublishOnly": "pnpm run build"
83
+ "prepublishOnly": "pnpm run build",
84
+ "screenshots": "node scripts/screenshots.mjs"
71
85
  },
72
86
  "packageManager": "pnpm@10.28.0"
73
87
  }