migration to postgres
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,8 +5,9 @@ import type { Subprocess } from "bun";
|
||||
import type { PiEvent, MessageCost } from "./types";
|
||||
import { readApiKeys } from "../server-settings/pi-mono";
|
||||
import { readSearxngConfig } from "../server-settings/searxng";
|
||||
import { PI_CONFIG_DIR, DATA_PATH, getHomeDir, getGlobalSkillsDir, getUserSkillsDir, getGlobalExtensionsDir, getUserExtensionsDir, getGlobalToolsDir, getUserToolsDir, getNativeResourcesDir, getGlobalResourcesDir } from "../../data-path";
|
||||
import { PI_CONFIG_DIR, DATA_PATH, SERVER_CONFIG_DIR, getHomeDir, getGlobalSkillsDir, getUserSkillsDir, getGlobalExtensionsDir, getUserExtensionsDir, getGlobalToolsDir, getUserToolsDir, getNativeResourcesDir, getGlobalResourcesDir } from "../../data-path";
|
||||
import { ensureDockerContainer } from "../terminal/websocket";
|
||||
import { getServerIntegration, getUserIntegration } from "officerdb";
|
||||
import { logger } from "./logger";
|
||||
import { parseFrontmatter } from "../skills/skills";
|
||||
|
||||
@@ -159,12 +160,33 @@ function buildResourcesEnv(): string {
|
||||
return JSON.stringify(result);
|
||||
}
|
||||
|
||||
function getGoogleConfigPath(): string {
|
||||
return join(homedir(), '.config', 'officer.dev', 'google-oauth.json');
|
||||
async function ensureGoogleConfigFile(): Promise<string> {
|
||||
const filePath = join(SERVER_CONFIG_DIR, 'google-oauth.json');
|
||||
try {
|
||||
const integration = await getServerIntegration('google');
|
||||
if (integration?.config) {
|
||||
mkdirSync(SERVER_CONFIG_DIR, { recursive: true });
|
||||
writeFileSync(filePath, JSON.stringify(integration.config, null, 2));
|
||||
}
|
||||
} catch {
|
||||
// No google config available
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
|
||||
function getGoogleTokenPath(email: string): string {
|
||||
return join(DATA_PATH, email, 'integrations', 'google.json');
|
||||
async function ensureGoogleTokenFile(userId: number, email: string): Promise<string> {
|
||||
const dir = join(DATA_PATH, email, 'integrations');
|
||||
const filePath = join(dir, 'google.json');
|
||||
try {
|
||||
const integration = await getUserIntegration(userId, 'google');
|
||||
if (integration?.config) {
|
||||
mkdirSync(dir, { recursive: true });
|
||||
writeFileSync(filePath, JSON.stringify(integration.config, null, 2));
|
||||
}
|
||||
} catch {
|
||||
// No user google integration available
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
|
||||
type SandboxOptions = {
|
||||
@@ -177,6 +199,7 @@ type SandboxOptions = {
|
||||
export async function spawnPi(
|
||||
cwd: string,
|
||||
model: string,
|
||||
userId: number,
|
||||
email: string,
|
||||
onEvent: PiEventHandler,
|
||||
sandbox?: SandboxOptions,
|
||||
@@ -217,8 +240,8 @@ export async function spawnPi(
|
||||
|
||||
const resourcesEnv = buildResourcesEnv();
|
||||
|
||||
const googleConfigHost = getGoogleConfigPath();
|
||||
const googleTokenHost = join(DATA_PATH, sandbox.email, 'integrations');
|
||||
const googleConfigHost = await ensureGoogleConfigFile();
|
||||
await ensureGoogleTokenFile(sandbox.userId, sandbox.email);
|
||||
|
||||
const envFlags = [
|
||||
'-e', `PI_CODING_AGENT_DIR=${containerPiConfig}`,
|
||||
@@ -275,13 +298,28 @@ export async function spawnPi(
|
||||
}
|
||||
|
||||
const toolsDirs = [getGlobalToolsDir(), getUserToolsDir(email)].join(':');
|
||||
const googleConfigPath = await ensureGoogleConfigFile();
|
||||
const googleTokenPath = await ensureGoogleTokenFile(userId, email);
|
||||
|
||||
proc = Bun.spawn(args, {
|
||||
cwd,
|
||||
stdin: 'pipe',
|
||||
stdout: 'pipe',
|
||||
stderr: 'pipe',
|
||||
env: { ...process.env, ...storedKeys, HOME: getHomeDir(email), OFFICER_USER_HOME: getHomeDir(email), OFFICER_USER_ROOT: join(DATA_PATH, email), PI_CODING_AGENT_DIR: PI_CONFIG_DIR, PI_TOOLS_DIRS: toolsDirs, PI_SEARXNG_URL: searxng.url, OFFICER_RESOURCES: buildResourcesEnv(), OFFICER_GOOGLE_CONFIG_PATH: getGoogleConfigPath(), OFFICER_GOOGLE_TOKEN_PATH: getGoogleTokenPath(email), OFFICER_EMAIL_DB: join(DATA_PATH, email, 'emails.db') },
|
||||
env: {
|
||||
...process.env,
|
||||
...storedKeys,
|
||||
HOME: getHomeDir(email),
|
||||
OFFICER_USER_HOME: getHomeDir(email),
|
||||
OFFICER_USER_ROOT: join(DATA_PATH, email),
|
||||
PI_CODING_AGENT_DIR: PI_CONFIG_DIR,
|
||||
PI_TOOLS_DIRS: toolsDirs,
|
||||
PI_SEARXNG_URL: searxng.url,
|
||||
OFFICER_RESOURCES: buildResourcesEnv(),
|
||||
OFFICER_GOOGLE_CONFIG_PATH: googleConfigPath,
|
||||
OFFICER_GOOGLE_TOKEN_PATH: googleTokenPath,
|
||||
OFFICER_EMAIL_DB: join(DATA_PATH, email, 'emails.db'),
|
||||
},
|
||||
});
|
||||
|
||||
logger.info('Spawned Pi locally', {
|
||||
|
||||
@@ -6,22 +6,20 @@ import * as storage from './storage';
|
||||
import * as piBridge from './pi-bridge';
|
||||
import { join, resolve } from 'path';
|
||||
import { homedir } from 'os';
|
||||
import { getHomeDir, getUserSettingsFile } from '../../../servers/data-path';
|
||||
import { getHomeDir } from '../../../servers/data-path';
|
||||
import { getUserSettings } from 'officerdb';
|
||||
import { logger } from './logger';
|
||||
|
||||
// Default model when no user preference is set
|
||||
const DEFAULT_MODEL = 'opencode/big-pickle';
|
||||
|
||||
async function getUserDefaultModel(email: string): Promise<string | null> {
|
||||
async function getUserDefaultModel(userId: number): Promise<string | null> {
|
||||
try {
|
||||
const settingsPath = getUserSettingsFile(email);
|
||||
const file = Bun.file(settingsPath);
|
||||
if (await file.exists()) {
|
||||
const settings = await file.json();
|
||||
return settings?.chat?.defaultModel || null;
|
||||
}
|
||||
const settings = await getUserSettings(userId);
|
||||
const chat = settings?.chat as Record<string, unknown> | undefined;
|
||||
return (chat?.defaultModel as string) || null;
|
||||
} catch (err) {
|
||||
logger.error('Failed to read user settings for default model', { email, error: String(err) });
|
||||
logger.error('Failed to read user settings for default model', { userId, error: String(err) });
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -254,7 +252,7 @@ async function handleChat(
|
||||
let modelSource = 'client-provided';
|
||||
let userDefault = null;
|
||||
if (!model) {
|
||||
userDefault = await getUserDefaultModel(email);
|
||||
userDefault = await getUserDefaultModel(userId);
|
||||
if (userDefault) {
|
||||
model = userDefault;
|
||||
modelSource = 'user-settings';
|
||||
@@ -288,7 +286,7 @@ async function handleChat(
|
||||
if (!session.piProcess) {
|
||||
try {
|
||||
const onEvent = createEventHandler(sessionId, model, cwd, homeDir);
|
||||
session.piProcess = await piBridge.spawnPi(cwd, model, email, onEvent, sandboxed ? { userId, username, email, homeDir } : undefined);
|
||||
session.piProcess = await piBridge.spawnPi(cwd, model, userId, email, onEvent, sandboxed ? { userId, username, email, homeDir } : undefined);
|
||||
logger.info('Spawned Pi process for session', { sessionId, model, cwd, sandboxed });
|
||||
} catch (err) {
|
||||
logger.error('Failed to spawn Pi process', { sessionId, model, error: String(err) });
|
||||
@@ -363,7 +361,7 @@ async function handleResume(
|
||||
const homeDir = getHomeDir(email);
|
||||
const sandbox = session.sandboxed && session.userId ? { userId: session.userId, username: ws.data.username, email, homeDir } : undefined;
|
||||
const onEvent = createEventHandler(sessionId, session.model, session.cwd, homeDir);
|
||||
session.piProcess = await piBridge.spawnPi(session.cwd, session.model, email, onEvent, sandbox);
|
||||
session.piProcess = await piBridge.spawnPi(session.cwd, session.model, session.userId!, email, onEvent, sandbox);
|
||||
logger.info('Spawned fresh Pi process for resumed session', { sessionId, model: session.model, sandboxed: session.sandboxed });
|
||||
} catch (err) {
|
||||
logger.error('Failed to spawn Pi process for resume', { sessionId, error: String(err) });
|
||||
|
||||
Reference in New Issue
Block a user