pi-soly 1.2.0 → 1.4.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 +8 -32
- package/agents-install.ts +11 -98
- package/commands.ts +48 -2
- package/config.ts +4 -6
- package/index.ts +10 -33
- package/package.json +1 -3
- package/skills/soly-framework/SKILL.md +25 -111
- package/agents/soly-manager.md +0 -124
- package/switch/README.md +0 -104
- package/switch/core.ts +0 -229
- package/switch/index.ts +0 -347
- package/switch/package.json +0 -52
- package/switch/prompt.ts +0 -131
- package/switch/tests/core.test.ts +0 -210
- package/switch/tests/index.test.ts +0 -48
- package/switch/tests/prompt.test.ts +0 -108
- package/switch/tsconfig.json +0 -28
package/switch/index.ts
DELETED
|
@@ -1,347 +0,0 @@
|
|
|
1
|
-
// =============================================================================
|
|
2
|
-
// index.ts — pi-switch extension entry (v2: footer-pill UI)
|
|
3
|
-
// =============================================================================
|
|
4
|
-
//
|
|
5
|
-
// Wires the rotor switcher into pi as a compact footer pill:
|
|
6
|
-
// - Footer status pill: "▶ ⚡ worker" (or "· ⚡ worker" for the default)
|
|
7
|
-
// - Click pill or `/rotor` → open full picker modal (SelectList)
|
|
8
|
-
// - Ctrl+Tab → cycle to next rotor (no popup, hot switch)
|
|
9
|
-
// - F2 → same, fallback if your terminal doesn't pass Ctrl+Tab through
|
|
10
|
-
// - Persists current rotor to .soly/agent or ~/.pi-switch/agent
|
|
11
|
-
// - Exposes `globalThis.__PI_SWITCH_ROTOR__` for other extensions
|
|
12
|
-
// - Injects a short system-prompt section so the LLM knows the current
|
|
13
|
-
// rotor and the available alternatives
|
|
14
|
-
//
|
|
15
|
-
// UI philosophy:
|
|
16
|
-
// - Header is for content, not for tool chrome. Move rotors to footer.
|
|
17
|
-
// - Click to explore, hotkey to power-use, no DOM clutter in between.
|
|
18
|
-
// - Visual change is the pill text only. Chat stays clean.
|
|
19
|
-
// =============================================================================
|
|
20
|
-
|
|
21
|
-
import type { ExtensionAPI, ExtensionUIContext } from "@earendil-works/pi-coding-agent";
|
|
22
|
-
import { Box, Text } from "@earendil-works/pi-tui";
|
|
23
|
-
import * as fs from "node:fs";
|
|
24
|
-
import * as os from "node:os";
|
|
25
|
-
import * as path from "node:path";
|
|
26
|
-
import {
|
|
27
|
-
DEFAULT_ROTOR,
|
|
28
|
-
BUILTIN_ROTORS,
|
|
29
|
-
availableAgents,
|
|
30
|
-
nextAgent,
|
|
31
|
-
parseRotorName,
|
|
32
|
-
groupedAvailableRotors,
|
|
33
|
-
getRotorMeta,
|
|
34
|
-
loadAgent,
|
|
35
|
-
saveAgent,
|
|
36
|
-
} from "./core.ts";
|
|
37
|
-
import { buildPiSwitchSection, recommendAgent } from "./prompt.ts";
|
|
38
|
-
|
|
39
|
-
const GLOBAL_KEY = "__PI_SWITCH_ROTOR__";
|
|
40
|
-
|
|
41
|
-
export default function piSwitchExtension(pi: ExtensionAPI) {
|
|
42
|
-
let cwd = "";
|
|
43
|
-
let currentRotor: string = DEFAULT_ROTOR;
|
|
44
|
-
let cycle: string[] = [DEFAULT_ROTOR];
|
|
45
|
-
let lastUi: ExtensionUIContext | null = null;
|
|
46
|
-
|
|
47
|
-
function refreshCycle(): void {
|
|
48
|
-
cycle = availableAgents(cwd);
|
|
49
|
-
if (!cycle.includes(currentRotor)) currentRotor = DEFAULT_ROTOR;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function publish(): void {
|
|
53
|
-
(globalThis as Record<string, unknown>)[GLOBAL_KEY] = currentRotor;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function rerender(): void {
|
|
57
|
-
if (!lastUi) return;
|
|
58
|
-
try {
|
|
59
|
-
const meta = getRotorMeta(currentRotor);
|
|
60
|
-
// Persistent pill — always visible above the input, even for the
|
|
61
|
-
// default rotor. The user wants a constant mode indicator, not a
|
|
62
|
-
// transient one. Marker "▶" makes it scannable.
|
|
63
|
-
const marker = currentRotor === DEFAULT_ROTOR ? "·" : "▶";
|
|
64
|
-
const pill = `${marker} ${meta.emoji} ${currentRotor}`;
|
|
65
|
-
lastUi.setStatus("pi-switch", pill);
|
|
66
|
-
} catch { /* no ui yet */ }
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function setRotor(next: string): void {
|
|
70
|
-
const prev = currentRotor;
|
|
71
|
-
if (next === prev) return;
|
|
72
|
-
currentRotor = next;
|
|
73
|
-
publish();
|
|
74
|
-
if (cwd) saveAgent(cwd, next);
|
|
75
|
-
rerender();
|
|
76
|
-
// Footer pill is the only visible signal of the switch.
|
|
77
|
-
// Chat stays clean — rotor is plumbing, not conversation content.
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// ----- session_start: load persisted agent + set initial pill -----
|
|
81
|
-
pi.on("session_start", async (_event, ctx) => {
|
|
82
|
-
cwd = ctx.cwd;
|
|
83
|
-
lastUi = ctx.ui;
|
|
84
|
-
publish();
|
|
85
|
-
const restored = loadAgent(cwd);
|
|
86
|
-
if (restored) currentRotor = restored;
|
|
87
|
-
refreshCycle();
|
|
88
|
-
publish();
|
|
89
|
-
rerender();
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
// ----- before_agent_start: inject system-prompt section -----
|
|
93
|
-
pi.on("before_agent_start", async (event, ctx) => {
|
|
94
|
-
lastUi = ctx.ui;
|
|
95
|
-
rerender();
|
|
96
|
-
return {
|
|
97
|
-
systemPrompt: event.systemPrompt + buildPiSwitchSection(),
|
|
98
|
-
};
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
// ----- Hot cycle (no popup, no confirmation) -----
|
|
102
|
-
// Ctrl+Tab is the primary shortcut (most terminals support it).
|
|
103
|
-
// F2 is kept as a backup for terminals that don't pass Ctrl+Tab through.
|
|
104
|
-
// Debounced: 180ms — terminal key auto-repeat can fire the same key 5+
|
|
105
|
-
// times per second, which would spam the chat with the same agent
|
|
106
|
-
// notification. The window covers auto-repeat but allows deliberate
|
|
107
|
-
// sequential presses.
|
|
108
|
-
let lastCycleTs = 0;
|
|
109
|
-
const CYCLE_DEBOUNCE_MS = 180;
|
|
110
|
-
const cycleShortcut = (sctx: { ui: ExtensionUIContext }): void => {
|
|
111
|
-
const now = Date.now();
|
|
112
|
-
if (now - lastCycleTs < CYCLE_DEBOUNCE_MS) return;
|
|
113
|
-
lastCycleTs = now;
|
|
114
|
-
lastUi = sctx.ui;
|
|
115
|
-
refreshCycle();
|
|
116
|
-
setRotor(nextAgent(currentRotor, cycle));
|
|
117
|
-
};
|
|
118
|
-
pi.registerShortcut("ctrl+tab", {
|
|
119
|
-
description: "Cycle to next agent (worker → oracle → scout → …)",
|
|
120
|
-
handler: (sctx) => cycleShortcut(sctx),
|
|
121
|
-
});
|
|
122
|
-
pi.registerShortcut("f2", {
|
|
123
|
-
description: "Cycle to next agent (F2 fallback if Ctrl+Tab isn't passed by your terminal)",
|
|
124
|
-
handler: (sctx) => cycleShortcut(sctx),
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
// ----- /rotor: open picker, or subcommands (create / doctor / recommend / set) -----
|
|
128
|
-
// (formerly /agent — renamed to /rotor in 1.0.0 to match the "rotor" naming convention)
|
|
129
|
-
pi.registerCommand("rotor", {
|
|
130
|
-
description: "open rotor picker, or `set <name>`, `create`, `doctor`, `recommend <task>`",
|
|
131
|
-
handler: async (args, ctx) => {
|
|
132
|
-
lastUi = ctx.ui;
|
|
133
|
-
refreshCycle();
|
|
134
|
-
const parts = args.trim().split(/\s+/);
|
|
135
|
-
const subcommand = parts[0]?.toLowerCase();
|
|
136
|
-
const arg = parts[1];
|
|
137
|
-
|
|
138
|
-
if (subcommand === "create") return createAgent(arg, ctx.ui, cwd);
|
|
139
|
-
if (subcommand === "doctor") return ctx.ui.notify(doctorReport(), "info");
|
|
140
|
-
if (subcommand === "recommend") return handleRecommend(parts.slice(1).join(" "), ctx.ui);
|
|
141
|
-
if (subcommand === "set" && arg) return handleSet(arg, ctx.ui);
|
|
142
|
-
|
|
143
|
-
// Direct agent name → set
|
|
144
|
-
if (subcommand && cycle.includes(subcommand)) return setRotor(subcommand);
|
|
145
|
-
if (arg && !subcommand) return handleSet(arg, ctx.ui);
|
|
146
|
-
|
|
147
|
-
// No arg: open picker modal
|
|
148
|
-
openPicker(ctx.ui);
|
|
149
|
-
},
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// ---------------------------------------------------------------------------
|
|
154
|
-
// Picker modal (TUI SelectList)
|
|
155
|
-
// ---------------------------------------------------------------------------
|
|
156
|
-
|
|
157
|
-
function openPicker(ui: ExtensionUIContext): void {
|
|
158
|
-
refreshAndBuild(ui, (groups) => {
|
|
159
|
-
const all: Array<{ value: string; label: string; description: string; isCurrent: boolean }> = [];
|
|
160
|
-
for (const g of groups) {
|
|
161
|
-
all.push({ value: "__sep__", label: `── ${g.header} `, description: "", isCurrent: false });
|
|
162
|
-
for (const a of g.agents) {
|
|
163
|
-
const m = getRotorMeta(a);
|
|
164
|
-
all.push({
|
|
165
|
-
value: a,
|
|
166
|
-
label: `${m.emoji} ${a}`,
|
|
167
|
-
description: `${m.description}${m.writesFiles ? "" : " · read-only"}`,
|
|
168
|
-
isCurrent: a === currentRotorRef(),
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
return all;
|
|
173
|
-
}, ui, (choice) => {
|
|
174
|
-
if (choice && choice !== "__sep__") setRotorRef(choice);
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function handleSet(name: string, ui: ExtensionUIContext): void {
|
|
179
|
-
const target = parseRotorName(name);
|
|
180
|
-
if (!target) return ui.notify(`pi-switch: invalid name "${name}".`, "error");
|
|
181
|
-
if (!availableAgents(cwd).includes(target)) {
|
|
182
|
-
return ui.notify(`pi-switch: unknown "${target}". available: ${availableAgents(cwd).join(", ")}`, "error");
|
|
183
|
-
}
|
|
184
|
-
setRotorRef(target);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function handleRecommend(task: string, ui: ExtensionUIContext): void {
|
|
188
|
-
if (!task) return ui.notify("pi-switch: usage — `/agent recommend <task>`", "info");
|
|
189
|
-
const rec = recommendAgent(task);
|
|
190
|
-
if (!rec) return ui.notify(`pi-switch: no clear match for: "${task}"`, "info");
|
|
191
|
-
ui.notify(`${rec.emoji} ${rec.agent} · why: ${rec.why}\n → /agent ${rec.agent} to switch`, "info");
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// ---------------------------------------------------------------------------
|
|
195
|
-
// setAgent / currentRotor — module-scope so the modal can mutate them
|
|
196
|
-
// ---------------------------------------------------------------------------
|
|
197
|
-
|
|
198
|
-
let currentRotorRef: () => string = () => DEFAULT_ROTOR;
|
|
199
|
-
let setRotorRef: (next: string) => void = () => {};
|
|
200
|
-
|
|
201
|
-
// The picker and the main extension share state via these refs.
|
|
202
|
-
// We patch them in `wire()` at the top of the default export.
|
|
203
|
-
function wire(get: () => string, set: (n: string) => void): void {
|
|
204
|
-
currentRotorRef = get;
|
|
205
|
-
setRotorRef = set;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function refreshAndBuild<T>(
|
|
209
|
-
ui: ExtensionUIContext,
|
|
210
|
-
build: (groups: ReturnType<typeof groupedAvailableRotors>) => T,
|
|
211
|
-
_ui: ExtensionUIContext,
|
|
212
|
-
_onSelect: (value: string) => void,
|
|
213
|
-
): void {
|
|
214
|
-
// Currently unused: we build inline in openPicker. Kept for future.
|
|
215
|
-
void build;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// ---------------------------------------------------------------------------
|
|
219
|
-
// /agent create — scaffold a new agent .md
|
|
220
|
-
// ---------------------------------------------------------------------------
|
|
221
|
-
|
|
222
|
-
function createAgent(
|
|
223
|
-
name: string | undefined,
|
|
224
|
-
ui: { notify: (t: string, k?: "info" | "warning" | "error") => void; input: (t: string, p?: string) => Promise<string | undefined> },
|
|
225
|
-
cwd: string,
|
|
226
|
-
): void {
|
|
227
|
-
if (!name) {
|
|
228
|
-
ui.notify("pi-switch: usage — `/agent create <name>`", "info");
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
if (!parseRotorName(name)) {
|
|
232
|
-
ui.notify(`pi-switch: invalid name "${name}". Use letters/digits/dashes/underscores, ≤64 chars.`, "error");
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
// Write to ~/.agents/ (vendor-neutral) first; fall back to ~/.pi/agent/agents/
|
|
236
|
-
// (pi's native) if .agents/ doesn't exist or is unwritable.
|
|
237
|
-
const home = os.homedir();
|
|
238
|
-
const candidates = [
|
|
239
|
-
path.join(home, ".agents"),
|
|
240
|
-
path.join(home, ".pi", "agent", "agents"),
|
|
241
|
-
];
|
|
242
|
-
const targetDir = candidates.find((d) => {
|
|
243
|
-
try {
|
|
244
|
-
if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true });
|
|
245
|
-
// Probe write permission
|
|
246
|
-
const probe = path.join(d, ".pi-switch-write-probe");
|
|
247
|
-
fs.writeFileSync(probe, "");
|
|
248
|
-
fs.unlinkSync(probe);
|
|
249
|
-
return true;
|
|
250
|
-
} catch {
|
|
251
|
-
return false;
|
|
252
|
-
}
|
|
253
|
-
});
|
|
254
|
-
if (!targetDir) {
|
|
255
|
-
ui.notify(`pi-switch: could not find a writable agents dir (tried ${candidates.join(", ")}).`, "error");
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
const file = path.join(targetDir, `${name}.md`);
|
|
259
|
-
if (fs.existsSync(file)) {
|
|
260
|
-
ui.notify(`pi-switch: ${file} already exists. edit it directly.`, "warning");
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
void ui.input(`description for "${name}":`, "one-liner that shows in the picker")?.then((desc) => {
|
|
264
|
-
const description = desc?.trim() || `custom agent (${name})`;
|
|
265
|
-
try {
|
|
266
|
-
// Atomic write: tmp + rename. Avoids partial files and the
|
|
267
|
-
// race where two parallel createAgent calls would clobber each
|
|
268
|
-
// other's write after both pass the existsSync check.
|
|
269
|
-
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
270
|
-
fs.writeFileSync(tmp, rotorTemplate(name, description), "utf-8");
|
|
271
|
-
fs.renameSync(tmp, file);
|
|
272
|
-
ui.notify(
|
|
273
|
-
`pi-switch: created ${file}\n → next Ctrl+Tab to see it in the cycle\n → edit the system prompt to specialize`,
|
|
274
|
-
"info",
|
|
275
|
-
);
|
|
276
|
-
} catch (err) {
|
|
277
|
-
ui.notify(`pi-switch: failed to write ${file}: ${(err as Error).message}`, "error");
|
|
278
|
-
}
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function rotorTemplate(name: string, description: string): string {
|
|
283
|
-
return `---
|
|
284
|
-
name: ${name}
|
|
285
|
-
description: ${description}
|
|
286
|
-
thinking: medium
|
|
287
|
-
systemPromptMode: replace
|
|
288
|
-
inheritProjectContext: true
|
|
289
|
-
inheritSkills: false
|
|
290
|
-
tools: read, grep, find, ls, bash, edit, write
|
|
291
|
-
defaultContext: fork
|
|
292
|
-
---
|
|
293
|
-
|
|
294
|
-
You are \`${name}\`. Describe what you specialize in, your process, and
|
|
295
|
-
what you should NOT do. Keep the rest of this frontmatter as-is unless
|
|
296
|
-
you have a specific reason to change it.
|
|
297
|
-
|
|
298
|
-
# Your role
|
|
299
|
-
|
|
300
|
-
<!-- Replace with a one-paragraph description of what you're for. -->
|
|
301
|
-
|
|
302
|
-
# Process
|
|
303
|
-
|
|
304
|
-
1. Read the user's request carefully.
|
|
305
|
-
2. Form a hypothesis about the right approach.
|
|
306
|
-
3. Verify with tools (read, grep, bash) before writing.
|
|
307
|
-
4. Commit changes in narrow, reviewable diffs.
|
|
308
|
-
|
|
309
|
-
# What you should NOT do
|
|
310
|
-
|
|
311
|
-
- Edit other agents' files
|
|
312
|
-
- Run subagents yourself (you're already a subagent)
|
|
313
|
-
- Skip verification ("trust me bro" is not a process)
|
|
314
|
-
`;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
function doctorReport(): string {
|
|
318
|
-
const cycle = availableAgents(cwd);
|
|
319
|
-
const userDir = path.join(os.homedir(), ".pi", "agent", "agents");
|
|
320
|
-
const lines: string[] = ["pi-switch doctor:", ""];
|
|
321
|
-
const builtins = cycle.filter((a) => BUILTIN_ROTORS.includes(a));
|
|
322
|
-
const users = cycle.filter((a) => !BUILTIN_ROTORS.includes(a));
|
|
323
|
-
lines.push(`cycle: ${cycle.length} agents (${builtins.length} built-in, ${users.length} user)`);
|
|
324
|
-
lines.push("");
|
|
325
|
-
if (!fs.existsSync(userDir)) {
|
|
326
|
-
lines.push(`user dir: ${userDir} (does not exist)`);
|
|
327
|
-
} else {
|
|
328
|
-
const files = fs.readdirSync(userDir).filter((f) => f.endsWith(".md"));
|
|
329
|
-
lines.push(`user dir: ${userDir} (${files.length} file(s))`);
|
|
330
|
-
const issues: string[] = [];
|
|
331
|
-
for (const f of files) {
|
|
332
|
-
try {
|
|
333
|
-
const raw = fs.readFileSync(path.join(userDir, f), "utf-8");
|
|
334
|
-
if (!raw.startsWith("---\n")) { issues.push(`${f}: no YAML frontmatter`); continue; }
|
|
335
|
-
const m = raw.match(/^---\n([\s\S]*?)\n---/);
|
|
336
|
-
if (!m) { issues.push(`${f}: malformed frontmatter`); continue; }
|
|
337
|
-
const fm = m[1] ?? "";
|
|
338
|
-
if (!/^name:\s*\S/m.test(fm)) issues.push(`${f}: missing 'name:' in frontmatter`);
|
|
339
|
-
else if (!/^description:\s*\S/m.test(fm)) issues.push(`${f}: missing 'description:' in frontmatter`);
|
|
340
|
-
} catch (e) {
|
|
341
|
-
issues.push(`${f}: read error: ${(e as Error).message}`);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
lines.push(issues.length === 0 ? "validation: all user agents OK ✓" : "validation issues:\n - " + issues.join("\n - "));
|
|
345
|
-
}
|
|
346
|
-
return lines.join("\n");
|
|
347
|
-
}
|
package/switch/package.json
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "pi-agented",
|
|
3
|
-
"version": "0.2.0",
|
|
4
|
-
"description": "Generic subagent switcher for pi. Header bar above chat, Ctrl+Shift+S to cycle, /agent slash command. Works with any agent in ~/.pi/agent/agents/.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "index.ts",
|
|
7
|
-
"scripts": {
|
|
8
|
-
"test": "bun test",
|
|
9
|
-
"typecheck": "bun x tsc --noEmit"
|
|
10
|
-
},
|
|
11
|
-
"dependencies": {},
|
|
12
|
-
"peerDependencies": {
|
|
13
|
-
"@earendil-works/pi-coding-agent": "*",
|
|
14
|
-
"@earendil-works/pi-tui": "*"
|
|
15
|
-
},
|
|
16
|
-
"devDependencies": {
|
|
17
|
-
"@earendil-works/pi-coding-agent": "0.78.1",
|
|
18
|
-
"@earendil-works/pi-tui": "0.78.1",
|
|
19
|
-
"@types/node": "^25.9.1",
|
|
20
|
-
"bun-types": "^1.3.14",
|
|
21
|
-
"typebox": "1.1.38",
|
|
22
|
-
"typescript": "^6.0.3"
|
|
23
|
-
},
|
|
24
|
-
"files": [
|
|
25
|
-
"index.ts",
|
|
26
|
-
"core.ts",
|
|
27
|
-
"prompt.ts",
|
|
28
|
-
"README.md"
|
|
29
|
-
],
|
|
30
|
-
"keywords": [
|
|
31
|
-
"pi",
|
|
32
|
-
"pi-extension",
|
|
33
|
-
"pi-package",
|
|
34
|
-
"subagent",
|
|
35
|
-
"agent-switcher",
|
|
36
|
-
"task-routing"
|
|
37
|
-
],
|
|
38
|
-
"license": "MIT",
|
|
39
|
-
"pi": {
|
|
40
|
-
"extensions": [
|
|
41
|
-
"./index.ts"
|
|
42
|
-
]
|
|
43
|
-
},
|
|
44
|
-
"publishConfig": {
|
|
45
|
-
"registry": "https://registry.npmjs.org/"
|
|
46
|
-
},
|
|
47
|
-
"repository": {
|
|
48
|
-
"type": "git",
|
|
49
|
-
"url": "http://git.local.stbl/lowern1ght/pi-soly.framework.git",
|
|
50
|
-
"directory": "packages/pi-switch"
|
|
51
|
-
}
|
|
52
|
-
}
|
package/switch/prompt.ts
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
// =============================================================================
|
|
2
|
-
// prompt.ts — System-prompt section for the pi-switch extension
|
|
3
|
-
// =============================================================================
|
|
4
|
-
|
|
5
|
-
/** Task-pattern → recommended agent. The LLM reads this and decides
|
|
6
|
-
* whether to invoke /agent before launching subagent(...). Match is
|
|
7
|
-
* by keyword (case-insensitive). First match wins; ties broken by order. */
|
|
8
|
-
export const TASK_AGENT_HINTS: ReadonlyArray<{
|
|
9
|
-
pattern: RegExp;
|
|
10
|
-
agent: string;
|
|
11
|
-
emoji: string;
|
|
12
|
-
why: string;
|
|
13
|
-
}> = [
|
|
14
|
-
// English keywords (use \b — ASCII word boundary works for English)
|
|
15
|
-
{ pattern: /\b(research|investigate|look\s*up|find\s*out|explore|survey|compare\s+libraries|what\s+is\s+the\s+best)\b/i,
|
|
16
|
-
agent: "researcher", emoji: "\ud83d\udcda",
|
|
17
|
-
why: "external docs, ecosystem behavior, primary sources" },
|
|
18
|
-
{ pattern: /\b(scout|scan|map|find\s+all|where\s+is|locate|explore\s+codebase|skim)\b/i,
|
|
19
|
-
agent: "scout", emoji: "\ud83d\udd0d",
|
|
20
|
-
why: "codebase recon, patterns, file locations" },
|
|
21
|
-
{ pattern: /\b(plan|design|architect|outline|structure|break\s*down|steps|order)\b/i,
|
|
22
|
-
agent: "soly-manager", emoji: "\ud83d\udccb",
|
|
23
|
-
why: "decompose into ordered steps, identify risks" },
|
|
24
|
-
{ pattern: /\b(review|audit|check|adversarial|critique|find\s+bugs|qa)\b/i,
|
|
25
|
-
agent: "reviewer", emoji: "\ud83d\udc40",
|
|
26
|
-
why: "adversarial review of correctness, security, style" },
|
|
27
|
-
{ pattern: /\b(oracle|decision|tradeoff|compare|which\s+approach|is\s+this\s+wise|drift)\b/i,
|
|
28
|
-
agent: "oracle", emoji: "\ud83d\udd2e",
|
|
29
|
-
why: "decision consistency, hidden assumptions, drift detection" },
|
|
30
|
-
{ pattern: /\b(debug|bug|fix|crash|error|stack\s*trace|repro|why\s+is\s+this\s+broken)\b/i,
|
|
31
|
-
agent: "soly-manager", emoji: "\ud83d\udc1e",
|
|
32
|
-
why: "isolated bug investigation with minimal repro" },
|
|
33
|
-
{ pattern: /\b(test|tests|coverage|spec|assert)\b/i,
|
|
34
|
-
agent: "soly-manager", emoji: "\ud83e\uddea",
|
|
35
|
-
why: "test-only work, never modifies prod code" },
|
|
36
|
-
{ pattern: /\b(refactor|clean\s*up|simplify|extract|rename|restructure|no\s+behavior\s+change)\b/i,
|
|
37
|
-
agent: "soly-manager", emoji: "\ud83d\udd04",
|
|
38
|
-
why: "pure refactoring, behavior-preserving" },
|
|
39
|
-
{ pattern: /\b(document|docs|readme|jsdoc|comment|annotate)\b/i,
|
|
40
|
-
agent: "soly-manager", emoji: "\ud83d\udcdd",
|
|
41
|
-
why: "doc updates, READMEs, inline annotations" },
|
|
42
|
-
{ pattern: /\b(implement|build|write\s+code|add\s+feature|create\s+the)\b/i,
|
|
43
|
-
agent: "worker", emoji: "\u26a1",
|
|
44
|
-
why: "generic implementation with all tools" },
|
|
45
|
-
{ pattern: /\b(orchestrate|coordinate|dispatch|chain|run\s+in\s+parallel|first\s+.+\s+then)\b/i,
|
|
46
|
-
agent: "soly-manager", emoji: "\ud83e\udd1d",
|
|
47
|
-
why: "multi-agent orchestration" },
|
|
48
|
-
// Russian keywords (loose match — Russian words inflect heavily; we match
|
|
49
|
-
// word stems, accepting some false positives as the cost of broader coverage)
|
|
50
|
-
{ pattern: /(изуч|исслед|разузн|найди\s+инфу|research|investigate|find\s+out)/i,
|
|
51
|
-
agent: "researcher", emoji: "\ud83d\udcda",
|
|
52
|
-
why: "external docs, ecosystem behavior, primary sources" },
|
|
53
|
-
{ pattern: /(где\s+это|где\s+находит|find\s+all|locate)/i,
|
|
54
|
-
agent: "scout", emoji: "\ud83d\udd0d",
|
|
55
|
-
why: "codebase recon, patterns, file locations" },
|
|
56
|
-
{ pattern: /(спланир|plan|design|architect)/i,
|
|
57
|
-
agent: "soly-manager", emoji: "\ud83d\udccb",
|
|
58
|
-
why: "decompose into ordered steps, identify risks" },
|
|
59
|
-
{ pattern: /(проверь|ревью|аудит|review|audit)/i,
|
|
60
|
-
agent: "reviewer", emoji: "\ud83d\udc40",
|
|
61
|
-
why: "adversarial review of correctness, security, style" },
|
|
62
|
-
{ pattern: /(решени|выбор|decision|tradeoff|drift)/i,
|
|
63
|
-
agent: "oracle", emoji: "\ud83d\udd2e",
|
|
64
|
-
why: "decision consistency, hidden assumptions, drift detection" },
|
|
65
|
-
{ pattern: /(баг|ошибк|почему\s+(?:падает|ломает)|debug|bug|crash|stack\s*trace|repro)/i,
|
|
66
|
-
agent: "soly-manager", emoji: "\ud83d\udc1e",
|
|
67
|
-
why: "isolated bug investigation with minimal repro" },
|
|
68
|
-
{ pattern: /(тест|покрыт|test|coverage|spec|assert)/i,
|
|
69
|
-
agent: "soly-manager", emoji: "\ud83e\uddea",
|
|
70
|
-
why: "test-only work, never modifies prod code" },
|
|
71
|
-
{ pattern: /(рефактор|упрост|refactor|simplify|extract|restructure)/i,
|
|
72
|
-
agent: "soly-manager", emoji: "\ud83d\udd04",
|
|
73
|
-
why: "pure refactoring, behavior-preserving" },
|
|
74
|
-
{ pattern: /(документ|описани|document|readme|jsdoc)/i,
|
|
75
|
-
agent: "soly-manager", emoji: "\ud83d\udcdd",
|
|
76
|
-
why: "doc updates, READMEs, inline annotations" },
|
|
77
|
-
{ pattern: /(реализуй|сделай|напиши|создай|implement|build|add\s+feature|create\s+the)/i,
|
|
78
|
-
agent: "worker", emoji: "\u26a1",
|
|
79
|
-
why: "generic implementation with all tools" },
|
|
80
|
-
{ pattern: /(оркестрируй|координируй|orchestrate|coordinate|dispatch|chain)/i,
|
|
81
|
-
agent: "soly-manager", emoji: "\ud83e\udd1d",
|
|
82
|
-
why: "multi-agent orchestration" },
|
|
83
|
-
];
|
|
84
|
-
|
|
85
|
-
/** Heuristic: which agent does the task look like? Returns null if no
|
|
86
|
-
* pattern matches (caller should leave the current agent as-is). */
|
|
87
|
-
export function recommendAgent(taskText: string): { agent: string; emoji: string; why: string } | null {
|
|
88
|
-
for (const hint of TASK_AGENT_HINTS) {
|
|
89
|
-
if (hint.pattern.test(taskText)) {
|
|
90
|
-
return { agent: hint.agent, emoji: hint.emoji, why: hint.why };
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return null;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function buildPiSwitchSection(): string {
|
|
97
|
-
return `
|
|
98
|
-
|
|
99
|
-
## pi-switch — when to use \`/agent\`
|
|
100
|
-
|
|
101
|
-
The \`/agent\` slash command + \`Ctrl+Tab\` shortcut cycle through 4 built-in cycle agents. Use the right one for the job:
|
|
102
|
-
|
|
103
|
-
- **Read-only / no edits** (oracle, scout, reviewer): for analysis, planning, review. They won't modify files.
|
|
104
|
-
- **Write tools** (worker): for implementation.
|
|
105
|
-
- **User-defined** in \`~/.pi/agent/agents/\`: any agent the user has added — drop a markdown file with YAML frontmatter (name, description) and it joins the cycle automatically.
|
|
106
|
-
|
|
107
|
-
The current agent is shown in the footer status line as \`[emoji name]\`.
|
|
108
|
-
|
|
109
|
-
When you need a specialist for a sub-task, use the right agent via the parent LLM's \`subagent(...)\` call.
|
|
110
|
-
|
|
111
|
-
**Soly subagent:** there is exactly one — \`soly-manager\`. It's a workflow executor that switches modes (worker/debugger/tester/reviewer/refactor/documenter/oracle/planner) based on the task brief. For any soly plan execution, spawn \`soly-manager\` via \`subagent(...)\` with the task — it picks the right mode itself.
|
|
112
|
-
|
|
113
|
-
**Task → agent heuristics.** Before launching a generic \`subagent(...)\`, scan the request for these keywords:
|
|
114
|
-
|
|
115
|
-
| Keywords in request | Suggested agent | Why |
|
|
116
|
-
|---|---|---|
|
|
117
|
-
| scout, scan, map, find all, where is, locate, explore codebase, skim | 🔍 scout | codebase recon, patterns, file locations |
|
|
118
|
-
| review, audit, check, adversarial, critique, find bugs, qa | 👀 reviewer | adversarial correctness, security, style review |
|
|
119
|
-
| oracle, decision, tradeoff, compare, which approach, is this wise, drift | 🔮 oracle | decision consistency, hidden assumptions |
|
|
120
|
-
| implement, build, write code, add feature, create the, debug, fix, test, refactor, document, plan, validate | ⚡ soly-manager | workflow executor, picks mode from task brief |
|
|
121
|
-
| (anything else) | ⚡ worker | generic implementation, all tools |
|
|
122
|
-
|
|
123
|
-
For multi-step tasks, the orchestrator (you) decides which agents run and in what order. You can chain agents via \`subagent({ chain: [...] })\` or run them in parallel via parallel tasks.
|
|
124
|
-
|
|
125
|
-
DON'T:
|
|
126
|
-
- Launch a worker for analysis (use oracle/scout/reviewer)
|
|
127
|
-
- Launch an oracle for implementation (it has no write tools)
|
|
128
|
-
- Spawn soly-worker / soly-debugger / soly-tester — there is only \`soly-manager\`
|
|
129
|
-
- Manually edit \`.soly/agent\` or \`~/.pi-switch/agent\` — use the slash command
|
|
130
|
-
`;
|
|
131
|
-
}
|