@the-librarian/pi-extension 1.36.0 → 1.38.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.
package/README.md
CHANGED
|
@@ -22,6 +22,12 @@ Librarian primer into the system prompt. One install, zero config files.
|
|
|
22
22
|
- **Primer injection** — the operator-editable `vault/primer.md` (≤2KB) is
|
|
23
23
|
fetched once per process from `GET <server>/primer.md` and appended to the
|
|
24
24
|
system prompt via `before_agent_start`.
|
|
25
|
+
- **The journal at session start** — the recent window of the
|
|
26
|
+
[journal](https://librarian-docs.codeministry.net/guides/journal/)
|
|
27
|
+
(`GET <server>/journal.md?max_chars=9500`) follows the primer in the system
|
|
28
|
+
prompt, fetched once per Pi session and then reused so the prompt stays the same
|
|
29
|
+
every turn. Captured turns are labelled with their project (the git repository
|
|
30
|
+
name, else the folder name) once `/healthz` shows the server accepts it.
|
|
25
31
|
- **Automatic per-turn capture** — after every completed turn Pi fires
|
|
26
32
|
`agent_end` with the turn's messages in-payload, and the adapter ships the
|
|
27
33
|
non-private prose as a delta to the server's `POST /transcript` door for the
|
|
@@ -84,6 +90,7 @@ Set two environment variables in the shell that launches `pi`:
|
|
|
84
90
|
| `LIBRARIAN_AGENT_TOKEN` | A per-agent bearer token issued by your Librarian |
|
|
85
91
|
| `LIBRARIAN_TIMEOUT_MS` | *(optional)* per-call timeout for the tool proxies, default `15000` |
|
|
86
92
|
| `LIBRARIAN_AUTO_SAVE` | *(optional)* set to `false` to disable automatic capture on this machine (default-on). Anything else (unset, `true`, …) leaves capture on. |
|
|
93
|
+
| `LIBRARIAN_JOURNAL` | *(optional)* set to `off` (or `false` / `0` / `no`) to stop loading the journal at session start on this machine. The `journal` tool stays available. |
|
|
87
94
|
| `LIBRARIAN_PI_DATA` | *(optional)* where the per-session capture state lives, default `$HOME/.librarian/pi-extension-data` |
|
|
88
95
|
|
|
89
96
|
Without both required variables the extension stays **dormant**: no tools, no
|
|
@@ -69,6 +69,8 @@ export interface CaptureInput {
|
|
|
69
69
|
env: Record<string, string | undefined>;
|
|
70
70
|
/** Explicit-end hint (the session is ending) — the settle-sweep accelerator. */
|
|
71
71
|
ended?: boolean;
|
|
72
|
+
/** Spec 078: the journal's project label, or null when the server can't take it. */
|
|
73
|
+
project?: string | null;
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
/** The orchestration outcome (mostly for tests / diagnostics). */
|
|
@@ -156,7 +158,13 @@ export async function runCapture(input: CaptureInput, deps: CaptureDeps): Promis
|
|
|
156
158
|
}
|
|
157
159
|
|
|
158
160
|
const seq = prior.seq + 1;
|
|
159
|
-
const payload = buildPayload({
|
|
161
|
+
const payload = buildPayload({
|
|
162
|
+
convId,
|
|
163
|
+
seq,
|
|
164
|
+
turns: kept,
|
|
165
|
+
ended,
|
|
166
|
+
project: input.project ?? null,
|
|
167
|
+
});
|
|
160
168
|
|
|
161
169
|
// SHIP. A non-2xx (`ok:false`) or a thrown network error → DO NOT advance seq:
|
|
162
170
|
// the delta re-ships next turn (idempotent; server/curator dedup). A 2xx →
|
|
@@ -195,7 +203,7 @@ function errMsg(error: unknown): string {
|
|
|
195
203
|
* "conv_id = getSessionId()" maps to `ctx.sessionManager.getSessionId()`. Any
|
|
196
204
|
* throw / non-string → "" so the orchestrator no-ops (NEVER cwd/$USER).
|
|
197
205
|
*/
|
|
198
|
-
function readSessionId(ctx: unknown): string {
|
|
206
|
+
export function readSessionId(ctx: unknown): string {
|
|
199
207
|
try {
|
|
200
208
|
const sm = (ctx as { sessionManager?: { getSessionId?: () => unknown } })?.sessionManager;
|
|
201
209
|
const id = sm?.getSessionId?.();
|
|
@@ -205,6 +213,16 @@ function readSessionId(ctx: unknown): string {
|
|
|
205
213
|
}
|
|
206
214
|
}
|
|
207
215
|
|
|
216
|
+
/** The session's working directory from the handler ctx (`ctx.cwd`), fail-soft. */
|
|
217
|
+
export function readCwd(ctx: unknown): string | undefined {
|
|
218
|
+
try {
|
|
219
|
+
const cwd = (ctx as { cwd?: unknown })?.cwd;
|
|
220
|
+
return typeof cwd === "string" ? cwd : undefined;
|
|
221
|
+
} catch {
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
208
226
|
/**
|
|
209
227
|
* Wire auto-capture into Pi's `agent_end` event — fired ONCE per completed user
|
|
210
228
|
* prompt with the turn's messages in-payload (the §11.2 per-turn-end hook). The
|
|
@@ -213,8 +231,13 @@ function readSessionId(ctx: unknown): string {
|
|
|
213
231
|
* getSessionId(); the delta re-ships next turn on any non-2xx.
|
|
214
232
|
*
|
|
215
233
|
* @param dataDir - the resolved plugin data dir (per-session state home).
|
|
234
|
+
* @param projectFor - the journal's project label for a cwd, or null (spec 078).
|
|
216
235
|
*/
|
|
217
|
-
export function registerCaptureHook(
|
|
236
|
+
export function registerCaptureHook(
|
|
237
|
+
pi: ExtensionAPI,
|
|
238
|
+
dataDir: string,
|
|
239
|
+
projectFor: (cwd: string | undefined) => Promise<string | null> = async () => null,
|
|
240
|
+
): void {
|
|
218
241
|
// Opportunistic, fail-soft pruning of stale sibling state (never clear-all).
|
|
219
242
|
try {
|
|
220
243
|
pruneOldState(dataDir, PRUNE_MAX_AGE_MS);
|
|
@@ -228,7 +251,8 @@ export function registerCaptureHook(pi: ExtensionAPI, dataDir: string): void {
|
|
|
228
251
|
const messages = Array.isArray((event as { messages?: unknown }).messages)
|
|
229
252
|
? (event as { messages: unknown[] }).messages
|
|
230
253
|
: [];
|
|
231
|
-
|
|
254
|
+
const project = await projectFor(readCwd(ctx));
|
|
255
|
+
await runCapture({ convId, messages, env: process.env, project }, { dataDir });
|
|
232
256
|
} catch {
|
|
233
257
|
// Defensive net — runCapture already swallows its own errors, but a
|
|
234
258
|
// top-level throw must never break the user's turn or leak a stack trace.
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
// proxies over the Librarian's stateless /mcp endpoint — see `tools.ts`;
|
|
10
10
|
// - the ≤2KB primer, fetched from `GET /primer.md` once per process and
|
|
11
11
|
// appended to the system prompt via `before_agent_start` — see `primer.ts`;
|
|
12
|
+
// - the recent window of the conversation journal, fetched once per session and
|
|
13
|
+
// appended after the primer (spec 078) — see `journal.ts`;
|
|
12
14
|
// - four user-facing slash commands (`/handoff`, `/takeover`, `/learn`,
|
|
13
15
|
// `/toggle-private`) as thin prompt templates — see `commands.ts`.
|
|
14
16
|
//
|
|
@@ -20,6 +22,7 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
20
22
|
import { registerCaptureHook, resolveDataDir } from "./capture.js";
|
|
21
23
|
import { registerCommands } from "./commands.js";
|
|
22
24
|
import { readConfig } from "./config.js";
|
|
25
|
+
import { createJournalSource, registerJournalHook } from "./journal.js";
|
|
23
26
|
import { createMcpClient } from "./mcp-client.js";
|
|
24
27
|
import { createPrimerSource, registerPrimerHook } from "./primer.js";
|
|
25
28
|
import { registerLibrarianTools } from "./tools.js";
|
|
@@ -60,6 +63,13 @@ export default function librarian(pi: ExtensionAPI): void {
|
|
|
60
63
|
// Primer → system prompt, once per turn, cached per process, fail-soft.
|
|
61
64
|
registerPrimerHook(pi, createPrimerSource({ endpoint: config.endpoint }));
|
|
62
65
|
|
|
66
|
+
// Spec 078: the recent journal window → system prompt, after the primer; fetched
|
|
67
|
+
// once per Pi session, fail-soft, `LIBRARIAN_JOURNAL=off` skips it. The same source
|
|
68
|
+
// labels captured conversations with their project once /healthz shows the server
|
|
69
|
+
// accepts the field.
|
|
70
|
+
const journal = createJournalSource({ endpoint: config.endpoint, token: config.token });
|
|
71
|
+
registerJournalHook(pi, journal);
|
|
72
|
+
|
|
63
73
|
// Auto-capture (Phase 2B, spec 2026-06-16-harness-auto-capture): wire the
|
|
64
74
|
// `agent_end` hook so each completed turn's NON-PRIVATE prose ships as a
|
|
65
75
|
// per-turn delta to POST /transcript. Default-on; the LIBRARIAN_AUTO_SAVE=false
|
|
@@ -68,7 +78,7 @@ export default function librarian(pi: ExtensionAPI): void {
|
|
|
68
78
|
// it stays inert when the server's curator.intake.enabled gate is off. The hook
|
|
69
79
|
// is registered only when the extension is configured (the dormant branch above
|
|
70
80
|
// returns before reaching here, so an unconfigured install captures nothing).
|
|
71
|
-
registerCaptureHook(pi, resolveDataDir(process.env));
|
|
81
|
+
registerCaptureHook(pi, resolveDataDir(process.env), (cwd) => journal.projectFor(cwd));
|
|
72
82
|
|
|
73
83
|
// The four user-facing slash commands (optional sugar, D9).
|
|
74
84
|
registerCommands(pi);
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// Journal injection + project label (spec 078) — the Pi port of
|
|
2
|
+
// integrations/claude/scripts/lib/journal.mjs.
|
|
3
|
+
//
|
|
4
|
+
// - The recent window of the conversation journal (`GET /journal.md`) is appended
|
|
5
|
+
// to the system prompt via `before_agent_start`, after the primer. It is fetched
|
|
6
|
+
// ONCE per Pi session and then served from memory: the prompt stays
|
|
7
|
+
// byte-identical every turn (prompt caching stays warm) and the server is asked
|
|
8
|
+
// once. Keyed by session id, not process — one Pi process can outlive several
|
|
9
|
+
// sessions, and each new session gets a fresh window.
|
|
10
|
+
// - The project label for /transcript (git repo name, else folder name), offered
|
|
11
|
+
// only after a `/healthz` probe shows the server accepts it — an older server
|
|
12
|
+
// rejects the unknown key and capture would stall. Probed once per process.
|
|
13
|
+
//
|
|
14
|
+
// Same rules as the Claude adapter: `max_chars=9500` (every harness gets the
|
|
15
|
+
// Claude-sized window), `LIBRARIAN_JOURNAL=off` skips the fetch, the token rides the
|
|
16
|
+
// Authorization header only, redirects are refused, and every failure resolves to
|
|
17
|
+
// "" / null — never a throw into Pi's turn.
|
|
18
|
+
|
|
19
|
+
import fs from "node:fs";
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
22
|
+
import { readSessionId } from "./capture.js";
|
|
23
|
+
|
|
24
|
+
/** The window size every harness asks for — Claude Code's, under its 10,000-char cap. */
|
|
25
|
+
export const JOURNAL_MAX_CHARS = 9_500;
|
|
26
|
+
|
|
27
|
+
const TRIMMED_NOTE =
|
|
28
|
+
"Older entries were left out to fit — call the `journal` tool to read further back.";
|
|
29
|
+
|
|
30
|
+
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
31
|
+
const PROBE_TIMEOUT_MS = 2_000;
|
|
32
|
+
/** Sessions remembered at once; the oldest is forgotten first. */
|
|
33
|
+
const MAX_SESSIONS = 200;
|
|
34
|
+
|
|
35
|
+
type FetchLike = typeof fetch;
|
|
36
|
+
|
|
37
|
+
/** `<origin>/journal.md?max_chars=N`, or null when the endpoint is unusable. */
|
|
38
|
+
export function journalUrl(endpoint: string, maxChars = JOURNAL_MAX_CHARS): string | null {
|
|
39
|
+
try {
|
|
40
|
+
const parsed = new URL(endpoint);
|
|
41
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
|
|
42
|
+
return `${parsed.origin}/journal.md?max_chars=${maxChars}`;
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Is the journal injection switched off locally (`LIBRARIAN_JOURNAL=off|false|0|no`)? */
|
|
49
|
+
export function isJournalInjectionOff(env: Record<string, string | undefined>): boolean {
|
|
50
|
+
const raw = env.LIBRARIAN_JOURNAL ?? "";
|
|
51
|
+
return ["off", "false", "0", "no"].includes(raw.trim().toLowerCase());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Keep the window under `limit` characters by dropping the OLDEST `## ` blocks (the
|
|
56
|
+
* window is oldest-first). A server that honours `max_chars` never needs this.
|
|
57
|
+
*/
|
|
58
|
+
export function fitJournal(text: string, limit = JOURNAL_MAX_CHARS): string {
|
|
59
|
+
if (text.length <= limit) return text;
|
|
60
|
+
const parts = text.split(/\n(?=## )/);
|
|
61
|
+
const header = parts.shift() ?? "";
|
|
62
|
+
const blocks = parts.filter((b) => !b.startsWith(TRIMMED_NOTE)).map((b) => b.trimEnd());
|
|
63
|
+
const render = (kept: string[]): string =>
|
|
64
|
+
`${header.trimEnd()}\n\n${kept.join("\n\n")}\n\n${TRIMMED_NOTE}\n`;
|
|
65
|
+
let start = 0;
|
|
66
|
+
while (start < blocks.length - 1 && render(blocks.slice(start)).length > limit) start += 1;
|
|
67
|
+
const out = render(blocks.slice(start));
|
|
68
|
+
return out.length <= limit ? out : out.slice(0, limit);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The project label: the name of the git repository containing `cwd` (the nearest
|
|
73
|
+
* ancestor holding a `.git` entry), else the name of `cwd`. Null when unusable.
|
|
74
|
+
*/
|
|
75
|
+
export function deriveProject(cwd: string | undefined): string | null {
|
|
76
|
+
if (!cwd || !path.isAbsolute(cwd)) return null;
|
|
77
|
+
try {
|
|
78
|
+
let dir = path.resolve(cwd);
|
|
79
|
+
let name: string | null = null;
|
|
80
|
+
for (;;) {
|
|
81
|
+
if (fs.existsSync(path.join(dir, ".git"))) {
|
|
82
|
+
name = path.basename(dir);
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
const parent = path.dirname(dir);
|
|
86
|
+
if (parent === dir) break;
|
|
87
|
+
dir = parent;
|
|
88
|
+
}
|
|
89
|
+
const label = (name ?? path.basename(path.resolve(cwd))).trim().slice(0, 200);
|
|
90
|
+
return label || null;
|
|
91
|
+
} catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface JournalSourceConfig {
|
|
97
|
+
endpoint: string;
|
|
98
|
+
token: string;
|
|
99
|
+
env?: Record<string, string | undefined>;
|
|
100
|
+
fetchImpl?: FetchLike;
|
|
101
|
+
timeoutMs?: number;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface JournalSource {
|
|
105
|
+
/** The window for this session ("" when there is none), fetched once per session. */
|
|
106
|
+
windowFor(sessionId: string): Promise<string>;
|
|
107
|
+
/** The project label for `cwd`, or null when the server can't take it. */
|
|
108
|
+
projectFor(cwd: string | undefined): Promise<string | null>;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function createJournalSource(config: JournalSourceConfig): JournalSource {
|
|
112
|
+
const env = config.env ?? process.env;
|
|
113
|
+
const fetchImpl = config.fetchImpl ?? fetch;
|
|
114
|
+
const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
115
|
+
const windows = new Map<string, Promise<string>>();
|
|
116
|
+
let supported: Promise<boolean> | null = null;
|
|
117
|
+
|
|
118
|
+
async function fetchWindow(): Promise<string> {
|
|
119
|
+
if (isJournalInjectionOff(env)) return "";
|
|
120
|
+
const url = journalUrl(config.endpoint);
|
|
121
|
+
if (!url) return "";
|
|
122
|
+
const controller = new AbortController();
|
|
123
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
124
|
+
try {
|
|
125
|
+
const response = await fetchImpl(url, {
|
|
126
|
+
method: "GET",
|
|
127
|
+
headers: { authorization: `Bearer ${config.token}` },
|
|
128
|
+
redirect: "error",
|
|
129
|
+
signal: controller.signal,
|
|
130
|
+
});
|
|
131
|
+
if (!response.ok) return "";
|
|
132
|
+
if (!(response.headers.get("content-type") ?? "").includes("text/markdown")) return "";
|
|
133
|
+
const text = await response.text();
|
|
134
|
+
return text.trim() ? fitJournal(text) : "";
|
|
135
|
+
} catch {
|
|
136
|
+
return "";
|
|
137
|
+
} finally {
|
|
138
|
+
clearTimeout(timer);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function probe(): Promise<boolean> {
|
|
143
|
+
let url: string;
|
|
144
|
+
try {
|
|
145
|
+
const parsed = new URL(config.endpoint);
|
|
146
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return false;
|
|
147
|
+
url = `${parsed.origin}/healthz`;
|
|
148
|
+
} catch {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
const controller = new AbortController();
|
|
152
|
+
const timer = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);
|
|
153
|
+
try {
|
|
154
|
+
// /healthz is unauthenticated, so no token is sent.
|
|
155
|
+
const response = await fetchImpl(url, { redirect: "error", signal: controller.signal });
|
|
156
|
+
if (!response.ok) return false;
|
|
157
|
+
const json: unknown = await response.json();
|
|
158
|
+
return (
|
|
159
|
+
typeof json === "object" &&
|
|
160
|
+
json !== null &&
|
|
161
|
+
!Array.isArray(json) &&
|
|
162
|
+
Object.prototype.hasOwnProperty.call(json, "journal")
|
|
163
|
+
);
|
|
164
|
+
} catch {
|
|
165
|
+
return false;
|
|
166
|
+
} finally {
|
|
167
|
+
clearTimeout(timer);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
async windowFor(sessionId) {
|
|
173
|
+
if (!sessionId) return "";
|
|
174
|
+
let pending = windows.get(sessionId);
|
|
175
|
+
if (!pending) {
|
|
176
|
+
pending = fetchWindow().catch(() => "");
|
|
177
|
+
windows.set(sessionId, pending);
|
|
178
|
+
if (windows.size > MAX_SESSIONS) {
|
|
179
|
+
const oldest = windows.keys().next().value;
|
|
180
|
+
if (oldest !== undefined) windows.delete(oldest);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return pending;
|
|
184
|
+
},
|
|
185
|
+
async projectFor(cwd) {
|
|
186
|
+
try {
|
|
187
|
+
supported ??= probe().catch(() => false);
|
|
188
|
+
return (await supported) ? deriveProject(cwd) : null;
|
|
189
|
+
} catch {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Append the session's journal window to Pi's system prompt. Registered AFTER the
|
|
198
|
+
* primer hook, so the SDK's chained `systemPrompt` returns put it after the primer.
|
|
199
|
+
* Empty window → no return value → Pi leaves the prompt untouched.
|
|
200
|
+
*/
|
|
201
|
+
export function registerJournalHook(pi: ExtensionAPI, source: JournalSource): void {
|
|
202
|
+
pi.on("before_agent_start", async (event, ctx) => {
|
|
203
|
+
try {
|
|
204
|
+
const window = await source.windowFor(readSessionId(ctx));
|
|
205
|
+
if (!window.trim()) return;
|
|
206
|
+
return { systemPrompt: `${event.systemPrompt}\n\n${window}` };
|
|
207
|
+
} catch {
|
|
208
|
+
// Defensive net — a top-level throw must never break the user's turn.
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
@@ -53,6 +53,8 @@ export interface CapturePayload {
|
|
|
53
53
|
seq: number;
|
|
54
54
|
turns: CaptureTurn[];
|
|
55
55
|
ended?: true;
|
|
56
|
+
/** Spec 078: the journal's project label — sent only to a server that accepts it. */
|
|
57
|
+
project?: string;
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
@@ -184,21 +186,25 @@ export function filterPrivateSpans(
|
|
|
184
186
|
|
|
185
187
|
/**
|
|
186
188
|
* Build the uniform, harness-agnostic delta the server contract expects:
|
|
187
|
-
* `{ conv_id, harness:"pi", seq, turns[], ended? }`. `ended` is omitted
|
|
188
|
-
* true (the server treats its mere presence as the explicit-end accelerator).
|
|
189
|
+
* `{ conv_id, harness:"pi", seq, turns[], ended?, project? }`. `ended` is omitted
|
|
190
|
+
* unless true (the server treats its mere presence as the explicit-end accelerator).
|
|
191
|
+
* `project` is set only by a caller that knows the server accepts it (spec 078).
|
|
189
192
|
*/
|
|
190
193
|
export function buildPayload({
|
|
191
194
|
convId,
|
|
192
195
|
seq,
|
|
193
196
|
turns,
|
|
194
197
|
ended = false,
|
|
198
|
+
project = null,
|
|
195
199
|
}: {
|
|
196
200
|
convId: string;
|
|
197
201
|
seq: number;
|
|
198
202
|
turns: CaptureTurn[];
|
|
199
203
|
ended?: boolean;
|
|
204
|
+
project?: string | null;
|
|
200
205
|
}): CapturePayload {
|
|
201
206
|
const payload: CapturePayload = { conv_id: convId, harness: HARNESS, seq, turns };
|
|
202
207
|
if (ended) payload.ended = true;
|
|
208
|
+
if (project) payload.project = project;
|
|
203
209
|
return payload;
|
|
204
210
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@the-librarian/pi-extension",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.38.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
8
|
-
"description": "Pi coding-agent package for The Librarian: the 8 memory/handoff/journal tools + primer injection, backed by a remote Librarian MCP server.",
|
|
8
|
+
"description": "Pi coding-agent package for The Librarian: the 8 memory/handoff/journal tools + primer and journal injection, backed by a remote Librarian MCP server.",
|
|
9
9
|
"license": "Apache-2.0",
|
|
10
10
|
"type": "module",
|
|
11
11
|
"author": {
|