unify agent items into a flat file-based store, drop the marketplace
Replace the marketplace service dependency and the native/global/user scope tiers with a single external directory ($OFFICER_ITEMS_DIR) holding skills, tools, tasks, processes and extensions as plain files. - tasks move from Postgres to TASK.md files (new file-backed task layer); task editing now works, which the DB path never supported - skills/tools/processes collapse into one shared file router (single dir) - remove the marketplace client (sync-marketplace/sync-version) and the boot-time sync; pi-bridge/pi-manager/sandbox point at the flat store - drop the dead tasks + vestigial skills/tools/processes/extensions + item_chats tables (migration 0004) - one-time migration script exports DB tasks and consolidates disk items Migration verified: all 6 tasks round-trip through the runtime parser identically to their DB rows (pipeline steps, triggers, script impls and agentic bodies all intact). NOTE: not yet functionally tested end-to-end — every item (each task mode, tool, skill, extension) still needs to be run/exercised in the app before this is trusted. To be done manually. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -21,12 +21,8 @@ const PI_CONFIG_DIR = join(require('node:os').homedir(), '.pi', 'agent');
|
||||
const getHomeDir = (email: string) => join(DATA_PATH, email, 'home');
|
||||
const getHomeDirForRole = (email: string, role: string | null): string =>
|
||||
role === 'Super Admin' && process.env.HOME_DIR ? process.env.HOME_DIR : getHomeDir(email);
|
||||
const getGlobalSkillsDir = () => join(DATA_PATH, 'skills');
|
||||
const getUserSkillsDir = (email: string) => join(DATA_PATH, email, 'skills');
|
||||
const getGlobalExtensionsDir = () => join(DATA_PATH, 'extensions');
|
||||
const getUserExtensionsDir = (email: string) => join(DATA_PATH, email, 'extensions');
|
||||
const getGlobalToolsDir = () => join(DATA_PATH, 'tools');
|
||||
const getUserToolsDir = (email: string) => join(DATA_PATH, email, 'tools');
|
||||
const OFFICER_ITEMS_DIR = process.env.OFFICER_ITEMS_DIR ?? join(process.cwd(), 'officer-items');
|
||||
const itemsDir = (type: 'skills' | 'tools' | 'extensions') => join(OFFICER_ITEMS_DIR, type);
|
||||
|
||||
function isPidAlive(pid: number): boolean {
|
||||
try {
|
||||
@@ -97,11 +93,8 @@ function collectSkillFlagsFromDir(scanDir: string, targetDir: string): string[]
|
||||
return flags;
|
||||
}
|
||||
|
||||
function collectSkillFlags(email: string): string[] {
|
||||
return [
|
||||
...collectSkillFlagsFromDir(getGlobalSkillsDir(), getGlobalSkillsDir()),
|
||||
...collectSkillFlagsFromDir(getUserSkillsDir(email), getUserSkillsDir(email)),
|
||||
];
|
||||
function collectSkillFlags(): string[] {
|
||||
return collectSkillFlagsFromDir(itemsDir('skills'), itemsDir('skills'));
|
||||
}
|
||||
|
||||
function collectExtensionFlagsFromDir(scanDir: string, targetDir: string): string[] {
|
||||
@@ -118,11 +111,8 @@ function collectExtensionFlagsFromDir(scanDir: string, targetDir: string): strin
|
||||
return flags;
|
||||
}
|
||||
|
||||
function collectExtensionFlags(email: string): string[] {
|
||||
return [
|
||||
...collectExtensionFlagsFromDir(getGlobalExtensionsDir(), getGlobalExtensionsDir()),
|
||||
...collectExtensionFlagsFromDir(getUserExtensionsDir(email), getUserExtensionsDir(email)),
|
||||
];
|
||||
function collectExtensionFlags(): string[] {
|
||||
return collectExtensionFlagsFromDir(itemsDir('extensions'), itemsDir('extensions'));
|
||||
}
|
||||
|
||||
async function resolveApiKeyForModel(model: string): Promise<string | null> {
|
||||
@@ -278,17 +268,11 @@ export async function spawnPi(options: PiSpawnOptions): Promise<void> {
|
||||
|
||||
const isSuperAdmin = role === 'Super Admin';
|
||||
const skillFlags = isSuperAdmin
|
||||
? collectSkillFlags(email)
|
||||
: [
|
||||
...collectSkillFlagsFromDir(getGlobalSkillsDir(), SANDBOX_GLOBAL_SKILLS),
|
||||
...collectSkillFlagsFromDir(getUserSkillsDir(email), `${SANDBOX_DATA}/skills`),
|
||||
];
|
||||
? collectSkillFlags()
|
||||
: collectSkillFlagsFromDir(itemsDir('skills'), SANDBOX_GLOBAL_SKILLS);
|
||||
const extensionFlags = isSuperAdmin
|
||||
? collectExtensionFlags(email)
|
||||
: [
|
||||
...collectExtensionFlagsFromDir(getGlobalExtensionsDir(), SANDBOX_GLOBAL_EXTENSIONS),
|
||||
...collectExtensionFlagsFromDir(getUserExtensionsDir(email), `${SANDBOX_DATA}/extensions`),
|
||||
];
|
||||
? collectExtensionFlags()
|
||||
: collectExtensionFlagsFromDir(itemsDir('extensions'), SANDBOX_GLOBAL_EXTENSIONS);
|
||||
|
||||
const piArgs = [
|
||||
...PI_CMD,
|
||||
@@ -311,7 +295,7 @@ export async function spawnPi(options: PiSpawnOptions): Promise<void> {
|
||||
}
|
||||
|
||||
const homeDir = getHomeDirForRole(email, role);
|
||||
const toolsDirs = [getGlobalToolsDir(), getUserToolsDir(email)].join(':');
|
||||
const toolsDirs = itemsDir('tools');
|
||||
|
||||
// Per-session JWT so tools (e.g. the gmail proxy) can call back to dev-platform
|
||||
// as the owning user. Mirrors the signin payload shape so userMiddleware accepts it.
|
||||
@@ -338,7 +322,7 @@ export async function spawnPi(options: PiSpawnOptions): Promise<void> {
|
||||
proc = Bun.spawn(piArgs, { cwd, stdin: 'pipe', stdout: 'pipe', stderr: 'pipe', env });
|
||||
} else {
|
||||
// Non-admin: run inside bwrap sandbox
|
||||
const sandboxToolsDirs = [SANDBOX_GLOBAL_TOOLS, `${SANDBOX_DATA}/tools`].join(':');
|
||||
const sandboxToolsDirs = SANDBOX_GLOBAL_TOOLS;
|
||||
const prefix = buildSandboxPrefix(email);
|
||||
|
||||
// Pi-specific env vars
|
||||
|
||||
@@ -10,6 +10,7 @@ const BUN_DIR = (() => {
|
||||
|
||||
const PROJECT_ROOT = resolve(import.meta.dir, '../../..');
|
||||
const DATA_PATH = process.env.DATA_PATH ?? join(process.cwd(), 'data');
|
||||
const OFFICER_ITEMS_DIR = process.env.OFFICER_ITEMS_DIR ?? join(process.cwd(), 'officer-items');
|
||||
const HOST_HOME = process.env.HOME!;
|
||||
|
||||
// Resolve the OS username for runuser to drop privileges inside the sandbox
|
||||
@@ -31,9 +32,9 @@ export const SANDBOX_GLOBAL_TOOLS = `${SANDBOX_GLOBAL_ROOT}/tools`;
|
||||
// Callers can append extra `--setenv` args before calling `buildRunuserSuffix()`.
|
||||
export function buildSandboxPrefix(email: string): string[] {
|
||||
const userDataDir = join(DATA_PATH, email);
|
||||
const globalSkillsDir = join(DATA_PATH, 'skills');
|
||||
const globalToolsDir = join(DATA_PATH, 'tools');
|
||||
const globalExtensionsDir = join(DATA_PATH, 'extensions');
|
||||
const globalSkillsDir = join(OFFICER_ITEMS_DIR, 'skills');
|
||||
const globalToolsDir = join(OFFICER_ITEMS_DIR, 'tools');
|
||||
const globalExtensionsDir = join(OFFICER_ITEMS_DIR, 'extensions');
|
||||
|
||||
const args = [
|
||||
'sudo',
|
||||
|
||||
Reference in New Issue
Block a user