@poodle64/librarian 2026.9.5 → 2026.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -1
- package/dist/client.js +1 -1
- package/dist/components/composer/composer.svelte +7 -7
- package/dist/components/tool-row/tool-row.svelte +5 -11
- package/dist/components/working/working.svelte +1 -9
- package/dist/transcript.svelte.d.ts +9 -4
- package/dist/transcript.svelte.js +23 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -135,4 +135,18 @@ pnpm run test # build + vitest
|
|
|
135
135
|
|
|
136
136
|
1. Change a component; bump `version` in `package.json` (CalVer).
|
|
137
137
|
2. `pnpm build`, which runs `svelte-package` then `publint`.
|
|
138
|
-
3. Commit, tag `librarian-v<version>`, push the tag
|
|
138
|
+
3. Commit, tag `librarian-v<version>`, push the tag.
|
|
139
|
+
4. `.github/workflows/publish.yaml` runs on that push and should publish via
|
|
140
|
+
npm OIDC trusted publishing: check it with
|
|
141
|
+
`gh run list --workflow=publish.yaml`. As of 2026.9.5 every run since
|
|
142
|
+
`ui-v2026.9.2` fails at the `npm publish` step with
|
|
143
|
+
`E404 Not Found - PUT .../@poodle64%2flibrarian` (an OIDC/trusted-publisher
|
|
144
|
+
binding issue, since `Build and test package` passes first). Until that
|
|
145
|
+
is diagnosed and fixed, publish manually from `packages/librarian/`:
|
|
146
|
+
```bash
|
|
147
|
+
printf '//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}\n' > .npmrc
|
|
148
|
+
signet exec --identity huginn-claude --broker https://portcullis.godswood.au \
|
|
149
|
+
--credential npm-publish-token --env-var NODE_AUTH_TOKEN -- npm publish
|
|
150
|
+
rm -f .npmrc
|
|
151
|
+
```
|
|
152
|
+
Confirm with `npm view @poodle64/librarian version`.
|
package/dist/client.js
CHANGED
|
@@ -23,7 +23,7 @@ export async function* ask(options) {
|
|
|
23
23
|
signal: options.signal
|
|
24
24
|
});
|
|
25
25
|
if (!response.ok || !response.body) {
|
|
26
|
-
yield { type: 'library_error', error:
|
|
26
|
+
yield { type: 'library_error', error: "Milton can't be reached right now." };
|
|
27
27
|
return;
|
|
28
28
|
}
|
|
29
29
|
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
|
|
@@ -39,16 +39,16 @@
|
|
|
39
39
|
onstop
|
|
40
40
|
}: Props = $props();
|
|
41
41
|
|
|
42
|
-
// What
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
42
|
+
// What Milton is being asked about — this document, this collection, or
|
|
43
|
+
// the whole library. Callers that have no narrower scope pass neither
|
|
44
|
+
// name, leaving "The whole library" the sole entry — and a sole entry is
|
|
45
|
+
// not a choice, so `hasChoice` below hides it rather than rendering a
|
|
46
|
+
// chip with nothing to switch to.
|
|
47
47
|
const choices = $derived(
|
|
48
48
|
[
|
|
49
49
|
documentName ? { id: 'document' as const, label: documentName } : null,
|
|
50
50
|
collectionName ? { id: 'collection' as const, label: collectionName } : null,
|
|
51
|
-
{ id: 'library' as const, label: '
|
|
51
|
+
{ id: 'library' as const, label: 'The whole library' }
|
|
52
52
|
].filter((c) => c !== null)
|
|
53
53
|
);
|
|
54
54
|
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
bind:value
|
|
80
80
|
onkeydown={keydown}
|
|
81
81
|
rows="2"
|
|
82
|
-
placeholder={running ? 'Queue another message…' : 'Ask
|
|
82
|
+
placeholder={running ? 'Queue another message…' : 'Ask Milton…'}
|
|
83
83
|
class="text-foreground placeholder:text-muted-foreground max-h-52 w-full resize-none bg-transparent px-4 pt-3 pb-2 text-base outline-none"
|
|
84
84
|
></textarea>
|
|
85
85
|
|
|
@@ -5,14 +5,13 @@
|
|
|
5
5
|
dimmed sub-line. Borders and badge pills make a run of five calls read as
|
|
6
6
|
five separate events rather than one train of thought.
|
|
7
7
|
|
|
8
|
-
The row says what
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
nothing is hidden from anyone who wants it.
|
|
8
|
+
The row says what Milton DID, in a reader's own words — never the tool's
|
|
9
|
+
name or the raw command it ran. Expanding a settled row shows what came
|
|
10
|
+
back, never what was typed.
|
|
12
11
|
-->
|
|
13
12
|
<script lang="ts">
|
|
14
13
|
import ChevronRightIcon from '@lucide/svelte/icons/chevron-right';
|
|
15
|
-
import { describe,
|
|
14
|
+
import { describe, type ToolBlock } from '../../transcript.svelte';
|
|
16
15
|
|
|
17
16
|
interface Props {
|
|
18
17
|
block: ToolBlock;
|
|
@@ -53,17 +52,12 @@
|
|
|
53
52
|
<span class="text-muted-foreground truncate">
|
|
54
53
|
<span class="text-foreground font-medium">{said.verb}</span>
|
|
55
54
|
{#if said.object}<span class="text-foreground/80">{said.object}</span>{/if}
|
|
56
|
-
{#if repeats > 1}<span class="text-muted-foreground">· {repeats}
|
|
55
|
+
{#if repeats > 1}<span class="text-muted-foreground">· {repeats} pages</span>{/if}
|
|
57
56
|
</span>
|
|
58
57
|
</button>
|
|
59
58
|
|
|
60
59
|
{#if open}
|
|
61
60
|
<div class="border-border mt-1 ml-5.5 border-l pl-3">
|
|
62
|
-
<!-- The real command, for anyone who wants it. -->
|
|
63
|
-
<p class="text-muted-foreground font-mono text-xs break-all">
|
|
64
|
-
{block.name}
|
|
65
|
-
{summarise(block)}
|
|
66
|
-
</p>
|
|
67
61
|
{#if block.result}
|
|
68
62
|
<pre
|
|
69
63
|
class="text-muted-foreground mt-1 max-h-72 overflow-auto font-mono text-xs whitespace-pre-wrap">{block.result}</pre>
|
|
@@ -18,15 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
let { label }: Props = $props();
|
|
20
20
|
|
|
21
|
-
const WORDS = [
|
|
22
|
-
'Starting',
|
|
23
|
-
'Reading the shelves',
|
|
24
|
-
'Rummaging',
|
|
25
|
-
'Cross-checking',
|
|
26
|
-
'Thumbing pages',
|
|
27
|
-
'Following a reference',
|
|
28
|
-
'Chasing it down'
|
|
29
|
-
];
|
|
21
|
+
const WORDS = ['Milton is looking', 'Milton is reading'];
|
|
30
22
|
|
|
31
23
|
let tick = $state(0);
|
|
32
24
|
let elapsed = $state(0);
|
|
@@ -34,12 +34,13 @@ export interface Outcome {
|
|
|
34
34
|
isError?: boolean;
|
|
35
35
|
error?: string;
|
|
36
36
|
}
|
|
37
|
-
/** What
|
|
37
|
+
/** What Milton DID, in a reader's own words — never the tool's name or the
|
|
38
|
+
* raw command it ran.
|
|
38
39
|
*
|
|
39
40
|
* The collapsed row is read by someone asking a question about documents, not
|
|
40
41
|
* by an engineer: `Bash ls -1 .` tells them nothing and looks like a leak from
|
|
41
|
-
* the machine room.
|
|
42
|
-
*
|
|
42
|
+
* the machine room. Milton never narrates how he searched, so a tool this
|
|
43
|
+
* does not recognise falls back to something that names no mechanism at all.
|
|
43
44
|
*/
|
|
44
45
|
export declare function describe(block: ToolBlock): {
|
|
45
46
|
verb: string;
|
|
@@ -101,5 +102,9 @@ export type Segment = ActivityGroup | TextBlock;
|
|
|
101
102
|
* steps with the same label collapse to one row carrying a count.
|
|
102
103
|
*/
|
|
103
104
|
export declare function segment(blocks: Block[]): Segment[];
|
|
104
|
-
/** One line describing a whole investigation, for the collapsed state.
|
|
105
|
+
/** One line describing a whole investigation, for the collapsed state.
|
|
106
|
+
*
|
|
107
|
+
* Counts of what Milton did are fine ("1 search · 2 documents read"); how he
|
|
108
|
+
* organises what he knows is not — no collection count, no shelf, no corpus.
|
|
109
|
+
*/
|
|
105
110
|
export declare function summariseActivity(group: ActivityGroup): string;
|
|
@@ -6,43 +6,44 @@
|
|
|
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
|
|
9
|
+
/** What Milton DID, in a reader's own words — never the tool's name or the
|
|
10
|
+
* raw command it ran.
|
|
10
11
|
*
|
|
11
12
|
* The collapsed row is read by someone asking a question about documents, not
|
|
12
13
|
* by an engineer: `Bash ls -1 .` tells them nothing and looks like a leak from
|
|
13
|
-
* the machine room.
|
|
14
|
-
*
|
|
14
|
+
* the machine room. Milton never narrates how he searched, so a tool this
|
|
15
|
+
* does not recognise falls back to something that names no mechanism at all.
|
|
15
16
|
*/
|
|
16
17
|
export function describe(block) {
|
|
17
18
|
const input = parseInput(block);
|
|
18
19
|
const command = typeof input.command === 'string' ? input.command : '';
|
|
19
20
|
if (block.name === 'Read') {
|
|
20
21
|
const path = str(input.file_path ?? input.path);
|
|
21
|
-
return { verb: '
|
|
22
|
+
return { verb: 'Read', object: documentName(path) };
|
|
22
23
|
}
|
|
23
24
|
if (block.name === 'Grep') {
|
|
24
|
-
return { verb: '
|
|
25
|
+
return { verb: 'Looked for', object: str(input.pattern) };
|
|
25
26
|
}
|
|
26
27
|
if (block.name === 'Glob') {
|
|
27
|
-
return { verb: '
|
|
28
|
+
return { verb: 'Looked for documents', object: '' };
|
|
28
29
|
}
|
|
29
30
|
if (block.name === 'Bash') {
|
|
30
31
|
if (/\bgrep\b|\brg\b/.test(command)) {
|
|
31
32
|
const quoted = command.match(/["']([^"']{2,60})["']/);
|
|
32
|
-
return { verb: '
|
|
33
|
+
return { verb: 'Looked for', object: quoted?.[1] ?? 'a phrase' };
|
|
33
34
|
}
|
|
34
35
|
if (/\bls\b/.test(command))
|
|
35
|
-
return { verb: '
|
|
36
|
+
return { verb: 'Looked through the library', object: '' };
|
|
36
37
|
if (/\bfind\b/.test(command))
|
|
37
|
-
return { verb: '
|
|
38
|
+
return { verb: 'Looked for documents', object: '' };
|
|
38
39
|
if (/\bcat\b|\bhead\b|\bsed\b/.test(command)) {
|
|
39
|
-
return { verb: '
|
|
40
|
+
return { verb: 'Read', object: documentName(lastPath(command)) };
|
|
40
41
|
}
|
|
41
42
|
if (/\bwc\b/.test(command))
|
|
42
|
-
return { verb: '
|
|
43
|
-
return { verb: '
|
|
43
|
+
return { verb: 'Checked', object: '' };
|
|
44
|
+
return { verb: 'Looked into it', object: '' };
|
|
44
45
|
}
|
|
45
|
-
return { verb:
|
|
46
|
+
return { verb: 'Looked into it', object: '' };
|
|
46
47
|
}
|
|
47
48
|
function parseInput(block) {
|
|
48
49
|
try {
|
|
@@ -64,12 +65,6 @@ function documentName(path) {
|
|
|
64
65
|
return prettify(segments.at(-2) ?? '');
|
|
65
66
|
return prettify(page.replace(/\.md$/, ''));
|
|
66
67
|
}
|
|
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
68
|
function lastPath(command) {
|
|
74
69
|
return (command
|
|
75
70
|
.split(/\s+/)
|
|
@@ -155,7 +150,7 @@ export class Transcript {
|
|
|
155
150
|
return;
|
|
156
151
|
}
|
|
157
152
|
if (event.type === 'library_error') {
|
|
158
|
-
this.outcome = { isError: true, error: event.error ?? '
|
|
153
|
+
this.outcome = { isError: true, error: event.error ?? "Milton can't be reached right now." };
|
|
159
154
|
return;
|
|
160
155
|
}
|
|
161
156
|
// A tool RESULT arrives as a user message carrying tool_result blocks.
|
|
@@ -289,9 +284,9 @@ function sameStep(a, b) {
|
|
|
289
284
|
}
|
|
290
285
|
function tally(group, block) {
|
|
291
286
|
const { verb } = describe(block);
|
|
292
|
-
if (verb === '
|
|
287
|
+
if (verb === 'Looked for')
|
|
293
288
|
group.searches += 1;
|
|
294
|
-
if (verb === '
|
|
289
|
+
if (verb === 'Read')
|
|
295
290
|
group.documents += 1;
|
|
296
291
|
const collection = collectionOf(block);
|
|
297
292
|
if (collection && !group.collections.includes(collection))
|
|
@@ -303,14 +298,16 @@ function collectionOf(block) {
|
|
|
303
298
|
const match = raw.match(/(?:^|["'\s/])([a-z0-9]+(?:-[a-z0-9]+)+)\/local\//);
|
|
304
299
|
return match?.[1] ?? '';
|
|
305
300
|
}
|
|
306
|
-
/** One line describing a whole investigation, for the collapsed state.
|
|
301
|
+
/** One line describing a whole investigation, for the collapsed state.
|
|
302
|
+
*
|
|
303
|
+
* Counts of what Milton did are fine ("1 search · 2 documents read"); how he
|
|
304
|
+
* organises what he knows is not — no collection count, no shelf, no corpus.
|
|
305
|
+
*/
|
|
307
306
|
export function summariseActivity(group) {
|
|
308
307
|
const parts = [];
|
|
309
308
|
if (group.searches)
|
|
310
309
|
parts.push(`${group.searches} search${group.searches === 1 ? '' : 'es'}`);
|
|
311
310
|
if (group.documents)
|
|
312
311
|
parts.push(`${group.documents} document${group.documents === 1 ? '' : 's'} read`);
|
|
313
|
-
|
|
314
|
-
parts.push(`${group.collections.length} collection${group.collections.length === 1 ? '' : 's'}`);
|
|
315
|
-
return parts.length ? parts.join(' · ') : 'Worked on it';
|
|
312
|
+
return parts.length ? parts.join(' · ') : 'Looked into it';
|
|
316
313
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poodle64/librarian",
|
|
3
|
-
"version": "2026.9.
|
|
3
|
+
"version": "2026.9.6",
|
|
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",
|