@diegopetrucci/pi-extensions 0.1.39 → 0.1.42
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 +2 -2
- package/extensions/claude-fast/index.ts +1 -1
- package/extensions/claude-fast/package.json +1 -1
- package/extensions/git-footer/README.md +11 -9
- package/extensions/git-footer/index.ts +37 -90
- package/extensions/git-footer/package.json +2 -2
- package/extensions/librarian/README.md +15 -3
- package/extensions/librarian/index.ts +324 -21
- package/extensions/librarian/package.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,10 +9,10 @@ A collection of [pi](https://github.com/earendil-works/pi-mono) agent extensions
|
|
|
9
9
|
- [`context-cap`](./extensions/context-cap): Caps effective model context windows at 200k tokens by default so pi avoids the `dumb zone`; toggle temporarily with `/context-cap`.
|
|
10
10
|
- [`context-inspector`](./extensions/context-inspector): Adds `/context`, a local self-contained HTML dashboard that breaks down where the current session context is going, with category overview, top offenders, and drilldown search.
|
|
11
11
|
- [`dirty-repo-guard`](./extensions/dirty-repo-guard): Prompts before new sessions, session switches, or forks when the current git repo has uncommitted changes.
|
|
12
|
-
- [`git-footer`](./extensions/git-footer): Standalone
|
|
12
|
+
- [`git-footer`](./extensions/git-footer): Standalone extension that adds TLH-style git dirty counts, ahead/behind, and optional PR number to pi's built-in footer status area.
|
|
13
13
|
- [`gnosis`](./extensions/gnosis): Exposes the `gn` repo-local knowledge base CLI as an agent tool for searching and recording durable project decisions, constraints, and intent.
|
|
14
14
|
- [`inline-bash`](./extensions/inline-bash): Expands `!{command}` snippets in user prompts by running them through bash before the prompt reaches the agent.
|
|
15
|
-
- [`librarian`](./extensions/librarian): Adds a GitHub research scout with a local repo checkout cache
|
|
15
|
+
- [`librarian`](./extensions/librarian): Adds a GitHub research scout with a local repo checkout cache disabled by default under the OS user cache directory, toggleable with `/librarian-cache`, configurable subagent model/thinking defaults via `/librarian-config`, and cached repos expiring after 7 days of non-use.
|
|
16
16
|
- [`minimal-footer`](./extensions/minimal-footer): Replaces pi's built-in footer with a minimal configurable two-line layout: branch/repo on the first line, context/model on the second, optional `DUMB ZONE`, plus OpenAI Codex 5-hour and 7-day usage when available.
|
|
17
17
|
- [`notify`](./extensions/notify): Sends configurable terminal, desktop, bell, and sound notifications when pi finishes and is ready for input.
|
|
18
18
|
- [`openai-fast`](./extensions/openai-fast): Adds `/fast` to enable OpenAI Codex Fast mode for ChatGPT-auth GPT-5.4 and GPT-5.5 by injecting the priority service tier.
|
|
@@ -167,7 +167,7 @@ function syncModelBetaHeader(ctx: ExtensionContext, state: SessionState): void {
|
|
|
167
167
|
const requiredBase = ctx.modelRegistry.isUsingOAuth(model) ? CLAUDE_CODE_OAUTH_BETAS : [];
|
|
168
168
|
const next = shouldEnable
|
|
169
169
|
? Array.from(new Set([...existing, ...requiredBase, FAST_BETA]))
|
|
170
|
-
: existing.filter((beta) => beta !== FAST_BETA
|
|
170
|
+
: existing.filter((beta) => beta !== FAST_BETA);
|
|
171
171
|
|
|
172
172
|
delete headers["Anthropic-Beta"];
|
|
173
173
|
if (next.length > 0) headers["anthropic-beta"] = next.join(",");
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
# git-footer
|
|
2
2
|
|
|
3
|
-
A TLH-style git status
|
|
3
|
+
A TLH-style git status add-on for pi's built-in footer.
|
|
4
4
|
|
|
5
|
-
This package is standalone-only and is not auto-loaded by the `@diegopetrucci/pi-extensions` collection package
|
|
5
|
+
This package is standalone-only and is not auto-loaded by the `@diegopetrucci/pi-extensions` collection package.
|
|
6
6
|
|
|
7
|
-
It
|
|
7
|
+
It keeps pi's default footer intact and adds a compact git status segment through pi's extension status API:
|
|
8
8
|
|
|
9
9
|
```text
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
~/repo (main) • session-name
|
|
11
|
+
↑12k ↓3k 44.1%/200k model
|
|
12
|
+
+2 ~1 ?3 ↑1 • PR #123
|
|
12
13
|
```
|
|
13
14
|
|
|
14
15
|
Git status indicators:
|
|
@@ -20,7 +21,7 @@ Git status indicators:
|
|
|
20
21
|
- `↑N`: commits ahead of upstream
|
|
21
22
|
- `↓N`: commits behind upstream
|
|
22
23
|
|
|
23
|
-
The extension polls git status in the background
|
|
24
|
+
The extension polls git status in the background and caches the latest snapshot. It also performs a best-effort `gh pr view` lookup for the current branch; if `gh` is unavailable or the branch has no PR, the PR segment is omitted.
|
|
24
25
|
|
|
25
26
|
## Install
|
|
26
27
|
|
|
@@ -36,6 +37,7 @@ Then reload pi:
|
|
|
36
37
|
|
|
37
38
|
## Notes
|
|
38
39
|
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
40
|
+
- Does not replace pi's built-in footer.
|
|
41
|
+
- Uses `ctx.ui.setStatus()`, so pi renders the git summary with other extension statuses.
|
|
42
|
+
- The current pi extension API does not support literally appending text inside the built-in footer's first `cwd (branch)` line without replacing the footer.
|
|
43
|
+
- Git and GitHub CLI lookups run on a short background interval with timeouts.
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { basename } from "node:path";
|
|
3
2
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
5
3
|
|
|
6
4
|
type GitStatusSnapshot = {
|
|
7
5
|
branch?: string;
|
|
@@ -48,12 +46,12 @@ type GitFooterCacheOptions = {
|
|
|
48
46
|
gitTimeoutMs?: number;
|
|
49
47
|
ghTimeoutMs?: number;
|
|
50
48
|
onChange?: () => void;
|
|
51
|
-
onBranchChangeSource?: (callback: () => void) => () => void;
|
|
52
49
|
};
|
|
53
50
|
|
|
54
51
|
const BRANCH_HEAD_PREFIX = "# branch.head ";
|
|
55
52
|
const BRANCH_AB_PREFIX = "# branch.ab ";
|
|
56
|
-
const
|
|
53
|
+
const STATUS_KEY = "git-footer";
|
|
54
|
+
const STATUS_SEPARATOR = " • ";
|
|
57
55
|
const DEFAULT_REFRESH_INTERVAL_MS = 8_000;
|
|
58
56
|
const DEFAULT_GIT_TIMEOUT_MS = 1_500;
|
|
59
57
|
const DEFAULT_GH_TIMEOUT_MS = 3_000;
|
|
@@ -157,21 +155,15 @@ function formatPullRequestFooterSegment(pullRequest: PullRequestSnapshot | undef
|
|
|
157
155
|
return undefined;
|
|
158
156
|
}
|
|
159
157
|
|
|
160
|
-
function
|
|
158
|
+
function formatGitFooterStatus(
|
|
161
159
|
status: GitStatusSnapshot | undefined,
|
|
162
160
|
pullRequest: PullRequestSnapshot | undefined,
|
|
163
|
-
): string
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if (statusSegment) segments.push(statusSegment);
|
|
170
|
-
|
|
171
|
-
const pullRequestSegment = formatPullRequestFooterSegment(pullRequest);
|
|
172
|
-
if (pullRequestSegment) segments.push(pullRequestSegment);
|
|
173
|
-
|
|
174
|
-
return segments;
|
|
161
|
+
): string | undefined {
|
|
162
|
+
const parts = [
|
|
163
|
+
formatGitStatusFooterSegment(status),
|
|
164
|
+
formatPullRequestFooterSegment(pullRequest),
|
|
165
|
+
].filter((part): part is string => !!part);
|
|
166
|
+
return parts.length > 0 ? parts.join(STATUS_SEPARATOR) : undefined;
|
|
175
167
|
}
|
|
176
168
|
|
|
177
169
|
function parsePullRequestJson(stdout: string): PullRequestSnapshot | undefined {
|
|
@@ -307,7 +299,6 @@ class GitFooterCache {
|
|
|
307
299
|
private readonly inflightControllers = new Set<AbortController>();
|
|
308
300
|
private disposed = false;
|
|
309
301
|
private refreshInFlight: Promise<void> | undefined;
|
|
310
|
-
private branchChangeUnsubscribe: (() => void) | undefined;
|
|
311
302
|
private statusSnapshot: GitStatusSnapshot | undefined;
|
|
312
303
|
private pullRequestSnapshot: PullRequestSnapshot | undefined;
|
|
313
304
|
private lastSeenBranch: string | undefined;
|
|
@@ -325,12 +316,6 @@ class GitFooterCache {
|
|
|
325
316
|
void this.refresh();
|
|
326
317
|
}, this.refreshIntervalMs);
|
|
327
318
|
void this.refresh();
|
|
328
|
-
|
|
329
|
-
if (options.onBranchChangeSource) {
|
|
330
|
-
this.branchChangeUnsubscribe = options.onBranchChangeSource(() => {
|
|
331
|
-
void this.refresh();
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
319
|
}
|
|
335
320
|
|
|
336
321
|
getStatusSnapshot(): GitStatusSnapshot | undefined {
|
|
@@ -453,81 +438,43 @@ class GitFooterCache {
|
|
|
453
438
|
this.clock.clearInterval(this.intervalHandle);
|
|
454
439
|
this.intervalHandle = undefined;
|
|
455
440
|
}
|
|
456
|
-
if (this.branchChangeUnsubscribe) {
|
|
457
|
-
try {
|
|
458
|
-
this.branchChangeUnsubscribe();
|
|
459
|
-
} catch {
|
|
460
|
-
// Ignore misbehaving notifier.
|
|
461
|
-
}
|
|
462
|
-
this.branchChangeUnsubscribe = undefined;
|
|
463
|
-
}
|
|
464
441
|
for (const controller of this.inflightControllers) controller.abort();
|
|
465
442
|
this.inflightControllers.clear();
|
|
466
443
|
}
|
|
467
444
|
}
|
|
468
445
|
|
|
469
|
-
function
|
|
470
|
-
|
|
471
|
-
sessionName?: string | null;
|
|
472
|
-
status?: GitStatusSnapshot;
|
|
473
|
-
pullRequest?: PullRequestSnapshot;
|
|
474
|
-
}): string {
|
|
475
|
-
const segments = [input.cwd];
|
|
476
|
-
if (input.status !== undefined) {
|
|
477
|
-
segments.push(...formatGitFooterSegments(input.status, input.pullRequest));
|
|
478
|
-
}
|
|
479
|
-
if (input.sessionName) segments.push(input.sessionName);
|
|
480
|
-
return segments.join(FOOTER_SEPARATOR);
|
|
481
|
-
}
|
|
446
|
+
export default function (pi: ExtensionAPI) {
|
|
447
|
+
let cache: GitFooterCache | undefined;
|
|
482
448
|
|
|
483
|
-
function
|
|
484
|
-
|
|
485
|
-
|
|
449
|
+
function disposeCache(): void {
|
|
450
|
+
cache?.dispose();
|
|
451
|
+
cache = undefined;
|
|
452
|
+
}
|
|
486
453
|
|
|
487
|
-
export default function (pi: ExtensionAPI) {
|
|
488
454
|
pi.on("session_start", (_event, ctx) => {
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
455
|
+
disposeCache();
|
|
456
|
+
|
|
457
|
+
const updateStatus = () => {
|
|
458
|
+
const text = formatGitFooterStatus(
|
|
459
|
+
cache?.getStatusSnapshot(),
|
|
460
|
+
cache?.getPullRequestSnapshot(),
|
|
461
|
+
);
|
|
462
|
+
ctx.ui.setStatus(STATUS_KEY, text ? ctx.ui.theme.fg("dim", text) : undefined);
|
|
463
|
+
};
|
|
497
464
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
cache = undefined;
|
|
502
|
-
},
|
|
503
|
-
invalidate() {},
|
|
504
|
-
render(width: number): string[] {
|
|
505
|
-
const status = cache?.getStatusSnapshot();
|
|
506
|
-
const pullRequest = cache?.getPullRequestSnapshot();
|
|
507
|
-
const firstLine = composeFooterFirstLine({
|
|
508
|
-
cwd: basename(ctx.cwd),
|
|
509
|
-
status,
|
|
510
|
-
pullRequest,
|
|
511
|
-
sessionName: pi.getSessionName(),
|
|
512
|
-
});
|
|
513
|
-
|
|
514
|
-
const usage = ctx.getContextUsage();
|
|
515
|
-
const context = usage?.percent == null ? "ctx ?" : `ctx ${usage.percent.toFixed(1)}%`;
|
|
516
|
-
const thinking = pi.getThinkingLevel();
|
|
517
|
-
const model = ctx.model?.id ?? "no-model";
|
|
518
|
-
const modelText = thinking === "off" ? model : `${model} ${thinking}`;
|
|
519
|
-
const statuses = [...footerData.getExtensionStatuses().values()]
|
|
520
|
-
.map(sanitizeFooterSegment)
|
|
521
|
-
.filter(Boolean);
|
|
522
|
-
const secondLine = [theme.fg("dim", context), theme.fg("dim", modelText), ...statuses]
|
|
523
|
-
.join(theme.fg("dim", FOOTER_SEPARATOR));
|
|
524
|
-
|
|
525
|
-
return [
|
|
526
|
-
truncateToWidth(theme.fg("dim", firstLine), width),
|
|
527
|
-
truncateToWidth(secondLine, width),
|
|
528
|
-
];
|
|
529
|
-
},
|
|
530
|
-
};
|
|
465
|
+
cache = new GitFooterCache({
|
|
466
|
+
cwd: () => ctx.cwd,
|
|
467
|
+
onChange: updateStatus,
|
|
531
468
|
});
|
|
469
|
+
updateStatus();
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
pi.on("turn_end", () => {
|
|
473
|
+
void cache?.refresh();
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
pi.on("session_shutdown", (_event, ctx) => {
|
|
477
|
+
disposeCache();
|
|
478
|
+
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
532
479
|
});
|
|
533
480
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# librarian
|
|
2
2
|
|
|
3
|
-
A pi GitHub research scout inspired by `pi-librarian`, with a local checkout cache
|
|
3
|
+
A pi GitHub research scout inspired by `pi-librarian`, with a local checkout cache disabled by default.
|
|
4
4
|
|
|
5
5
|
When the `librarian` tool runs, it can cache/reuse repository checkouts locally. Use `/librarian-cache off` to force GitHub API/search and temporary fetched files only, or `/librarian-cache on` to re-enable cached local checkouts.
|
|
6
6
|
|
|
7
|
+
The internal librarian subagent uses a lightweight auto-selected model by default and requests `medium` thinking. Use `/librarian-config` to set a persistent internal model or thinking-level preference.
|
|
8
|
+
|
|
7
9
|
## Install
|
|
8
10
|
|
|
9
11
|
### Standalone npm package
|
|
@@ -35,8 +37,9 @@ Then reload pi:
|
|
|
35
37
|
- Tool name: `librarian`
|
|
36
38
|
- Uses a restricted subagent with `bash` and `read`
|
|
37
39
|
- Uses `gh` for GitHub search/API access
|
|
38
|
-
- Uses cached local checkouts
|
|
40
|
+
- Uses cached local checkouts only when enabled
|
|
39
41
|
- Toggle cache behavior for future calls with `/librarian-cache on | off | toggle | status`
|
|
42
|
+
- Configure internal subagent defaults with `/librarian-config status | model <provider/model|auto|current> | thinking <off|minimal|low|medium|high|xhigh|auto> | clear [all|model|thinking]`
|
|
40
43
|
- Cached repos are removed lazily after 7 days without use
|
|
41
44
|
|
|
42
45
|
## Commands
|
|
@@ -48,7 +51,16 @@ Then reload pi:
|
|
|
48
51
|
/librarian-cache toggle
|
|
49
52
|
```
|
|
50
53
|
|
|
51
|
-
|
|
54
|
+
```text
|
|
55
|
+
/librarian-config status
|
|
56
|
+
/librarian-config model auto
|
|
57
|
+
/librarian-config model current
|
|
58
|
+
/librarian-config model anthropic/claude-haiku-4-5:medium
|
|
59
|
+
/librarian-config thinking medium
|
|
60
|
+
/librarian-config clear model
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The commands work in interactive mode, RPC mode, and print/JSON mode. They write global preferences to `~/.pi/agent/extensions/librarian.json`, so separate non-UI invocations use the same settings. In non-UI modes, command feedback is written to stderr so stdout remains usable for normal output or JSON events.
|
|
52
64
|
|
|
53
65
|
## Cache location
|
|
54
66
|
|
|
@@ -30,8 +30,11 @@ const CACHE_CONFIG_FILE = "librarian.json";
|
|
|
30
30
|
type LibrarianStatus = "running" | "done" | "error" | "aborted";
|
|
31
31
|
|
|
32
32
|
type CacheMode = "disabled" | "enabled";
|
|
33
|
+
type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
33
34
|
|
|
34
|
-
const DEFAULT_CACHE_MODE: CacheMode = "
|
|
35
|
+
const DEFAULT_CACHE_MODE: CacheMode = "disabled";
|
|
36
|
+
const DEFAULT_THINKING_LEVEL: ThinkingLevel = "medium";
|
|
37
|
+
const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"] as const;
|
|
35
38
|
|
|
36
39
|
type ToolCall = {
|
|
37
40
|
id: string;
|
|
@@ -51,10 +54,20 @@ type CacheDetails = {
|
|
|
51
54
|
decisionReason: string;
|
|
52
55
|
};
|
|
53
56
|
|
|
57
|
+
type LibrarianModelDetails = {
|
|
58
|
+
modelRef: string;
|
|
59
|
+
modelId: string;
|
|
60
|
+
provider: string;
|
|
61
|
+
thinkingLevel: ThinkingLevel;
|
|
62
|
+
autoSelected: boolean;
|
|
63
|
+
selectionReason: string;
|
|
64
|
+
};
|
|
65
|
+
|
|
54
66
|
type LibrarianDetails = {
|
|
55
67
|
status: LibrarianStatus;
|
|
56
68
|
workspace: string;
|
|
57
69
|
cache: CacheDetails;
|
|
70
|
+
model: LibrarianModelDetails;
|
|
58
71
|
turns: number;
|
|
59
72
|
toolCalls: ToolCall[];
|
|
60
73
|
startedAt: number;
|
|
@@ -91,6 +104,16 @@ const LibrarianParams = Type.Object({
|
|
|
91
104
|
default: DEFAULT_MAX_SEARCH_RESULTS,
|
|
92
105
|
}),
|
|
93
106
|
),
|
|
107
|
+
model: Type.Optional(
|
|
108
|
+
Type.String({
|
|
109
|
+
description: "Optional model override for the internal librarian subagent. Use provider/model, auto, or current.",
|
|
110
|
+
}),
|
|
111
|
+
),
|
|
112
|
+
thinkingLevel: Type.Optional(
|
|
113
|
+
Type.String({
|
|
114
|
+
description: `Optional thinking override for the internal librarian subagent (${THINKING_LEVELS.join(" | ")}). Default ${DEFAULT_THINKING_LEVEL}.`,
|
|
115
|
+
}),
|
|
116
|
+
),
|
|
94
117
|
});
|
|
95
118
|
|
|
96
119
|
function asStringArray(value: unknown, maxItems = 30): string[] {
|
|
@@ -259,6 +282,12 @@ function getCacheConfigPath(): string {
|
|
|
259
282
|
return path.join(getAgentDir(), "extensions", CACHE_CONFIG_FILE);
|
|
260
283
|
}
|
|
261
284
|
|
|
285
|
+
type LibrarianPreferences = {
|
|
286
|
+
cacheMode: CacheMode;
|
|
287
|
+
model?: string;
|
|
288
|
+
thinkingLevel: ThinkingLevel;
|
|
289
|
+
};
|
|
290
|
+
|
|
262
291
|
function parseCacheMode(value: unknown): CacheMode | undefined {
|
|
263
292
|
if (typeof value === "string") {
|
|
264
293
|
const normalized = value.trim().toLowerCase();
|
|
@@ -270,31 +299,69 @@ function parseCacheMode(value: unknown): CacheMode | undefined {
|
|
|
270
299
|
return undefined;
|
|
271
300
|
}
|
|
272
301
|
|
|
273
|
-
|
|
302
|
+
function parseThinkingLevel(value: unknown): ThinkingLevel | undefined {
|
|
303
|
+
if (typeof value !== "string") return undefined;
|
|
304
|
+
const normalized = value.trim().toLowerCase();
|
|
305
|
+
return (THINKING_LEVELS as readonly string[]).includes(normalized) ? (normalized as ThinkingLevel) : undefined;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function normalizeModelPreference(value: unknown): string | undefined {
|
|
309
|
+
if (typeof value !== "string") return undefined;
|
|
310
|
+
const trimmed = value.trim();
|
|
311
|
+
if (!trimmed || trimmed.toLowerCase() === "auto") return undefined;
|
|
312
|
+
return trimmed;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function parseModelPreference(value: unknown): { model?: string; thinkingLevel?: ThinkingLevel } {
|
|
316
|
+
const model = normalizeModelPreference(value);
|
|
317
|
+
if (!model) return {};
|
|
318
|
+
const match = model.match(/^(.*):(off|minimal|low|medium|high|xhigh)$/i);
|
|
319
|
+
if (!match?.[1]) return { model };
|
|
320
|
+
return { model: match[1], thinkingLevel: parseThinkingLevel(match[2]) };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async function readLibrarianPreferences(): Promise<LibrarianPreferences> {
|
|
274
324
|
try {
|
|
275
325
|
const raw = await fs.readFile(getCacheConfigPath(), "utf8");
|
|
276
326
|
const parsed = JSON.parse(raw) as {
|
|
277
327
|
cacheMode?: unknown;
|
|
278
328
|
cacheEnabled?: unknown;
|
|
279
329
|
cache?: { mode?: unknown; enabled?: unknown };
|
|
330
|
+
model?: unknown;
|
|
331
|
+
defaultModel?: unknown;
|
|
332
|
+
thinkingLevel?: unknown;
|
|
333
|
+
defaultThinkingLevel?: unknown;
|
|
334
|
+
};
|
|
335
|
+
return {
|
|
336
|
+
cacheMode:
|
|
337
|
+
parseCacheMode(parsed.cacheMode) ??
|
|
338
|
+
parseCacheMode(parsed.cache?.mode) ??
|
|
339
|
+
parseCacheMode(parsed.cacheEnabled) ??
|
|
340
|
+
parseCacheMode(parsed.cache?.enabled) ??
|
|
341
|
+
DEFAULT_CACHE_MODE,
|
|
342
|
+
model: parseModelPreference(parsed.model).model ?? parseModelPreference(parsed.defaultModel).model,
|
|
343
|
+
thinkingLevel:
|
|
344
|
+
parseThinkingLevel(parsed.thinkingLevel) ??
|
|
345
|
+
parseThinkingLevel(parsed.defaultThinkingLevel) ??
|
|
346
|
+
parseModelPreference(parsed.model).thinkingLevel ??
|
|
347
|
+
parseModelPreference(parsed.defaultModel).thinkingLevel ??
|
|
348
|
+
DEFAULT_THINKING_LEVEL,
|
|
280
349
|
};
|
|
281
|
-
return (
|
|
282
|
-
parseCacheMode(parsed.cacheMode) ??
|
|
283
|
-
parseCacheMode(parsed.cache?.mode) ??
|
|
284
|
-
parseCacheMode(parsed.cacheEnabled) ??
|
|
285
|
-
parseCacheMode(parsed.cache?.enabled) ??
|
|
286
|
-
DEFAULT_CACHE_MODE
|
|
287
|
-
);
|
|
288
350
|
} catch (error) {
|
|
289
|
-
return
|
|
351
|
+
return {
|
|
352
|
+
cacheMode: (error as NodeJS.ErrnoException).code === "ENOENT" ? DEFAULT_CACHE_MODE : "disabled",
|
|
353
|
+
thinkingLevel: DEFAULT_THINKING_LEVEL,
|
|
354
|
+
};
|
|
290
355
|
}
|
|
291
356
|
}
|
|
292
357
|
|
|
293
|
-
async function
|
|
358
|
+
async function writeLibrarianPreferences(preferences: LibrarianPreferences): Promise<void> {
|
|
294
359
|
const configPath = getCacheConfigPath();
|
|
295
360
|
const config = {
|
|
296
|
-
cacheMode:
|
|
297
|
-
cacheEnabled:
|
|
361
|
+
cacheMode: preferences.cacheMode,
|
|
362
|
+
cacheEnabled: preferences.cacheMode === "enabled",
|
|
363
|
+
...(preferences.model ? { model: preferences.model } : {}),
|
|
364
|
+
thinkingLevel: preferences.thinkingLevel,
|
|
298
365
|
updatedAt: new Date().toISOString(),
|
|
299
366
|
};
|
|
300
367
|
|
|
@@ -302,6 +369,11 @@ async function writeCachePreference(preference: CacheMode): Promise<void> {
|
|
|
302
369
|
await fs.writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
303
370
|
}
|
|
304
371
|
|
|
372
|
+
async function writeCachePreference(preference: CacheMode): Promise<void> {
|
|
373
|
+
const preferences = await readLibrarianPreferences();
|
|
374
|
+
await writeLibrarianPreferences({ ...preferences, cacheMode: preference });
|
|
375
|
+
}
|
|
376
|
+
|
|
305
377
|
function resolveCacheDecision(preference: CacheMode): { enabled: boolean; reason: string } {
|
|
306
378
|
if (preference === "enabled") {
|
|
307
379
|
return { enabled: true, reason: "cache preference enabled; using cached local checkouts" };
|
|
@@ -314,6 +386,10 @@ function formatCachePreference(preference: CacheMode): string {
|
|
|
314
386
|
return preference === "enabled" ? "on" : "off";
|
|
315
387
|
}
|
|
316
388
|
|
|
389
|
+
function formatLibrarianPreferences(preferences: LibrarianPreferences): string {
|
|
390
|
+
return `Librarian defaults: cache=${formatCachePreference(preferences.cacheMode)}, model=${preferences.model ?? "auto"}, thinkingLevel=${preferences.thinkingLevel}. Config: ${getCacheConfigPath()}`;
|
|
391
|
+
}
|
|
392
|
+
|
|
317
393
|
function notifyCommand(ctx: ExtensionContext, message: string, type: "info" | "warning" | "error" = "info"): void {
|
|
318
394
|
if (ctx.hasUI) {
|
|
319
395
|
ctx.ui.notify(message, type);
|
|
@@ -323,6 +399,115 @@ function notifyCommand(ctx: ExtensionContext, message: string, type: "info" | "w
|
|
|
323
399
|
console.error(message);
|
|
324
400
|
}
|
|
325
401
|
|
|
402
|
+
function modelRef(model: any): string {
|
|
403
|
+
return `${model.provider}/${model.id}`;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function modelCostScore(model: any): number {
|
|
407
|
+
const cost = model.cost ?? {};
|
|
408
|
+
const input = typeof cost.input === "number" ? cost.input : 0;
|
|
409
|
+
const output = typeof cost.output === "number" ? cost.output : 0;
|
|
410
|
+
return input + output;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function rankLibrarianModel(model: any): number {
|
|
414
|
+
const text = `${model.id ?? ""} ${model.name ?? ""}`.toLowerCase();
|
|
415
|
+
let score = modelCostScore(model) * 1_000_000;
|
|
416
|
+
if (model.reasoning) score += 50;
|
|
417
|
+
if (/\b(?:mini|nano|haiku|flash|lite|small|fast|instant)\b/.test(text)) score -= 10;
|
|
418
|
+
if (/\b(?:opus|pro|ultra|max)\b/.test(text)) score += 1_000;
|
|
419
|
+
if ((model.contextWindow ?? 0) < 32_000) score += 100;
|
|
420
|
+
return score;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
async function findAvailableModel(
|
|
424
|
+
ctx: { model?: any; modelRegistry: { getAvailable(): any[] | Promise<any[]> } },
|
|
425
|
+
modelPreference: string,
|
|
426
|
+
): Promise<any | undefined> {
|
|
427
|
+
const available = await ctx.modelRegistry.getAvailable();
|
|
428
|
+
const trimmed = modelPreference.trim();
|
|
429
|
+
const provider = trimmed.includes("/") ? trimmed.split("/")[0].toLowerCase() : ctx.model?.provider?.toLowerCase();
|
|
430
|
+
const id = trimmed.includes("/") ? trimmed.split("/").slice(1).join("/").toLowerCase() : trimmed.toLowerCase();
|
|
431
|
+
|
|
432
|
+
const exact = available.find(
|
|
433
|
+
(model) => model.id.toLowerCase() === id && (!provider || model.provider.toLowerCase() === provider),
|
|
434
|
+
);
|
|
435
|
+
if (exact) return exact;
|
|
436
|
+
|
|
437
|
+
const partial = available.find(
|
|
438
|
+
(model) => model.id.toLowerCase().includes(id) && (!provider || model.provider.toLowerCase() === provider),
|
|
439
|
+
);
|
|
440
|
+
if (partial) return partial;
|
|
441
|
+
|
|
442
|
+
if (!provider) {
|
|
443
|
+
const uniqueById = available.filter((model) => model.id.toLowerCase() === id);
|
|
444
|
+
if (uniqueById.length === 1) return uniqueById[0];
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return undefined;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
async function selectLibrarianModel(
|
|
451
|
+
ctx: { model?: any; modelRegistry: { getAvailable(): any[] | Promise<any[]> } },
|
|
452
|
+
modelPreference: string | undefined,
|
|
453
|
+
thinkingLevel: ThinkingLevel,
|
|
454
|
+
): Promise<{ model: any; details: LibrarianModelDetails }> {
|
|
455
|
+
const normalized = modelPreference?.trim();
|
|
456
|
+
if (normalized?.toLowerCase() === "current") {
|
|
457
|
+
if (!ctx.model) throw new Error("Librarian model=current needs an active pi model, but ctx.model is unavailable.");
|
|
458
|
+
return {
|
|
459
|
+
model: ctx.model,
|
|
460
|
+
details: {
|
|
461
|
+
modelRef: modelRef(ctx.model),
|
|
462
|
+
provider: ctx.model.provider,
|
|
463
|
+
modelId: ctx.model.id,
|
|
464
|
+
thinkingLevel,
|
|
465
|
+
autoSelected: false,
|
|
466
|
+
selectionReason: "Using the caller's current model because librarian model=current is configured.",
|
|
467
|
+
},
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (normalized) {
|
|
472
|
+
const matched = await findAvailableModel(ctx, normalized);
|
|
473
|
+
if (matched) {
|
|
474
|
+
return {
|
|
475
|
+
model: matched,
|
|
476
|
+
details: {
|
|
477
|
+
modelRef: modelRef(matched),
|
|
478
|
+
provider: matched.provider,
|
|
479
|
+
modelId: matched.id,
|
|
480
|
+
thinkingLevel,
|
|
481
|
+
autoSelected: false,
|
|
482
|
+
selectionReason: "Using the configured librarian model.",
|
|
483
|
+
},
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const available = await ctx.modelRegistry.getAvailable();
|
|
489
|
+
const currentProvider = ctx.model?.provider;
|
|
490
|
+
const sameProvider = currentProvider ? available.filter((model) => model.provider === currentProvider) : [];
|
|
491
|
+
const candidates = sameProvider.length > 0 ? sameProvider : available;
|
|
492
|
+
const winner = [...candidates].sort((a, b) => rankLibrarianModel(a) - rankLibrarianModel(b))[0] ?? ctx.model;
|
|
493
|
+
if (!winner) {
|
|
494
|
+
throw new Error("No authenticated models are available for Librarian. Log in or configure an API key first.");
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const fallbackText = normalized ? ` Configured model ${normalized} was unavailable, so Librarian fell back to auto-selection.` : "";
|
|
498
|
+
return {
|
|
499
|
+
model: winner,
|
|
500
|
+
details: {
|
|
501
|
+
modelRef: modelRef(winner),
|
|
502
|
+
provider: winner.provider,
|
|
503
|
+
modelId: winner.id,
|
|
504
|
+
thinkingLevel,
|
|
505
|
+
autoSelected: true,
|
|
506
|
+
selectionReason: `Selected the cheapest available model${sameProvider.length > 0 ? " on the current provider" : ""}.${fallbackText}`,
|
|
507
|
+
},
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
|
|
326
511
|
function resolveToolPath(cwd: string, rawPath: string): string {
|
|
327
512
|
const normalized = rawPath.startsWith("@") ? rawPath.slice(1) : rawPath;
|
|
328
513
|
return path.isAbsolute(normalized) ? path.resolve(normalized) : path.resolve(cwd, normalized);
|
|
@@ -489,9 +674,115 @@ function isAbortLikeError(error: unknown): boolean {
|
|
|
489
674
|
|
|
490
675
|
export default function librarianExtension(pi: ExtensionAPI) {
|
|
491
676
|
let cachePreference: CacheMode = DEFAULT_CACHE_MODE;
|
|
677
|
+
let modelPreference: string | undefined;
|
|
678
|
+
let thinkingPreference: ThinkingLevel = DEFAULT_THINKING_LEVEL;
|
|
492
679
|
|
|
493
680
|
pi.on("session_start", async () => {
|
|
494
|
-
|
|
681
|
+
const preferences = await readLibrarianPreferences();
|
|
682
|
+
cachePreference = preferences.cacheMode;
|
|
683
|
+
modelPreference = preferences.model;
|
|
684
|
+
thinkingPreference = preferences.thinkingLevel;
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
const currentPreferences = (): LibrarianPreferences => ({
|
|
688
|
+
cacheMode: cachePreference,
|
|
689
|
+
...(modelPreference ? { model: modelPreference } : {}),
|
|
690
|
+
thinkingLevel: thinkingPreference,
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
const savePreferences = async (preferences: LibrarianPreferences): Promise<string | undefined> => {
|
|
694
|
+
cachePreference = preferences.cacheMode;
|
|
695
|
+
modelPreference = preferences.model;
|
|
696
|
+
thinkingPreference = preferences.thinkingLevel;
|
|
697
|
+
try {
|
|
698
|
+
await writeLibrarianPreferences(preferences);
|
|
699
|
+
return undefined;
|
|
700
|
+
} catch (error) {
|
|
701
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
702
|
+
return `Preference changed for this process, but could not save ${getCacheConfigPath()}: ${message}`;
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
pi.registerCommand("librarian-config", {
|
|
707
|
+
description: "Configure Librarian subagent model and thinking defaults",
|
|
708
|
+
getArgumentCompletions: (prefix) => {
|
|
709
|
+
const parts = prefix.trim().split(/\s+/).filter(Boolean);
|
|
710
|
+
if (parts.length <= 1) {
|
|
711
|
+
const commands = ["status", "model", "thinking", "clear"];
|
|
712
|
+
const query = parts[0]?.toLowerCase() ?? "";
|
|
713
|
+
return commands.filter((command) => command.startsWith(query)).map((value) => ({ value, label: value }));
|
|
714
|
+
}
|
|
715
|
+
if (parts[0]?.toLowerCase() === "thinking") {
|
|
716
|
+
const query = parts[1]?.toLowerCase() ?? "";
|
|
717
|
+
return [...THINKING_LEVELS, "auto"].filter((level) => level.startsWith(query)).map((value) => ({ value, label: value }));
|
|
718
|
+
}
|
|
719
|
+
return null;
|
|
720
|
+
},
|
|
721
|
+
handler: async (args, ctx) => {
|
|
722
|
+
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
|
723
|
+
const [command = "status", ...rest] = tokens;
|
|
724
|
+
const action = command.toLowerCase();
|
|
725
|
+
|
|
726
|
+
if (action === "status" || action === "show") {
|
|
727
|
+
notifyCommand(ctx, formatLibrarianPreferences(currentPreferences()));
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
if (action === "model") {
|
|
732
|
+
const value = rest.join(" ").trim();
|
|
733
|
+
if (!value) {
|
|
734
|
+
notifyCommand(ctx, "Usage: /librarian-config model <provider/model|auto|current>", "warning");
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
const normalized = value.toLowerCase();
|
|
738
|
+
const parsedModel = parseModelPreference(value);
|
|
739
|
+
const next = normalized === "auto" || normalized === "clear" || normalized === "default"
|
|
740
|
+
? { ...currentPreferences(), model: undefined }
|
|
741
|
+
: {
|
|
742
|
+
...currentPreferences(),
|
|
743
|
+
model: normalized === "current" ? "current" : parsedModel.model,
|
|
744
|
+
thinkingLevel: parsedModel.thinkingLevel ?? thinkingPreference,
|
|
745
|
+
};
|
|
746
|
+
const warning = await savePreferences(next);
|
|
747
|
+
notifyCommand(ctx, `Librarian model default updated. ${warning ?? formatLibrarianPreferences(currentPreferences())}`, warning ? "warning" : "info");
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
if (action === "thinking" || action === "think" || action === "thinking-level") {
|
|
752
|
+
const value = rest[0]?.trim().toLowerCase();
|
|
753
|
+
if (!value) {
|
|
754
|
+
notifyCommand(ctx, `Usage: /librarian-config thinking ${THINKING_LEVELS.join(" | ")} | auto`, "warning");
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
const thinkingLevel = value === "auto" || value === "clear" || value === "default"
|
|
758
|
+
? DEFAULT_THINKING_LEVEL
|
|
759
|
+
: parseThinkingLevel(value);
|
|
760
|
+
if (!thinkingLevel) {
|
|
761
|
+
notifyCommand(ctx, `Usage: /librarian-config thinking ${THINKING_LEVELS.join(" | ")} | auto`, "warning");
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
const warning = await savePreferences({ ...currentPreferences(), thinkingLevel });
|
|
765
|
+
notifyCommand(ctx, `Librarian thinking default set to ${thinkingLevel}. ${warning ?? formatLibrarianPreferences(currentPreferences())}`, warning ? "warning" : "info");
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
if (action === "clear" || action === "reset") {
|
|
770
|
+
const target = rest[0]?.trim().toLowerCase() || "all";
|
|
771
|
+
let next: LibrarianPreferences;
|
|
772
|
+
if (target === "all") next = { cacheMode: cachePreference, thinkingLevel: DEFAULT_THINKING_LEVEL };
|
|
773
|
+
else if (target === "model") next = { ...currentPreferences(), model: undefined };
|
|
774
|
+
else if (target === "thinking" || target === "thinking-level") next = { ...currentPreferences(), thinkingLevel: DEFAULT_THINKING_LEVEL };
|
|
775
|
+
else {
|
|
776
|
+
notifyCommand(ctx, "Usage: /librarian-config clear [all|model|thinking]", "warning");
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
const warning = await savePreferences(next);
|
|
780
|
+
notifyCommand(ctx, `Librarian defaults cleared (${target}). ${warning ?? formatLibrarianPreferences(currentPreferences())}`, warning ? "warning" : "info");
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
notifyCommand(ctx, "Usage: /librarian-config status | model <provider/model|auto|current> | thinking <off|minimal|low|medium|high|xhigh|auto> | clear [all|model|thinking]", "warning");
|
|
785
|
+
},
|
|
495
786
|
});
|
|
496
787
|
|
|
497
788
|
pi.registerCommand("librarian-cache", {
|
|
@@ -560,12 +851,13 @@ export default function librarianExtension(pi: ExtensionAPI) {
|
|
|
560
851
|
name: "librarian",
|
|
561
852
|
label: "Librarian",
|
|
562
853
|
description:
|
|
563
|
-
"GitHub research scout for coding and personal-assistant tasks. Use when the answer likely lives in GitHub repos, exact repo/path locations are unknown, or you'd otherwise do exploratory gh search/tree probes plus local rg/read inspection. Librarian uses an optional 7-day local checkout cache by default; toggle it with /librarian-cache.",
|
|
854
|
+
"GitHub research scout for coding and personal-assistant tasks. Use when the answer likely lives in GitHub repos, exact repo/path locations are unknown, or you'd otherwise do exploratory gh search/tree probes plus local rg/read inspection. Librarian uses an optional 7-day local checkout cache that is disabled by default; toggle it with /librarian-cache. Configure its internal subagent defaults with /librarian-config.",
|
|
564
855
|
promptSnippet:
|
|
565
|
-
"Research GitHub repositories with evidence-first path and line citations; local checkout cache is
|
|
856
|
+
"Research GitHub repositories with evidence-first path and line citations; local checkout cache is disabled by default and user-toggleable with /librarian-cache. Internal subagent defaults are user-configurable with /librarian-config and default to medium thinking.",
|
|
566
857
|
promptGuidelines: [
|
|
567
858
|
"Use librarian when the answer likely requires exploratory GitHub repository search or line-cited evidence from external repos.",
|
|
568
859
|
"Do not use librarian for files already present in the current workspace unless the user asks for external GitHub research.",
|
|
860
|
+
"Use model or thinkingLevel only when the user explicitly asks for a non-default internal librarian model or thinking level.",
|
|
569
861
|
],
|
|
570
862
|
parameters: LibrarianParams,
|
|
571
863
|
|
|
@@ -573,7 +865,6 @@ export default function librarianExtension(pi: ExtensionAPI) {
|
|
|
573
865
|
const rawQuery = (params as { query?: unknown }).query;
|
|
574
866
|
const query = typeof rawQuery === "string" ? rawQuery.trim() : "";
|
|
575
867
|
if (!query) throw new Error("Invalid parameters: expected query to be a non-empty string.");
|
|
576
|
-
if (!ctx.model) throw new Error("Librarian needs an active pi model, but ctx.model is unavailable.");
|
|
577
868
|
|
|
578
869
|
const repos = asStringArray((params as { repos?: unknown }).repos);
|
|
579
870
|
const owners = asStringArray((params as { owners?: unknown }).owners);
|
|
@@ -583,6 +874,12 @@ export default function librarianExtension(pi: ExtensionAPI) {
|
|
|
583
874
|
MAX_SEARCH_RESULTS,
|
|
584
875
|
DEFAULT_MAX_SEARCH_RESULTS,
|
|
585
876
|
);
|
|
877
|
+
const explicitModel = parseModelPreference((params as { model?: unknown }).model);
|
|
878
|
+
const thinkingLevel =
|
|
879
|
+
parseThinkingLevel((params as { thinkingLevel?: unknown }).thinkingLevel) ??
|
|
880
|
+
explicitModel.thinkingLevel ??
|
|
881
|
+
thinkingPreference;
|
|
882
|
+
const selectedModel = await selectLibrarianModel(ctx, explicitModel.model ?? modelPreference, thinkingLevel);
|
|
586
883
|
|
|
587
884
|
const workspaceBase = path.join(os.tmpdir(), "pi-librarian");
|
|
588
885
|
await fs.mkdir(workspaceBase, { recursive: true });
|
|
@@ -617,6 +914,7 @@ export default function librarianExtension(pi: ExtensionAPI) {
|
|
|
617
914
|
cleanupErrors: cleanup.errors,
|
|
618
915
|
decisionReason: cacheDecision.reason,
|
|
619
916
|
},
|
|
917
|
+
model: selectedModel.details,
|
|
620
918
|
turns: 0,
|
|
621
919
|
toolCalls: [],
|
|
622
920
|
startedAt: Date.now(),
|
|
@@ -687,8 +985,8 @@ export default function librarianExtension(pi: ExtensionAPI) {
|
|
|
687
985
|
modelRegistry: ctx.modelRegistry,
|
|
688
986
|
resourceLoader,
|
|
689
987
|
sessionManager: SessionManager.inMemory(workspace),
|
|
690
|
-
model:
|
|
691
|
-
thinkingLevel
|
|
988
|
+
model: selectedModel.model,
|
|
989
|
+
thinkingLevel,
|
|
692
990
|
tools: ["read", "bash"],
|
|
693
991
|
});
|
|
694
992
|
|
|
@@ -807,6 +1105,10 @@ export default function librarianExtension(pi: ExtensionAPI) {
|
|
|
807
1105
|
)}${cacheLabel}`;
|
|
808
1106
|
|
|
809
1107
|
const workspaceLine = `${theme.fg("muted", "workspace: ")}${theme.fg("toolOutput", details.workspace)}`;
|
|
1108
|
+
const modelLine = `${theme.fg("muted", "model: ")}${theme.fg("toolOutput", details.model.modelRef)} ${theme.fg(
|
|
1109
|
+
"dim",
|
|
1110
|
+
`(${details.model.thinkingLevel}, ${details.model.autoSelected ? "auto" : "configured"})`,
|
|
1111
|
+
)}`;
|
|
810
1112
|
const cacheLine = `${theme.fg("muted", "cache: ")}${theme.fg("toolOutput", details.cache.root)} ${theme.fg(
|
|
811
1113
|
"dim",
|
|
812
1114
|
`(${details.cache.mode}, ${details.cache.ttlDays}d TTL, cleaned ${details.cache.cleanupDeleted})`,
|
|
@@ -822,7 +1124,7 @@ export default function librarianExtension(pi: ExtensionAPI) {
|
|
|
822
1124
|
if (!expanded && details.toolCalls.length > 6) toolLines.unshift(theme.fg("muted", "…"));
|
|
823
1125
|
|
|
824
1126
|
if (status === "running") {
|
|
825
|
-
const parts = [header, workspaceLine, cacheLine];
|
|
1127
|
+
const parts = [header, workspaceLine, modelLine, cacheLine];
|
|
826
1128
|
if (toolLines.length) parts.push("", theme.fg("muted", "Tools:"), ...toolLines);
|
|
827
1129
|
parts.push("", theme.fg("muted", "Searching GitHub…"));
|
|
828
1130
|
return new Text(parts.join("\n"), 0, 0);
|
|
@@ -830,7 +1132,7 @@ export default function librarianExtension(pi: ExtensionAPI) {
|
|
|
830
1132
|
|
|
831
1133
|
if (!expanded) {
|
|
832
1134
|
const previewLines = answer.split("\n").slice(0, 18);
|
|
833
|
-
const parts = [header, workspaceLine, cacheLine, "", theme.fg("toolOutput", previewLines.join("\n"))];
|
|
1135
|
+
const parts = [header, workspaceLine, modelLine, cacheLine, "", theme.fg("toolOutput", previewLines.join("\n"))];
|
|
834
1136
|
if (answer.split("\n").length > previewLines.length) parts.push(theme.fg("muted", "(Ctrl+O to expand)"));
|
|
835
1137
|
if (toolLines.length) parts.push("", theme.fg("muted", "Tools:"), ...toolLines);
|
|
836
1138
|
return new Text(parts.join("\n"), 0, 0);
|
|
@@ -839,6 +1141,7 @@ export default function librarianExtension(pi: ExtensionAPI) {
|
|
|
839
1141
|
const container = new Container();
|
|
840
1142
|
container.addChild(new Text(header, 0, 0));
|
|
841
1143
|
container.addChild(new Text(workspaceLine, 0, 0));
|
|
1144
|
+
container.addChild(new Text(modelLine, 0, 0));
|
|
842
1145
|
container.addChild(new Text(cacheLine, 0, 0));
|
|
843
1146
|
if (details.cache.cleanupErrors.length) {
|
|
844
1147
|
container.addChild(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-extensions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.42",
|
|
4
4
|
"description": "A collection of pi extensions for context management, workflow audits, review-comment triage, notifications, brrr push alerts, safety guards, GitHub research, repo-local knowledge, todos, tool rendering, and model/provider helpers.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|