wip: remove opencode, searxng, resources; fix user settings read
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+20
-145
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user