wip: remove opencode, searxng, resources; fix user settings read

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 07:27:19 +00:00
co-authored by Claude Opus 4.6
parent 5925ac49a1
commit 7bbcccabf1
46 changed files with 325 additions and 2037 deletions
+49 -61
View File
@@ -1,5 +1,5 @@
import { join } from 'node:path';
import { readdirSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
import { readdirSync, existsSync, mkdirSync } from 'node:fs';
import type { Subprocess } from 'bun';
import type { PiEvent, MessageCost } from '../api/pi/types';
import type { PiSpawnParams, PiSessionInfo } from './protocol';
@@ -7,8 +7,6 @@ import { isPidAlive } from './state';
const DATA_PATH = process.env.DATA_PATH ?? join(process.cwd(), 'data');
const PI_CONFIG_DIR = join(require('node:os').homedir(), '.pi', 'agent');
const SEED_PATH = join(import.meta.dir, '../../../seed');
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);
@@ -18,12 +16,15 @@ 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 getNativeResourcesDir = () => join(SEED_PATH, 'resources');
const getGlobalResourcesDir = () => join(DATA_PATH, 'resources');
const toShellUsername = (username: string, email: string): string => {
const raw = username || email.split('@')[0]!;
return raw.replace(/@.*$/, '').replace(/[^a-zA-Z0-9._-]/g, '_').toLowerCase().slice(0, 32) || 'officer';
return (
raw
.replace(/@.*$/, '')
.replace(/[^a-zA-Z0-9._-]/g, '_')
.toLowerCase()
.slice(0, 32) || 'officer'
);
};
// Resolve pi as [node, cli.js]
@@ -85,35 +86,6 @@ function collectExtensionFlags(email: string): string[] {
return flags;
}
function buildResourcesEnv(): string {
const nativeDir = getNativeResourcesDir();
const globalDir = getGlobalResourcesDir();
const resourceDirs = new Map<string, string>();
for (const dir of [nativeDir, globalDir]) {
if (!existsSync(dir)) continue;
for (const entry of readdirSync(dir, { withFileTypes: true })) {
if (!entry.isDirectory()) continue;
if (existsSync(join(dir, entry.name, 'RESOURCE.md')) || existsSync(join(dir, entry.name, 'config.json'))) {
resourceDirs.set(entry.name, dir);
}
}
}
const result: Record<string, Record<string, string>> = {};
for (const [name] of resourceDirs) {
let nativeConfig: Record<string, string> = {};
let globalConfig: Record<string, string> = {};
try { nativeConfig = JSON.parse(readFileSync(join(nativeDir, name, 'config.json'), 'utf-8')); } catch {}
try { globalConfig = JSON.parse(readFileSync(join(globalDir, name, 'config.json'), 'utf-8')); } catch {}
const config: Record<string, string> = {};
for (const key of Object.keys(nativeConfig)) config[key] = globalConfig[key] ?? nativeConfig[key]!;
for (const key of Object.keys(globalConfig)) if (!(key in config)) config[key] = globalConfig[key]!;
if (Object.values(config).some((v) => v !== '')) {
result[name] = config;
}
}
return JSON.stringify(result);
}
async function resolveApiKeyForModel(model: string): Promise<string | null> {
const provider = model.split('/')[0];
if (!provider) return null;
@@ -134,7 +106,9 @@ function parseErrorMessage(raw: string): string {
const parsed = JSON.parse(raw.replace(/^\d+\s*/, ''));
const inner = parsed?.error;
if (inner?.message) return inner.message;
} catch { /* not JSON */ }
} catch {
/* not JSON */
}
return raw;
}
@@ -181,12 +155,14 @@ function parsePiEvent(event: Record<string, unknown>, currentStreamBuffer: strin
}
case 'tool_execution_start':
return [{
type: 'tool:start',
toolCallId: (event.toolCallId as string) ?? '',
toolName: (event.toolName as string) ?? 'unknown',
toolInput: (event.args as Record<string, unknown>) ?? {},
}];
return [
{
type: 'tool:start',
toolCallId: (event.toolCallId as string) ?? '',
toolName: (event.toolName as string) ?? 'unknown',
toolInput: (event.args as Record<string, unknown>) ?? {},
},
];
case 'tool_execution_end': {
const toolCallId = (event.toolCallId as string) ?? '';
@@ -195,7 +171,11 @@ function parsePiEvent(event: Record<string, unknown>, currentStreamBuffer: strin
if (typeof result === 'object' && result !== null) {
resultObj = result as Record<string, unknown>;
} else if (typeof result === 'string') {
try { resultObj = JSON.parse(result); } catch { /* not JSON */ }
try {
resultObj = JSON.parse(result);
} catch {
/* not JSON */
}
}
const isError = (event.isError as boolean) ?? (resultObj?.isError as boolean) ?? false;
const output = result != null ? (typeof result === 'string' ? result : JSON.stringify(result)) : '';
@@ -260,18 +240,15 @@ export async function spawnPi(options: PiSpawnOptions): Promise<void> {
const skillFlags = collectSkillFlags(email);
const extensionFlags = collectExtensionFlags(email);
// Generate resource skill
const { generateResourceSkill } = await import('../api/pi/pi-bridge');
const resourceSkillDir = generateResourceSkill(DATA_PATH);
const resourceSkillFlags = resourceSkillDir ? ['--skill', resourceSkillDir] : [];
const piArgs = [
...PI_CMD,
'--mode', 'rpc',
'--no-skills', '--no-prompt-templates', '--no-themes',
'--mode',
'rpc',
'--no-skills',
'--no-prompt-templates',
'--no-themes',
...skillFlags,
...extensionFlags,
...resourceSkillFlags,
];
if (model) piArgs.push('--model', model);
if (sessionFile) piArgs.push('--session', sessionFile);
@@ -294,7 +271,6 @@ export async function spawnPi(options: PiSpawnOptions): Promise<void> {
OFFICER_USER_ROOT: join(DATA_PATH, email),
PI_CODING_AGENT_DIR: isServiceUser ? PI_CONFIG_DIR : join(homeDir, '.pi', 'agent'),
PI_TOOLS_DIRS: toolsDirs,
OFFICER_RESOURCES: buildResourcesEnv(),
OFFICER_EMAIL_DB: join(DATA_PATH, email, 'emails.db'),
TERM: 'xterm-256color',
PATH: process.env.PATH ?? '',
@@ -302,10 +278,12 @@ export async function spawnPi(options: PiSpawnOptions): Promise<void> {
const proc = isServiceUser
? Bun.spawn(piArgs, { cwd, stdin: 'pipe', stdout: 'pipe', stderr: 'pipe', env: { ...process.env, ...env } })
: Bun.spawn(
['sudo', '-u', shellUsername, 'env', ...Object.entries(env).map(([k, v]) => `${k}=${v}`), ...piArgs],
{ cwd, stdin: 'pipe', stdout: 'pipe', stderr: 'pipe' },
);
: Bun.spawn(['sudo', '-u', shellUsername, 'env', ...Object.entries(env).map(([k, v]) => `${k}=${v}`), ...piArgs], {
cwd,
stdin: 'pipe',
stdout: 'pipe',
stderr: 'pipe',
});
const session: PiSession = { sessionId, email, userId, model, cwd, proc, onEvent };
sessions.set(sessionId, session);
@@ -340,10 +318,14 @@ export async function spawnPi(options: PiSpawnOptions): Promise<void> {
}
onEvent(piEvent);
}
} catch { /* skip */ }
} catch {
/* skip */
}
}
}
} catch { /* process ended */ }
} catch {
/* process ended */
}
})();
// Stderr → log
@@ -358,7 +340,9 @@ export async function spawnPi(options: PiSpawnOptions): Promise<void> {
const text = stderrDecoder.decode(value, { stream: true });
if (text.trim()) console.log(`[sidecar:pi:stderr] ${text.trim()}`);
}
} catch { /* process ended */ }
} catch {
/* process ended */
}
})();
// Handle exit
@@ -394,7 +378,11 @@ export function setThinkingLevel(sessionId: string, level: string): boolean {
export function killPiSession(sessionId: string): boolean {
const session = sessions.get(sessionId);
if (!session) return false;
try { session.proc.kill(); } catch { /* already dead */ }
try {
session.proc.kill();
} catch {
/* already dead */
}
sessions.delete(sessionId);
return true;
}