Merge main into model-registry

This commit is contained in:
Mario Zechner
2026-06-22 14:00:18 +02:00
220 changed files with 10488 additions and 4354 deletions
@@ -1,12 +1,12 @@
{
"name": "pi-extension-custom-provider",
"version": "0.79.1",
"version": "0.79.10",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pi-extension-custom-provider",
"version": "0.79.1",
"version": "0.79.10",
"dependencies": {
"@anthropic-ai/sdk": "^0.52.0"
}
@@ -1,7 +1,7 @@
{
"name": "pi-extension-custom-provider-anthropic",
"private": true,
"version": "0.79.1",
"version": "0.79.10",
"type": "module",
"scripts": {
"clean": "echo 'nothing to clean'",
@@ -1,7 +1,7 @@
{
"name": "pi-extension-custom-provider-gitlab-duo",
"private": true,
"version": "0.79.1",
"version": "0.79.10",
"type": "module",
"scripts": {
"clean": "echo 'nothing to clean'",
@@ -1,12 +1,12 @@
{
"name": "pi-extension-gondolin",
"version": "0.79.1",
"version": "0.79.10",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pi-extension-gondolin",
"version": "0.79.1",
"version": "0.79.10",
"dependencies": {
"@earendil-works/gondolin": "0.12.0"
}
@@ -1,7 +1,7 @@
{
"name": "pi-extension-gondolin",
"private": true,
"version": "0.79.1",
"version": "0.79.10",
"type": "module",
"scripts": {
"clean": "echo 'nothing to clean'",
@@ -4,7 +4,7 @@ Read-only exploration mode for safe code analysis.
## Features
- **Read-only tools**: Restricts available tools to read, bash, grep, find, ls, question
- **Built-in write tools disabled**: Disables edit/write while preserving other active tools
- **Bash allowlist**: Only read-only bash commands are allowed
- **Plan extraction**: Extracts numbered steps from `Plan:` sections
- **Progress tracking**: Widget shows completion status during execution
@@ -37,7 +37,8 @@ Plan:
## How It Works
### Plan Mode (Read-Only)
- Only read-only tools available
- Built-in edit/write tools disabled
- Other active tools remain available
- Bash commands filtered through allowlist
- Agent creates a plan without making changes
@@ -2,7 +2,7 @@
* Plan Mode Extension
*
* Read-only exploration mode for safe code analysis.
* When enabled, only read-only tools are available.
* When enabled, built-in write tools are disabled.
*
* Features:
* - /plan command or Ctrl+Alt+P to toggle
@@ -21,6 +21,15 @@ import { extractTodoItems, isSafeCommand, markCompletedSteps, type TodoItem } fr
// Tools
const PLAN_MODE_TOOLS = ["read", "bash", "grep", "find", "ls", "questionnaire"];
const NORMAL_MODE_TOOLS = ["read", "bash", "edit", "write"];
const PLAN_MODE_DISABLED_TOOLS = new Set<string>(["edit", "write"]);
const PLAN_MANAGED_TOOLS = new Set<string>([...PLAN_MODE_TOOLS, ...NORMAL_MODE_TOOLS]);
interface PlanModeState {
enabled: boolean;
todos?: TodoItem[];
executing?: boolean;
toolsBeforePlanMode?: string[];
}
// Type guard for assistant messages
function isAssistantMessage(m: AgentMessage): m is AssistantMessage {
@@ -39,6 +48,7 @@ export default function planModeExtension(pi: ExtensionAPI): void {
let planModeEnabled = false;
let executionMode = false;
let todoItems: TodoItem[] = [];
let toolsBeforePlanMode: string[] | undefined;
pi.registerFlag("plan", {
description: "Start in plan mode (read-only exploration)",
@@ -73,19 +83,34 @@ export default function planModeExtension(pi: ExtensionAPI): void {
}
}
function togglePlanMode(ctx: ExtensionContext): void {
planModeEnabled = !planModeEnabled;
executionMode = false;
todoItems = [];
function uniqueToolNames(toolNames: string[]): string[] {
return [...new Set(toolNames)];
}
if (planModeEnabled) {
pi.setActiveTools(PLAN_MODE_TOOLS);
ctx.ui.notify(`Plan mode enabled. Tools: ${PLAN_MODE_TOOLS.join(", ")}`);
} else {
pi.setActiveTools(NORMAL_MODE_TOOLS);
ctx.ui.notify("Plan mode disabled. Full access restored.");
function getPlanModeTools(activeToolNames: string[]): string[] {
return uniqueToolNames([
...activeToolNames.filter((name) => !PLAN_MODE_DISABLED_TOOLS.has(name)),
...PLAN_MODE_TOOLS,
]);
}
function getNormalModeTools(activeToolNames: string[]): string[] {
return uniqueToolNames([
...NORMAL_MODE_TOOLS,
...activeToolNames.filter((name) => !PLAN_MANAGED_TOOLS.has(name)),
]);
}
function enablePlanModeTools(): void {
if (toolsBeforePlanMode === undefined) {
toolsBeforePlanMode = pi.getActiveTools();
}
updateStatus(ctx);
pi.setActiveTools(getPlanModeTools(toolsBeforePlanMode));
}
function restoreNormalModeTools(): void {
pi.setActiveTools(toolsBeforePlanMode ?? getNormalModeTools(pi.getActiveTools()));
toolsBeforePlanMode = undefined;
}
function persistState(): void {
@@ -93,9 +118,26 @@ export default function planModeExtension(pi: ExtensionAPI): void {
enabled: planModeEnabled,
todos: todoItems,
executing: executionMode,
toolsBeforePlanMode,
});
}
function togglePlanMode(ctx: ExtensionContext): void {
planModeEnabled = !planModeEnabled;
executionMode = false;
todoItems = [];
if (planModeEnabled) {
enablePlanModeTools();
ctx.ui.notify("Plan mode enabled. Built-in write tools disabled.");
} else {
restoreNormalModeTools();
ctx.ui.notify("Plan mode disabled. Full access restored.");
}
updateStatus(ctx);
persistState();
}
pi.registerCommand("plan", {
description: "Toggle plan mode (read-only exploration)",
handler: async (_args, ctx) => togglePlanMode(ctx),
@@ -165,8 +207,8 @@ export default function planModeExtension(pi: ExtensionAPI): void {
You are in plan mode - a read-only exploration mode for safe code analysis.
Restrictions:
- You can only use: read, bash, grep, find, ls, questionnaire
- You CANNOT use: edit, write (file modifications are disabled)
- Built-in edit and write tools are disabled
- Other currently active tools remain available
- Bash is restricted to an allowlist of read-only commands
Ask clarifying questions using the questionnaire tool.
@@ -228,7 +270,6 @@ After completing a step, include a [DONE:n] tag in your response.`,
);
executionMode = false;
todoItems = [];
pi.setActiveTools(NORMAL_MODE_TOOLS);
updateStatus(ctx);
persistState(); // Save cleared state so resume doesn't restore old execution mode
}
@@ -246,43 +287,51 @@ After completing a step, include a [DONE:n] tag in your response.`,
}
}
if (todoItems.length === 0) return;
persistState();
// Show plan steps and prompt for next action
if (todoItems.length > 0) {
const todoListText = todoItems.map((t, i) => `${i + 1}. ☐ ${t.text}`).join("\n");
pi.sendMessage(
{
customType: "plan-todo-list",
content: `**Plan Steps (${todoItems.length}):**\n\n${todoListText}`,
display: true,
},
{ triggerTurn: false },
);
}
const todoListText = todoItems.map((t, i) => `${i + 1}. ☐ ${t.text}`).join("\n");
const planTodoListMessage = {
customType: "plan-todo-list",
content: `**Plan Steps (${todoItems.length}):**\n\n${todoListText}`,
display: true,
};
const choice = await ctx.ui.select("Plan mode - what next?", [
todoItems.length > 0 ? "Execute the plan (track progress)" : "Execute the plan",
"Execute the plan (track progress)",
"Stay in plan mode",
"Refine the plan",
]);
if (choice?.startsWith("Execute")) {
planModeEnabled = false;
executionMode = todoItems.length > 0;
pi.setActiveTools(NORMAL_MODE_TOOLS);
updateStatus(ctx);
const firstTodoItem = todoItems[0];
if (!firstTodoItem) return;
const execMessage =
todoItems.length > 0
? `Execute the plan. Start with: ${todoItems[0].text}`
: "Execute the plan you just created.";
planModeEnabled = false;
executionMode = true;
restoreNormalModeTools();
updateStatus(ctx);
persistState();
const remainingList = todoItems.map((t) => `${t.step}. ${t.text}`).join("\n");
const execMessage = `Execute the plan.
Remaining steps:
${remainingList}
Start with: ${firstTodoItem.text}
After completing a step, include a [DONE:n] tag in your response.`;
pi.sendMessage(planTodoListMessage, { deliverAs: "followUp" });
pi.sendMessage(
{ customType: "plan-mode-execute", content: execMessage, display: true },
{ triggerTurn: true },
{ triggerTurn: true, deliverAs: "followUp" },
);
} else if (choice === "Refine the plan") {
const refinement = await ctx.ui.editor("Refine the plan:", "");
if (refinement?.trim()) {
pi.sendUserMessage(refinement.trim());
pi.sendMessage(planTodoListMessage, { deliverAs: "followUp" });
pi.sendUserMessage(refinement.trim(), { deliverAs: "followUp" });
}
}
});
@@ -298,12 +347,13 @@ After completing a step, include a [DONE:n] tag in your response.`,
// Restore persisted state
const planModeEntry = entries
.filter((e: { type: string; customType?: string }) => e.type === "custom" && e.customType === "plan-mode")
.pop() as { data?: { enabled: boolean; todos?: TodoItem[]; executing?: boolean } } | undefined;
.pop() as { data?: PlanModeState } | undefined;
if (planModeEntry?.data) {
planModeEnabled = planModeEntry.data.enabled ?? planModeEnabled;
todoItems = planModeEntry.data.todos ?? todoItems;
executionMode = planModeEntry.data.executing ?? executionMode;
toolsBeforePlanMode = planModeEntry.data.toolsBeforePlanMode ?? toolsBeforePlanMode;
}
// On resume: re-scan messages to rebuild completion state
@@ -333,7 +383,7 @@ After completing a step, include a [DONE:n] tag in your response.`,
}
if (planModeEnabled) {
pi.setActiveTools(PLAN_MODE_TOOLS);
enablePlanModeTools();
}
updateStatus(ctx);
});
@@ -42,7 +42,7 @@ import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import type { Api, Model } from "@earendil-works/pi-ai";
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
import { DynamicBorder, getAgentDir } from "@earendil-works/pi-coding-agent";
import { CONFIG_DIR_NAME, DynamicBorder, getAgentDir } from "@earendil-works/pi-coding-agent";
import { Container, Key, type SelectItem, SelectList, Text } from "@earendil-works/pi-tui";
// Preset configuration
@@ -69,7 +69,7 @@ interface PresetsConfig {
*/
function loadPresets(cwd: string): PresetsConfig {
const globalPath = join(getAgentDir(), "presets.json");
const projectPath = join(cwd, ".pi", "presets.json");
const projectPath = join(cwd, CONFIG_DIR_NAME, "presets.json");
let globalPresets: PresetsConfig = {};
let projectPresets: PresetsConfig = {};
@@ -200,7 +200,10 @@ export default function presetExtension(pi: ExtensionAPI) {
const presetNames = Object.keys(presets);
if (presetNames.length === 0) {
ctx.ui.notify("No presets defined. Add presets to ~/.pi/agent/presets.json or .pi/presets.json", "warning");
ctx.ui.notify(
`No presets defined. Add presets to ${join(getAgentDir(), "presets.json")} or ${join(ctx.cwd, CONFIG_DIR_NAME, "presets.json")}`,
"warning",
);
return;
}
@@ -308,7 +311,10 @@ export default function presetExtension(pi: ExtensionAPI) {
async function cyclePreset(ctx: ExtensionContext): Promise<void> {
const presetNames = getPresetOrder();
if (presetNames.length === 0) {
ctx.ui.notify("No presets defined. Add presets to ~/.pi/agent/presets.json or .pi/presets.json", "warning");
ctx.ui.notify(
`No presets defined. Add presets to ${join(getAgentDir(), "presets.json")} or ${join(ctx.cwd, CONFIG_DIR_NAME, "presets.json")}`,
"warning",
);
return;
}
@@ -1,18 +1,18 @@
import { appendFileSync } from "node:fs";
import { join } from "node:path";
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { CONFIG_DIR_NAME, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
export default function (pi: ExtensionAPI) {
const logFile = join(process.cwd(), ".pi", "provider-payload.log");
pi.on("before_provider_request", (event) => {
pi.on("before_provider_request", (event, ctx) => {
const logFile = join(ctx.cwd, CONFIG_DIR_NAME, "provider-payload.log");
appendFileSync(logFile, `${JSON.stringify(event.payload, null, 2)}\n\n`, "utf8");
// Optional: replace the payload instead of only logging it.
// return { ...event.payload, temperature: 0 };
});
pi.on("after_provider_response", (event) => {
pi.on("after_provider_response", (event, ctx) => {
const logFile = join(ctx.cwd, CONFIG_DIR_NAME, "provider-payload.log");
appendFileSync(logFile, `[${event.status}] ${JSON.stringify(event.headers)}\n\n`, "utf8");
});
}
@@ -5,7 +5,15 @@
*/
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { Editor, type EditorTheme, Key, matchesKey, Text, truncateToWidth } from "@earendil-works/pi-tui";
import {
Editor,
type EditorTheme,
Key,
matchesKey,
Text,
visibleWidth,
wrapTextWithAnsi,
} from "@earendil-works/pi-tui";
import { Type } from "typebox";
interface OptionWithDesc {
@@ -139,10 +147,27 @@ export default function question(pi: ExtensionAPI) {
if (cachedLines) return cachedLines;
const lines: string[] = [];
const add = (s: string) => lines.push(truncateToWidth(s, width));
const renderWidth = Math.max(1, width);
add(theme.fg("accent", "─".repeat(width)));
add(theme.fg("text", ` ${params.question}`));
function addWrapped(text: string) {
lines.push(...wrapTextWithAnsi(text, renderWidth));
}
function addWrappedWithPrefix(prefix: string, text: string) {
const prefixWidth = visibleWidth(prefix);
if (prefixWidth >= renderWidth) {
addWrapped(prefix + text);
return;
}
const wrapped = wrapTextWithAnsi(text, renderWidth - prefixWidth);
const continuationPrefix = " ".repeat(prefixWidth);
for (let i = 0; i < wrapped.length; i++) {
lines.push(`${i === 0 ? prefix : continuationPrefix}${wrapped[i]}`);
}
}
lines.push(theme.fg("accent", "─".repeat(renderWidth)));
addWrappedWithPrefix(" ", theme.fg("text", params.question));
lines.push("");
for (let i = 0; i < allOptions.length; i++) {
@@ -150,36 +175,32 @@ export default function question(pi: ExtensionAPI) {
const selected = i === optionIndex;
const isOther = opt.isOther === true;
const prefix = selected ? theme.fg("accent", "> ") : " ";
const label = `${i + 1}. ${opt.label}${isOther && editMode ? " ✎" : ""}`;
const color = selected || (isOther && editMode) ? "accent" : "text";
if (isOther && editMode) {
add(prefix + theme.fg("accent", `${i + 1}. ${opt.label}`));
} else if (selected) {
add(prefix + theme.fg("accent", `${i + 1}. ${opt.label}`));
} else {
add(` ${theme.fg("text", `${i + 1}. ${opt.label}`)}`);
}
addWrappedWithPrefix(prefix, theme.fg(color, label));
// Show description if present
if (opt.description) {
add(` ${theme.fg("muted", opt.description)}`);
addWrappedWithPrefix(" ", theme.fg("muted", opt.description));
}
}
if (editMode) {
lines.push("");
add(theme.fg("muted", " Your answer:"));
for (const line of editor.render(width - 2)) {
add(` ${line}`);
addWrappedWithPrefix(" ", theme.fg("muted", "Your answer:"));
for (const line of editor.render(Math.max(1, renderWidth - 2))) {
lines.push(` ${line}`);
}
}
lines.push("");
if (editMode) {
add(theme.fg("dim", " Enter to submit • Esc to go back"));
addWrappedWithPrefix(" ", theme.fg("dim", "Enter to submit • Esc to go back"));
} else {
add(theme.fg("dim", " ↑↓ navigate • Enter to select • Esc to cancel"));
addWrappedWithPrefix(" ", theme.fg("dim", "↑↓ navigate • Enter to select • Esc to cancel"));
}
add(theme.fg("accent", "─".repeat(width)));
lines.push(theme.fg("accent", "─".repeat(renderWidth)));
cachedLines = lines;
return lines;
@@ -6,7 +6,15 @@
*/
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { Editor, type EditorTheme, Key, matchesKey, Text, truncateToWidth } from "@earendil-works/pi-tui";
import {
Editor,
type EditorTheme,
Key,
matchesKey,
Text,
visibleWidth,
wrapTextWithAnsi,
} from "@earendil-works/pi-tui";
import { Type } from "typebox";
// Types
@@ -259,13 +267,28 @@ export default function questionnaire(pi: ExtensionAPI) {
if (cachedLines) return cachedLines;
const lines: string[] = [];
const renderWidth = Math.max(1, width);
const q = currentQuestion();
const opts = currentOptions();
// Helper to add truncated line
const add = (s: string) => lines.push(truncateToWidth(s, width));
function addWrapped(text: string) {
lines.push(...wrapTextWithAnsi(text, renderWidth));
}
add(theme.fg("accent", "─".repeat(width)));
function addWrappedWithPrefix(prefix: string, text: string) {
const prefixWidth = visibleWidth(prefix);
if (prefixWidth >= renderWidth) {
addWrapped(prefix + text);
return;
}
const wrapped = wrapTextWithAnsi(text, renderWidth - prefixWidth);
const continuationPrefix = " ".repeat(prefixWidth);
for (let i = 0; i < wrapped.length; i++) {
lines.push(`${i === 0 ? prefix : continuationPrefix}${wrapped[i]}`);
}
}
lines.push(theme.fg("accent", "─".repeat(renderWidth)));
// Tab bar (multi-question only)
if (isMulti) {
@@ -287,7 +310,7 @@ export default function questionnaire(pi: ExtensionAPI) {
? theme.bg("selectedBg", theme.fg("text", submitText))
: theme.fg(canSubmit ? "success" : "dim", submitText);
tabs.push(`${submitStyled}`);
add(` ${tabs.join("")}`);
addWrappedWithPrefix(" ", tabs.join(""));
lines.push("");
}
@@ -298,54 +321,52 @@ export default function questionnaire(pi: ExtensionAPI) {
const selected = i === optionIndex;
const isOther = opt.isOther === true;
const prefix = selected ? theme.fg("accent", "> ") : " ";
const color = selected ? "accent" : "text";
// Mark "Type something" differently when in input mode
if (isOther && inputMode) {
add(prefix + theme.fg("accent", `${i + 1}. ${opt.label}`));
} else {
add(prefix + theme.fg(color, `${i + 1}. ${opt.label}`));
}
const label = `${i + 1}. ${opt.label}${isOther && inputMode ? "" : ""}`;
const color = selected || (isOther && inputMode) ? "accent" : "text";
addWrappedWithPrefix(prefix, theme.fg(color, label));
if (opt.description) {
add(` ${theme.fg("muted", opt.description)}`);
addWrappedWithPrefix(" ", theme.fg("muted", opt.description));
}
}
}
// Content
if (inputMode && q) {
add(theme.fg("text", ` ${q.prompt}`));
addWrappedWithPrefix(" ", theme.fg("text", q.prompt));
lines.push("");
// Show options for reference
renderOptions();
lines.push("");
add(theme.fg("muted", " Your answer:"));
for (const line of editor.render(width - 2)) {
add(` ${line}`);
addWrappedWithPrefix(" ", theme.fg("muted", "Your answer:"));
for (const line of editor.render(Math.max(1, renderWidth - 2))) {
lines.push(` ${line}`);
}
lines.push("");
add(theme.fg("dim", " Enter to submit • Esc to cancel"));
addWrappedWithPrefix(" ", theme.fg("dim", "Enter to submit • Esc to cancel"));
} else if (currentTab === questions.length) {
add(theme.fg("accent", theme.bold(" Ready to submit")));
addWrappedWithPrefix(" ", theme.fg("accent", theme.bold("Ready to submit")));
lines.push("");
for (const question of questions) {
const answer = answers.get(question.id);
if (answer) {
const prefix = answer.wasCustom ? "(wrote) " : "";
add(`${theme.fg("muted", ` ${question.label}: `)}${theme.fg("text", prefix + answer.label)}`);
const summary = `${theme.fg("muted", `${question.label}: `)}${theme.fg("text", prefix + answer.label)}`;
addWrappedWithPrefix(" ", summary);
}
}
lines.push("");
if (allAnswered()) {
add(theme.fg("success", " Press Enter to submit"));
addWrappedWithPrefix(" ", theme.fg("success", "Press Enter to submit"));
} else {
const missing = questions
.filter((q) => !answers.has(q.id))
.map((q) => q.label)
.join(", ");
add(theme.fg("warning", ` Unanswered: ${missing}`));
addWrappedWithPrefix(" ", theme.fg("warning", `Unanswered: ${missing}`));
}
} else if (q) {
add(theme.fg("text", ` ${q.prompt}`));
addWrappedWithPrefix(" ", theme.fg("text", q.prompt));
lines.push("");
renderOptions();
}
@@ -353,11 +374,11 @@ export default function questionnaire(pi: ExtensionAPI) {
lines.push("");
if (!inputMode) {
const help = isMulti
? " Tab/←→ navigate • ↑↓ select • Enter confirm • Esc cancel"
: " ↑↓ navigate • Enter select • Esc cancel";
add(theme.fg("dim", help));
? "Tab/←→ navigate • ↑↓ select • Enter confirm • Esc cancel"
: "↑↓ navigate • Enter select • Esc cancel";
addWrappedWithPrefix(" ", theme.fg("dim", help));
}
add(theme.fg("accent", "─".repeat(width)));
lines.push(theme.fg("accent", "─".repeat(renderWidth)));
cachedLines = lines;
return lines;
@@ -400,7 +421,7 @@ export default function questionnaire(pi: ExtensionAPI) {
let text = theme.fg("toolTitle", theme.bold("questionnaire "));
text += theme.fg("muted", `${count} question${count !== 1 ? "s" : ""}`);
if (labels) {
text += theme.fg("dim", ` (${truncateToWidth(labels, 40)})`);
text += theme.fg("dim", ` (${labels})`);
}
return new Text(text, 0, 0);
},
@@ -46,7 +46,7 @@ import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { SandboxManager, type SandboxRuntimeConfig } from "@anthropic-ai/sandbox-runtime";
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { type BashOperations, createBashTool, getAgentDir } from "@earendil-works/pi-coding-agent";
import { type BashOperations, CONFIG_DIR_NAME, createBashTool, getAgentDir } from "@earendil-works/pi-coding-agent";
interface SandboxConfig extends SandboxRuntimeConfig {
enabled?: boolean;
@@ -77,7 +77,7 @@ const DEFAULT_CONFIG: SandboxConfig = {
};
function loadConfig(cwd: string): SandboxConfig {
const projectConfigPath = join(cwd, ".pi", "sandbox.json");
const projectConfigPath = join(cwd, CONFIG_DIR_NAME, "sandbox.json");
const globalConfigPath = join(getAgentDir(), "extensions", "sandbox.json");
let globalConfig: Partial<SandboxConfig> = {};
@@ -1,12 +1,12 @@
{
"name": "pi-extension-sandbox",
"version": "1.9.1",
"version": "1.9.10",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pi-extension-sandbox",
"version": "1.9.1",
"version": "1.9.10",
"dependencies": {
"@anthropic-ai/sandbox-runtime": "^0.0.26"
}
@@ -1,7 +1,7 @@
{
"name": "pi-extension-sandbox",
"private": true,
"version": "1.9.1",
"version": "1.9.10",
"type": "module",
"scripts": {
"clean": "echo 'nothing to clean'",
@@ -4,7 +4,7 @@
import * as fs from "node:fs";
import * as path from "node:path";
import { getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
import { CONFIG_DIR_NAME, getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
export type AgentScope = "user" | "project" | "both";
@@ -85,7 +85,7 @@ function isDirectory(p: string): boolean {
function findNearestProjectAgentsDir(cwd: string): string | null {
let currentDir = cwd;
while (true) {
const candidate = path.join(currentDir, ".pi", "agents");
const candidate = path.join(currentDir, CONFIG_DIR_NAME, "agents");
if (isDirectory(candidate)) return candidate;
const parentDir = path.dirname(currentDir);
@@ -19,7 +19,13 @@ import * as path from "node:path";
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
import type { Message } from "@earendil-works/pi-ai";
import { StringEnum } from "@earendil-works/pi-ai";
import { type ExtensionAPI, getMarkdownTheme, withFileMutationQueue } from "@earendil-works/pi-coding-agent";
import {
CONFIG_DIR_NAME,
type ExtensionAPI,
getAgentDir,
getMarkdownTheme,
withFileMutationQueue,
} from "@earendil-works/pi-coding-agent";
import { Container, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
import { Type } from "typebox";
import { type AgentConfig, type AgentScope, discoverAgents } from "./agents.ts";
@@ -458,8 +464,8 @@ export default function (pi: ExtensionAPI) {
description: [
"Delegate tasks to specialized subagents with isolated context.",
"Modes: single (agent + task), parallel (tasks array), chain (sequential with {previous} placeholder).",
'Default agent scope is "user" (from ~/.pi/agent/agents).',
'To enable project-local agents in .pi/agents, set agentScope: "both" (or "project").',
`Default agent scope is "user" (from ${path.join(getAgentDir(), "agents")}).`,
`To enable project-local agents in ${CONFIG_DIR_NAME}/agents, set agentScope: "both" (or "project").`,
].join(" "),
parameters: SubagentParams,
@@ -1,12 +1,12 @@
{
"name": "pi-extension-with-deps",
"version": "0.79.1",
"version": "0.79.10",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pi-extension-with-deps",
"version": "0.79.1",
"version": "0.79.10",
"dependencies": {
"ms": "^2.1.3"
},
@@ -1,7 +1,7 @@
{
"name": "pi-extension-with-deps",
"private": true,
"version": "0.79.1",
"version": "0.79.10",
"type": "module",
"scripts": {
"clean": "echo 'nothing to clean'",