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:
2026-07-21 00:39:17 +00:00
co-authored by Claude Opus 4.8
parent 5fd3d4faac
commit f3492512ba
29 changed files with 2366 additions and 1357 deletions
+15 -31
View File
@@ -1,8 +1,23 @@
import { join, resolve } from 'node:path';
import { mkdirSync } from 'node:fs';
import { homedir } from 'node:os';
export const DATA_PATH = process.env.DATA_PATH ?? join(process.cwd(), 'data');
// Unified, file-based store for all agent items, living outside the repo. Every skill/tool/task/
// process/extension is a directory under one of these type subfolders — no scope tiers, no DB.
export const OFFICER_ITEMS_DIR = process.env.OFFICER_ITEMS_DIR ?? join(process.cwd(), 'officer-items');
export type ItemType = 'skills' | 'tools' | 'tasks' | 'processes' | 'extensions';
export const ITEM_TYPES: ItemType[] = ['skills', 'tools', 'tasks', 'processes', 'extensions'];
export const itemsDir = (type: ItemType) => join(OFFICER_ITEMS_DIR, type);
export const ensureItemDirs = () => {
for (const type of ITEM_TYPES) mkdirSync(itemsDir(type), { recursive: true });
};
export const SERVER_CONFIG_DIR = join(DATA_PATH, 'server-settings');
export const PI_CONFIG_DIR = join(homedir(), '.pi', 'agent');
@@ -33,43 +48,12 @@ export const getUserPiConfigDir = (email: string) => join(DATA_PATH, email, 'hom
export const getUserProjectsDir = (email: string) => join(DATA_PATH, email, 'home', 'Projects');
export const getNativeSkillsDir = () => join(SEED_PATH, 'skills');
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');
export const getUserTasksDir = (email: string) => join(DATA_PATH, email, 'tasks');
export const getNativeProcessesDir = () => join(SEED_PATH, 'processes');
export const getGlobalProcessesDir = () => join(DATA_PATH, 'processes');
export const getUserProcessesDir = (email: string) => join(DATA_PATH, email, 'processes');
export const getTmpAttachmentsDir = (email: string) => join(DATA_PATH, email, 'attachments', 'tmp');
export const getAttachmentsDir = (email: string, sessionId: string) => join(DATA_PATH, email, 'attachments', sessionId);
export const getUserEmailDir = (email: string) => join(DATA_PATH, email, 'Gmail', 'emails');
/** Derive a valid Linux username from a display username or email. */
export const toShellUsername = (username: string, email: string): string => {
const raw = username || email.split('@')[0]!;