tools system - web-fetch
This commit is contained in:
@@ -3,7 +3,7 @@ import { readdirSync, existsSync, mkdirSync } from "node:fs";
|
||||
import type { Subprocess } from "bun";
|
||||
import type { PiEvent, MessageCost } from "./types";
|
||||
import { readApiKeys } from "../server-settings/pi-mono";
|
||||
import { PI_CONFIG_DIR, getGlobalSkillsDir, getUserSkillsDir } from "../../data-path";
|
||||
import { PI_CONFIG_DIR, getGlobalSkillsDir, getUserSkillsDir, getGlobalExtensionsDir, getUserExtensionsDir, getGlobalToolsDir, getUserToolsDir } from "../../data-path";
|
||||
import { ensureDockerContainer } from "../terminal/websocket";
|
||||
import { logger } from "./logger";
|
||||
|
||||
@@ -28,6 +28,25 @@ function collectSkillFlags(email: string): string[] {
|
||||
return flags;
|
||||
}
|
||||
|
||||
function collectExtensionFlags(email: string): string[] {
|
||||
const flags: string[] = [];
|
||||
const dirs = [getGlobalExtensionsDir(), getUserExtensionsDir(email)];
|
||||
|
||||
for (const dir of dirs) {
|
||||
if (!existsSync(dir)) continue;
|
||||
const entries = readdirSync(dir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
const entryFile = join(dir, entry.name, 'index.ts');
|
||||
if (existsSync(entryFile)) {
|
||||
flags.push('--extension', entryFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
type SandboxOptions = {
|
||||
userId: number;
|
||||
username: string;
|
||||
@@ -81,22 +100,29 @@ export async function spawnPi(
|
||||
} else {
|
||||
const storedKeys = await readApiKeys();
|
||||
const skillFlags = collectSkillFlags(email);
|
||||
const args = ['pi', '--mode', 'rpc', '--no-extensions', '--no-skills', '--no-prompt-templates', '--no-themes', ...skillFlags];
|
||||
const extensionFlags = collectExtensionFlags(email);
|
||||
const args = ['pi', '--mode', 'rpc', '--no-skills', '--no-prompt-templates', '--no-themes', ...skillFlags, ...extensionFlags];
|
||||
if (model) args.push('--model', model);
|
||||
|
||||
if (!existsSync(cwd)) {
|
||||
mkdirSync(cwd, { recursive: true });
|
||||
}
|
||||
|
||||
const toolsDirs = [getGlobalToolsDir(), getUserToolsDir(email)].join(':');
|
||||
|
||||
proc = Bun.spawn(args, {
|
||||
cwd,
|
||||
stdin: 'pipe',
|
||||
stdout: 'pipe',
|
||||
stderr: 'pipe',
|
||||
env: { ...process.env, ...storedKeys, PI_CODING_AGENT_DIR: PI_CONFIG_DIR },
|
||||
env: { ...process.env, ...storedKeys, PI_CODING_AGENT_DIR: PI_CONFIG_DIR, PI_TOOLS_DIRS: toolsDirs },
|
||||
});
|
||||
|
||||
logger.info('Spawned Pi locally', { model, skills: skillFlags.filter((f) => f !== '--skill').length });
|
||||
logger.info('Spawned Pi locally', {
|
||||
model,
|
||||
skills: skillFlags.filter((f) => f !== '--skill').length,
|
||||
extensions: extensionFlags.filter((f) => f !== '--extension').length,
|
||||
});
|
||||
}
|
||||
|
||||
// Read stdout JSON event stream (runs in background)
|
||||
|
||||
@@ -6,6 +6,8 @@ import { syncLocalProvidersToPiConfig } from './api/server-settings/sync-pi-conf
|
||||
import { syncAllUserPiConfigs } from './api/server-settings/sync-user-pi-config';
|
||||
import { initAuthStore } from 'officerdb';
|
||||
import { syncSeedSkills } from './sync-skills';
|
||||
import { syncSeedTools } from './sync-tools';
|
||||
import { syncSeedExtensions } from './sync-extensions';
|
||||
|
||||
mkdirSync(DATA_PATH, { recursive: true });
|
||||
mkdirSync(PI_CONFIG_DIR, { recursive: true });
|
||||
@@ -71,6 +73,8 @@ function seedPiConfig(): void {
|
||||
|
||||
seedPiConfig();
|
||||
syncSeedSkills();
|
||||
syncSeedTools();
|
||||
syncSeedExtensions();
|
||||
|
||||
await syncLocalProvidersToPiConfig().catch(err => {
|
||||
console.error('[bootstrap] Failed to sync local providers to Pi config:', err);
|
||||
|
||||
@@ -50,6 +50,18 @@ export const getGlobalSkillsDir = () => join(DATA_PATH, 'skills');
|
||||
|
||||
export const getUserSkillsDir = (email: string) => join(DATA_PATH, email, 'skills');
|
||||
|
||||
export const getNativeToolsDir = () => join(SEED_PATH, 'tools');
|
||||
|
||||
export const getGlobalToolsDir = () => join(DATA_PATH, 'tools');
|
||||
|
||||
export const getUserToolsDir = (email: string) => join(DATA_PATH, email, 'tools');
|
||||
|
||||
export const getNativeExtensionsDir = () => join(SEED_PATH, 'extensions');
|
||||
|
||||
export const getGlobalExtensionsDir = () => join(DATA_PATH, 'extensions');
|
||||
|
||||
export const getUserExtensionsDir = (email: string) => join(DATA_PATH, email, 'extensions');
|
||||
|
||||
export const getNativeTasksDir = () => join(SEED_PATH, 'tasks');
|
||||
|
||||
export const getGlobalTasksDir = () => join(DATA_PATH, 'tasks');
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { readdirSync, existsSync, mkdirSync, cpSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { SEED_PATH, DATA_PATH } from './data-path';
|
||||
|
||||
const SEED_EXTENSIONS_DIR = join(SEED_PATH, 'extensions');
|
||||
const GLOBAL_EXTENSIONS_DIR = join(DATA_PATH, 'extensions');
|
||||
|
||||
export function syncSeedExtensions(): void {
|
||||
if (!existsSync(SEED_EXTENSIONS_DIR)) return;
|
||||
|
||||
mkdirSync(GLOBAL_EXTENSIONS_DIR, { recursive: true });
|
||||
|
||||
const seedEntries = readdirSync(SEED_EXTENSIONS_DIR, { withFileTypes: true });
|
||||
|
||||
for (const entry of seedEntries) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
|
||||
const seedExtensionDir = join(SEED_EXTENSIONS_DIR, entry.name);
|
||||
const entryFile = join(seedExtensionDir, 'index.ts');
|
||||
if (!existsSync(entryFile)) continue;
|
||||
|
||||
const targetDir = join(GLOBAL_EXTENSIONS_DIR, entry.name);
|
||||
|
||||
if (existsSync(targetDir)) {
|
||||
// Extension already exists in DATA_PATH — always overwrite (managed by us, not the user)
|
||||
cpSync(seedExtensionDir, targetDir, { recursive: true });
|
||||
continue;
|
||||
}
|
||||
|
||||
cpSync(seedExtensionDir, targetDir, { recursive: true });
|
||||
console.log(`[extensions] Synced seed extension: ${entry.name}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { readdirSync, existsSync, mkdirSync, cpSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { SEED_PATH, DATA_PATH } from './data-path';
|
||||
|
||||
const SEED_TOOLS_DIR = join(SEED_PATH, 'tools');
|
||||
const GLOBAL_TOOLS_DIR = join(DATA_PATH, 'tools');
|
||||
|
||||
export function syncSeedTools(): void {
|
||||
if (!existsSync(SEED_TOOLS_DIR)) return;
|
||||
|
||||
mkdirSync(GLOBAL_TOOLS_DIR, { recursive: true });
|
||||
|
||||
const seedEntries = readdirSync(SEED_TOOLS_DIR, { withFileTypes: true });
|
||||
|
||||
for (const entry of seedEntries) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
|
||||
const seedToolDir = join(SEED_TOOLS_DIR, entry.name);
|
||||
const toolFile = join(seedToolDir, 'TOOL.md');
|
||||
if (!existsSync(toolFile)) continue;
|
||||
|
||||
const targetDir = join(GLOBAL_TOOLS_DIR, entry.name);
|
||||
|
||||
if (existsSync(targetDir)) {
|
||||
// Tool already exists in DATA_PATH — skip to preserve user edits
|
||||
continue;
|
||||
}
|
||||
|
||||
cpSync(seedToolDir, targetDir, { recursive: true });
|
||||
console.log(`[tools] Synced seed tool: ${entry.name}`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user