@poodle64/librarian 2026.9.13 → 2026.9.15

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 (28) hide show
  1. package/README.md +77 -19
  2. package/dist/client.d.ts +16 -1
  3. package/dist/client.js +55 -8
  4. package/dist/components/activity-group/activity-group.svelte +76 -8
  5. package/dist/components/agent-transcript/agent-transcript.svelte +346 -150
  6. package/dist/components/agent-transcript/agent-transcript.svelte.d.ts +7 -0
  7. package/dist/components/artefact-card/artefact-card.svelte +30 -2
  8. package/dist/components/artefact-pane/artefact-pane.svelte +141 -50
  9. package/dist/components/composer/composer.svelte +255 -38
  10. package/dist/components/composer/composer.svelte.d.ts +6 -0
  11. package/dist/components/conversation/conversation.svelte +254 -81
  12. package/dist/components/conversation/conversation.svelte.d.ts +7 -0
  13. package/dist/components/document-pane/document-pane.svelte +216 -33
  14. package/dist/components/markdown/markdown.svelte +73 -24
  15. package/dist/components/scope-statement/scope-statement.svelte +79 -12
  16. package/dist/components/source-list/index.d.ts +2 -0
  17. package/dist/components/source-list/index.js +2 -0
  18. package/dist/components/source-list/source-list.svelte +137 -0
  19. package/dist/components/source-list/source-list.svelte.d.ts +13 -0
  20. package/dist/components/thinking-row/thinking-row.svelte +69 -18
  21. package/dist/components/tool-row/tool-row.svelte +132 -26
  22. package/dist/components/working/working.svelte +51 -7
  23. package/dist/components/working/working.svelte.d.ts +3 -0
  24. package/dist/copy.d.ts +45 -10
  25. package/dist/copy.js +77 -32
  26. package/dist/transcript.svelte.d.ts +10 -0
  27. package/dist/transcript.svelte.js +4 -1
  28. package/package.json +4 -2
package/dist/copy.js CHANGED
@@ -2,48 +2,93 @@
2
2
  * Every word this package puts in front of a reader, in one place.
3
3
  *
4
4
  * Here rather than inline in each component because these sentences are the
5
- * package's opinion about how Milton talks to a colleague, and an app that
6
- * needs a different register — a different name for the librarian, a room
7
- * whose boundary is worded by its own subject-matter owner — must be able to
8
- * change one string without forking a component.
5
+ * package's opinion about how an agent talks to a colleague, and an app that
6
+ * needs a different register — a room whose boundary is worded by its own
7
+ * subject-matter owner — must be able to change one string without forking a
8
+ * component.
9
+ *
10
+ * NO STRING HERE NAMES A PERSONA. The name is an argument: a consuming app
11
+ * says who is speaking once, and every sentence the package renders is built
12
+ * from that. Milton is the library's own persona and therefore the default,
13
+ * which is a different thing from being baked in — a package that hardcodes
14
+ * one app's persona is a package that lies in every other app, and did:
15
+ * Pebblestone's Penny introduced herself as Milton for a fortnight.
9
16
  *
10
17
  * Every component takes `copy` as a PARTIAL and resolves it itself, so a host
11
- * overriding one line does not have to restate the other eleven, and a
12
- * component used directly behaves the same as one reached through
13
- * `Conversation`.
18
+ * overriding one line does not have to restate the others, and a component
19
+ * used directly behaves the same as one reached through `Conversation`.
14
20
  */
15
- export const DEFAULT_COPY = {
16
- verified: 'verified',
17
- notVerified: 'not verified',
18
- // Two sentences, both plain: what happened, and the likeliest reason. It
19
- // says nothing about collections, indexes or retrieval — a colleague
20
- // reading it has no model of any of those, and the room's own instruction
21
- // to Milton forbids him naming them either.
22
- notHeld: 'Milton answered this one without a source. He may not hold a document that covers it.',
23
- // A run can end `is_error` carrying no message at all, and a turn that
24
- // simply stops is the one thing a reader must not have to guess about.
25
- answerFailed: 'Milton stopped before he finished this one.',
26
- sources: 'Sources',
27
- suggestions: 'Ask next',
28
- scope: 'What Milton answers from',
29
- copyAnswer: 'Copy',
30
- copiedAnswer: 'Copied',
31
- askAgain: 'Ask again',
32
- jumpToLatest: 'Jump to latest',
33
- closeSource: 'Close source',
34
- documentUnavailable: "That document can't be opened right now."
35
- };
21
+ /** The library's own persona: the default wherever a consumer names none. */
22
+ export const DEFAULT_PERSONA = 'Milton';
23
+ /**
24
+ * The display name for a persona a host identified by its slug.
25
+ *
26
+ * A route parameter is `penny`, and a colleague reading the screen is talking
27
+ * to Penny. A host that already wrote the name the way it should read — any
28
+ * capital at all — is left alone, so "McTavish" and "eight" both survive.
29
+ */
30
+ export function personaName(name) {
31
+ const given = name.trim();
32
+ if (!given)
33
+ return DEFAULT_PERSONA;
34
+ if (given !== given.toLowerCase())
35
+ return given;
36
+ return given
37
+ .split(/[\s_-]+/)
38
+ .filter(Boolean)
39
+ .map((word) => word[0].toUpperCase() + word.slice(1))
40
+ .join(' ');
41
+ }
42
+ /**
43
+ * The package's words, spoken by one named persona.
44
+ *
45
+ * Deliberately free of pronouns: the persona is whoever the consuming app
46
+ * says it is, and a sentence that guesses is wrong for half of them.
47
+ */
48
+ export function copyFor(name = DEFAULT_PERSONA) {
49
+ const who = personaName(name);
50
+ return {
51
+ verified: 'verified',
52
+ notVerified: 'not verified',
53
+ // Two sentences, both plain: what happened, and the likeliest reason. It
54
+ // says nothing about collections, indexes or retrieval — a colleague
55
+ // reading it has no model of any of those, and the room's own
56
+ // instruction forbids naming them either.
57
+ notHeld: `${who} answered this one without a source. There may be no document here that covers it.`,
58
+ // A run can end `is_error` carrying no message at all, and a turn that
59
+ // simply stops is the one thing a reader must not have to guess about.
60
+ answerFailed: `${who} stopped before finishing this one.`,
61
+ unreachable: `${who} can't be reached right now.`,
62
+ sources: 'Sources',
63
+ suggestions: 'Ask next',
64
+ scope: `What ${who} answers from`,
65
+ welcome: `Ask ${who} a question.`,
66
+ askPlaceholder: `Ask ${who}…`,
67
+ answeringPlaceholder: `${who} is answering…`,
68
+ working: [`${who} is looking`, `${who} is reading`],
69
+ conversationLabel: `Conversation with ${who}`,
70
+ copyAnswer: 'Copy',
71
+ copiedAnswer: 'Copied',
72
+ askAgain: 'Ask again',
73
+ jumpToLatest: 'Jump to latest',
74
+ closeSource: 'Close source',
75
+ documentUnavailable: "That document can't be opened right now."
76
+ };
77
+ }
78
+ /** The library's own words, for a host that names no persona. */
79
+ export const DEFAULT_COPY = copyFor();
36
80
  /**
37
- * The package's words with a host's overrides on top.
81
+ * One persona's words with a host's overrides on top.
38
82
  *
39
83
  * An explicitly `undefined` key is dropped rather than spread, because
40
84
  * `{...defaults, ...{ sources: undefined }}` leaves a component rendering
41
85
  * nothing at all — and that is exactly the shape a host produces by passing a
42
86
  * value it computed from state that has not loaded yet.
43
87
  */
