fix(coding-agent): remove process-cwd tool singletons and use tool-name allowlists
- switch SDK/CLI tool selection to name-based allowlists - apply allowlists across built-in, extension, and SDK tools - remove ambient process.cwd defaults from core tooling and resource helpers - update tests, examples, and TUI callers for explicit cwd plumbing - add regression coverage for extension tool filtering closes #3452 closes #2835
This commit is contained in:
@@ -741,11 +741,16 @@ Customize the inline working indicator shown while pi is streaming a response.
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Static indicator
|
// Static indicator
|
||||||
ctx.ui.setWorkingIndicator({ frames: ["●"] });
|
ctx.ui.setWorkingIndicator({ frames: [ctx.ui.theme.fg("accent", "●")] });
|
||||||
|
|
||||||
// Custom animated indicator
|
// Custom animated indicator
|
||||||
ctx.ui.setWorkingIndicator({
|
ctx.ui.setWorkingIndicator({
|
||||||
frames: ["·", "•", "●", "•"],
|
frames: [
|
||||||
|
ctx.ui.theme.fg("dim", "·"),
|
||||||
|
ctx.ui.theme.fg("muted", "•"),
|
||||||
|
ctx.ui.theme.fg("accent", "●"),
|
||||||
|
ctx.ui.theme.fg("muted", "•"),
|
||||||
|
],
|
||||||
intervalMs: 120,
|
intervalMs: 120,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -756,7 +761,7 @@ ctx.ui.setWorkingIndicator({ frames: [] });
|
|||||||
ctx.ui.setWorkingIndicator();
|
ctx.ui.setWorkingIndicator();
|
||||||
```
|
```
|
||||||
|
|
||||||
This only affects the normal streaming working indicator. Compaction and retry loaders keep their built-in styling.
|
This only affects the normal streaming working indicator. Compaction and retry loaders keep their built-in styling. Custom frames are rendered verbatim, so extensions must add their own colors when needed.
|
||||||
|
|
||||||
**Examples:** [working-indicator.ts](../examples/extensions/working-indicator.ts)
|
**Examples:** [working-indicator.ts](../examples/extensions/working-indicator.ts)
|
||||||
|
|
||||||
|
|||||||
@@ -20,31 +20,49 @@ import type { ExtensionAPI, ExtensionContext, WorkingIndicatorOptions } from "@m
|
|||||||
|
|
||||||
type WorkingIndicatorMode = "dot" | "none" | "pulse" | "spinner" | "default";
|
type WorkingIndicatorMode = "dot" | "none" | "pulse" | "spinner" | "default";
|
||||||
|
|
||||||
const SPINNER_INDICATOR: WorkingIndicatorOptions = {
|
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
||||||
frames: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
|
const PASTEL_RAINBOW = [
|
||||||
intervalMs: 80,
|
"\x1b[38;2;255;179;186m",
|
||||||
};
|
"\x1b[38;2;255;223;186m",
|
||||||
const DOT_INDICATOR: WorkingIndicatorOptions = {
|
"\x1b[38;2;255;255;186m",
|
||||||
frames: ["●"],
|
"\x1b[38;2;186;255;201m",
|
||||||
};
|
"\x1b[38;2;186;225;255m",
|
||||||
const PULSE_INDICATOR: WorkingIndicatorOptions = {
|
"\x1b[38;2;218;186;255m",
|
||||||
frames: ["·", "•", "●", "•"],
|
];
|
||||||
intervalMs: 120,
|
const RESET_FG = "\x1b[39m";
|
||||||
};
|
|
||||||
const HIDDEN_INDICATOR: WorkingIndicatorOptions = {
|
const HIDDEN_INDICATOR: WorkingIndicatorOptions = {
|
||||||
frames: [],
|
frames: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function colorize(text: string, color: string): string {
|
||||||
|
return `${color}${text}${RESET_FG}`;
|
||||||
|
}
|
||||||
|
|
||||||
function getIndicator(mode: WorkingIndicatorMode): WorkingIndicatorOptions | undefined {
|
function getIndicator(mode: WorkingIndicatorMode): WorkingIndicatorOptions | undefined {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case "dot":
|
case "dot":
|
||||||
return DOT_INDICATOR;
|
return {
|
||||||
|
frames: [colorize("●", PASTEL_RAINBOW[0])],
|
||||||
|
};
|
||||||
case "none":
|
case "none":
|
||||||
return HIDDEN_INDICATOR;
|
return HIDDEN_INDICATOR;
|
||||||
case "pulse":
|
case "pulse":
|
||||||
return PULSE_INDICATOR;
|
return {
|
||||||
|
frames: [
|
||||||
|
colorize("·", PASTEL_RAINBOW[0]),
|
||||||
|
colorize("•", PASTEL_RAINBOW[2]),
|
||||||
|
colorize("●", PASTEL_RAINBOW[4]),
|
||||||
|
colorize("•", PASTEL_RAINBOW[5]),
|
||||||
|
],
|
||||||
|
intervalMs: 120,
|
||||||
|
};
|
||||||
case "spinner":
|
case "spinner":
|
||||||
return SPINNER_INDICATOR;
|
return {
|
||||||
|
frames: SPINNER_FRAMES.map((frame, index) =>
|
||||||
|
colorize(frame, PASTEL_RAINBOW[index % PASTEL_RAINBOW.length]!),
|
||||||
|
),
|
||||||
|
intervalMs: 80,
|
||||||
|
};
|
||||||
case "default":
|
case "default":
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -66,7 +84,7 @@ function describeMode(mode: WorkingIndicatorMode): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function (pi: ExtensionAPI) {
|
export default function (pi: ExtensionAPI) {
|
||||||
let mode: WorkingIndicatorMode = "dot";
|
let mode: WorkingIndicatorMode = "spinner";
|
||||||
|
|
||||||
const applyIndicator = (ctx: ExtensionContext) => {
|
const applyIndicator = (ctx: ExtensionContext) => {
|
||||||
ctx.ui.setWorkingIndicator(getIndicator(mode));
|
ctx.ui.setWorkingIndicator(getIndicator(mode));
|
||||||
|
|||||||
@@ -4,10 +4,15 @@
|
|||||||
* Shows how to replace or modify the default system prompt.
|
* Shows how to replace or modify the default system prompt.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createAgentSession, DefaultResourceLoader, SessionManager } from "@mariozechner/pi-coding-agent";
|
import { createAgentSession, DefaultResourceLoader, getAgentDir, SessionManager } from "@mariozechner/pi-coding-agent";
|
||||||
|
|
||||||
|
const cwd = process.cwd();
|
||||||
|
const agentDir = getAgentDir();
|
||||||
|
|
||||||
// Option 1: Replace prompt entirely
|
// Option 1: Replace prompt entirely
|
||||||
const loader1 = new DefaultResourceLoader({
|
const loader1 = new DefaultResourceLoader({
|
||||||
|
cwd,
|
||||||
|
agentDir,
|
||||||
systemPromptOverride: () => `You are a helpful assistant that speaks like a pirate.
|
systemPromptOverride: () => `You are a helpful assistant that speaks like a pirate.
|
||||||
Always end responses with "Arrr!"`,
|
Always end responses with "Arrr!"`,
|
||||||
// Needed to avoid DefaultResourceLoader appending APPEND_SYSTEM.md from ~/.pi/agent or <cwd>/.pi.
|
// Needed to avoid DefaultResourceLoader appending APPEND_SYSTEM.md from ~/.pi/agent or <cwd>/.pi.
|
||||||
@@ -32,6 +37,8 @@ console.log("\n");
|
|||||||
|
|
||||||
// Option 2: Append instructions to the default prompt
|
// Option 2: Append instructions to the default prompt
|
||||||
const loader2 = new DefaultResourceLoader({
|
const loader2 = new DefaultResourceLoader({
|
||||||
|
cwd,
|
||||||
|
agentDir,
|
||||||
appendSystemPromptOverride: (base) => [
|
appendSystemPromptOverride: (base) => [
|
||||||
...base,
|
...base,
|
||||||
"## Additional Instructions\n- Always be concise\n- Use bullet points when listing things",
|
"## Additional Instructions\n- Always be concise\n- Use bullet points when listing things",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
createAgentSession,
|
createAgentSession,
|
||||||
createSyntheticSourceInfo,
|
createSyntheticSourceInfo,
|
||||||
DefaultResourceLoader,
|
DefaultResourceLoader,
|
||||||
|
getAgentDir,
|
||||||
SessionManager,
|
SessionManager,
|
||||||
type Skill,
|
type Skill,
|
||||||
} from "@mariozechner/pi-coding-agent";
|
} from "@mariozechner/pi-coding-agent";
|
||||||
@@ -24,6 +25,8 @@ const customSkill: Skill = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const loader = new DefaultResourceLoader({
|
const loader = new DefaultResourceLoader({
|
||||||
|
cwd: process.cwd(),
|
||||||
|
agentDir: getAgentDir(),
|
||||||
skillsOverride: (current) => {
|
skillsOverride: (current) => {
|
||||||
const filteredSkills = current.skills.filter((s) => s.name.includes("browser") || s.name.includes("search"));
|
const filteredSkills = current.skills.filter((s) => s.name.includes("browser") || s.name.includes("search"));
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -13,12 +13,14 @@
|
|||||||
* export default function (pi: ExtensionAPI) { ... }
|
* export default function (pi: ExtensionAPI) { ... }
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createAgentSession, DefaultResourceLoader, SessionManager } from "@mariozechner/pi-coding-agent";
|
import { createAgentSession, DefaultResourceLoader, getAgentDir, SessionManager } from "@mariozechner/pi-coding-agent";
|
||||||
|
|
||||||
// Extensions are discovered automatically from standard locations.
|
// Extensions are discovered automatically from standard locations.
|
||||||
// You can also add paths via settings.json or DefaultResourceLoader options.
|
// You can also add paths via settings.json or DefaultResourceLoader options.
|
||||||
|
|
||||||
const resourceLoader = new DefaultResourceLoader({
|
const resourceLoader = new DefaultResourceLoader({
|
||||||
|
cwd: process.cwd(),
|
||||||
|
agentDir: getAgentDir(),
|
||||||
additionalExtensionPaths: ["./my-logging-extension.ts", "./my-safety-extension.ts"],
|
additionalExtensionPaths: ["./my-logging-extension.ts", "./my-safety-extension.ts"],
|
||||||
extensionFactories: [
|
extensionFactories: [
|
||||||
(pi) => {
|
(pi) => {
|
||||||
|
|||||||
@@ -4,10 +4,12 @@
|
|||||||
* Context files provide project-specific instructions loaded into the system prompt.
|
* Context files provide project-specific instructions loaded into the system prompt.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createAgentSession, DefaultResourceLoader, SessionManager } from "@mariozechner/pi-coding-agent";
|
import { createAgentSession, DefaultResourceLoader, getAgentDir, SessionManager } from "@mariozechner/pi-coding-agent";
|
||||||
|
|
||||||
// Disable context files entirely by returning an empty list in agentsFilesOverride.
|
// Disable context files entirely by returning an empty list in agentsFilesOverride.
|
||||||
const loader = new DefaultResourceLoader({
|
const loader = new DefaultResourceLoader({
|
||||||
|
cwd: process.cwd(),
|
||||||
|
agentDir: getAgentDir(),
|
||||||
agentsFilesOverride: (current) => ({
|
agentsFilesOverride: (current) => ({
|
||||||
agentsFiles: [
|
agentsFiles: [
|
||||||
...current.agentsFiles,
|
...current.agentsFiles,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
createAgentSession,
|
createAgentSession,
|
||||||
createSyntheticSourceInfo,
|
createSyntheticSourceInfo,
|
||||||
DefaultResourceLoader,
|
DefaultResourceLoader,
|
||||||
|
getAgentDir,
|
||||||
type PromptTemplate,
|
type PromptTemplate,
|
||||||
SessionManager,
|
SessionManager,
|
||||||
} from "@mariozechner/pi-coding-agent";
|
} from "@mariozechner/pi-coding-agent";
|
||||||
@@ -26,6 +27,8 @@ const deployTemplate: PromptTemplate = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const loader = new DefaultResourceLoader({
|
const loader = new DefaultResourceLoader({
|
||||||
|
cwd: process.cwd(),
|
||||||
|
agentDir: getAgentDir(),
|
||||||
promptsOverride: (current) => ({
|
promptsOverride: (current) => ({
|
||||||
prompts: [...current.prompts, deployTemplate],
|
prompts: [...current.prompts, deployTemplate],
|
||||||
diagnostics: current.diagnostics,
|
diagnostics: current.diagnostics,
|
||||||
|
|||||||
@@ -6,12 +6,14 @@
|
|||||||
|
|
||||||
import { createAgentSession, SessionManager, SettingsManager } from "@mariozechner/pi-coding-agent";
|
import { createAgentSession, SessionManager, SettingsManager } from "@mariozechner/pi-coding-agent";
|
||||||
|
|
||||||
|
const cwd = process.cwd();
|
||||||
|
|
||||||
// Load current settings (merged global + project)
|
// Load current settings (merged global + project)
|
||||||
const settingsManagerFromDisk = SettingsManager.create();
|
const settingsManagerFromDisk = SettingsManager.create(cwd);
|
||||||
console.log("Current settings:", JSON.stringify(settingsManagerFromDisk.getGlobalSettings(), null, 2));
|
console.log("Current settings:", JSON.stringify(settingsManagerFromDisk.getGlobalSettings(), null, 2));
|
||||||
|
|
||||||
// Override specific settings
|
// Override specific settings
|
||||||
const settingsManager = SettingsManager.create();
|
const settingsManager = SettingsManager.create(cwd);
|
||||||
settingsManager.applyOverrides({
|
settingsManager.applyOverrides({
|
||||||
compaction: { enabled: false },
|
compaction: { enabled: false },
|
||||||
retry: { enabled: true, maxRetries: 5, baseDelayMs: 1000 },
|
retry: { enabled: true, maxRetries: 5, baseDelayMs: 1000 },
|
||||||
|
|||||||
@@ -133,9 +133,6 @@ export class AgentSessionRuntime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private apply(result: CreateAgentSessionRuntimeResult): void {
|
private apply(result: CreateAgentSessionRuntimeResult): void {
|
||||||
if (process.cwd() !== result.services.cwd) {
|
|
||||||
process.chdir(result.services.cwd);
|
|
||||||
}
|
|
||||||
this._session = result.session;
|
this._session = result.session;
|
||||||
this._services = result.services;
|
this._services = result.services;
|
||||||
this._diagnostics = result.diagnostics;
|
this._diagnostics = result.diagnostics;
|
||||||
@@ -346,9 +343,6 @@ export async function createAgentSessionRuntime(
|
|||||||
): Promise<AgentSessionRuntime> {
|
): Promise<AgentSessionRuntime> {
|
||||||
assertSessionCwdExists(options.sessionManager, options.cwd);
|
assertSessionCwdExists(options.sessionManager, options.cwd);
|
||||||
const result = await createRuntime(options);
|
const result = await createRuntime(options);
|
||||||
if (process.cwd() !== result.services.cwd) {
|
|
||||||
process.chdir(result.services.cwd);
|
|
||||||
}
|
|
||||||
return new AgentSessionRuntime(
|
return new AgentSessionRuntime(
|
||||||
result.session,
|
result.session,
|
||||||
result.services,
|
result.services,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { tmpdir } from "node:os";
|
|||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import stripAnsi from "strip-ansi";
|
import stripAnsi from "strip-ansi";
|
||||||
import { sanitizeBinaryOutput } from "../utils/shell.js";
|
import { sanitizeBinaryOutput } from "../utils/shell.js";
|
||||||
import { type BashOperations, createLocalBashOperations } from "./tools/bash.js";
|
import type { BashOperations } from "./tools/bash.js";
|
||||||
import { DEFAULT_MAX_BYTES, truncateTail } from "./tools/truncate.js";
|
import { DEFAULT_MAX_BYTES, truncateTail } from "./tools/truncate.js";
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -43,23 +43,6 @@ export interface BashResult {
|
|||||||
// Implementation
|
// Implementation
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute a bash command with optional streaming and cancellation support.
|
|
||||||
*
|
|
||||||
* Uses the same local BashOperations backend as createBashTool() so interactive
|
|
||||||
* user bash and tool-invoked bash share the same process spawning behavior.
|
|
||||||
* Sanitization, newline normalization, temp-file capture, and truncation still
|
|
||||||
* happen in executeBashWithOperations(), so reusing the local backend does not
|
|
||||||
* change output processing behavior.
|
|
||||||
*
|
|
||||||
* @param command - The bash command to execute
|
|
||||||
* @param options - Optional streaming callback and abort signal
|
|
||||||
* @returns Promise resolving to execution result
|
|
||||||
*/
|
|
||||||
export function executeBash(command: string, options?: BashExecutorOptions): Promise<BashResult> {
|
|
||||||
return executeBashWithOperations(command, process.cwd(), createLocalBashOperations(), options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute a bash command using custom BashOperations.
|
* Execute a bash command using custom BashOperations.
|
||||||
* Used for remote execution (SSH, containers, etc.).
|
* Used for remote execution (SSH, containers, etc.).
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ export type TerminalInputHandler = (data: string) => { consume?: boolean; data?:
|
|||||||
|
|
||||||
/** Working indicator configuration for the interactive streaming loader. */
|
/** Working indicator configuration for the interactive streaming loader. */
|
||||||
export interface WorkingIndicatorOptions {
|
export interface WorkingIndicatorOptions {
|
||||||
/** Animation frames. Use an empty array to hide the indicator entirely. */
|
/** Animation frames. Use an empty array to hide the indicator entirely. Custom frames are rendered verbatim. */
|
||||||
frames?: string[];
|
frames?: string[];
|
||||||
/** Frame interval in milliseconds for animated indicators. */
|
/** Frame interval in milliseconds for animated indicators. */
|
||||||
intervalMs?: number;
|
intervalMs?: number;
|
||||||
@@ -142,6 +142,7 @@ export interface ExtensionUIContext {
|
|||||||
* - Omit the argument to restore the default animated spinner.
|
* - Omit the argument to restore the default animated spinner.
|
||||||
* - Use `frames: ["●"]` for a static indicator.
|
* - Use `frames: ["●"]` for a static indicator.
|
||||||
* - Use `frames: []` to hide the indicator entirely.
|
* - Use `frames: []` to hide the indicator entirely.
|
||||||
|
* - Custom frames are rendered as provided, so extensions must add their own colors.
|
||||||
*/
|
*/
|
||||||
setWorkingIndicator(options?: WorkingIndicatorOptions): void;
|
setWorkingIndicator(options?: WorkingIndicatorOptions): void;
|
||||||
|
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export class FooterDataProvider {
|
|||||||
private refreshPending = false;
|
private refreshPending = false;
|
||||||
private disposed = false;
|
private disposed = false;
|
||||||
|
|
||||||
constructor(cwd: string = process.cwd()) {
|
constructor(cwd: string) {
|
||||||
this.cwd = cwd;
|
this.cwd = cwd;
|
||||||
this.gitPaths = findGitPaths(cwd);
|
this.gitPaths = findGitPaths(cwd);
|
||||||
this.setupGitWatcher();
|
this.setupGitWatcher();
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export {
|
|||||||
createAgentSessionFromServices,
|
createAgentSessionFromServices,
|
||||||
createAgentSessionServices,
|
createAgentSessionServices,
|
||||||
} from "./agent-session-services.js";
|
} from "./agent-session-services.js";
|
||||||
export { type BashExecutorOptions, type BashResult, executeBash, executeBashWithOperations } from "./bash-executor.js";
|
export { type BashExecutorOptions, type BashResult, executeBashWithOperations } from "./bash-executor.js";
|
||||||
export type { CompactionResult } from "./compaction/index.js";
|
export type { CompactionResult } from "./compaction/index.js";
|
||||||
export { createEventBus, type EventBus, type EventBusController } from "./event-bus.js";
|
export { createEventBus, type EventBus, type EventBusController } from "./event-bus.js";
|
||||||
// Extensions system
|
// Extensions system
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { existsSync, readdirSync, readFileSync, statSync } from "fs";
|
import { existsSync, readdirSync, readFileSync, statSync } from "fs";
|
||||||
import { homedir } from "os";
|
import { homedir } from "os";
|
||||||
import { basename, dirname, isAbsolute, join, resolve, sep } from "path";
|
import { basename, dirname, isAbsolute, join, resolve, sep } from "path";
|
||||||
import { CONFIG_DIR_NAME, getPromptsDir } from "../config.js";
|
import { CONFIG_DIR_NAME } from "../config.js";
|
||||||
import { parseFrontmatter } from "../utils/frontmatter.js";
|
import { parseFrontmatter } from "../utils/frontmatter.js";
|
||||||
import { createSyntheticSourceInfo, type SourceInfo } from "./source-info.js";
|
import { createSyntheticSourceInfo, type SourceInfo } from "./source-info.js";
|
||||||
|
|
||||||
@@ -175,14 +175,14 @@ function loadTemplatesFromDir(dir: string, getSourceInfo: (filePath: string) =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface LoadPromptTemplatesOptions {
|
export interface LoadPromptTemplatesOptions {
|
||||||
/** Working directory for project-local templates. Default: process.cwd() */
|
/** Working directory for project-local templates. */
|
||||||
cwd?: string;
|
cwd: string;
|
||||||
/** Agent config directory for global templates. Default: from getPromptsDir() */
|
/** Agent config directory for global templates. */
|
||||||
agentDir?: string;
|
agentDir: string;
|
||||||
/** Explicit prompt template paths (files or directories) */
|
/** Explicit prompt template paths (files or directories). */
|
||||||
promptPaths?: string[];
|
promptPaths: string[];
|
||||||
/** Include default prompt directories. Default: true */
|
/** Include default prompt directories. */
|
||||||
includeDefaults?: boolean;
|
includeDefaults: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizePath(input: string): string {
|
function normalizePath(input: string): string {
|
||||||
@@ -204,11 +204,11 @@ function resolvePromptPath(p: string, cwd: string): string {
|
|||||||
* 2. Project: cwd/{CONFIG_DIR_NAME}/prompts/
|
* 2. Project: cwd/{CONFIG_DIR_NAME}/prompts/
|
||||||
* 3. Explicit prompt paths
|
* 3. Explicit prompt paths
|
||||||
*/
|
*/
|
||||||
export function loadPromptTemplates(options: LoadPromptTemplatesOptions = {}): PromptTemplate[] {
|
export function loadPromptTemplates(options: LoadPromptTemplatesOptions): PromptTemplate[] {
|
||||||
const resolvedCwd = options.cwd ?? process.cwd();
|
const resolvedCwd = options.cwd;
|
||||||
const resolvedAgentDir = options.agentDir ?? getPromptsDir();
|
const resolvedAgentDir = options.agentDir;
|
||||||
const promptPaths = options.promptPaths ?? [];
|
const promptPaths = options.promptPaths;
|
||||||
const includeDefaults = options.includeDefaults ?? true;
|
const includeDefaults = options.includeDefaults;
|
||||||
|
|
||||||
const templates: PromptTemplate[] = [];
|
const templates: PromptTemplate[] = [];
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|||||||
import { homedir } from "node:os";
|
import { homedir } from "node:os";
|
||||||
import { join, resolve, sep } from "node:path";
|
import { join, resolve, sep } from "node:path";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import { CONFIG_DIR_NAME, getAgentDir } from "../config.js";
|
import { CONFIG_DIR_NAME } from "../config.js";
|
||||||
import { loadThemeFromPath, type Theme } from "../modes/interactive/theme/theme.js";
|
import { loadThemeFromPath, type Theme } from "../modes/interactive/theme/theme.js";
|
||||||
import type { ResourceDiagnostic } from "./diagnostics.js";
|
import type { ResourceDiagnostic } from "./diagnostics.js";
|
||||||
|
|
||||||
@@ -73,11 +73,12 @@ function loadContextFileFromDir(dir: string): { path: string; content: string }
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadProjectContextFiles(
|
export function loadProjectContextFiles(options: {
|
||||||
options: { cwd?: string; agentDir?: string } = {},
|
cwd: string;
|
||||||
): Array<{ path: string; content: string }> {
|
agentDir: string;
|
||||||
const resolvedCwd = options.cwd ?? process.cwd();
|
}): Array<{ path: string; content: string }> {
|
||||||
const resolvedAgentDir = options.agentDir ?? getAgentDir();
|
const resolvedCwd = options.cwd;
|
||||||
|
const resolvedAgentDir = options.agentDir;
|
||||||
|
|
||||||
const contextFiles: Array<{ path: string; content: string }> = [];
|
const contextFiles: Array<{ path: string; content: string }> = [];
|
||||||
const seenPaths = new Set<string>();
|
const seenPaths = new Set<string>();
|
||||||
@@ -113,8 +114,8 @@ export function loadProjectContextFiles(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface DefaultResourceLoaderOptions {
|
export interface DefaultResourceLoaderOptions {
|
||||||
cwd?: string;
|
cwd: string;
|
||||||
agentDir?: string;
|
agentDir: string;
|
||||||
settingsManager?: SettingsManager;
|
settingsManager?: SettingsManager;
|
||||||
eventBus?: EventBus;
|
eventBus?: EventBus;
|
||||||
additionalExtensionPaths?: string[];
|
additionalExtensionPaths?: string[];
|
||||||
@@ -204,8 +205,8 @@ export class DefaultResourceLoader implements ResourceLoader {
|
|||||||
private lastThemePaths: string[];
|
private lastThemePaths: string[];
|
||||||
|
|
||||||
constructor(options: DefaultResourceLoaderOptions) {
|
constructor(options: DefaultResourceLoaderOptions) {
|
||||||
this.cwd = options.cwd ?? process.cwd();
|
this.cwd = options.cwd;
|
||||||
this.agentDir = options.agentDir ?? getAgentDir();
|
this.agentDir = options.agentDir;
|
||||||
this.settingsManager = options.settingsManager ?? SettingsManager.create(this.cwd, this.agentDir);
|
this.settingsManager = options.settingsManager ?? SettingsManager.create(this.cwd, this.agentDir);
|
||||||
this.eventBus = options.eventBus ?? createEventBus();
|
this.eventBus = options.eventBus ?? createEventBus();
|
||||||
this.packageManager = new DefaultPackageManager({
|
this.packageManager = new DefaultPackageManager({
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ export class FileSettingsStorage implements SettingsStorage {
|
|||||||
private globalSettingsPath: string;
|
private globalSettingsPath: string;
|
||||||
private projectSettingsPath: string;
|
private projectSettingsPath: string;
|
||||||
|
|
||||||
constructor(cwd: string = process.cwd(), agentDir: string = getAgentDir()) {
|
constructor(cwd: string, agentDir: string) {
|
||||||
this.globalSettingsPath = join(agentDir, "settings.json");
|
this.globalSettingsPath = join(agentDir, "settings.json");
|
||||||
this.projectSettingsPath = join(cwd, CONFIG_DIR_NAME, "settings.json");
|
this.projectSettingsPath = join(cwd, CONFIG_DIR_NAME, "settings.json");
|
||||||
}
|
}
|
||||||
@@ -256,7 +256,7 @@ export class SettingsManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Create a SettingsManager that loads from files */
|
/** Create a SettingsManager that loads from files */
|
||||||
static create(cwd: string = process.cwd(), agentDir: string = getAgentDir()): SettingsManager {
|
static create(cwd: string, agentDir: string = getAgentDir()): SettingsManager {
|
||||||
const storage = new FileSettingsStorage(cwd, agentDir);
|
const storage = new FileSettingsStorage(cwd, agentDir);
|
||||||
return SettingsManager.fromStorage(storage);
|
return SettingsManager.fromStorage(storage);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -374,14 +374,14 @@ function escapeXml(str: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface LoadSkillsOptions {
|
export interface LoadSkillsOptions {
|
||||||
/** Working directory for project-local skills. Default: process.cwd() */
|
/** Working directory for project-local skills. */
|
||||||
cwd?: string;
|
cwd: string;
|
||||||
/** Agent config directory for global skills. Default: ~/.pi/agent */
|
/** Agent config directory for global skills. */
|
||||||
agentDir?: string;
|
agentDir: string;
|
||||||
/** Explicit skill paths (files or directories) */
|
/** Explicit skill paths (files or directories) */
|
||||||
skillPaths?: string[];
|
skillPaths: string[];
|
||||||
/** Include default skills directories. Default: true */
|
/** Include default skills directories. */
|
||||||
includeDefaults?: boolean;
|
includeDefaults: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizePath(input: string): string {
|
function normalizePath(input: string): string {
|
||||||
@@ -401,8 +401,8 @@ function resolveSkillPath(p: string, cwd: string): string {
|
|||||||
* Load skills from all configured locations.
|
* Load skills from all configured locations.
|
||||||
* Returns skills and any validation diagnostics.
|
* Returns skills and any validation diagnostics.
|
||||||
*/
|
*/
|
||||||
export function loadSkills(options: LoadSkillsOptions = {}): LoadSkillsResult {
|
export function loadSkills(options: LoadSkillsOptions): LoadSkillsResult {
|
||||||
const { cwd = process.cwd(), agentDir, skillPaths = [], includeDefaults = true } = options;
|
const { cwd, agentDir, skillPaths, includeDefaults } = options;
|
||||||
|
|
||||||
// Resolve agentDir - if not provided, use default from config
|
// Resolve agentDir - if not provided, use default from config
|
||||||
const resolvedAgentDir = agentDir ?? getAgentDir();
|
const resolvedAgentDir = agentDir ?? getAgentDir();
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ export interface BuildSystemPromptOptions {
|
|||||||
promptGuidelines?: string[];
|
promptGuidelines?: string[];
|
||||||
/** Text to append to system prompt. */
|
/** Text to append to system prompt. */
|
||||||
appendSystemPrompt?: string;
|
appendSystemPrompt?: string;
|
||||||
/** Working directory. Default: process.cwd() */
|
/** Working directory. */
|
||||||
cwd?: string;
|
cwd: string;
|
||||||
/** Pre-loaded context files. */
|
/** Pre-loaded context files. */
|
||||||
contextFiles?: Array<{ path: string; content: string }>;
|
contextFiles?: Array<{ path: string; content: string }>;
|
||||||
/** Pre-loaded skills. */
|
/** Pre-loaded skills. */
|
||||||
@@ -25,7 +25,7 @@ export interface BuildSystemPromptOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Build the system prompt with tools, guidelines, and context */
|
/** Build the system prompt with tools, guidelines, and context */
|
||||||
export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): string {
|
export function buildSystemPrompt(options: BuildSystemPromptOptions): string {
|
||||||
const {
|
const {
|
||||||
customPrompt,
|
customPrompt,
|
||||||
selectedTools,
|
selectedTools,
|
||||||
@@ -36,7 +36,7 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): strin
|
|||||||
contextFiles: providedContextFiles,
|
contextFiles: providedContextFiles,
|
||||||
skills: providedSkills,
|
skills: providedSkills,
|
||||||
} = options;
|
} = options;
|
||||||
const resolvedCwd = cwd ?? process.cwd();
|
const resolvedCwd = cwd;
|
||||||
const promptCwd = resolvedCwd.replace(/\\/g, "/");
|
const promptCwd = resolvedCwd.replace(/\\/g, "/");
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|||||||
@@ -444,7 +444,3 @@ export function createBashToolDefinition(
|
|||||||
export function createBashTool(cwd: string, options?: BashToolOptions): AgentTool<typeof bashSchema> {
|
export function createBashTool(cwd: string, options?: BashToolOptions): AgentTool<typeof bashSchema> {
|
||||||
return wrapToolDefinition(createBashToolDefinition(cwd, options));
|
return wrapToolDefinition(createBashToolDefinition(cwd, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Default bash tool using process.cwd() for backwards compatibility. */
|
|
||||||
export const bashToolDefinition = createBashToolDefinition(process.cwd());
|
|
||||||
export const bashTool = createBashTool(process.cwd());
|
|
||||||
|
|||||||
@@ -485,7 +485,3 @@ export function createEditToolDefinition(
|
|||||||
export function createEditTool(cwd: string, options?: EditToolOptions): AgentTool<typeof editSchema> {
|
export function createEditTool(cwd: string, options?: EditToolOptions): AgentTool<typeof editSchema> {
|
||||||
return wrapToolDefinition(createEditToolDefinition(cwd, options));
|
return wrapToolDefinition(createEditToolDefinition(cwd, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Default edit tool using process.cwd() for backwards compatibility. */
|
|
||||||
export const editToolDefinition = createEditToolDefinition(process.cwd());
|
|
||||||
export const editTool = createEditTool(process.cwd());
|
|
||||||
|
|||||||
@@ -368,7 +368,3 @@ export function createFindToolDefinition(
|
|||||||
export function createFindTool(cwd: string, options?: FindToolOptions): AgentTool<typeof findSchema> {
|
export function createFindTool(cwd: string, options?: FindToolOptions): AgentTool<typeof findSchema> {
|
||||||
return wrapToolDefinition(createFindToolDefinition(cwd, options));
|
return wrapToolDefinition(createFindToolDefinition(cwd, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Default find tool using process.cwd() for backwards compatibility. */
|
|
||||||
export const findToolDefinition = createFindToolDefinition(process.cwd());
|
|
||||||
export const findTool = createFindTool(process.cwd());
|
|
||||||
|
|||||||
@@ -382,7 +382,3 @@ export function createGrepToolDefinition(
|
|||||||
export function createGrepTool(cwd: string, options?: GrepToolOptions): AgentTool<typeof grepSchema> {
|
export function createGrepTool(cwd: string, options?: GrepToolOptions): AgentTool<typeof grepSchema> {
|
||||||
return wrapToolDefinition(createGrepToolDefinition(cwd, options));
|
return wrapToolDefinition(createGrepToolDefinition(cwd, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Default grep tool using process.cwd() for backwards compatibility. */
|
|
||||||
export const grepToolDefinition = createGrepToolDefinition(process.cwd());
|
|
||||||
export const grepTool = createGrepTool(process.cwd());
|
|
||||||
|
|||||||
@@ -227,7 +227,3 @@ export function createLsToolDefinition(
|
|||||||
export function createLsTool(cwd: string, options?: LsToolOptions): AgentTool<typeof lsSchema> {
|
export function createLsTool(cwd: string, options?: LsToolOptions): AgentTool<typeof lsSchema> {
|
||||||
return wrapToolDefinition(createLsToolDefinition(cwd, options));
|
return wrapToolDefinition(createLsToolDefinition(cwd, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Default ls tool using process.cwd() for backwards compatibility. */
|
|
||||||
export const lsToolDefinition = createLsToolDefinition(process.cwd());
|
|
||||||
export const lsTool = createLsTool(process.cwd());
|
|
||||||
|
|||||||
@@ -271,7 +271,3 @@ export function createReadToolDefinition(
|
|||||||
export function createReadTool(cwd: string, options?: ReadToolOptions): AgentTool<typeof readSchema> {
|
export function createReadTool(cwd: string, options?: ReadToolOptions): AgentTool<typeof readSchema> {
|
||||||
return wrapToolDefinition(createReadToolDefinition(cwd, options));
|
return wrapToolDefinition(createReadToolDefinition(cwd, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Default read tool using process.cwd() for backwards compatibility. */
|
|
||||||
export const readToolDefinition = createReadToolDefinition(process.cwd());
|
|
||||||
export const readTool = createReadTool(process.cwd());
|
|
||||||
|
|||||||
@@ -279,7 +279,3 @@ export function createWriteToolDefinition(
|
|||||||
export function createWriteTool(cwd: string, options?: WriteToolOptions): AgentTool<typeof writeSchema> {
|
export function createWriteTool(cwd: string, options?: WriteToolOptions): AgentTool<typeof writeSchema> {
|
||||||
return wrapToolDefinition(createWriteToolDefinition(cwd, options));
|
return wrapToolDefinition(createWriteToolDefinition(cwd, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Default write tool using process.cwd() for backwards compatibility. */
|
|
||||||
export const writeToolDefinition = createWriteToolDefinition(process.cwd());
|
|
||||||
export const writeTool = createWriteTool(process.cwd());
|
|
||||||
|
|||||||
@@ -183,8 +183,6 @@ export {
|
|||||||
createReadTool,
|
createReadTool,
|
||||||
createWriteTool,
|
createWriteTool,
|
||||||
type PromptTemplate,
|
type PromptTemplate,
|
||||||
// Pre-built tools (use process.cwd())
|
|
||||||
readOnlyTools,
|
|
||||||
} from "./core/sdk.js";
|
} from "./core/sdk.js";
|
||||||
export {
|
export {
|
||||||
type BranchSummaryEntry,
|
type BranchSummaryEntry,
|
||||||
@@ -235,9 +233,6 @@ export {
|
|||||||
type BashToolDetails,
|
type BashToolDetails,
|
||||||
type BashToolInput,
|
type BashToolInput,
|
||||||
type BashToolOptions,
|
type BashToolOptions,
|
||||||
bashTool,
|
|
||||||
bashToolDefinition,
|
|
||||||
codingTools,
|
|
||||||
createBashToolDefinition,
|
createBashToolDefinition,
|
||||||
createEditToolDefinition,
|
createEditToolDefinition,
|
||||||
createFindToolDefinition,
|
createFindToolDefinition,
|
||||||
@@ -252,33 +247,23 @@ export {
|
|||||||
type EditToolDetails,
|
type EditToolDetails,
|
||||||
type EditToolInput,
|
type EditToolInput,
|
||||||
type EditToolOptions,
|
type EditToolOptions,
|
||||||
editTool,
|
|
||||||
editToolDefinition,
|
|
||||||
type FindOperations,
|
type FindOperations,
|
||||||
type FindToolDetails,
|
type FindToolDetails,
|
||||||
type FindToolInput,
|
type FindToolInput,
|
||||||
type FindToolOptions,
|
type FindToolOptions,
|
||||||
findTool,
|
|
||||||
findToolDefinition,
|
|
||||||
formatSize,
|
formatSize,
|
||||||
type GrepOperations,
|
type GrepOperations,
|
||||||
type GrepToolDetails,
|
type GrepToolDetails,
|
||||||
type GrepToolInput,
|
type GrepToolInput,
|
||||||
type GrepToolOptions,
|
type GrepToolOptions,
|
||||||
grepTool,
|
|
||||||
grepToolDefinition,
|
|
||||||
type LsOperations,
|
type LsOperations,
|
||||||
type LsToolDetails,
|
type LsToolDetails,
|
||||||
type LsToolInput,
|
type LsToolInput,
|
||||||
type LsToolOptions,
|
type LsToolOptions,
|
||||||
lsTool,
|
|
||||||
lsToolDefinition,
|
|
||||||
type ReadOperations,
|
type ReadOperations,
|
||||||
type ReadToolDetails,
|
type ReadToolDetails,
|
||||||
type ReadToolInput,
|
type ReadToolInput,
|
||||||
type ReadToolOptions,
|
type ReadToolOptions,
|
||||||
readTool,
|
|
||||||
readToolDefinition,
|
|
||||||
type ToolsOptions,
|
type ToolsOptions,
|
||||||
type TruncationOptions,
|
type TruncationOptions,
|
||||||
type TruncationResult,
|
type TruncationResult,
|
||||||
@@ -289,8 +274,6 @@ export {
|
|||||||
type WriteToolInput,
|
type WriteToolInput,
|
||||||
type WriteToolOptions,
|
type WriteToolOptions,
|
||||||
withFileMutationQueue,
|
withFileMutationQueue,
|
||||||
writeTool,
|
|
||||||
writeToolDefinition,
|
|
||||||
} from "./core/tools/index.js";
|
} from "./core/tools/index.js";
|
||||||
// Main entry point
|
// Main entry point
|
||||||
export { type MainOptions, main } from "./main.js";
|
export { type MainOptions, main } from "./main.js";
|
||||||
|
|||||||
@@ -301,7 +301,7 @@ export async function showDeprecationWarnings(warnings: string[]): Promise<void>
|
|||||||
*
|
*
|
||||||
* @returns Object with migration results and deprecation warnings
|
* @returns Object with migration results and deprecation warnings
|
||||||
*/
|
*/
|
||||||
export function runMigrations(cwd: string = process.cwd()): {
|
export function runMigrations(cwd: string): {
|
||||||
migratedAuthProviders: string[];
|
migratedAuthProviders: string[];
|
||||||
deprecationWarnings: string[];
|
deprecationWarnings: string[];
|
||||||
} {
|
} {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Box, type Component, Container, getCapabilities, Image, Spacer, Text, type TUI } from "@mariozechner/pi-tui";
|
import { Box, type Component, Container, getCapabilities, Image, Spacer, Text, type TUI } from "@mariozechner/pi-tui";
|
||||||
import type { ToolDefinition, ToolRenderContext } from "../../../core/extensions/types.js";
|
import type { ToolDefinition, ToolRenderContext } from "../../../core/extensions/types.js";
|
||||||
import { allToolDefinitions } from "../../../core/tools/index.js";
|
import { createAllToolDefinitions, type ToolName } from "../../../core/tools/index.js";
|
||||||
import { getTextOutput as getRenderedTextOutput } from "../../../core/tools/render-utils.js";
|
import { getTextOutput as getRenderedTextOutput } from "../../../core/tools/render-utils.js";
|
||||||
import { convertToPng } from "../../../utils/image-convert.js";
|
import { convertToPng } from "../../../utils/image-convert.js";
|
||||||
import { theme } from "../theme/theme.js";
|
import { theme } from "../theme/theme.js";
|
||||||
@@ -45,14 +45,14 @@ export class ToolExecutionComponent extends Container {
|
|||||||
options: ToolExecutionOptions = {},
|
options: ToolExecutionOptions = {},
|
||||||
toolDefinition: ToolDefinition<any, any> | undefined,
|
toolDefinition: ToolDefinition<any, any> | undefined,
|
||||||
ui: TUI,
|
ui: TUI,
|
||||||
cwd: string = process.cwd(),
|
cwd: string,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.toolName = toolName;
|
this.toolName = toolName;
|
||||||
this.toolCallId = toolCallId;
|
this.toolCallId = toolCallId;
|
||||||
this.args = args;
|
this.args = args;
|
||||||
this.toolDefinition = toolDefinition;
|
this.toolDefinition = toolDefinition;
|
||||||
this.builtInToolDefinition = allToolDefinitions[toolName as keyof typeof allToolDefinitions];
|
this.builtInToolDefinition = createAllToolDefinitions(cwd)[toolName as ToolName];
|
||||||
this.showImages = options.showImages ?? true;
|
this.showImages = options.showImages ?? true;
|
||||||
this.ui = ui;
|
this.ui = ui;
|
||||||
this.cwd = cwd;
|
this.cwd = cwd;
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export function getShellConfig(): { shell: string; args: string[] } {
|
|||||||
return cachedShellConfig;
|
return cachedShellConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
const settings = SettingsManager.create();
|
const settings = SettingsManager.create(process.cwd());
|
||||||
const customShellPath = settings.getShellPath();
|
const customShellPath = settings.getShellPath();
|
||||||
|
|
||||||
// 1. Check user-specified shell path
|
// 1. Check user-specified shell path
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { AuthStorage } from "../src/core/auth-storage.js";
|
|||||||
import { ModelRegistry } from "../src/core/model-registry.js";
|
import { ModelRegistry } from "../src/core/model-registry.js";
|
||||||
import { SessionManager } from "../src/core/session-manager.js";
|
import { SessionManager } from "../src/core/session-manager.js";
|
||||||
import { SettingsManager } from "../src/core/settings-manager.js";
|
import { SettingsManager } from "../src/core/settings-manager.js";
|
||||||
import { codingTools } from "../src/core/tools/index.js";
|
import { createCodingTools } from "../src/index.js";
|
||||||
import { API_KEY, createTestResourceLoader } from "./utilities.js";
|
import { API_KEY, createTestResourceLoader } from "./utilities.js";
|
||||||
|
|
||||||
describe.skipIf(!API_KEY)("AgentSession compaction e2e", () => {
|
describe.skipIf(!API_KEY)("AgentSession compaction e2e", () => {
|
||||||
@@ -52,7 +52,7 @@ describe.skipIf(!API_KEY)("AgentSession compaction e2e", () => {
|
|||||||
initialState: {
|
initialState: {
|
||||||
model,
|
model,
|
||||||
systemPrompt: "You are a helpful assistant. Be concise.",
|
systemPrompt: "You are a helpful assistant. Be concise.",
|
||||||
tools: codingTools,
|
tools: createCodingTools(process.cwd()),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
|
|||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||||
import { executeBash } from "../src/core/bash-executor.js";
|
import { executeBashWithOperations } from "../src/core/bash-executor.js";
|
||||||
import { createBashTool } from "../src/core/tools/bash.js";
|
import { createBashTool, createLocalBashOperations } from "../src/core/tools/bash.js";
|
||||||
|
|
||||||
function toBashSingleQuotedArg(value: string): string {
|
function toBashSingleQuotedArg(value: string): string {
|
||||||
return `'${value.replace(/\\/g, "/").replace(/'/g, `'"'"'`)}'`;
|
return `'${value.replace(/\\/g, "/").replace(/'/g, `'"'"'`)}'`;
|
||||||
@@ -87,9 +87,15 @@ describe.skipIf(process.platform !== "win32")("Windows child-process close handl
|
|||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await withTimeout(executeBash(command, { signal: controller.signal }), 3000, () => {
|
const result = await withTimeout(
|
||||||
controller.abort();
|
executeBashWithOperations(command, process.cwd(), createLocalBashOperations(), {
|
||||||
});
|
signal: controller.signal,
|
||||||
|
}),
|
||||||
|
3000,
|
||||||
|
() => {
|
||||||
|
controller.abort();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
expect(result.output).toContain("child-exiting");
|
expect(result.output).toContain("child-exiting");
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { ModelRegistry } from "../src/core/model-registry.js";
|
|||||||
import { SessionManager } from "../src/core/session-manager.js";
|
import { SessionManager } from "../src/core/session-manager.js";
|
||||||
import { SettingsManager } from "../src/core/settings-manager.js";
|
import { SettingsManager } from "../src/core/settings-manager.js";
|
||||||
import { createSyntheticSourceInfo } from "../src/core/source-info.js";
|
import { createSyntheticSourceInfo } from "../src/core/source-info.js";
|
||||||
import { codingTools } from "../src/core/tools/index.js";
|
import { createCodingTools } from "../src/index.js";
|
||||||
import { createTestResourceLoader } from "./utilities.js";
|
import { createTestResourceLoader } from "./utilities.js";
|
||||||
|
|
||||||
const API_KEY = process.env.ANTHROPIC_OAUTH_TOKEN || process.env.ANTHROPIC_API_KEY;
|
const API_KEY = process.env.ANTHROPIC_OAUTH_TOKEN || process.env.ANTHROPIC_API_KEY;
|
||||||
@@ -92,7 +92,7 @@ describe.skipIf(!API_KEY)("Compaction extensions", () => {
|
|||||||
initialState: {
|
initialState: {
|
||||||
model,
|
model,
|
||||||
systemPrompt: "You are a helpful assistant. Be concise.",
|
systemPrompt: "You are a helpful assistant. Be concise.",
|
||||||
tools: codingTools,
|
tools: createCodingTools(process.cwd()),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ describe("FooterDataProvider reftable branch detection", () => {
|
|||||||
mkdirSync(nestedDir, { recursive: true });
|
mkdirSync(nestedDir, { recursive: true });
|
||||||
process.chdir(nestedDir);
|
process.chdir(nestedDir);
|
||||||
|
|
||||||
const provider = new FooterDataProvider();
|
const provider = new FooterDataProvider(nestedDir);
|
||||||
try {
|
try {
|
||||||
expect(provider.getGitBranch()).toBe("main");
|
expect(provider.getGitBranch()).toBe("main");
|
||||||
expect(vi.mocked(spawnSync)).not.toHaveBeenCalled();
|
expect(vi.mocked(spawnSync)).not.toHaveBeenCalled();
|
||||||
@@ -125,7 +125,7 @@ describe("FooterDataProvider reftable branch detection", () => {
|
|||||||
const repoDir = createPlainReftableRepo(tempDir);
|
const repoDir = createPlainReftableRepo(tempDir);
|
||||||
process.chdir(repoDir);
|
process.chdir(repoDir);
|
||||||
|
|
||||||
const provider = new FooterDataProvider();
|
const provider = new FooterDataProvider(repoDir);
|
||||||
try {
|
try {
|
||||||
expect(provider.getGitBranch()).toBe("main");
|
expect(provider.getGitBranch()).toBe("main");
|
||||||
expect(vi.mocked(spawnSync)).toHaveBeenCalledWith(
|
expect(vi.mocked(spawnSync)).toHaveBeenCalledWith(
|
||||||
@@ -146,7 +146,7 @@ describe("FooterDataProvider reftable branch detection", () => {
|
|||||||
const { worktreeDir } = createReftableWorktree(tempDir);
|
const { worktreeDir } = createReftableWorktree(tempDir);
|
||||||
process.chdir(worktreeDir);
|
process.chdir(worktreeDir);
|
||||||
|
|
||||||
const provider = new FooterDataProvider();
|
const provider = new FooterDataProvider(worktreeDir);
|
||||||
try {
|
try {
|
||||||
expect(provider.getGitBranch()).toBe("main");
|
expect(provider.getGitBranch()).toBe("main");
|
||||||
} finally {
|
} finally {
|
||||||
@@ -159,7 +159,7 @@ describe("FooterDataProvider reftable branch detection", () => {
|
|||||||
process.chdir(repoDir);
|
process.chdir(repoDir);
|
||||||
resolvedBranch = "";
|
resolvedBranch = "";
|
||||||
|
|
||||||
const provider = new FooterDataProvider();
|
const provider = new FooterDataProvider(repoDir);
|
||||||
try {
|
try {
|
||||||
expect(provider.getGitBranch()).toBe("detached");
|
expect(provider.getGitBranch()).toBe("detached");
|
||||||
} finally {
|
} finally {
|
||||||
@@ -171,7 +171,7 @@ describe("FooterDataProvider reftable branch detection", () => {
|
|||||||
const { worktreeDir, reftableDir } = createReftableWorktree(tempDir);
|
const { worktreeDir, reftableDir } = createReftableWorktree(tempDir);
|
||||||
process.chdir(worktreeDir);
|
process.chdir(worktreeDir);
|
||||||
|
|
||||||
const provider = new FooterDataProvider();
|
const provider = new FooterDataProvider(worktreeDir);
|
||||||
try {
|
try {
|
||||||
expect(provider.getGitBranch()).toBe("main");
|
expect(provider.getGitBranch()).toBe("main");
|
||||||
vi.mocked(spawnSync).mockClear();
|
vi.mocked(spawnSync).mockClear();
|
||||||
@@ -194,7 +194,7 @@ describe("FooterDataProvider reftable branch detection", () => {
|
|||||||
const { worktreeDir, reftableDir } = createReftableWorktree(tempDir);
|
const { worktreeDir, reftableDir } = createReftableWorktree(tempDir);
|
||||||
process.chdir(worktreeDir);
|
process.chdir(worktreeDir);
|
||||||
|
|
||||||
const provider = new FooterDataProvider();
|
const provider = new FooterDataProvider(worktreeDir);
|
||||||
try {
|
try {
|
||||||
expect(provider.getGitBranch()).toBe("main");
|
expect(provider.getGitBranch()).toBe("main");
|
||||||
vi.mocked(execFile).mockClear();
|
vi.mocked(execFile).mockClear();
|
||||||
@@ -215,7 +215,7 @@ describe("FooterDataProvider reftable branch detection", () => {
|
|||||||
const { worktreeDir, reftableDir } = createReftableWorktree(tempDir);
|
const { worktreeDir, reftableDir } = createReftableWorktree(tempDir);
|
||||||
process.chdir(worktreeDir);
|
process.chdir(worktreeDir);
|
||||||
|
|
||||||
const provider = new FooterDataProvider();
|
const provider = new FooterDataProvider(worktreeDir);
|
||||||
try {
|
try {
|
||||||
expect(provider.getGitBranch()).toBe("main");
|
expect(provider.getGitBranch()).toBe("main");
|
||||||
resolvedBranch = "foo";
|
resolvedBranch = "foo";
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { mkdirSync, rmSync, writeFileSync } from "fs";
|
|||||||
import { tmpdir } from "os";
|
import { tmpdir } from "os";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { afterAll, describe, expect, test } from "vitest";
|
import { afterAll, describe, expect, test } from "vitest";
|
||||||
|
import { getAgentDir } from "../src/config.js";
|
||||||
import { loadPromptTemplates, parseCommandArgs, substituteArgs } from "../src/core/prompt-templates.js";
|
import { loadPromptTemplates, parseCommandArgs, substituteArgs } from "../src/core/prompt-templates.js";
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -406,6 +407,8 @@ You are given one or more GitHub PR URLs: $@`,
|
|||||||
);
|
);
|
||||||
|
|
||||||
const templates = loadPromptTemplates({
|
const templates = loadPromptTemplates({
|
||||||
|
cwd: process.cwd(),
|
||||||
|
agentDir: getAgentDir(),
|
||||||
promptPaths: [testDir],
|
promptPaths: [testDir],
|
||||||
includeDefaults: false,
|
includeDefaults: false,
|
||||||
});
|
});
|
||||||
@@ -427,6 +430,8 @@ Wrap it. Additional instructions: $ARGUMENTS`,
|
|||||||
);
|
);
|
||||||
|
|
||||||
const templates = loadPromptTemplates({
|
const templates = loadPromptTemplates({
|
||||||
|
cwd: process.cwd(),
|
||||||
|
agentDir: getAgentDir(),
|
||||||
promptPaths: [testDir],
|
promptPaths: [testDir],
|
||||||
includeDefaults: false,
|
includeDefaults: false,
|
||||||
});
|
});
|
||||||
@@ -447,6 +452,8 @@ Audit changelog entries for all commits since the last release.`,
|
|||||||
);
|
);
|
||||||
|
|
||||||
const templates = loadPromptTemplates({
|
const templates = loadPromptTemplates({
|
||||||
|
cwd: process.cwd(),
|
||||||
|
agentDir: getAgentDir(),
|
||||||
promptPaths: [testDir],
|
promptPaths: [testDir],
|
||||||
includeDefaults: false,
|
includeDefaults: false,
|
||||||
});
|
});
|
||||||
@@ -467,6 +474,8 @@ Do something`,
|
|||||||
);
|
);
|
||||||
|
|
||||||
const templates = loadPromptTemplates({
|
const templates = loadPromptTemplates({
|
||||||
|
cwd: process.cwd(),
|
||||||
|
agentDir: getAgentDir(),
|
||||||
promptPaths: [testDir],
|
promptPaths: [testDir],
|
||||||
includeDefaults: false,
|
includeDefaults: false,
|
||||||
});
|
});
|
||||||
@@ -487,6 +496,8 @@ Analyze GitHub issue(s): $ARGUMENTS`,
|
|||||||
);
|
);
|
||||||
|
|
||||||
const templates = loadPromptTemplates({
|
const templates = loadPromptTemplates({
|
||||||
|
cwd: process.cwd(),
|
||||||
|
agentDir: getAgentDir(),
|
||||||
promptPaths: [testDir],
|
promptPaths: [testDir],
|
||||||
includeDefaults: false,
|
includeDefaults: false,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -354,6 +354,7 @@ describe("skills", () => {
|
|||||||
agentDir: emptyAgentDir,
|
agentDir: emptyAgentDir,
|
||||||
cwd: emptyCwd,
|
cwd: emptyCwd,
|
||||||
skillPaths: [join(fixturesDir, "valid-skill")],
|
skillPaths: [join(fixturesDir, "valid-skill")],
|
||||||
|
includeDefaults: true,
|
||||||
});
|
});
|
||||||
expect(skills).toHaveLength(1);
|
expect(skills).toHaveLength(1);
|
||||||
expect(skills[0].sourceInfo.scope).toBe("temporary");
|
expect(skills[0].sourceInfo.scope).toBe("temporary");
|
||||||
@@ -365,6 +366,7 @@ describe("skills", () => {
|
|||||||
agentDir: emptyAgentDir,
|
agentDir: emptyAgentDir,
|
||||||
cwd: emptyCwd,
|
cwd: emptyCwd,
|
||||||
skillPaths: ["/non/existent/path"],
|
skillPaths: ["/non/existent/path"],
|
||||||
|
includeDefaults: true,
|
||||||
});
|
});
|
||||||
expect(skills).toHaveLength(0);
|
expect(skills).toHaveLength(0);
|
||||||
expect(diagnostics.some((d: ResourceDiagnostic) => d.message.includes("does not exist"))).toBe(true);
|
expect(diagnostics.some((d: ResourceDiagnostic) => d.message.includes("does not exist"))).toBe(true);
|
||||||
@@ -376,11 +378,13 @@ describe("skills", () => {
|
|||||||
agentDir: emptyAgentDir,
|
agentDir: emptyAgentDir,
|
||||||
cwd: emptyCwd,
|
cwd: emptyCwd,
|
||||||
skillPaths: ["~/.pi/agent/skills"],
|
skillPaths: ["~/.pi/agent/skills"],
|
||||||
|
includeDefaults: true,
|
||||||
});
|
});
|
||||||
const { skills: withoutTilde } = loadSkills({
|
const { skills: withoutTilde } = loadSkills({
|
||||||
agentDir: emptyAgentDir,
|
agentDir: emptyAgentDir,
|
||||||
cwd: emptyCwd,
|
cwd: emptyCwd,
|
||||||
skillPaths: [homeSkillsDir],
|
skillPaths: [homeSkillsDir],
|
||||||
|
includeDefaults: true,
|
||||||
});
|
});
|
||||||
expect(withTilde.length).toBe(withoutTilde.length);
|
expect(withTilde.length).toBe(withoutTilde.length);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ describe("buildSystemPrompt", () => {
|
|||||||
selectedTools: [],
|
selectedTools: [],
|
||||||
contextFiles: [],
|
contextFiles: [],
|
||||||
skills: [],
|
skills: [],
|
||||||
|
cwd: process.cwd(),
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(prompt).toContain("Available tools:\n(none)");
|
expect(prompt).toContain("Available tools:\n(none)");
|
||||||
@@ -18,6 +19,7 @@ describe("buildSystemPrompt", () => {
|
|||||||
selectedTools: [],
|
selectedTools: [],
|
||||||
contextFiles: [],
|
contextFiles: [],
|
||||||
skills: [],
|
skills: [],
|
||||||
|
cwd: process.cwd(),
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(prompt).toContain("Show file paths clearly");
|
expect(prompt).toContain("Show file paths clearly");
|
||||||
@@ -35,6 +37,7 @@ describe("buildSystemPrompt", () => {
|
|||||||
},
|
},
|
||||||
contextFiles: [],
|
contextFiles: [],
|
||||||
skills: [],
|
skills: [],
|
||||||
|
cwd: process.cwd(),
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(prompt).toContain("- read:");
|
expect(prompt).toContain("- read:");
|
||||||
@@ -53,6 +56,7 @@ describe("buildSystemPrompt", () => {
|
|||||||
},
|
},
|
||||||
contextFiles: [],
|
contextFiles: [],
|
||||||
skills: [],
|
skills: [],
|
||||||
|
cwd: process.cwd(),
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(prompt).toContain("- dynamic_tool: Run dynamic test behavior");
|
expect(prompt).toContain("- dynamic_tool: Run dynamic test behavior");
|
||||||
@@ -63,6 +67,7 @@ describe("buildSystemPrompt", () => {
|
|||||||
selectedTools: ["read", "dynamic_tool"],
|
selectedTools: ["read", "dynamic_tool"],
|
||||||
contextFiles: [],
|
contextFiles: [],
|
||||||
skills: [],
|
skills: [],
|
||||||
|
cwd: process.cwd(),
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(prompt).not.toContain("dynamic_tool");
|
expect(prompt).not.toContain("dynamic_tool");
|
||||||
@@ -76,6 +81,7 @@ describe("buildSystemPrompt", () => {
|
|||||||
promptGuidelines: ["Use dynamic_tool for project summaries."],
|
promptGuidelines: ["Use dynamic_tool for project summaries."],
|
||||||
contextFiles: [],
|
contextFiles: [],
|
||||||
skills: [],
|
skills: [],
|
||||||
|
cwd: process.cwd(),
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(prompt).toContain("- Use dynamic_tool for project summaries.");
|
expect(prompt).toContain("- Use dynamic_tool for project summaries.");
|
||||||
@@ -87,6 +93,7 @@ describe("buildSystemPrompt", () => {
|
|||||||
promptGuidelines: ["Use dynamic_tool for summaries.", " Use dynamic_tool for summaries. ", " "],
|
promptGuidelines: ["Use dynamic_tool for summaries.", " Use dynamic_tool for summaries. ", " "],
|
||||||
contextFiles: [],
|
contextFiles: [],
|
||||||
skills: [],
|
skills: [],
|
||||||
|
cwd: process.cwd(),
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(prompt.match(/- Use dynamic_tool for summaries\./g)).toHaveLength(1);
|
expect(prompt.match(/- Use dynamic_tool for summaries\./g)).toHaveLength(1);
|
||||||
|
|||||||
@@ -40,7 +40,15 @@ describe("ToolExecutionComponent parity", () => {
|
|||||||
renderResult: () => new Text("custom result", 0, 0),
|
renderResult: () => new Text("custom result", 0, 0),
|
||||||
};
|
};
|
||||||
|
|
||||||
const component = new ToolExecutionComponent("custom_tool", "tool-1", {}, {}, toolDefinition, createFakeTui());
|
const component = new ToolExecutionComponent(
|
||||||
|
"custom_tool",
|
||||||
|
"tool-1",
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
toolDefinition,
|
||||||
|
createFakeTui(),
|
||||||
|
process.cwd(),
|
||||||
|
);
|
||||||
expect(stripAnsi(component.render(120).join("\n"))).toContain("custom call");
|
expect(stripAnsi(component.render(120).join("\n"))).toContain("custom call");
|
||||||
|
|
||||||
component.updateResult(
|
component.updateResult(
|
||||||
@@ -69,6 +77,7 @@ describe("ToolExecutionComponent parity", () => {
|
|||||||
{},
|
{},
|
||||||
overrideDefinition,
|
overrideDefinition,
|
||||||
createFakeTui(),
|
createFakeTui(),
|
||||||
|
process.cwd(),
|
||||||
);
|
);
|
||||||
component.updateResult({ content: [], details: { diff: "+1 after", firstChangedLine: 1 }, isError: false });
|
component.updateResult({ content: [], details: { diff: "+1 after", firstChangedLine: 1 }, isError: false });
|
||||||
const rendered = stripAnsi(component.render(120).join("\n"));
|
const rendered = stripAnsi(component.render(120).join("\n"));
|
||||||
@@ -85,6 +94,7 @@ describe("ToolExecutionComponent parity", () => {
|
|||||||
{},
|
{},
|
||||||
undefined,
|
undefined,
|
||||||
createFakeTui(),
|
createFakeTui(),
|
||||||
|
process.cwd(),
|
||||||
);
|
);
|
||||||
const rendered = stripAnsi(component.render(120).join("\n"));
|
const rendered = stripAnsi(component.render(120).join("\n"));
|
||||||
expect(rendered).toContain("read");
|
expect(rendered).toContain("read");
|
||||||
@@ -119,6 +129,7 @@ describe("ToolExecutionComponent parity", () => {
|
|||||||
{},
|
{},
|
||||||
createReadToolDefinition(process.cwd()),
|
createReadToolDefinition(process.cwd()),
|
||||||
createFakeTui(),
|
createFakeTui(),
|
||||||
|
process.cwd(),
|
||||||
);
|
);
|
||||||
component.updateResult({ content: [{ type: "text", text: "hello" }], details: undefined, isError: false }, false);
|
component.updateResult({ content: [{ type: "text", text: "hello" }], details: undefined, isError: false }, false);
|
||||||
const rendered = stripAnsi(component.render(120).join("\n"));
|
const rendered = stripAnsi(component.render(120).join("\n"));
|
||||||
@@ -138,6 +149,7 @@ describe("ToolExecutionComponent parity", () => {
|
|||||||
{},
|
{},
|
||||||
overrideDefinition,
|
overrideDefinition,
|
||||||
createFakeTui(),
|
createFakeTui(),
|
||||||
|
process.cwd(),
|
||||||
);
|
);
|
||||||
component.updateResult({ content: [{ type: "text", text: "hello" }], details: undefined, isError: false }, false);
|
component.updateResult({ content: [{ type: "text", text: "hello" }], details: undefined, isError: false }, false);
|
||||||
const rendered = stripAnsi(component.render(120).join("\n"));
|
const rendered = stripAnsi(component.render(120).join("\n"));
|
||||||
@@ -158,6 +170,7 @@ describe("ToolExecutionComponent parity", () => {
|
|||||||
{},
|
{},
|
||||||
overrideDefinition,
|
overrideDefinition,
|
||||||
createFakeTui(),
|
createFakeTui(),
|
||||||
|
process.cwd(),
|
||||||
);
|
);
|
||||||
component.updateResult({ content: [{ type: "text", text: "hello" }], details: undefined, isError: false }, false);
|
component.updateResult({ content: [{ type: "text", text: "hello" }], details: undefined, isError: false }, false);
|
||||||
const rendered = stripAnsi(component.render(120).join("\n"));
|
const rendered = stripAnsi(component.render(120).join("\n"));
|
||||||
@@ -179,6 +192,7 @@ describe("ToolExecutionComponent parity", () => {
|
|||||||
renderResult: () => new Text("override result", 0, 0),
|
renderResult: () => new Text("override result", 0, 0),
|
||||||
},
|
},
|
||||||
createFakeTui(),
|
createFakeTui(),
|
||||||
|
process.cwd(),
|
||||||
);
|
);
|
||||||
component.updateResult({ content: [{ type: "text", text: "hello" }], details: undefined, isError: false }, false);
|
component.updateResult({ content: [{ type: "text", text: "hello" }], details: undefined, isError: false }, false);
|
||||||
const rendered = stripAnsi(component.render(120).join("\n"));
|
const rendered = stripAnsi(component.render(120).join("\n"));
|
||||||
@@ -201,6 +215,7 @@ describe("ToolExecutionComponent parity", () => {
|
|||||||
renderResult: () => new Text("wrapped override result", 0, 0),
|
renderResult: () => new Text("wrapped override result", 0, 0),
|
||||||
},
|
},
|
||||||
createFakeTui(),
|
createFakeTui(),
|
||||||
|
process.cwd(),
|
||||||
);
|
);
|
||||||
component.updateResult({ content: [{ type: "text", text: "hello" }], details: undefined, isError: false }, false);
|
component.updateResult({ content: [{ type: "text", text: "hello" }], details: undefined, isError: false }, false);
|
||||||
const rendered = stripAnsi(component.render(120).join("\n"));
|
const rendered = stripAnsi(component.render(120).join("\n"));
|
||||||
@@ -221,7 +236,15 @@ describe("ToolExecutionComponent parity", () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const component = new ToolExecutionComponent("custom_tool", "tool-5", {}, {}, toolDefinition, createFakeTui());
|
const component = new ToolExecutionComponent(
|
||||||
|
"custom_tool",
|
||||||
|
"tool-5",
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
toolDefinition,
|
||||||
|
createFakeTui(),
|
||||||
|
process.cwd(),
|
||||||
|
);
|
||||||
component.updateResult({ content: [{ type: "text", text: "done" }], details: {}, isError: false }, false);
|
component.updateResult({ content: [{ type: "text", text: "done" }], details: {}, isError: false }, false);
|
||||||
const rendered = stripAnsi(component.render(120).join("\n"));
|
const rendered = stripAnsi(component.render(120).join("\n"));
|
||||||
expect(rendered).toContain("custom call shared-token");
|
expect(rendered).toContain("custom call shared-token");
|
||||||
@@ -243,6 +266,7 @@ describe("ToolExecutionComponent parity", () => {
|
|||||||
{},
|
{},
|
||||||
toolDefinition,
|
toolDefinition,
|
||||||
createFakeTui(),
|
createFakeTui(),
|
||||||
|
process.cwd(),
|
||||||
);
|
);
|
||||||
component.updateResult({ content: [{ type: "text", text: "done" }], details: {}, isError: false }, false);
|
component.updateResult({ content: [{ type: "text", text: "done" }], details: {}, isError: false }, false);
|
||||||
const rendered = stripAnsi(component.render(120).join("\n"));
|
const rendered = stripAnsi(component.render(120).join("\n"));
|
||||||
@@ -261,6 +285,7 @@ describe("ToolExecutionComponent parity", () => {
|
|||||||
{},
|
{},
|
||||||
toolDefinition,
|
toolDefinition,
|
||||||
createFakeTui(),
|
createFakeTui(),
|
||||||
|
process.cwd(),
|
||||||
);
|
);
|
||||||
component.updateResult({ content: [{ type: "text", text: "done" }], details: {}, isError: false }, false);
|
component.updateResult({ content: [{ type: "text", text: "done" }], details: {}, isError: false }, false);
|
||||||
const rendered = stripAnsi(component.render(120).join("\n"));
|
const rendered = stripAnsi(component.render(120).join("\n"));
|
||||||
@@ -276,6 +301,7 @@ describe("ToolExecutionComponent parity", () => {
|
|||||||
{},
|
{},
|
||||||
createWriteToolDefinition(process.cwd()),
|
createWriteToolDefinition(process.cwd()),
|
||||||
createFakeTui(),
|
createFakeTui(),
|
||||||
|
process.cwd(),
|
||||||
);
|
);
|
||||||
const rendered = stripAnsi(component.render(120).join("\n"));
|
const rendered = stripAnsi(component.render(120).join("\n"));
|
||||||
expect(rendered).toContain("one");
|
expect(rendered).toContain("one");
|
||||||
@@ -291,6 +317,7 @@ describe("ToolExecutionComponent parity", () => {
|
|||||||
{},
|
{},
|
||||||
createReadToolDefinition(process.cwd()),
|
createReadToolDefinition(process.cwd()),
|
||||||
createFakeTui(),
|
createFakeTui(),
|
||||||
|
process.cwd(),
|
||||||
);
|
);
|
||||||
component.updateResult(
|
component.updateResult(
|
||||||
{ content: [{ type: "text", text: "one\ntwo\n" }], details: undefined, isError: false },
|
{ content: [{ type: "text", text: "one\ntwo\n" }], details: undefined, isError: false },
|
||||||
|
|||||||
@@ -2,16 +2,26 @@ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|||||||
import { tmpdir } from "os";
|
import { tmpdir } from "os";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import { executeBash } from "../src/core/bash-executor.js";
|
import { executeBashWithOperations } from "../src/core/bash-executor.js";
|
||||||
import { bashTool, createBashTool, createLocalBashOperations } from "../src/core/tools/bash.js";
|
import { createBashTool, createLocalBashOperations } from "../src/core/tools/bash.js";
|
||||||
import { editTool } from "../src/core/tools/edit.js";
|
import {
|
||||||
import { findTool } from "../src/core/tools/find.js";
|
createEditTool,
|
||||||
import { grepTool } from "../src/core/tools/grep.js";
|
createFindTool,
|
||||||
import { lsTool } from "../src/core/tools/ls.js";
|
createGrepTool,
|
||||||
import { readTool } from "../src/core/tools/read.js";
|
createLsTool,
|
||||||
import { writeTool } from "../src/core/tools/write.js";
|
createReadTool,
|
||||||
|
createWriteTool,
|
||||||
|
} from "../src/index.js";
|
||||||
import * as shellModule from "../src/utils/shell.js";
|
import * as shellModule from "../src/utils/shell.js";
|
||||||
|
|
||||||
|
const readTool = createReadTool(process.cwd());
|
||||||
|
const writeTool = createWriteTool(process.cwd());
|
||||||
|
const editTool = createEditTool(process.cwd());
|
||||||
|
const bashTool = createBashTool(process.cwd());
|
||||||
|
const grepTool = createGrepTool(process.cwd());
|
||||||
|
const findTool = createFindTool(process.cwd());
|
||||||
|
const lsTool = createLsTool(process.cwd());
|
||||||
|
|
||||||
// Helper to extract text from content blocks
|
// Helper to extract text from content blocks
|
||||||
function getTextOutput(result: any): string {
|
function getTextOutput(result: any): string {
|
||||||
return (
|
return (
|
||||||
@@ -438,7 +448,11 @@ describe("Coding Agent Tools", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should preserve executeBash sanitization when using local bash operations", async () => {
|
it("should preserve executeBash sanitization when using local bash operations", async () => {
|
||||||
const result = await executeBash("printf '\\033[31mred\\033[0m\\r\\n'");
|
const result = await executeBashWithOperations(
|
||||||
|
"printf '\\033[31mred\\033[0m\\r\\n'",
|
||||||
|
process.cwd(),
|
||||||
|
createLocalBashOperations(),
|
||||||
|
);
|
||||||
|
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
expect(result.output).toBe("red\n");
|
expect(result.output).toBe("red\n");
|
||||||
@@ -468,7 +482,7 @@ describe("Coding Agent Tools", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("executeBash should persist full output when truncation happens by line count only", async () => {
|
it("executeBash should persist full output when truncation happens by line count only", async () => {
|
||||||
const result = await executeBash("seq 3000");
|
const result = await executeBashWithOperations("seq 3000", process.cwd(), createLocalBashOperations());
|
||||||
const fullOutputPath = result.fullOutputPath;
|
const fullOutputPath = result.fullOutputPath;
|
||||||
|
|
||||||
expect(result.truncated).toBe(true);
|
expect(result.truncated).toBe(true);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { ModelRegistry } from "../src/core/model-registry.js";
|
|||||||
import type { ResourceLoader } from "../src/core/resource-loader.js";
|
import type { ResourceLoader } from "../src/core/resource-loader.js";
|
||||||
import { SessionManager } from "../src/core/session-manager.js";
|
import { SessionManager } from "../src/core/session-manager.js";
|
||||||
import { SettingsManager } from "../src/core/settings-manager.js";
|
import { SettingsManager } from "../src/core/settings-manager.js";
|
||||||
import { codingTools } from "../src/core/tools/index.js";
|
import { createCodingTools } from "../src/index.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API key for authenticated tests. Tests using this should be wrapped in
|
* API key for authenticated tests. Tests using this should be wrapped in
|
||||||
@@ -242,7 +242,7 @@ export function createTestSession(options: TestSessionOptions = {}): TestSession
|
|||||||
initialState: {
|
initialState: {
|
||||||
model,
|
model,
|
||||||
systemPrompt: options.systemPrompt ?? "You are a helpful assistant. Be extremely concise.",
|
systemPrompt: options.systemPrompt ?? "You are a helpful assistant. Be extremely concise.",
|
||||||
tools: codingTools,
|
tools: createCodingTools(process.cwd()),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -265,11 +265,7 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|||||||
private basePath: string;
|
private basePath: string;
|
||||||
private fdPath: string | null;
|
private fdPath: string | null;
|
||||||
|
|
||||||
constructor(
|
constructor(commands: (SlashCommand | AutocompleteItem)[] = [], basePath: string, fdPath: string | null = null) {
|
||||||
commands: (SlashCommand | AutocompleteItem)[] = [],
|
|
||||||
basePath: string = process.cwd(),
|
|
||||||
fdPath: string | null = null,
|
|
||||||
) {
|
|
||||||
this.commands = commands;
|
this.commands = commands;
|
||||||
this.basePath = basePath;
|
this.basePath = basePath;
|
||||||
this.fdPath = fdPath;
|
this.fdPath = fdPath;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export class Loader extends Text {
|
|||||||
private currentFrame = 0;
|
private currentFrame = 0;
|
||||||
private intervalId: NodeJS.Timeout | null = null;
|
private intervalId: NodeJS.Timeout | null = null;
|
||||||
private ui: TUI | null = null;
|
private ui: TUI | null = null;
|
||||||
|
private renderIndicatorVerbatim = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
ui: TUI,
|
ui: TUI,
|
||||||
@@ -55,7 +56,8 @@ export class Loader extends Text {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setIndicator(indicator?: LoaderIndicatorOptions): void {
|
setIndicator(indicator?: LoaderIndicatorOptions): void {
|
||||||
this.frames = indicator?.frames ? [...indicator.frames] : [...DEFAULT_FRAMES];
|
this.renderIndicatorVerbatim = indicator !== undefined;
|
||||||
|
this.frames = indicator?.frames !== undefined ? [...indicator.frames] : [...DEFAULT_FRAMES];
|
||||||
this.intervalMs = indicator?.intervalMs && indicator.intervalMs > 0 ? indicator.intervalMs : DEFAULT_INTERVAL_MS;
|
this.intervalMs = indicator?.intervalMs && indicator.intervalMs > 0 ? indicator.intervalMs : DEFAULT_INTERVAL_MS;
|
||||||
this.currentFrame = 0;
|
this.currentFrame = 0;
|
||||||
this.start();
|
this.start();
|
||||||
@@ -74,7 +76,8 @@ export class Loader extends Text {
|
|||||||
|
|
||||||
private updateDisplay(): void {
|
private updateDisplay(): void {
|
||||||
const frame = this.frames[this.currentFrame] ?? "";
|
const frame = this.frames[this.currentFrame] ?? "";
|
||||||
const indicator = frame.length > 0 ? `${this.spinnerColorFn(frame)} ` : "";
|
const renderedFrame = this.renderIndicatorVerbatim ? frame : this.spinnerColorFn(frame);
|
||||||
|
const indicator = frame.length > 0 ? `${renderedFrame} ` : "";
|
||||||
this.setText(`${indicator}${this.messageColorFn(this.message)}`);
|
this.setText(`${indicator}${this.messageColorFn(this.message)}`);
|
||||||
if (this.ui) {
|
if (this.ui) {
|
||||||
this.ui.requestRender();
|
this.ui.requestRender();
|
||||||
|
|||||||
@@ -2476,14 +2476,17 @@ describe("Editor component", () => {
|
|||||||
|
|
||||||
it("awaits async slash command argument completions", async () => {
|
it("awaits async slash command argument completions", async () => {
|
||||||
const editor = new Editor(createTestTUI(), defaultEditorTheme);
|
const editor = new Editor(createTestTUI(), defaultEditorTheme);
|
||||||
const provider = new CombinedAutocompleteProvider([
|
const provider = new CombinedAutocompleteProvider(
|
||||||
{
|
[
|
||||||
name: "load-skills",
|
{
|
||||||
description: "Load skills",
|
name: "load-skills",
|
||||||
getArgumentCompletions: async (prefix) =>
|
description: "Load skills",
|
||||||
prefix.startsWith("s") ? [{ value: "skill-a", label: "skill-a" }] : null,
|
getArgumentCompletions: async (prefix) =>
|
||||||
},
|
prefix.startsWith("s") ? [{ value: "skill-a", label: "skill-a" }] : null,
|
||||||
]);
|
},
|
||||||
|
],
|
||||||
|
process.cwd(),
|
||||||
|
);
|
||||||
editor.setAutocompleteProvider(provider);
|
editor.setAutocompleteProvider(provider);
|
||||||
editor.setText("/load-skills ");
|
editor.setText("/load-skills ");
|
||||||
|
|
||||||
@@ -2498,15 +2501,18 @@ describe("Editor component", () => {
|
|||||||
|
|
||||||
it("ignores invalid slash command argument completion results", async () => {
|
it("ignores invalid slash command argument completion results", async () => {
|
||||||
const editor = new Editor(createTestTUI(), defaultEditorTheme);
|
const editor = new Editor(createTestTUI(), defaultEditorTheme);
|
||||||
const provider = new CombinedAutocompleteProvider([
|
const provider = new CombinedAutocompleteProvider(
|
||||||
{
|
[
|
||||||
name: "load-skills",
|
{
|
||||||
description: "Load skills",
|
name: "load-skills",
|
||||||
getArgumentCompletions: (() => "not-an-array") as unknown as (
|
description: "Load skills",
|
||||||
argumentPrefix: string,
|
getArgumentCompletions: (() => "not-an-array") as unknown as (
|
||||||
) => Promise<{ value: string; label: string }[] | null>,
|
argumentPrefix: string,
|
||||||
},
|
) => Promise<{ value: string; label: string }[] | null>,
|
||||||
]);
|
},
|
||||||
|
],
|
||||||
|
process.cwd(),
|
||||||
|
);
|
||||||
editor.setAutocompleteProvider(provider);
|
editor.setAutocompleteProvider(provider);
|
||||||
editor.setText("/load-skills ");
|
editor.setText("/load-skills ");
|
||||||
|
|
||||||
@@ -2518,14 +2524,17 @@ describe("Editor component", () => {
|
|||||||
|
|
||||||
it("does not show argument completions when command has no argument completer", async () => {
|
it("does not show argument completions when command has no argument completer", async () => {
|
||||||
const editor = new Editor(createTestTUI(), defaultEditorTheme);
|
const editor = new Editor(createTestTUI(), defaultEditorTheme);
|
||||||
const provider = new CombinedAutocompleteProvider([
|
const provider = new CombinedAutocompleteProvider(
|
||||||
{ name: "help", description: "Show help" },
|
[
|
||||||
{
|
{ name: "help", description: "Show help" },
|
||||||
name: "model",
|
{
|
||||||
description: "Switch model",
|
name: "model",
|
||||||
getArgumentCompletions: () => [{ value: "claude-opus", label: "claude-opus" }],
|
description: "Switch model",
|
||||||
},
|
getArgumentCompletions: () => [{ value: "claude-opus", label: "claude-opus" }],
|
||||||
]);
|
},
|
||||||
|
],
|
||||||
|
process.cwd(),
|
||||||
|
);
|
||||||
editor.setAutocompleteProvider(provider);
|
editor.setAutocompleteProvider(provider);
|
||||||
|
|
||||||
editor.handleInput("/");
|
editor.handleInput("/");
|
||||||
|
|||||||
Reference in New Issue
Block a user