pi-magi-theme 0.3.0 → 0.3.1
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 +6 -4
- package/extensions/magi/index.ts +62 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,8 +36,8 @@ Clone the repo and point pi at it instead (edits in the repo are live on the nex
|
|
|
36
36
|
|
|
37
37
|
| Command | What it does |
|
|
38
38
|
|---------|--------------|
|
|
39
|
-
| `/magi <question>` | the council answers a question (recent conversation as context) |
|
|
40
|
-
| `/magi review [focus]` | the council reviews your pending changes (`git diff HEAD` plus untracked file names) before you commit |
|
|
39
|
+
| `/magi council <question>` | the council answers a question (recent conversation as context) |
|
|
40
|
+
| `/magi council review [focus]` | the council reviews your pending changes (`git diff HEAD` plus untracked file names) before you commit |
|
|
41
41
|
| `/magi config` | pick a model for each MAGI |
|
|
42
42
|
| `/magi mecha` | MECHA SELECT: pick the llama-swap model to activate, each shown as a mecha head lit by its real state |
|
|
43
43
|
| `/magi compact` | toggle the compact side panel (basic info and animations only); remembered across sessions |
|
|
@@ -120,7 +120,7 @@ When the limit is reached llama.cpp does not abort the reply: it inserts the clo
|
|
|
120
120
|
|
|
121
121
|
## The council
|
|
122
122
|
|
|
123
|
-
`/magi <question>` asks three models in parallel, each with its own nature, then shows the votes and a majority verdict:
|
|
123
|
+
`/magi council <question>` asks three models in parallel, each with its own nature, then shows the votes and a majority verdict:
|
|
124
124
|
|
|
125
125
|
| Unit | Nature | Looks at |
|
|
126
126
|
|------|--------|----------|
|
|
@@ -130,7 +130,7 @@ When the limit is reached llama.cpp does not abort the reply: it inserts the clo
|
|
|
130
130
|
|
|
131
131
|
Each nature is a lens, not a specialty, so the council answers any question, not only software ones. Every MAGI first answers the question, then judges it through its lens, naming concrete tools, numbers and scenarios from your question instead of generic advice. Votes: **APPROVE** = go ahead or clear recommendation; **CONDITIONAL** = only if the named conditions hold, or when information is missing (it says what it needs); **REJECT** = a concrete problem, with what to do instead. A MAGI never rejects because a topic is outside its nature. Answers come back in the language of your question.
|
|
132
132
|
|
|
133
|
-
`/magi <question>` gives the MAGI the recent conversation as context; `/magi review` gives them the pending diff (truncated at 24k characters). Full opinions are added to the chat (not sent to the agent), and the last verdict stays under the MAGI in the side panel.
|
|
133
|
+
`/magi council <question>` gives the MAGI the recent conversation as context; `/magi council review` gives them the pending diff (truncated at 24k characters). Full opinions are added to the chat (not sent to the agent), and the last verdict stays under the MAGI in the side panel.
|
|
134
134
|
|
|
135
135
|
## Configuration
|
|
136
136
|
|
|
@@ -166,6 +166,8 @@ No token: npmjs is configured to trust this repository's `publish.yml` (npm trus
|
|
|
166
166
|
|
|
167
167
|
## llama-swap
|
|
168
168
|
|
|
169
|
+
**Thinking levels.** [pi-llama-swap](https://www.npmjs.com/package/@danielmeneses/pi-llama-swap) registers every model with reasoning off, so `/thinking` only offers `off`. At session start MAGI reads the reasoning levels llama-swap publishes in `/v1/models` (`meta.llamaswap.reasoning.levels`) and re-registers those models with thinking on: `/thinking` then offers exactly those levels, sent as `chat_template_kwargs` (`enable_thinking`, `reasoning_effort`). Aliases (e.g. the Instruct twin of a Thinking model) stay off, and image input follows `architecture.input_modalities`. A new or renamed model works without a `modelOverrides` entry in `models.json`; entries you keep there still apply on top (e.g. `samplingParams`). The starting level is pi's usual one for a model switch: the level saved for that model (`Ctrl+S` in `/thinking`), else `defaultThinkingLevel`.
|
|
170
|
+
|
|
169
171
|
When the session model uses the `llama-swap` provider, the side panel:
|
|
170
172
|
|
|
171
173
|
- loads nothing at startup: a new session opens MECHA SELECT, a resumed one shows whether its model is already in VRAM;
|
package/extensions/magi/index.ts
CHANGED
|
@@ -907,6 +907,56 @@ async function swapAliases(): Promise<Map<string, string>> {
|
|
|
907
907
|
return aliases;
|
|
908
908
|
}
|
|
909
909
|
|
|
910
|
+
const PI_THINKING_LEVELS = ["minimal", "low", "medium", "high", "xhigh", "max"];
|
|
911
|
+
|
|
912
|
+
/**
|
|
913
|
+
* pi-llama-swap registers every model with reasoning off, so /thinking only offers "off".
|
|
914
|
+
* llama-swap publishes each model's reasoning levels in /v1/models: re-register the provider with them,
|
|
915
|
+
* sent as chat_template_kwargs (enable_thinking + reasoning_effort). Aliases (the Instruct twins) stay off.
|
|
916
|
+
* models.json modelOverrides still apply on top.
|
|
917
|
+
*/
|
|
918
|
+
async function enableSwapReasoning(pi: ExtensionAPI, ctx: ExtensionContext): Promise<void> {
|
|
919
|
+
const config = ctx.modelRegistry.getRegisteredProviderConfig("llama-swap") as any;
|
|
920
|
+
if (!config?.models?.length || !config.baseUrl) return;
|
|
921
|
+
try {
|
|
922
|
+
const headers: Record<string, string> = config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {};
|
|
923
|
+
const res = await fetch(`${config.baseUrl.replace(/\/$/, "")}/models`, { headers, signal: AbortSignal.timeout(5000) });
|
|
924
|
+
const { data } = (await res.json()) as { data?: any[] };
|
|
925
|
+
const meta = new Map((data ?? []).map((m) => [m.id, m]));
|
|
926
|
+
let changed = false;
|
|
927
|
+
const models = config.models.map((m: any) => {
|
|
928
|
+
const entry = meta.get(m.id);
|
|
929
|
+
const levels: string[] | undefined = entry?.meta?.llamaswap?.reasoning?.levels;
|
|
930
|
+
const input = entry?.architecture?.input_modalities?.includes("image") ? ["text", "image"] : ["text"];
|
|
931
|
+
if (!levels?.length || entry.meta.llamaswap.type === "alias") return { ...m, input };
|
|
932
|
+
changed = true;
|
|
933
|
+
return {
|
|
934
|
+
...m,
|
|
935
|
+
input,
|
|
936
|
+
reasoning: true,
|
|
937
|
+
thinkingLevelMap: Object.fromEntries(PI_THINKING_LEVELS.map((l) => [l, levels.includes(l) ? l : null])),
|
|
938
|
+
compat: {
|
|
939
|
+
...m.compat,
|
|
940
|
+
thinkingFormat: "chat-template",
|
|
941
|
+
chatTemplateKwargs: {
|
|
942
|
+
enable_thinking: { $var: "thinking.enabled" },
|
|
943
|
+
preserve_thinking: true,
|
|
944
|
+
reasoning_effort: { $var: "thinking.effort", omitWhenOff: true },
|
|
945
|
+
},
|
|
946
|
+
},
|
|
947
|
+
};
|
|
948
|
+
});
|
|
949
|
+
// ponytail: a /llama-swap refresh re-registers the plain models until the next session start
|
|
950
|
+
if (!changed) return;
|
|
951
|
+
ctx.modelRegistry.registerProvider("llama-swap", { ...config, models });
|
|
952
|
+
// the session already holds the old model object: swap in the new one
|
|
953
|
+
const fresh = ctx.model?.provider === "llama-swap" ? ctx.modelRegistry.find("llama-swap", ctx.model.id) : undefined;
|
|
954
|
+
if (fresh?.reasoning) await pi.setModel(fresh);
|
|
955
|
+
} catch {
|
|
956
|
+
// server unreachable: models stay as pi-llama-swap registered them
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
910
960
|
/** Models llama-swap keeps in memory: real id → "ready" | "starting" | … (empty when the server is unreachable). */
|
|
911
961
|
async function swapRunning(): Promise<Map<string, string>> {
|
|
912
962
|
try {
|
|
@@ -1545,10 +1595,11 @@ type MagiConfig = Partial<Record<MagiUnit, MagiUnitConfig>> & {
|
|
|
1545
1595
|
|
|
1546
1596
|
const MAGI_CONFIG_PATH = join(homedir(), ".pi", "agent", "magi.json");
|
|
1547
1597
|
/** /magi arguments that manage the theme instead of asking the council. */
|
|
1548
|
-
const UI_ARGS = /^(on|off|panel|compact|status|cost)$|^(hygiene|budget)(\s|$)/i;
|
|
1598
|
+
const UI_ARGS = /^(on|off|panel|compact|status|cost)$|^(hygiene|budget)(\s|$)/i;
|
|
1549
1599
|
/** /magi arguments offered by autocomplete: the full argument, and what it does. */
|
|
1550
1600
|
const MAGI_ARGS: [string, string][] = [
|
|
1551
|
-
["
|
|
1601
|
+
["council", "ask the three MAGI a question (recent conversation as context)"],
|
|
1602
|
+
["council review", "the council reviews your pending changes before you commit"],
|
|
1552
1603
|
["config", "pick a model for each MAGI"],
|
|
1553
1604
|
["mecha", "MECHA SELECT: pick the llama-swap model to activate"],
|
|
1554
1605
|
["status", "llama-swap report: speed, tokens, cache hits, errors per model"],
|
|
@@ -1628,7 +1679,7 @@ function conversationExcerpt(ctx: ExtensionContext, maxChars = 6000): string {
|
|
|
1628
1679
|
|
|
1629
1680
|
const REVIEW_MAX_CHARS = 24_000;
|
|
1630
1681
|
|
|
1631
|
-
/** The pending changes for /magi review: tracked changes against HEAD plus the names of untracked files. */
|
|
1682
|
+
/** The pending changes for /magi council review: tracked changes against HEAD plus the names of untracked files. */
|
|
1632
1683
|
async function pendingChanges(cwd: string): Promise<{ diff: string; untracked: string[] }> {
|
|
1633
1684
|
const git = (args: string[]) => promisify(execFile)("git", args, { cwd, maxBuffer: 32 * 1024 * 1024 }).then((r) => r.stdout);
|
|
1634
1685
|
let diff: string;
|
|
@@ -2271,6 +2322,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2271
2322
|
fixedBudget = { planning: tb.planning ?? BUDGET_DEFAULTS.planning, acting: tb.acting ?? BUDGET_DEFAULTS.acting };
|
|
2272
2323
|
budgetMessage = tb.message ?? true;
|
|
2273
2324
|
learned = tb.learned ?? {};
|
|
2325
|
+
await enableSwapReasoning(pi, ctx);
|
|
2274
2326
|
// the local-model rules live next to AGENTS.md, created once so the user can edit them
|
|
2275
2327
|
const rules = join(ctx.cwd, "MAGI.md");
|
|
2276
2328
|
if (!existsSync(rules)) {
|
|
@@ -2600,8 +2652,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
2600
2652
|
return pickModel(ctx);
|
|
2601
2653
|
}
|
|
2602
2654
|
|
|
2603
|
-
if (arg
|
|
2604
|
-
|
|
2655
|
+
if (arg !== "council" && !arg.startsWith("council ")) {
|
|
2656
|
+
return ctx.ui.notify(arg ? `Unknown /magi command "${arg}": to ask the MAGI, /magi council <question>` : "Ask the MAGI with /magi council <question>; type /magi and a space to see every command", arg ? "warning" : "info");
|
|
2657
|
+
}
|
|
2658
|
+
const ask = arg.slice("council".length).trim();
|
|
2659
|
+
if (ask === "review" || ask.startsWith("review ")) {
|
|
2660
|
+
const focus = ask.slice("review".length).trim();
|
|
2605
2661
|
let changes: { diff: string; untracked: string[] };
|
|
2606
2662
|
try {
|
|
2607
2663
|
changes = await pendingChanges(ctx.cwd);
|
|
@@ -2624,7 +2680,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2624
2680
|
return runCouncil(ctx, question, "```diff\n" + diff + "\n```" + untracked, "Pending changes (git diff HEAD)");
|
|
2625
2681
|
}
|
|
2626
2682
|
|
|
2627
|
-
const question =
|
|
2683
|
+
const question = ask || (await ctx.ui.input("Question for the MAGI:", "should we …?"))?.trim() || "";
|
|
2628
2684
|
if (!question) return;
|
|
2629
2685
|
return runCouncil(ctx, question, conversationExcerpt(ctx));
|
|
2630
2686
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-magi-theme",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "MAGI SYSTEM theme + extension for pi (Evangelion fan art): MAGI control screen panel, three-model /magi council, MECHA SELECT model picker, angel-attack loading, seven-seal context gauge, llama-swap telemetry",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|