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
+20 -145
View File
@@ -1,8 +1,7 @@
import { join } from 'path';
import { readdirSync, existsSync, mkdirSync, writeFileSync, readFileSync } from 'node:fs';
import { readdirSync, existsSync, mkdirSync } from 'node:fs';
import type { Subprocess } from 'bun';
import type { PiEvent, MessageCost } from './types';
import { readSearxngConfig } from '../server-settings/searxng';
import {
PI_CONFIG_DIR,
DATA_PATH,
@@ -13,13 +12,10 @@ import {
getUserExtensionsDir,
getGlobalToolsDir,
getUserToolsDir,
getNativeResourcesDir,
getGlobalResourcesDir,
toShellUsername,
} from '../../data-path';
import { getServerIntegration, getUserIntegration } from 'officerdb';
import { getServerIntegration, getUserIntegration, readConfigValue } from 'officerdb';
import { logger } from './logger';
import { parseFrontmatter } from '../skills/skills';
import { getRelayPort } from '../browser/relay';
import { registerUserToken } from '../browser/relay-auth';
@@ -75,125 +71,6 @@ function collectExtensionFlags(email: string): string[] {
return flags;
}
export function generateResourceSkill(outputDir: string): string | null {
const nativeDir = getNativeResourcesDir();
const globalDir = getGlobalResourcesDir();
// Collect all resource dirs (global overrides native)
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'))) {
resourceDirs.set(entry.name, dir);
}
}
}
if (resourceDirs.size === 0) return null;
const sections: string[] = [];
for (const [name, baseDir] of resourceDirs) {
const resourceMd = join(baseDir, name, 'RESOURCE.md');
let mdContent = '';
try {
mdContent = readFileSync(resourceMd, 'utf-8');
} catch {
continue;
}
const { frontmatter } = parseFrontmatter(mdContent);
// Merge native + global config
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]!;
const hasValues = Object.values(config).some((v) => v !== '');
const configLines = Object.entries(config)
.filter(([, v]) => v)
.map(([k, v]) => (/key|secret|password|token/i.test(k) ? `- **${k}**: (configured)` : `- **${k}**: ${v}`));
sections.push(
[
`### ${frontmatter.name || name}`,
hasValues ? 'Status: **configured**' : 'Status: not configured',
...configLines,
].join('\n'),
);
}
const skillContent = [
'---',
'name: Available Resources',
'description: External services and APIs configured on this Officer instance',
'---',
'',
'These are external services available to you. Use their configured URLs directly via HTTP requests.',
'Do NOT try to install local alternatives (like tesseract, whisper, etc.) — use the configured HTTP APIs instead.',
'',
...sections,
].join('\n');
const skillDir = join(outputDir, '.generated', 'available-resources');
try {
mkdirSync(skillDir, { recursive: true });
writeFileSync(join(skillDir, 'SKILL.md'), skillContent, 'utf-8');
return skillDir;
} catch {
logger.error(`Failed to write resource skill to ${skillDir}`);
return null;
}
}
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]!;
// Only include resources that have at least one non-empty value
if (Object.values(config).some((v) => v !== '')) {
result[name] = config;
}
}
return JSON.stringify(result);
}
async function getApifyToken(): Promise<string> {
try {
const integration = await getServerIntegration('apify');
@@ -248,13 +125,10 @@ export async function spawnPi(
onEvent: PiEventHandler,
options?: SpawnPiOptions,
): Promise<Subprocess> {
const searxng = await readSearxngConfig();
const searxngUrl = await readConfigValue('searxng-url', '');
const skillFlags = collectSkillFlags(email);
const extensionFlags = collectExtensionFlags(email);
const resourceSkillDir = generateResourceSkill(DATA_PATH);
const resourceSkillFlags = resourceSkillDir ? ['--skill', resourceSkillDir] : [];
const piArgs = [
...PI_CMD,
'--mode',
@@ -264,7 +138,6 @@ export async function spawnPi(
'--no-themes',
...skillFlags,
...extensionFlags,
...resourceSkillFlags,
];
if (model) piArgs.push('--model', model);
if (options?.sessionFile) piArgs.push('--session', options.sessionFile);
@@ -290,8 +163,7 @@ export async function spawnPi(
OFFICER_USER_ROOT: join(DATA_PATH, email),
PI_CODING_AGENT_DIR: isServiceUser ? PI_CONFIG_DIR : join(homeDir, '.pi', 'agent'),
PI_TOOLS_DIRS: toolsDirs,
PI_SEARXNG_URL: searxng.url,
OFFICER_RESOURCES: buildResourcesEnv(),
PI_SEARXNG_URL: searxngUrl,
OFFICER_EMAIL_DB: join(DATA_PATH, email, 'emails.db'),
TERM: 'xterm-256color',
PATH: process.env.PATH ?? '',
@@ -465,12 +337,14 @@ function parsePiEvent(event: Record<string, unknown>, currentStreamBuffer: strin
const toolName = (event.toolName as string) ?? 'unknown';
const args = (event.args as Record<string, unknown>) ?? {};
return [{
type: 'tool:start',
toolCallId,
toolName,
toolInput: args,
}];
return [
{
type: 'tool:start',
toolCallId,
toolName,
toolInput: args,
},
];
}
case 'tool_execution_end': {
@@ -489,12 +363,14 @@ function parsePiEvent(event: Record<string, unknown>, currentStreamBuffer: strin
const isError = (event.isError as boolean) ?? (resultObj?.isError as boolean) ?? false;
const output = result != null ? (typeof result === 'string' ? result : JSON.stringify(result)) : '';
return [{
type: 'tool:result',
toolCallId,
output,
isError,
}];
return [
{
type: 'tool:result',
toolCallId,
output,
isError,
},
];
}
case 'agent_end': {
@@ -586,7 +462,6 @@ export async function buildHostToolEnv(userId: number, email: string, role?: str
HOME: homeDir,
OFFICER_USER_HOME: homeDir,
OFFICER_USER_ROOT: join(DATA_PATH, email),
OFFICER_RESOURCES: buildResourcesEnv(),
OFFICER_EMAIL_DB: join(DATA_PATH, email, 'emails.db'),
...(apifyToken ? { OFFICER_APIFY_TOKEN: apifyToken } : {}),
...browserRelayEnv,