44
- export function resolveCopy(overrides) {
88
+ export function resolveCopy(overrides, name) {
89
+ const base = name === undefined ? DEFAULT_COPY : copyFor(name);
45
90
  if (!overrides)
46
- return DEFAULT_COPY;
91
+ return base;
47
92
  const given = Object.fromEntries(Object.entries(overrides).filter(([, value]) => value !== undefined));
48
- return { ...DEFAULT_COPY, ...given };
93
+ return { ...base, ...given };
49
94
  }
@@ -33,7 +33,12 @@ export interface Outcome {
33
33
  costUsd?: number;
34
34
  durationMs?: number;
35
35
  isError?: boolean;
36
+ /** A message the run itself gave. Words, never a code. */
36
37
  error?: string;
38
+ /** The stream never opened, or died before the run said anything. The
39
+ * sentence a reader sees is the persona's own (`copy.unreachable`), which
40
+ * only the rendering layer knows, so this carries the FACT and no words. */
41
+ unreachable?: boolean;
37
42
  }
38
43
  /**
39
44
  * The question as the READER asked it.
@@ -117,6 +122,11 @@ export type Segment = ActivityGroup | TextBlock;
117
122
  export interface Turn {
118
123
  id: string;
119
124
  question: string;
125
+ /** When the question was asked, epoch ms. A host that persists its
126
+ * conversations supplies it; a host that does not gets one stamped when
127
+ * the turn first appears on screen, which is right for a live turn and
128
+ * honestly absent for one read back out of history. */
129
+ at?: number;
120
130
  blocks: Block[];
121
131
  outcome: Outcome | null;
122
132
  citations?: Citation[];
@@ -189,8 +189,11 @@ export class Transcript {
189
189
  this.suggestions = (event.items ?? []).filter((item) => typeof item === 'string' && item.trim().length > 0);
190
190
  return;
191
191
  }
192
+ // No persona and no sentence at this layer: `client.ts` raises this for a
193
+ // stream that would not open or died half-way, and the turn renders the
194
+ // unreachable line in whichever persona's voice the host named.
192
195
  if (event.type === 'library_error') {
193
- this.outcome = { isError: true, error: event.error ?? "Milton can't be reached right now." };
196
+ this.outcome = { isError: true, unreachable: true, error: event.error };
194
197
  return;
195
198
  }
196
199
  // A tool RESULT arrives as a user message carrying tool_result blocks.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@poodle64/librarian",
3
- "version": "2026.9.13",
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.",
3
+ "version": "2026.9.15",
4
+ "description": "An agent's conversation surface as a consumable Svelte 5 package: the stream client, transcript state and self-styling chat components (transcript, composer, markdown) every household app renders instead of rebuilding, speaking as whichever persona the app names.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -53,6 +53,7 @@
53
53
  },
54
54
  "peerDependencies": {
55
55
  "svelte": "^5.54.0",
56
+ "@poodle64/design-tokens": "^2026.8.1",
56
57
  "@poodle64/ui": "^2026.9.2",
57
58
  "@lucide/svelte": "^1.7.0",
58
59
  "marked": "^18.0.11",
@@ -61,6 +62,7 @@
61
62
  },
62
63
  "devDependencies": {
63
64
  "@lucide/svelte": "^1.7.0",
65
+ "@poodle64/design-tokens": "workspace:*",
64
66
  "@poodle64/ui": "workspace:*",
65
67
  "@sveltejs/kit": "^2.63.0",
66
68
  "@sveltejs/package": "^2.5.8",