@difflab/pi 0.1.0 → 0.2.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 +13 -1
- package/agents/diffpi-copilot.md +22 -0
- package/agents/diffpi-orchestrator.md +23 -0
- package/agents/diffpi-tutor.md +20 -0
- package/agents/diffpi-worker.md +19 -0
- package/dist/assets.d.ts +2 -0
- package/dist/assets.d.ts.map +1 -0
- package/dist/config.d.ts +28 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/extensions/index.js +666 -123
- package/dist/fsx.d.ts +6 -0
- package/dist/fsx.d.ts.map +1 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +540 -98
- package/dist/modes.d.ts +59 -0
- package/dist/modes.d.ts.map +1 -0
- package/dist/pi.d.ts +21 -5
- package/dist/pi.d.ts.map +1 -1
- package/dist/setup.d.ts +7 -0
- package/dist/setup.d.ts.map +1 -1
- package/dist/tools/index.d.ts +4 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +386 -111
- package/dist/tools/modes.d.ts +4 -0
- package/dist/tools/modes.d.ts.map +1 -0
- package/dist/tools/setup.d.ts.map +1 -1
- package/package.json +2 -1
- package/skills/diffpi-setup/SKILL.md +2 -0
- package/skills/mode/SKILL.md +38 -0
package/dist/extensions/index.js
CHANGED
|
@@ -13323,11 +13323,376 @@ function rpiv_ask_user_question_default(pi) {
|
|
|
13323
13323
|
registerAskUserQuestionReconciler(pi);
|
|
13324
13324
|
}
|
|
13325
13325
|
|
|
13326
|
+
// src/modes.ts
|
|
13327
|
+
import {
|
|
13328
|
+
getAgentDir,
|
|
13329
|
+
parseFrontmatter as parseFrontmatter2
|
|
13330
|
+
} from "@earendil-works/pi-coding-agent";
|
|
13331
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
13332
|
+
import { homedir as homedir3 } from "node:os";
|
|
13333
|
+
import { basename, extname, join as join5 } from "node:path";
|
|
13334
|
+
|
|
13335
|
+
// src/assets.ts
|
|
13336
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
13337
|
+
import { dirname as dirname2, join as join3 } from "node:path";
|
|
13338
|
+
import { fileURLToPath } from "node:url";
|
|
13339
|
+
function resolveBundledAgentsDir(moduleUrl = import.meta.url) {
|
|
13340
|
+
const moduleDir = dirname2(fileURLToPath(moduleUrl));
|
|
13341
|
+
const candidates = [
|
|
13342
|
+
join3(moduleDir, "agents"),
|
|
13343
|
+
join3(moduleDir, "..", "agents"),
|
|
13344
|
+
join3(moduleDir, "..", "..", "agents")
|
|
13345
|
+
];
|
|
13346
|
+
return candidates.find((path) => existsSync2(path)) ?? candidates[1];
|
|
13347
|
+
}
|
|
13348
|
+
|
|
13349
|
+
// src/config.ts
|
|
13350
|
+
import { parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
13351
|
+
import { z } from "zod";
|
|
13352
|
+
import { homedir as homedir2 } from "node:os";
|
|
13353
|
+
import { join as join4 } from "node:path";
|
|
13354
|
+
|
|
13355
|
+
// src/fsx.ts
|
|
13356
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
13357
|
+
async function readDirectoryIfExists(path) {
|
|
13358
|
+
try {
|
|
13359
|
+
return await readdir(path, { withFileTypes: true });
|
|
13360
|
+
} catch (error) {
|
|
13361
|
+
if (isMissingPath(error))
|
|
13362
|
+
return [];
|
|
13363
|
+
throw error;
|
|
13364
|
+
}
|
|
13365
|
+
}
|
|
13366
|
+
async function readTextIfExists(path) {
|
|
13367
|
+
try {
|
|
13368
|
+
return await readFile(path, "utf8");
|
|
13369
|
+
} catch (error) {
|
|
13370
|
+
if (isMissingPath(error))
|
|
13371
|
+
return;
|
|
13372
|
+
throw error;
|
|
13373
|
+
}
|
|
13374
|
+
}
|
|
13375
|
+
function isMissingPath(error) {
|
|
13376
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
13377
|
+
}
|
|
13378
|
+
|
|
13379
|
+
// src/config.ts
|
|
13380
|
+
var modelReferenceSchema = z.string().trim().min(1);
|
|
13381
|
+
var agentConfigSchema = z.object({
|
|
13382
|
+
models: z.array(modelReferenceSchema).optional()
|
|
13383
|
+
}).strict();
|
|
13384
|
+
var diffpiConfigSchema = z.object({
|
|
13385
|
+
agents: z.record(z.string(), agentConfigSchema).optional()
|
|
13386
|
+
}).strict();
|
|
13387
|
+
function diffpiConfigPaths(homeDir = homedir2()) {
|
|
13388
|
+
const directory = join4(homeDir, ".difflab", "diffpi");
|
|
13389
|
+
return {
|
|
13390
|
+
yaml: join4(directory, "config.yaml"),
|
|
13391
|
+
json: join4(directory, "config.json")
|
|
13392
|
+
};
|
|
13393
|
+
}
|
|
13394
|
+
async function loadDiffpiConfig(options = {}) {
|
|
13395
|
+
const paths = diffpiConfigPaths(options.homeDir);
|
|
13396
|
+
for (const [format, path] of [
|
|
13397
|
+
["yaml", paths.yaml],
|
|
13398
|
+
["json", paths.json]
|
|
13399
|
+
]) {
|
|
13400
|
+
const content = await readTextIfExists(path);
|
|
13401
|
+
if (content === undefined)
|
|
13402
|
+
continue;
|
|
13403
|
+
try {
|
|
13404
|
+
const value = format === "yaml" ? parseYamlConfig(content) : JSON.parse(content);
|
|
13405
|
+
return { config: diffpiConfigSchema.parse(value ?? {}), path };
|
|
13406
|
+
} catch (error) {
|
|
13407
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
13408
|
+
throw new Error(`Invalid Diffpi config at ${path}: ${reason}`, { cause: error });
|
|
13409
|
+
}
|
|
13410
|
+
}
|
|
13411
|
+
return { config: {} };
|
|
13412
|
+
}
|
|
13413
|
+
function resolveAgentModelPreferences(agentId, profilePreferences, config) {
|
|
13414
|
+
const override = config.agents?.[agentId];
|
|
13415
|
+
if (override && Object.hasOwn(override, "models"))
|
|
13416
|
+
return [...override.models ?? []];
|
|
13417
|
+
return [...profilePreferences];
|
|
13418
|
+
}
|
|
13419
|
+
function findPreferredModel(models, preference) {
|
|
13420
|
+
const normalizedPreference = normalizeModelReference(preference);
|
|
13421
|
+
const exactReference = models.find((model) => normalizeModelReference(`${model.provider}/${model.id}`) === normalizedPreference);
|
|
13422
|
+
if (exactReference)
|
|
13423
|
+
return exactReference;
|
|
13424
|
+
const idPreference = preference.includes("/") ? preference.slice(preference.indexOf("/") + 1) : preference;
|
|
13425
|
+
const normalizedIdPreference = normalizeModelReference(idPreference);
|
|
13426
|
+
const exactId = models.find((model) => normalizeModelReference(model.id) === normalizedIdPreference);
|
|
13427
|
+
if (exactId)
|
|
13428
|
+
return exactId;
|
|
13429
|
+
const preferenceTokens = normalizedIdPreference.split("-").filter(Boolean);
|
|
13430
|
+
return models.find((model) => {
|
|
13431
|
+
const modelTokens = new Set(normalizeModelReference(model.id).split("-").filter(Boolean));
|
|
13432
|
+
return preferenceTokens.every((token) => modelTokens.has(token));
|
|
13433
|
+
});
|
|
13434
|
+
}
|
|
13435
|
+
function parseYamlConfig(content) {
|
|
13436
|
+
const document = content.replace(/^\uFEFF/, "").replace(/^---[^\S\r\n]*(?:#.*)?(?:\r?\n|$)/, "");
|
|
13437
|
+
return parseFrontmatter(`---
|
|
13438
|
+
${document}
|
|
13439
|
+
---
|
|
13440
|
+
`).frontmatter;
|
|
13441
|
+
}
|
|
13442
|
+
function normalizeModelReference(value) {
|
|
13443
|
+
return value.toLowerCase().replace(/^~/, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
13444
|
+
}
|
|
13445
|
+
|
|
13446
|
+
// src/modes.ts
|
|
13447
|
+
async function discoverAgentModes(options) {
|
|
13448
|
+
const agentDir = options.agentDir ?? getAgentDir();
|
|
13449
|
+
const homeDir = options.homeDir ?? homedir3();
|
|
13450
|
+
const modes = new Map;
|
|
13451
|
+
const diagnostics = [];
|
|
13452
|
+
await loadAgentModes(options.bundledAgentsDir ?? BUNDLED_AGENTS_DIR, "diffpi agent", modes, diagnostics);
|
|
13453
|
+
await loadAgentModes(join5(agentDir, "agents"), "user agent", modes, diagnostics);
|
|
13454
|
+
if (options.includeSkills) {
|
|
13455
|
+
await loadSkillModes(join5(homeDir, ".agents", "skills"), "user skill", modes, diagnostics);
|
|
13456
|
+
await loadSkillModes(join5(agentDir, "skills"), "pi user skill", modes, diagnostics);
|
|
13457
|
+
}
|
|
13458
|
+
if (options.projectTrusted === true) {
|
|
13459
|
+
if (options.includeSkills) {
|
|
13460
|
+
await loadSkillModes(join5(options.cwd, ".agents", "skills"), "project skill", modes, diagnostics);
|
|
13461
|
+
await loadSkillModes(join5(options.cwd, ".pi", "skills"), "pi project skill", modes, diagnostics);
|
|
13462
|
+
}
|
|
13463
|
+
await loadAgentModes(join5(options.cwd, ".agents", "agents"), "project agent", modes, diagnostics);
|
|
13464
|
+
await loadAgentModes(join5(options.cwd, ".pi", "agents"), "pi project agent", modes, diagnostics);
|
|
13465
|
+
}
|
|
13466
|
+
const userConfig = await loadDiffpiConfig({ homeDir });
|
|
13467
|
+
const configuredModes = [...modes.values()].map((mode) => ({
|
|
13468
|
+
...mode,
|
|
13469
|
+
modelPreferences: resolveAgentModelPreferences(mode.id, mode.modelPreferences, userConfig.config)
|
|
13470
|
+
}));
|
|
13471
|
+
return {
|
|
13472
|
+
modes: configuredModes.sort((left, right) => left.id.localeCompare(right.id)),
|
|
13473
|
+
diagnostics
|
|
13474
|
+
};
|
|
13475
|
+
}
|
|
13476
|
+
function resolveAgentMode(modes, requested) {
|
|
13477
|
+
const name = requested.trim();
|
|
13478
|
+
if (!name)
|
|
13479
|
+
return { ok: false, message: "Agent name is required." };
|
|
13480
|
+
const exact = modes.find((mode) => mode.id === name);
|
|
13481
|
+
if (exact)
|
|
13482
|
+
return { ok: true, active: exact, message: `Active inline agent: ${exact.id}.` };
|
|
13483
|
+
const lowerName = name.toLowerCase();
|
|
13484
|
+
const matches = modes.filter((mode) => mode.id.toLowerCase() === lowerName);
|
|
13485
|
+
if (matches.length === 1) {
|
|
13486
|
+
const active = matches[0];
|
|
13487
|
+
return { ok: true, active, message: `Active inline agent: ${active.id}.` };
|
|
13488
|
+
}
|
|
13489
|
+
if (matches.length > 1) {
|
|
13490
|
+
return {
|
|
13491
|
+
ok: false,
|
|
13492
|
+
message: `Inline agent "${name}" is ambiguous. Use one of: ${matches.map((mode) => mode.id).join(", ")}.`
|
|
13493
|
+
};
|
|
13494
|
+
}
|
|
13495
|
+
return { ok: false, message: `Unknown inline agent "${name}". Run /skill:mode or diffpi_modes_list.` };
|
|
13496
|
+
}
|
|
13497
|
+
function createModeController(pi, options = {}) {
|
|
13498
|
+
let active;
|
|
13499
|
+
let baseline;
|
|
13500
|
+
const updateStatus = (ctx) => {
|
|
13501
|
+
ctx.ui.setStatus(MODE_STATUS_KEY, active ? `mode: ${active.id}` : undefined);
|
|
13502
|
+
};
|
|
13503
|
+
const list = (ctx, listOptions = {}) => discoverAgentModes({
|
|
13504
|
+
cwd: ctx.cwd,
|
|
13505
|
+
agentDir: options.agentDir,
|
|
13506
|
+
bundledAgentsDir: options.bundledAgentsDir,
|
|
13507
|
+
homeDir: options.homeDir,
|
|
13508
|
+
projectTrusted: ctx.isProjectTrusted(),
|
|
13509
|
+
includeSkills: listOptions.includeSkills
|
|
13510
|
+
});
|
|
13511
|
+
return {
|
|
13512
|
+
list,
|
|
13513
|
+
async set(agent, ctx) {
|
|
13514
|
+
const catalog = await list(ctx, { includeSkills: agent.includes(":") });
|
|
13515
|
+
const result = resolveAgentMode(catalog.modes, agent);
|
|
13516
|
+
if (!result.ok || !result.active)
|
|
13517
|
+
return result;
|
|
13518
|
+
baseline ??= captureRuntime(pi, ctx);
|
|
13519
|
+
if (active && baseline)
|
|
13520
|
+
await restoreRuntime(pi, baseline, ctx);
|
|
13521
|
+
active = result.active;
|
|
13522
|
+
const runtimeMessage = await applyModeRuntime(pi, active, ctx);
|
|
13523
|
+
pi.appendEntry(MODE_STATE_ENTRY, { active, baseline });
|
|
13524
|
+
updateStatus(ctx);
|
|
13525
|
+
return { ...result, message: `${result.message} ${runtimeMessage}` };
|
|
13526
|
+
},
|
|
13527
|
+
async unset(ctx) {
|
|
13528
|
+
if (!active)
|
|
13529
|
+
return { ok: true, message: "Inline agent is already clear." };
|
|
13530
|
+
if (baseline)
|
|
13531
|
+
await restoreRuntime(pi, baseline, ctx);
|
|
13532
|
+
active = undefined;
|
|
13533
|
+
pi.appendEntry(MODE_STATE_ENTRY, { active: null });
|
|
13534
|
+
baseline = undefined;
|
|
13535
|
+
updateStatus(ctx);
|
|
13536
|
+
return { ok: true, message: "Inline agent cleared. The previous model, thinking, tools, and prompt resume." };
|
|
13537
|
+
},
|
|
13538
|
+
async restore(ctx) {
|
|
13539
|
+
const previousActive = active;
|
|
13540
|
+
const previousBaseline = baseline;
|
|
13541
|
+
const entry = [...ctx.sessionManager.getBranch()].reverse().find((candidate) => candidate.type === "custom" && candidate.customType === MODE_STATE_ENTRY);
|
|
13542
|
+
const restored = entry?.data?.active;
|
|
13543
|
+
const restoredBaseline = entry?.data?.baseline;
|
|
13544
|
+
if (isAgentModeSnapshot(restored)) {
|
|
13545
|
+
active = restored;
|
|
13546
|
+
baseline = isModeBaseline(restoredBaseline) ? restoredBaseline : previousBaseline;
|
|
13547
|
+
await applyModeRuntime(pi, active, ctx);
|
|
13548
|
+
} else {
|
|
13549
|
+
if (previousActive && previousBaseline)
|
|
13550
|
+
pi.setActiveTools(previousBaseline.tools);
|
|
13551
|
+
active = undefined;
|
|
13552
|
+
baseline = undefined;
|
|
13553
|
+
}
|
|
13554
|
+
updateStatus(ctx);
|
|
13555
|
+
},
|
|
13556
|
+
apply(systemPrompt) {
|
|
13557
|
+
if (!active)
|
|
13558
|
+
return systemPrompt;
|
|
13559
|
+
if (active.promptStrategy === "replace")
|
|
13560
|
+
return active.systemPrompt;
|
|
13561
|
+
return `${systemPrompt}
|
|
13562
|
+
|
|
13563
|
+
## Active inline agent: ${active.label}
|
|
13564
|
+
|
|
13565
|
+
${active.systemPrompt}`;
|
|
13566
|
+
},
|
|
13567
|
+
getActive() {
|
|
13568
|
+
return active;
|
|
13569
|
+
}
|
|
13570
|
+
};
|
|
13571
|
+
}
|
|
13572
|
+
var MODE_STATE_ENTRY = "diffpi-mode-state";
|
|
13573
|
+
var MODE_STATUS_KEY = "diffpi-mode";
|
|
13574
|
+
var MODE_CONTROL_TOOLS = ["ask_user_question", "diffpi_modes_list", "diffpi_modes_set", "diffpi_modes_unset"];
|
|
13575
|
+
var BUNDLED_AGENTS_DIR = resolveBundledAgentsDir();
|
|
13576
|
+
var THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
13577
|
+
async function applyModeRuntime(pi, mode, ctx) {
|
|
13578
|
+
let selectedModel;
|
|
13579
|
+
if (mode.modelPreferences.length > 0) {
|
|
13580
|
+
const scoped = ctx.scopedModels.length > 0 ? ctx.scopedModels.map((entry) => entry.model) : undefined;
|
|
13581
|
+
const availableModels = scoped ?? ctx.modelRegistry.getAvailable();
|
|
13582
|
+
for (const preference of mode.modelPreferences) {
|
|
13583
|
+
const model = findPreferredModel(availableModels, preference);
|
|
13584
|
+
if (model && await pi.setModel(model)) {
|
|
13585
|
+
selectedModel = `${model.provider}/${model.id}`;
|
|
13586
|
+
break;
|
|
13587
|
+
}
|
|
13588
|
+
}
|
|
13589
|
+
}
|
|
13590
|
+
if (mode.thinkingLevel)
|
|
13591
|
+
pi.setThinkingLevel(mode.thinkingLevel);
|
|
13592
|
+
if (mode.tools.length > 0) {
|
|
13593
|
+
const availableTools = new Set(pi.getAllTools().map((tool) => tool.name));
|
|
13594
|
+
const selectedTools = [...new Set([...mode.tools, ...MODE_CONTROL_TOOLS])].filter((tool) => availableTools.has(tool));
|
|
13595
|
+
if (selectedTools.length > 0)
|
|
13596
|
+
pi.setActiveTools(selectedTools);
|
|
13597
|
+
}
|
|
13598
|
+
const parts = [];
|
|
13599
|
+
if (mode.modelPreferences.length > 0) {
|
|
13600
|
+
parts.push(selectedModel ? `Model: ${selectedModel}.` : "No preferred model was available; kept the current model.");
|
|
13601
|
+
}
|
|
13602
|
+
if (mode.thinkingLevel)
|
|
13603
|
+
parts.push(`Thinking: ${mode.thinkingLevel}.`);
|
|
13604
|
+
if (mode.tools.length > 0)
|
|
13605
|
+
parts.push("Applied the profile tool set.");
|
|
13606
|
+
return parts.join(" ") || "The profile changes the prompt only.";
|
|
13607
|
+
}
|
|
13608
|
+
async function restoreRuntime(pi, state, ctx) {
|
|
13609
|
+
if (state.model) {
|
|
13610
|
+
const model = ctx.modelRegistry.find(state.model.provider, state.model.id);
|
|
13611
|
+
if (model)
|
|
13612
|
+
await pi.setModel(model);
|
|
13613
|
+
}
|
|
13614
|
+
pi.setThinkingLevel(state.thinkingLevel);
|
|
13615
|
+
pi.setActiveTools(state.tools);
|
|
13616
|
+
}
|
|
13617
|
+
function captureRuntime(pi, ctx) {
|
|
13618
|
+
return {
|
|
13619
|
+
model: ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
|
|
13620
|
+
thinkingLevel: pi.getThinkingLevel(),
|
|
13621
|
+
tools: pi.getActiveTools()
|
|
13622
|
+
};
|
|
13623
|
+
}
|
|
13624
|
+
async function loadSkillModes(skillsDir, source, modes, diagnostics) {
|
|
13625
|
+
const entries = await readDirectoryIfExists(skillsDir);
|
|
13626
|
+
for (const entry of entries.filter((item) => item.isDirectory()).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
13627
|
+
await loadAgentModes(join5(skillsDir, entry.name, "agents"), `${source} ${entry.name}`, modes, diagnostics, entry.name);
|
|
13628
|
+
}
|
|
13629
|
+
}
|
|
13630
|
+
async function loadAgentModes(directory, source, modes, diagnostics, skillName) {
|
|
13631
|
+
const entries = await readDirectoryIfExists(directory);
|
|
13632
|
+
for (const entry of entries.filter((item) => item.isFile() && item.name.endsWith(".md")).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
13633
|
+
const path = join5(directory, entry.name);
|
|
13634
|
+
try {
|
|
13635
|
+
const content = await readFile2(path, "utf8");
|
|
13636
|
+
const { frontmatter, body } = parseFrontmatter2(content.startsWith("\uFEFF") ? content.slice(1) : content);
|
|
13637
|
+
if (frontmatter.enabled === false || frontmatter.inline === false)
|
|
13638
|
+
continue;
|
|
13639
|
+
const name = getFrontmatterText(frontmatter.name) ?? basename(path, extname(path));
|
|
13640
|
+
const systemPrompt = body.trim();
|
|
13641
|
+
if (!name || name.includes(":") || !systemPrompt) {
|
|
13642
|
+
diagnostics.push(`Skipped ${path}: agent name must not contain ":" and prompt body is required.`);
|
|
13643
|
+
continue;
|
|
13644
|
+
}
|
|
13645
|
+
const id = skillName ? `${skillName}:${name}` : name;
|
|
13646
|
+
modes.set(id, {
|
|
13647
|
+
id,
|
|
13648
|
+
label: getFrontmatterText(frontmatter.display_name) ?? name,
|
|
13649
|
+
description: getFrontmatterText(frontmatter.description) ?? `Inline agent from ${basename(path)}`,
|
|
13650
|
+
systemPrompt,
|
|
13651
|
+
promptStrategy: frontmatter.prompt_mode === "append" ? "append" : "replace",
|
|
13652
|
+
modelPreferences: [
|
|
13653
|
+
...getFrontmatterList(frontmatter.model),
|
|
13654
|
+
...getFrontmatterList(frontmatter.model_fallbacks)
|
|
13655
|
+
],
|
|
13656
|
+
thinkingLevel: getThinkingLevel(frontmatter.thinking),
|
|
13657
|
+
tools: getFrontmatterList(frontmatter.tools),
|
|
13658
|
+
source,
|
|
13659
|
+
sourcePath: path
|
|
13660
|
+
});
|
|
13661
|
+
} catch (error) {
|
|
13662
|
+
diagnostics.push(`Skipped ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
13663
|
+
}
|
|
13664
|
+
}
|
|
13665
|
+
}
|
|
13666
|
+
function getFrontmatterText(value) {
|
|
13667
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
13668
|
+
}
|
|
13669
|
+
function getFrontmatterList(value) {
|
|
13670
|
+
const values = Array.isArray(value) ? value : typeof value === "string" ? value.split(",") : [];
|
|
13671
|
+
return values.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
|
|
13672
|
+
}
|
|
13673
|
+
function getThinkingLevel(value) {
|
|
13674
|
+
const level = getFrontmatterText(value);
|
|
13675
|
+
return level && THINKING_LEVELS.has(level) ? level : undefined;
|
|
13676
|
+
}
|
|
13677
|
+
function isAgentModeSnapshot(value) {
|
|
13678
|
+
if (!value || typeof value !== "object")
|
|
13679
|
+
return false;
|
|
13680
|
+
const candidate = value;
|
|
13681
|
+
return typeof candidate.id === "string" && typeof candidate.label === "string" && typeof candidate.description === "string" && typeof candidate.systemPrompt === "string" && (candidate.promptStrategy === "append" || candidate.promptStrategy === "replace") && Array.isArray(candidate.modelPreferences) && (candidate.thinkingLevel === undefined || THINKING_LEVELS.has(candidate.thinkingLevel)) && Array.isArray(candidate.tools) && typeof candidate.source === "string" && typeof candidate.sourcePath === "string";
|
|
13682
|
+
}
|
|
13683
|
+
function isModeBaseline(value) {
|
|
13684
|
+
if (!value || typeof value !== "object")
|
|
13685
|
+
return false;
|
|
13686
|
+
const candidate = value;
|
|
13687
|
+
const model = candidate.model;
|
|
13688
|
+
return (model === undefined || typeof model.provider === "string" && typeof model.id === "string") && candidate.thinkingLevel !== undefined && THINKING_LEVELS.has(candidate.thinkingLevel) && Array.isArray(candidate.tools) && candidate.tools.every((tool) => typeof tool === "string");
|
|
13689
|
+
}
|
|
13690
|
+
|
|
13326
13691
|
// src/tools/reload.ts
|
|
13327
13692
|
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
13328
|
-
import { z } from "zod";
|
|
13329
|
-
var reloadParametersSchema =
|
|
13330
|
-
var reloadParameters =
|
|
13693
|
+
import { z as z2 } from "zod";
|
|
13694
|
+
var reloadParametersSchema = z2.object({});
|
|
13695
|
+
var reloadParameters = z2.toJSONSchema(reloadParametersSchema, { io: "input" });
|
|
13331
13696
|
function createDiffpiReloadTool(pi) {
|
|
13332
13697
|
return defineTool({
|
|
13333
13698
|
name: "diffpi_reload",
|
|
@@ -13350,21 +13715,122 @@ function createDiffpiReloadTool(pi) {
|
|
|
13350
13715
|
});
|
|
13351
13716
|
}
|
|
13352
13717
|
|
|
13353
|
-
// src/tools/
|
|
13718
|
+
// src/tools/modes.ts
|
|
13354
13719
|
import { defineTool as defineTool2 } from "@earendil-works/pi-coding-agent";
|
|
13355
|
-
import { z as
|
|
13720
|
+
import { z as z3 } from "zod";
|
|
13721
|
+
var emptyParametersSchema = z3.object({});
|
|
13722
|
+
var emptyParameters = z3.toJSONSchema(emptyParametersSchema, { io: "input" });
|
|
13723
|
+
var listParametersSchema = z3.object({
|
|
13724
|
+
includeSkills: z3.boolean().optional().describe("Include skill-owned agents using skill:agent ids.")
|
|
13725
|
+
});
|
|
13726
|
+
var listParameters = z3.toJSONSchema(listParametersSchema, { io: "input" });
|
|
13727
|
+
var setParametersSchema = z3.object({
|
|
13728
|
+
agent: z3.string().trim().min(1).describe("Inline agent id from diffpi_modes_list.")
|
|
13729
|
+
});
|
|
13730
|
+
var setParameters = z3.toJSONSchema(setParametersSchema, { io: "input" });
|
|
13731
|
+
function createModeTools(controller) {
|
|
13732
|
+
return [
|
|
13733
|
+
defineTool2({
|
|
13734
|
+
name: "diffpi_modes_list",
|
|
13735
|
+
label: "diffpi modes list",
|
|
13736
|
+
description: "List inline agents shared with the subagent plugin, optionally including skill-owned agents.",
|
|
13737
|
+
promptSnippet: "List inline agents before selecting one when the requested agent is unclear",
|
|
13738
|
+
promptGuidelines: [
|
|
13739
|
+
"Call diffpi_modes_list when the user asks which inline agents are available.",
|
|
13740
|
+
"Set includeSkills to true only when the user asks for skill agents or runs /skill:mode --include-skills.",
|
|
13741
|
+
"Inline mode applies the profile prompt, first available preferred model, thinking level, and available tools."
|
|
13742
|
+
],
|
|
13743
|
+
parameters: listParameters,
|
|
13744
|
+
executionMode: "parallel",
|
|
13745
|
+
async execute(_toolCallId, input, _signal, _onUpdate, ctx) {
|
|
13746
|
+
const params = listParametersSchema.parse(input);
|
|
13747
|
+
const catalog = await controller.list(ctx, { includeSkills: params.includeSkills });
|
|
13748
|
+
return {
|
|
13749
|
+
content: [{ type: "text", text: formatCatalog(catalog, controller.getActive()?.id) }],
|
|
13750
|
+
details: { active: controller.getActive()?.id, catalog }
|
|
13751
|
+
};
|
|
13752
|
+
}
|
|
13753
|
+
}),
|
|
13754
|
+
defineTool2({
|
|
13755
|
+
name: "diffpi_modes_set",
|
|
13756
|
+
label: "diffpi modes set",
|
|
13757
|
+
description: "Set a validated available agent as the inline behavioral agent for subsequent chat turns.",
|
|
13758
|
+
promptSnippet: "Set the inline behavioral agent only after the user chooses one",
|
|
13759
|
+
promptGuidelines: [
|
|
13760
|
+
"Call diffpi_modes_set only after the user explicitly selects an agent.",
|
|
13761
|
+
"Use the exact skill:agent id for a skill-owned agent.",
|
|
13762
|
+
"The selected prompt takes effect on the next model turn."
|
|
13763
|
+
],
|
|
13764
|
+
parameters: setParameters,
|
|
13765
|
+
executionMode: "sequential",
|
|
13766
|
+
async execute(_toolCallId, input, _signal, _onUpdate, ctx) {
|
|
13767
|
+
const params = setParametersSchema.parse(input);
|
|
13768
|
+
const result = await controller.set(params.agent, ctx);
|
|
13769
|
+
if (!result.ok)
|
|
13770
|
+
throw new Error(result.message);
|
|
13771
|
+
return {
|
|
13772
|
+
content: [{ type: "text", text: `${result.message} The prompt takes effect on the next turn.` }],
|
|
13773
|
+
details: { active: result.active }
|
|
13774
|
+
};
|
|
13775
|
+
}
|
|
13776
|
+
}),
|
|
13777
|
+
defineTool2({
|
|
13778
|
+
name: "diffpi_modes_unset",
|
|
13779
|
+
label: "diffpi modes unset",
|
|
13780
|
+
description: "Clear the inline behavioral agent and restore default Pi prompting for subsequent turns.",
|
|
13781
|
+
promptSnippet: "Clear the inline agent when the user asks for default behavior",
|
|
13782
|
+
promptGuidelines: [
|
|
13783
|
+
"Call diffpi_modes_unset only when the user explicitly asks to clear the active inline agent."
|
|
13784
|
+
],
|
|
13785
|
+
parameters: emptyParameters,
|
|
13786
|
+
executionMode: "sequential",
|
|
13787
|
+
async execute(_toolCallId, input, _signal, _onUpdate, ctx) {
|
|
13788
|
+
emptyParametersSchema.parse(input);
|
|
13789
|
+
const result = await controller.unset(ctx);
|
|
13790
|
+
return {
|
|
13791
|
+
content: [{ type: "text", text: result.message }],
|
|
13792
|
+
details: { active: controller.getActive()?.id }
|
|
13793
|
+
};
|
|
13794
|
+
}
|
|
13795
|
+
})
|
|
13796
|
+
];
|
|
13797
|
+
}
|
|
13798
|
+
function formatCatalog(catalog, active) {
|
|
13799
|
+
const lines = [`Active inline agent: ${active ?? "default"}.`, "", "Available inline agents:"];
|
|
13800
|
+
for (const mode of catalog.modes) {
|
|
13801
|
+
const runtime = [mode.modelPreferences[0], mode.thinkingLevel].filter(Boolean).join(", ");
|
|
13802
|
+
lines.push(`- ${mode.id} [${mode.promptStrategy}${runtime ? `; ${runtime}` : ""}] — ${sanitize(mode.description)} (${mode.source})`);
|
|
13803
|
+
}
|
|
13804
|
+
if (catalog.diagnostics.length > 0) {
|
|
13805
|
+
lines.push("", "Skipped agent files:");
|
|
13806
|
+
for (const diagnostic of catalog.diagnostics)
|
|
13807
|
+
lines.push(`- ${sanitize(diagnostic)}`);
|
|
13808
|
+
}
|
|
13809
|
+
lines.push("", "Inline mode applies the profile prompt, preferred available model, thinking level, and tool set.");
|
|
13810
|
+
return lines.join(`
|
|
13811
|
+
`);
|
|
13812
|
+
}
|
|
13813
|
+
function sanitize(value) {
|
|
13814
|
+
return value.replace(/[\r\n\t]+/g, " ").replace(/\s+/g, " ").trim();
|
|
13815
|
+
}
|
|
13816
|
+
|
|
13817
|
+
// src/tools/setup.ts
|
|
13818
|
+
import { defineTool as defineTool3 } from "@earendil-works/pi-coding-agent";
|
|
13819
|
+
import { z as z4 } from "zod";
|
|
13356
13820
|
|
|
13357
13821
|
// src/setup.ts
|
|
13358
|
-
import {
|
|
13359
|
-
import {
|
|
13822
|
+
import { parseFrontmatter as parseFrontmatter3 } from "@earendil-works/pi-coding-agent";
|
|
13823
|
+
import { readdir as readdir2, readFile as readFile5 } from "node:fs/promises";
|
|
13824
|
+
import { homedir as homedir7 } from "node:os";
|
|
13825
|
+
import { basename as basename3, join as join10 } from "node:path";
|
|
13360
13826
|
|
|
13361
13827
|
// src/mcp.ts
|
|
13362
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
13363
|
-
import { homedir as
|
|
13364
|
-
import { dirname as
|
|
13828
|
+
import { mkdir, readFile as readFile3, writeFile } from "node:fs/promises";
|
|
13829
|
+
import { homedir as homedir4 } from "node:os";
|
|
13830
|
+
import { dirname as dirname3, join as join6 } from "node:path";
|
|
13365
13831
|
var mcp = {
|
|
13366
|
-
globalConfigPath(homeDir =
|
|
13367
|
-
return
|
|
13832
|
+
globalConfigPath(homeDir = homedir4()) {
|
|
13833
|
+
return join6(homeDir, ".config", "mcp", "mcp.json");
|
|
13368
13834
|
},
|
|
13369
13835
|
async serversEnsure(servers, options = {}) {
|
|
13370
13836
|
const path = options.path ?? mcp.globalConfigPath();
|
|
@@ -13377,7 +13843,7 @@ var mcp = {
|
|
|
13377
13843
|
const next = { ...current, mcpServers: nextServers };
|
|
13378
13844
|
const changed = JSON.stringify(current) !== JSON.stringify(next);
|
|
13379
13845
|
if (changed && !options.dryRun) {
|
|
13380
|
-
await mkdir(
|
|
13846
|
+
await mkdir(dirname3(path), { recursive: true });
|
|
13381
13847
|
await writeFile(path, `${JSON.stringify(next, null, 2)}
|
|
13382
13848
|
`, "utf8");
|
|
13383
13849
|
}
|
|
@@ -13407,7 +13873,7 @@ function getParsedConfig(content, path) {
|
|
|
13407
13873
|
}
|
|
13408
13874
|
async function getOptionalFile(path) {
|
|
13409
13875
|
try {
|
|
13410
|
-
return await
|
|
13876
|
+
return await readFile3(path, "utf8");
|
|
13411
13877
|
} catch (error) {
|
|
13412
13878
|
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
13413
13879
|
return;
|
|
@@ -13419,14 +13885,14 @@ function isRecord(value) {
|
|
|
13419
13885
|
}
|
|
13420
13886
|
|
|
13421
13887
|
// src/mise.ts
|
|
13422
|
-
import { mkdir as mkdir2, readFile as
|
|
13423
|
-
import { homedir as
|
|
13424
|
-
import { basename, dirname as
|
|
13888
|
+
import { mkdir as mkdir2, readFile as readFile4, writeFile as writeFile2 } from "node:fs/promises";
|
|
13889
|
+
import { homedir as homedir5 } from "node:os";
|
|
13890
|
+
import { basename as basename2, dirname as dirname4, join as join8 } from "node:path";
|
|
13425
13891
|
|
|
13426
13892
|
// src/process.ts
|
|
13427
13893
|
import { constants } from "node:fs";
|
|
13428
13894
|
import { access } from "node:fs/promises";
|
|
13429
|
-
import { delimiter, join as
|
|
13895
|
+
import { delimiter, join as join7 } from "node:path";
|
|
13430
13896
|
import { spawn as spawn2 } from "node:child_process";
|
|
13431
13897
|
var MAX_CAPTURED_OUTPUT_LENGTH = 65536;
|
|
13432
13898
|
async function findExecutable(name) {
|
|
@@ -13441,7 +13907,7 @@ async function findExecutable(name) {
|
|
|
13441
13907
|
for (const directory of (process.env.PATH ?? "").split(delimiter)) {
|
|
13442
13908
|
if (!directory)
|
|
13443
13909
|
continue;
|
|
13444
|
-
const candidate =
|
|
13910
|
+
const candidate = join7(directory, name);
|
|
13445
13911
|
try {
|
|
13446
13912
|
await access(candidate, constants.X_OK);
|
|
13447
13913
|
return candidate;
|
|
@@ -13488,11 +13954,11 @@ var mise = {
|
|
|
13488
13954
|
return findExecutable(name);
|
|
13489
13955
|
},
|
|
13490
13956
|
async install(options = {}) {
|
|
13491
|
-
const homeDir = options.homeDir ??
|
|
13957
|
+
const homeDir = options.homeDir ?? homedir5();
|
|
13492
13958
|
const platform = options.platform ?? process.platform;
|
|
13493
13959
|
if (platform === "win32")
|
|
13494
13960
|
throw new Error("Automatic mise installation supports macOS and Linux only.");
|
|
13495
|
-
const installedPath =
|
|
13961
|
+
const installedPath = join8(homeDir, ".local", "bin", "mise");
|
|
13496
13962
|
if (options.dryRun)
|
|
13497
13963
|
return installedPath;
|
|
13498
13964
|
await runChecked("sh", ["-c", "curl -fsSL https://mise.run | sh"]);
|
|
@@ -13502,8 +13968,8 @@ var mise = {
|
|
|
13502
13968
|
return executable;
|
|
13503
13969
|
},
|
|
13504
13970
|
async hookEnsure(executable, options = {}) {
|
|
13505
|
-
const homeDir = options.homeDir ??
|
|
13506
|
-
const hook = getShellHook(
|
|
13971
|
+
const homeDir = options.homeDir ?? homedir5();
|
|
13972
|
+
const hook = getShellHook(basename2(options.shell ?? process.env.SHELL ?? ""), executable, homeDir);
|
|
13507
13973
|
const current = await getOptionalFile2(hook.path);
|
|
13508
13974
|
if (current.includes(MISE_HOOK_START))
|
|
13509
13975
|
return { path: hook.path, changed: false, planned: false };
|
|
@@ -13512,7 +13978,7 @@ var mise = {
|
|
|
13512
13978
|
const separator = current.length === 0 || current.endsWith(`
|
|
13513
13979
|
`) ? "" : `
|
|
13514
13980
|
`;
|
|
13515
|
-
await mkdir2(
|
|
13981
|
+
await mkdir2(dirname4(hook.path), { recursive: true });
|
|
13516
13982
|
await writeFile2(hook.path, `${current}${separator}${hook.content}`, "utf8");
|
|
13517
13983
|
return { path: hook.path, changed: true, planned: false };
|
|
13518
13984
|
},
|
|
@@ -13530,7 +13996,7 @@ var mise = {
|
|
|
13530
13996
|
async toolInstallLocal(executable, specification, cwd = process.cwd()) {
|
|
13531
13997
|
await runChecked(executable, ["use", "--path", cwd, specification], { cwd });
|
|
13532
13998
|
},
|
|
13533
|
-
async toolUpdateAllGlobal(executable, homeDir =
|
|
13999
|
+
async toolUpdateAllGlobal(executable, homeDir = homedir5()) {
|
|
13534
14000
|
await runChecked(executable, ["upgrade"], { cwd: homeDir });
|
|
13535
14001
|
}
|
|
13536
14002
|
};
|
|
@@ -13539,7 +14005,7 @@ function getShellHook(shell, executable, homeDir) {
|
|
|
13539
14005
|
switch (shell.toLowerCase()) {
|
|
13540
14006
|
case "zsh":
|
|
13541
14007
|
return {
|
|
13542
|
-
path:
|
|
14008
|
+
path: join8(homeDir, ".zshrc"),
|
|
13543
14009
|
content: `${MISE_HOOK_START}
|
|
13544
14010
|
eval "$(${command} activate zsh)"
|
|
13545
14011
|
${MISE_HOOK_END}
|
|
@@ -13547,7 +14013,7 @@ ${MISE_HOOK_END}
|
|
|
13547
14013
|
};
|
|
13548
14014
|
case "fish":
|
|
13549
14015
|
return {
|
|
13550
|
-
path:
|
|
14016
|
+
path: join8(homeDir, ".config", "fish", "config.fish"),
|
|
13551
14017
|
content: `${MISE_HOOK_START}
|
|
13552
14018
|
${command} activate fish | source
|
|
13553
14019
|
${MISE_HOOK_END}
|
|
@@ -13556,7 +14022,7 @@ ${MISE_HOOK_END}
|
|
|
13556
14022
|
case "nu":
|
|
13557
14023
|
case "nushell":
|
|
13558
14024
|
return {
|
|
13559
|
-
path:
|
|
14025
|
+
path: join8(homeDir, ".config", "nushell", "config.nu"),
|
|
13560
14026
|
content: `${MISE_HOOK_START}
|
|
13561
14027
|
let mise_bin = ${command}
|
|
13562
14028
|
let mise_path = $nu.default-config-dir | path join mise.nu
|
|
@@ -13567,7 +14033,7 @@ ${MISE_HOOK_END}
|
|
|
13567
14033
|
};
|
|
13568
14034
|
case "xonsh":
|
|
13569
14035
|
return {
|
|
13570
|
-
path:
|
|
14036
|
+
path: join8(homeDir, ".xonshrc"),
|
|
13571
14037
|
content: `${MISE_HOOK_START}
|
|
13572
14038
|
execx($(${command} activate xonsh))
|
|
13573
14039
|
${MISE_HOOK_END}
|
|
@@ -13575,7 +14041,7 @@ ${MISE_HOOK_END}
|
|
|
13575
14041
|
};
|
|
13576
14042
|
case "elvish":
|
|
13577
14043
|
return {
|
|
13578
|
-
path:
|
|
14044
|
+
path: join8(homeDir, ".config", "elvish", "rc.elv"),
|
|
13579
14045
|
content: `${MISE_HOOK_START}
|
|
13580
14046
|
var mise: = (ns [&])
|
|
13581
14047
|
eval (${command} activate elvish | slurp) &ns=$mise: &on-end={|ns| set mise: = $ns }
|
|
@@ -13586,7 +14052,7 @@ ${MISE_HOOK_END}
|
|
|
13586
14052
|
case "pwsh":
|
|
13587
14053
|
case "powershell":
|
|
13588
14054
|
return {
|
|
13589
|
-
path:
|
|
14055
|
+
path: join8(homeDir, ".config", "powershell", "Microsoft.PowerShell_profile.ps1"),
|
|
13590
14056
|
content: `${MISE_HOOK_START}
|
|
13591
14057
|
(& ${command} activate pwsh) | Out-String | Invoke-Expression
|
|
13592
14058
|
${MISE_HOOK_END}
|
|
@@ -13595,7 +14061,7 @@ ${MISE_HOOK_END}
|
|
|
13595
14061
|
case "bash":
|
|
13596
14062
|
default:
|
|
13597
14063
|
return {
|
|
13598
|
-
path:
|
|
14064
|
+
path: join8(homeDir, ".bashrc"),
|
|
13599
14065
|
content: `${MISE_HOOK_START}
|
|
13600
14066
|
eval "$(${command} activate bash)"
|
|
13601
14067
|
${MISE_HOOK_END}
|
|
@@ -13634,7 +14100,7 @@ function isVersionAtLeast(version, minimumVersion) {
|
|
|
13634
14100
|
}
|
|
13635
14101
|
async function getOptionalFile2(path) {
|
|
13636
14102
|
try {
|
|
13637
|
-
return await
|
|
14103
|
+
return await readFile4(path, "utf8");
|
|
13638
14104
|
} catch (error) {
|
|
13639
14105
|
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
13640
14106
|
return "";
|
|
@@ -13646,79 +14112,86 @@ function getShellQuoted(value) {
|
|
|
13646
14112
|
}
|
|
13647
14113
|
|
|
13648
14114
|
// src/pi.ts
|
|
13649
|
-
import { mkdir as mkdir3,
|
|
13650
|
-
import { homedir as
|
|
13651
|
-
import { dirname as
|
|
14115
|
+
import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
|
|
14116
|
+
import { homedir as homedir6 } from "node:os";
|
|
14117
|
+
import { dirname as dirname5, join as join9 } from "node:path";
|
|
13652
14118
|
var pi = {
|
|
13653
|
-
|
|
13654
|
-
|
|
13655
|
-
|
|
13656
|
-
|
|
13657
|
-
|
|
13658
|
-
|
|
13659
|
-
|
|
13660
|
-
|
|
14119
|
+
executableCheck: findPiExecutable,
|
|
14120
|
+
packageList: listPiPackages,
|
|
14121
|
+
packageCheck: hasPiPackage,
|
|
14122
|
+
packageInstall: installPiPackage,
|
|
14123
|
+
agentDir: resolvePiAgentDir,
|
|
14124
|
+
agentEnsure: ensurePiAgent,
|
|
14125
|
+
skillCheckGlobal: checkGlobalPiSkill,
|
|
14126
|
+
skillInstallGlobal: installGlobalPiSkills,
|
|
14127
|
+
configEnsure: ensurePiConfig
|
|
14128
|
+
};
|
|
14129
|
+
async function ensurePiAgent(filename, content, agentDir = resolvePiAgentDir(), dryRun = false) {
|
|
14130
|
+
const path = join9(agentDir, "agents", filename);
|
|
14131
|
+
const currentText = await readTextIfExists(path);
|
|
14132
|
+
const changed = currentText !== content;
|
|
14133
|
+
if (changed && !dryRun) {
|
|
14134
|
+
await mkdir3(dirname5(path), { recursive: true });
|
|
14135
|
+
await writeFile3(path, content, "utf8");
|
|
14136
|
+
}
|
|
14137
|
+
return { path, changed, existed: currentText !== undefined, planned: changed && dryRun };
|
|
14138
|
+
}
|
|
14139
|
+
async function checkGlobalPiSkill(name, agentDir = resolvePiAgentDir(), sharedSkillsDir = join9(homedir6(), ".agents", "skills")) {
|
|
14140
|
+
const roots = [join9(agentDir, "skills"), sharedSkillsDir];
|
|
14141
|
+
for (const root of roots) {
|
|
14142
|
+
if (await readTextIfExists(join9(root, name, "SKILL.md")) !== undefined)
|
|
13661
14143
|
return true;
|
|
13662
|
-
return source.startsWith("https://") && listOutput.includes(source.slice("https://".length));
|
|
13663
|
-
},
|
|
13664
|
-
async packageInstall(executable, source) {
|
|
13665
|
-
await runChecked(executable, ["install", source]);
|
|
13666
|
-
},
|
|
13667
|
-
agentDir(homeDir = homedir4()) {
|
|
13668
|
-
return getAgentDir(homeDir);
|
|
13669
|
-
},
|
|
13670
|
-
async skillCheckGlobal(name, agentDir = getAgentDir(), sharedSkillsDir = join6(homedir4(), ".agents", "skills")) {
|
|
13671
|
-
const roots = [join6(agentDir, "skills"), sharedSkillsDir];
|
|
13672
|
-
for (const root of roots) {
|
|
13673
|
-
if (await getOptionalFile3(join6(root, name, "SKILL.md")) !== undefined)
|
|
13674
|
-
return true;
|
|
13675
|
-
}
|
|
13676
|
-
return false;
|
|
13677
|
-
},
|
|
13678
|
-
async skillInstallGlobal(miseExecutable, source, names) {
|
|
13679
|
-
const selection = names.flatMap((name) => ["--skill", name]);
|
|
13680
|
-
await runChecked(miseExecutable, [
|
|
13681
|
-
"x",
|
|
13682
|
-
"node@22",
|
|
13683
|
-
"--",
|
|
13684
|
-
"npx",
|
|
13685
|
-
"-y",
|
|
13686
|
-
"skills",
|
|
13687
|
-
"add",
|
|
13688
|
-
source,
|
|
13689
|
-
...selection,
|
|
13690
|
-
"--global",
|
|
13691
|
-
"--agent",
|
|
13692
|
-
"pi",
|
|
13693
|
-
"--yes"
|
|
13694
|
-
]);
|
|
13695
|
-
},
|
|
13696
|
-
async configEnsure(path, update, dryRun = false) {
|
|
13697
|
-
const currentText = await getOptionalFile3(path);
|
|
13698
|
-
const current = getParsedObject(currentText, path);
|
|
13699
|
-
const next = update(current);
|
|
13700
|
-
const changed = JSON.stringify(current) !== JSON.stringify(next);
|
|
13701
|
-
if (changed && !dryRun) {
|
|
13702
|
-
await mkdir3(dirname4(path), { recursive: true });
|
|
13703
|
-
await writeFile3(path, `${JSON.stringify(next, null, 2)}
|
|
13704
|
-
`, "utf8");
|
|
13705
|
-
}
|
|
13706
|
-
return { path, changed, existed: currentText !== undefined, planned: changed && dryRun };
|
|
13707
14144
|
}
|
|
13708
|
-
|
|
13709
|
-
function getAgentDir(homeDir = homedir4()) {
|
|
13710
|
-
return process.env.PI_CODING_AGENT_DIR ?? (process.env.XDG_CONFIG_HOME ? join6(process.env.XDG_CONFIG_HOME, "pi") : join6(homeDir, ".pi", "agent"));
|
|
14145
|
+
return false;
|
|
13711
14146
|
}
|
|
13712
|
-
async function
|
|
13713
|
-
|
|
13714
|
-
|
|
13715
|
-
|
|
13716
|
-
|
|
13717
|
-
|
|
13718
|
-
|
|
14147
|
+
async function installGlobalPiSkills(miseExecutable, source, names) {
|
|
14148
|
+
const selection = names.flatMap((name) => ["--skill", name]);
|
|
14149
|
+
await runChecked(miseExecutable, [
|
|
14150
|
+
"x",
|
|
14151
|
+
"node@22",
|
|
14152
|
+
"--",
|
|
14153
|
+
"npx",
|
|
14154
|
+
"-y",
|
|
14155
|
+
"skills",
|
|
14156
|
+
"add",
|
|
14157
|
+
source,
|
|
14158
|
+
...selection,
|
|
14159
|
+
"--global",
|
|
14160
|
+
"--agent",
|
|
14161
|
+
"pi",
|
|
14162
|
+
"--yes"
|
|
14163
|
+
]);
|
|
14164
|
+
}
|
|
14165
|
+
async function ensurePiConfig(path, update, dryRun = false) {
|
|
14166
|
+
const currentText = await readTextIfExists(path);
|
|
14167
|
+
const current = parseJsonObject(currentText, path);
|
|
14168
|
+
const next = update(current);
|
|
14169
|
+
const changed = JSON.stringify(current) !== JSON.stringify(next);
|
|
14170
|
+
if (changed && !dryRun) {
|
|
14171
|
+
await mkdir3(dirname5(path), { recursive: true });
|
|
14172
|
+
await writeFile3(path, `${JSON.stringify(next, null, 2)}
|
|
14173
|
+
`, "utf8");
|
|
13719
14174
|
}
|
|
14175
|
+
return { path, changed, existed: currentText !== undefined, planned: changed && dryRun };
|
|
14176
|
+
}
|
|
14177
|
+
async function findPiExecutable() {
|
|
14178
|
+
return findExecutable("pi");
|
|
14179
|
+
}
|
|
14180
|
+
async function listPiPackages(executable) {
|
|
14181
|
+
return (await runChecked(executable, ["list"])).stdout;
|
|
13720
14182
|
}
|
|
13721
|
-
function
|
|
14183
|
+
function hasPiPackage(listOutput, source) {
|
|
14184
|
+
if (listOutput.includes(source))
|
|
14185
|
+
return true;
|
|
14186
|
+
return source.startsWith("https://") && listOutput.includes(source.slice("https://".length));
|
|
14187
|
+
}
|
|
14188
|
+
async function installPiPackage(executable, source) {
|
|
14189
|
+
await runChecked(executable, ["install", source]);
|
|
14190
|
+
}
|
|
14191
|
+
function resolvePiAgentDir(homeDir = homedir6()) {
|
|
14192
|
+
return process.env.PI_CODING_AGENT_DIR ?? (process.env.XDG_CONFIG_HOME ? join9(process.env.XDG_CONFIG_HOME, "pi") : join9(homeDir, ".pi", "agent"));
|
|
14193
|
+
}
|
|
14194
|
+
function parseJsonObject(content, path) {
|
|
13722
14195
|
if (!content?.trim())
|
|
13723
14196
|
return {};
|
|
13724
14197
|
try {
|
|
@@ -13760,9 +14233,10 @@ var PI_SKILL_SOURCES = [
|
|
|
13760
14233
|
{ repository: "AminBlg/SimpleEnglish", skills: ["simple-english"] }
|
|
13761
14234
|
];
|
|
13762
14235
|
var MCP_ADAPTER_PACKAGE = "npm:pi-mcp-adapter";
|
|
14236
|
+
var BUNDLED_AGENTS_DIR2 = resolveBundledAgentsDir();
|
|
13763
14237
|
async function ensureMise(options = {}) {
|
|
13764
|
-
const homeDir = options.homeDir ??
|
|
13765
|
-
const current = await mise.executableCheck() ?? await mise.executableCheck(
|
|
14238
|
+
const homeDir = options.homeDir ?? homedir7();
|
|
14239
|
+
const current = await mise.executableCheck() ?? await mise.executableCheck(join10(homeDir, ".local", "bin", "mise"));
|
|
13766
14240
|
if (current)
|
|
13767
14241
|
return { executable: current, action: createSetupAction("mise", "ready", current) };
|
|
13768
14242
|
reportProgress(options, "Installing mise");
|
|
@@ -13807,18 +14281,33 @@ async function ensureMiseDeps(miseExecutable, options = {}) {
|
|
|
13807
14281
|
async function ensurePiPlugins(options = {}) {
|
|
13808
14282
|
const actions = await ensurePiPackages(PI_PACKAGES, options);
|
|
13809
14283
|
const agentDir = options.agentDir ?? pi.agentDir(options.homeDir);
|
|
13810
|
-
const webSearch = await pi.configEnsure(
|
|
14284
|
+
const webSearch = await pi.configEnsure(join10(agentDir, "web-search.json"), (config) => ({ ...config, workflow: "auto-summary" }), options.dryRun);
|
|
13811
14285
|
actions.push(getConfigSetupAction("web search settings", webSearch));
|
|
13812
|
-
const lsp = await pi.configEnsure(
|
|
14286
|
+
const lsp = await pi.configEnsure(join10(agentDir, "pi-lsp.json"), (config) => ({
|
|
13813
14287
|
...config,
|
|
13814
14288
|
progressive: { ...getRecord(config.progressive), enabled: true, inject: "none" }
|
|
13815
14289
|
}), options.dryRun);
|
|
13816
14290
|
actions.push(getConfigSetupAction("pi-lsp settings", lsp));
|
|
13817
14291
|
return actions;
|
|
13818
14292
|
}
|
|
14293
|
+
async function ensurePiAgents(options = {}) {
|
|
14294
|
+
const agentDir = options.agentDir ?? pi.agentDir(options.homeDir);
|
|
14295
|
+
const bundledAgentsDir = options.bundledAgentsDir ?? BUNDLED_AGENTS_DIR2;
|
|
14296
|
+
const userConfig = await loadDiffpiConfig({ homeDir: options.homeDir });
|
|
14297
|
+
const entries = (await readdir2(bundledAgentsDir, { withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.startsWith("diffpi-") && entry.name.endsWith(".md")).sort((left, right) => left.name.localeCompare(right.name));
|
|
14298
|
+
const actions = [];
|
|
14299
|
+
for (const entry of entries) {
|
|
14300
|
+
const id = basename3(entry.name, ".md").replace(/^diffpi-/, "");
|
|
14301
|
+
const source = await readFile5(join10(bundledAgentsDir, entry.name), "utf8");
|
|
14302
|
+
const content = materializeAgentModels(source, id, userConfig.config, options.availableModels);
|
|
14303
|
+
const result = await pi.agentEnsure(entry.name, content, agentDir, options.dryRun);
|
|
14304
|
+
actions.push(getConfigSetupAction(`pi agent ${id}`, result));
|
|
14305
|
+
}
|
|
14306
|
+
return actions;
|
|
14307
|
+
}
|
|
13819
14308
|
async function ensurePiSkills(miseExecutable, options = {}) {
|
|
13820
14309
|
const agentDir = options.agentDir ?? pi.agentDir(options.homeDir);
|
|
13821
|
-
const sharedSkillsDir =
|
|
14310
|
+
const sharedSkillsDir = join10(options.homeDir ?? homedir7(), ".agents", "skills");
|
|
13822
14311
|
const actions = [];
|
|
13823
14312
|
for (const source of PI_SKILL_SOURCES) {
|
|
13824
14313
|
const missing = [];
|
|
@@ -13875,13 +14364,55 @@ async function setupPi(options = {}) {
|
|
|
13875
14364
|
actions.push(await ensureMiseHooks(miseResult.executable, options));
|
|
13876
14365
|
actions.push(...await ensureMiseDeps(miseResult.executable, options));
|
|
13877
14366
|
actions.push(...await ensurePiPlugins(options));
|
|
14367
|
+
actions.push(...await ensurePiAgents(options));
|
|
13878
14368
|
actions.push(...await ensurePiSkills(miseResult.executable, options));
|
|
13879
14369
|
actions.push(...await ensureMcpAdapters(miseResult.executable, options));
|
|
13880
14370
|
return {
|
|
13881
14371
|
actions,
|
|
13882
|
-
restartPi: actions
|
|
14372
|
+
restartPi: setupRequiresRestart(actions)
|
|
13883
14373
|
};
|
|
13884
14374
|
}
|
|
14375
|
+
function setupRequiresRestart(actions) {
|
|
14376
|
+
return actions.some((item) => (item.status === "installed" || item.status === "updated") && (item.name.startsWith("pi package ") || item.name.startsWith("pi agent ") || item.name.startsWith("pi skill ") || item.name === "MCP configuration" || item.name === "web search settings" || item.name === "pi-lsp settings"));
|
|
14377
|
+
}
|
|
14378
|
+
function materializeAgentModels(content, agentId, config, availableModels) {
|
|
14379
|
+
const { frontmatter } = parseFrontmatter3(content.startsWith("\uFEFF") ? content.slice(1) : content);
|
|
14380
|
+
const profilePreferences = [...getTextList(frontmatter.model), ...getTextList(frontmatter.model_fallbacks)];
|
|
14381
|
+
const preferences = resolveAgentModelPreferences(agentId, profilePreferences, config);
|
|
14382
|
+
let selectedIndex = availableModels === undefined && preferences.length > 0 ? 0 : -1;
|
|
14383
|
+
let selectedModel = selectedIndex === 0 ? preferences[0] : undefined;
|
|
14384
|
+
if (availableModels) {
|
|
14385
|
+
for (const [index, preference] of preferences.entries()) {
|
|
14386
|
+
const match = findPreferredModel(availableModels, preference);
|
|
14387
|
+
if (!match)
|
|
14388
|
+
continue;
|
|
14389
|
+
selectedIndex = index;
|
|
14390
|
+
selectedModel = `${match.provider}/${match.id}`;
|
|
14391
|
+
break;
|
|
14392
|
+
}
|
|
14393
|
+
}
|
|
14394
|
+
const fallbacks = preferences.filter((_preference, index) => index !== selectedIndex);
|
|
14395
|
+
return replaceAgentModelFields(content, selectedModel, fallbacks);
|
|
14396
|
+
}
|
|
14397
|
+
function replaceAgentModelFields(content, model, fallbacks) {
|
|
14398
|
+
const newline = content.includes(`\r
|
|
14399
|
+
`) ? `\r
|
|
14400
|
+
` : `
|
|
14401
|
+
`;
|
|
14402
|
+
const lines = content.replaceAll(`\r
|
|
14403
|
+
`, `
|
|
14404
|
+
`).split(`
|
|
14405
|
+
`);
|
|
14406
|
+
const closingDelimiter = lines.indexOf("---", 1);
|
|
14407
|
+
if (lines[0] !== "---" || closingDelimiter < 0)
|
|
14408
|
+
return content;
|
|
14409
|
+
const frontmatter = lines.slice(1, closingDelimiter).filter((line) => !/^model(?:_fallbacks)?:/.test(line));
|
|
14410
|
+
if (model)
|
|
14411
|
+
frontmatter.push(`model: ${model}`);
|
|
14412
|
+
if (fallbacks.length > 0)
|
|
14413
|
+
frontmatter.push(`model_fallbacks: ${fallbacks.join(", ")}`);
|
|
14414
|
+
return ["---", ...frontmatter, "---", ...lines.slice(closingDelimiter + 1)].join(newline);
|
|
14415
|
+
}
|
|
13885
14416
|
async function ensurePiPackages(packages, options) {
|
|
13886
14417
|
const executable = await pi.executableCheck();
|
|
13887
14418
|
if (!executable && !options.dryRun)
|
|
@@ -13903,6 +14434,10 @@ ${source}`;
|
|
|
13903
14434
|
}
|
|
13904
14435
|
return actions;
|
|
13905
14436
|
}
|
|
14437
|
+
function getTextList(value) {
|
|
14438
|
+
const values = Array.isArray(value) ? value : typeof value === "string" ? value.split(",") : [];
|
|
14439
|
+
return values.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
|
|
14440
|
+
}
|
|
13906
14441
|
function getConfigSetupAction(name, result) {
|
|
13907
14442
|
if (!result.changed)
|
|
13908
14443
|
return createSetupAction(name, "ready", result.path);
|
|
@@ -13921,11 +14456,11 @@ function getRecord(value) {
|
|
|
13921
14456
|
}
|
|
13922
14457
|
|
|
13923
14458
|
// src/tools/setup.ts
|
|
13924
|
-
var setupParametersSchema =
|
|
13925
|
-
issueTracker:
|
|
14459
|
+
var setupParametersSchema = z4.object({
|
|
14460
|
+
issueTracker: z4.enum(["none", "linear", "jira"]).default("none").describe("Issue tracker MCP server to configure. Use none unless the user explicitly selects Linear or Jira.")
|
|
13926
14461
|
});
|
|
13927
|
-
var setupParameters =
|
|
13928
|
-
var diffpiSetupTool =
|
|
14462
|
+
var setupParameters = z4.toJSONSchema(setupParametersSchema, { io: "input" });
|
|
14463
|
+
var diffpiSetupTool = defineTool3({
|
|
13929
14464
|
name: "diffpi_setup",
|
|
13930
14465
|
label: "diffpi setup",
|
|
13931
14466
|
description: "Install or repair the @difflab/pi environment. This mutates user-level tool installations and configuration files.",
|
|
@@ -13938,11 +14473,12 @@ var diffpiSetupTool = defineTool2({
|
|
|
13938
14473
|
],
|
|
13939
14474
|
parameters: setupParameters,
|
|
13940
14475
|
executionMode: "sequential",
|
|
13941
|
-
async execute(_toolCallId, input, _signal, onUpdate) {
|
|
14476
|
+
async execute(_toolCallId, input, _signal, onUpdate, ctx) {
|
|
13942
14477
|
const params = setupParametersSchema.parse(input);
|
|
13943
14478
|
const result = await setupPi({
|
|
13944
14479
|
issueTracker: params.issueTracker,
|
|
13945
14480
|
installMiseHook: true,
|
|
14481
|
+
availableModels: ctx.modelRegistry.getAvailable(),
|
|
13946
14482
|
onProgress(message) {
|
|
13947
14483
|
onUpdate?.({ content: [{ type: "text", text: message }], details: {} });
|
|
13948
14484
|
}
|
|
@@ -13950,7 +14486,7 @@ var diffpiSetupTool = defineTool2({
|
|
|
13950
14486
|
return formatResult(result, "Setup complete.");
|
|
13951
14487
|
}
|
|
13952
14488
|
});
|
|
13953
|
-
var diffpiValidateTool =
|
|
14489
|
+
var diffpiValidateTool = defineTool3({
|
|
13954
14490
|
name: "diffpi_validate",
|
|
13955
14491
|
label: "diffpi validate",
|
|
13956
14492
|
description: "Inspect the @difflab/pi environment without installing software or changing configuration files.",
|
|
@@ -13962,12 +14498,13 @@ var diffpiValidateTool = defineTool2({
|
|
|
13962
14498
|
],
|
|
13963
14499
|
parameters: setupParameters,
|
|
13964
14500
|
executionMode: "sequential",
|
|
13965
|
-
async execute(_toolCallId, input) {
|
|
14501
|
+
async execute(_toolCallId, input, _signal, _onUpdate, ctx) {
|
|
13966
14502
|
const params = setupParametersSchema.parse(input);
|
|
13967
14503
|
const result = await setupPi({
|
|
13968
14504
|
issueTracker: params.issueTracker,
|
|
13969
14505
|
installMiseHook: true,
|
|
13970
|
-
dryRun: true
|
|
14506
|
+
dryRun: true,
|
|
14507
|
+
availableModels: ctx.modelRegistry.getAvailable()
|
|
13971
14508
|
});
|
|
13972
14509
|
const incomplete = result.actions.some((item) => item.status === "planned");
|
|
13973
14510
|
return formatResult(result, incomplete ? "Setup is incomplete." : "Setup is ready.");
|
|
@@ -13988,31 +14525,37 @@ ${lines.join(`
|
|
|
13988
14525
|
}
|
|
13989
14526
|
|
|
13990
14527
|
// src/tools/index.ts
|
|
13991
|
-
function createPiTools(pi) {
|
|
13992
|
-
return [diffpiSetupTool, diffpiValidateTool, createDiffpiReloadTool(pi)];
|
|
14528
|
+
function createPiTools(pi, modes) {
|
|
14529
|
+
return [diffpiSetupTool, diffpiValidateTool, createDiffpiReloadTool(pi), ...createModeTools(modes)];
|
|
13993
14530
|
}
|
|
13994
14531
|
|
|
13995
14532
|
// extensions/index.ts
|
|
13996
14533
|
var RELOAD_COMMAND = "diffpi-reload";
|
|
13997
|
-
var
|
|
14534
|
+
var SKILL_ROUTING_GUIDANCE = `## Skill and tool routing
|
|
13998
14535
|
Use the docs-search skill before web search for library or API documentation.
|
|
13999
14536
|
Use docs-manage when the required documentation is absent or stale.
|
|
14000
|
-
Use fetch-url for a one-time read that does not belong in the documentation index
|
|
14537
|
+
Use fetch-url for a one-time read that does not belong in the documentation index.
|
|
14538
|
+
Use the context-mode skill for commands, tests, builds, logs, API responses, and other output that can be large.
|
|
14539
|
+
Use ctx_execute or ctx_execute_file to analyze that output, and use ctx_fetch_and_index with ctx_search for external documentation.`;
|
|
14001
14540
|
function difflabPiExtension(pi) {
|
|
14002
14541
|
rpiv_ask_user_question_default(pi);
|
|
14542
|
+
const modes = createModeController(pi);
|
|
14003
14543
|
pi.registerCommand(RELOAD_COMMAND, {
|
|
14004
14544
|
description: "Reload extensions, skills, prompts, themes, and context files",
|
|
14005
14545
|
handler: async (_args, ctx) => {
|
|
14006
14546
|
await ctx.reload();
|
|
14007
14547
|
}
|
|
14008
14548
|
});
|
|
14009
|
-
for (const tool of createPiTools(pi))
|
|
14549
|
+
for (const tool of createPiTools(pi, modes))
|
|
14010
14550
|
pi.registerTool(tool);
|
|
14011
|
-
pi.on("
|
|
14012
|
-
|
|
14551
|
+
pi.on("session_start", async (_event, ctx) => modes.restore(ctx));
|
|
14552
|
+
pi.on("session_tree", async (_event, ctx) => modes.restore(ctx));
|
|
14553
|
+
pi.on("before_agent_start", (event) => {
|
|
14554
|
+
const defaultPrompt = `${event.systemPrompt}
|
|
14013
14555
|
|
|
14014
|
-
${
|
|
14015
|
-
|
|
14556
|
+
${SKILL_ROUTING_GUIDANCE}`;
|
|
14557
|
+
return { systemPrompt: modes.apply(defaultPrompt) };
|
|
14558
|
+
});
|
|
14016
14559
|
}
|
|
14017
14560
|
export {
|
|
14018
14561
|
difflabPiExtension as default
|