403 lines
13 KiB
TypeScript
403 lines
13 KiB
TypeScript
import { stringify } from 'yaml';
|
|
|
|
interface ProviderConfig {
|
|
provider: string;
|
|
model: string;
|
|
api_key?: string;
|
|
auth_token?: string;
|
|
endpoint?: string;
|
|
}
|
|
|
|
interface EmbeddingConfig {
|
|
provider: string;
|
|
api_key?: string;
|
|
endpoint?: string;
|
|
}
|
|
|
|
export interface SetupConfig {
|
|
log_level?: string;
|
|
models: Record<string, ProviderConfig & Record<string, unknown>>;
|
|
server: {
|
|
port?: number;
|
|
localhost?: boolean;
|
|
token?: string;
|
|
lock?: boolean;
|
|
tailscale?: { serve?: boolean };
|
|
queue?: {
|
|
mode?: 'collect' | 'followup' | 'steer' | 'steer_backlog' | 'interrupt';
|
|
};
|
|
} & Record<string, unknown>;
|
|
hooks?: Record<string, unknown>;
|
|
telegram?: { bot_token: string; allowed_chat_ids: number[] };
|
|
discord?: { bot_token: string; allowed_guild_ids: string[] };
|
|
slack?: { bot_token: string; app_token: string; signing_secret: string; allowed_channel_ids: string[] };
|
|
whatsapp?: { allowed_numbers: string[] };
|
|
memory?: { embedding?: { enabled?: boolean; provider?: string; api_key?: string; endpoint?: string } };
|
|
audio?: {
|
|
enabled?: boolean;
|
|
talk_mode?: {
|
|
enabled?: boolean;
|
|
wake_phrase?: string;
|
|
timeout_ms?: number;
|
|
allow_manual_toggle?: boolean;
|
|
};
|
|
};
|
|
tts?: {
|
|
enabled?: boolean;
|
|
fallback?: {
|
|
max_attempts?: number;
|
|
failure_cooldown_ms?: number;
|
|
};
|
|
};
|
|
sandbox?: { enabled?: boolean };
|
|
pairing?: { enabled?: boolean };
|
|
tools?: { profile?: string };
|
|
agents?: {
|
|
sensitive_mode?: 'deny_without_elevation' | 'confirm_without_elevation';
|
|
};
|
|
agent_configs?: Record<string, {
|
|
model_tier?: 'fast' | 'default' | 'complex' | 'local';
|
|
tool_profile?: string;
|
|
system_prompt?: string;
|
|
}>;
|
|
intents?: {
|
|
enabled?: boolean;
|
|
match_threshold?: number;
|
|
rules?: Array<Record<string, unknown>>;
|
|
};
|
|
automation?: {
|
|
cron?: Array<Record<string, unknown>>;
|
|
webhooks?: Array<Record<string, unknown>>;
|
|
gmail?: { enabled?: boolean };
|
|
gcal?: { enabled?: boolean };
|
|
gdocs?: { enabled?: boolean };
|
|
gdrive?: { enabled?: boolean };
|
|
gtasks?: { enabled?: boolean };
|
|
heartbeat?: { enabled?: boolean };
|
|
daily_briefing?: {
|
|
enabled?: boolean;
|
|
schedule?: string;
|
|
output?: {
|
|
channel?: string;
|
|
peer?: string;
|
|
};
|
|
model_tier?: 'fast' | 'default' | 'complex' | 'local';
|
|
};
|
|
minio_sync?: { enabled?: boolean };
|
|
} & Record<string, unknown>;
|
|
backup?: {
|
|
enabled?: boolean;
|
|
schedule?: string;
|
|
run_on_start?: boolean;
|
|
notify?: { channel: string; peer: string };
|
|
} & Record<string, unknown>;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
interface OperatorPackOptions {
|
|
outputChannel: string;
|
|
outputPeer: string;
|
|
backupSchedule: string;
|
|
dailyBriefingSchedule: string;
|
|
enableMinioSync?: boolean;
|
|
}
|
|
|
|
interface ResearchAgentOptions {
|
|
modelTier: 'fast' | 'default' | 'complex' | 'local';
|
|
}
|
|
|
|
interface PersonalAssistantModeOptions {
|
|
enableTalkMode?: boolean;
|
|
enableTts?: boolean;
|
|
}
|
|
|
|
export class ConfigBuilder {
|
|
private config: SetupConfig;
|
|
|
|
constructor() {
|
|
this.config = {
|
|
log_level: 'info',
|
|
models: {},
|
|
server: { port: 18800, localhost: false },
|
|
hooks: {
|
|
confirm: [
|
|
'shell.*',
|
|
'process.start',
|
|
'process.kill',
|
|
'browser.*',
|
|
'message.send',
|
|
'cron.create',
|
|
'cron.delete',
|
|
'file.write',
|
|
'file.patch',
|
|
],
|
|
log: ['web.*', 'file.read'],
|
|
silent: ['notify'],
|
|
},
|
|
tools: { profile: 'full' },
|
|
agents: { sensitive_mode: 'confirm_without_elevation' },
|
|
};
|
|
}
|
|
|
|
static fromObject(obj: Record<string, unknown>): ConfigBuilder {
|
|
const builder = new ConfigBuilder();
|
|
builder.config = structuredClone(obj) as SetupConfig;
|
|
return builder;
|
|
}
|
|
|
|
setProvider(tier: 'default' | 'fast' | 'complex' | 'local', cfg: ProviderConfig): void {
|
|
const models = (this.config.models ?? {}) as SetupConfig['models'];
|
|
const entry: ProviderConfig & Record<string, unknown> = { provider: cfg.provider, model: cfg.model };
|
|
if (cfg.api_key) {entry.api_key = cfg.api_key;}
|
|
if (cfg.auth_token) {entry.auth_token = cfg.auth_token;}
|
|
if (cfg.endpoint) {entry.endpoint = cfg.endpoint;}
|
|
models[tier] = entry;
|
|
this.config.models = models;
|
|
}
|
|
|
|
setTelegram(botToken: string, chatIds: number[]): void {
|
|
this.config.telegram = { bot_token: botToken, allowed_chat_ids: chatIds };
|
|
}
|
|
|
|
setDiscord(botToken: string, guildIds: string[]): void {
|
|
this.config.discord = { bot_token: botToken, allowed_guild_ids: guildIds };
|
|
}
|
|
|
|
setSlack(botToken: string, appToken: string, signingSecret: string, channelIds: string[]): void {
|
|
this.config.slack = { bot_token: botToken, app_token: appToken, signing_secret: signingSecret, allowed_channel_ids: channelIds };
|
|
}
|
|
|
|
setWhatsApp(allowedNumbers: string[]): void {
|
|
this.config.whatsapp = { allowed_numbers: allowedNumbers };
|
|
}
|
|
|
|
setGatewayPort(port: number): void {
|
|
const server = (this.config.server ?? {}) as Record<string, unknown>;
|
|
server.port = port;
|
|
this.config.server = server;
|
|
}
|
|
|
|
setGatewayToken(token: string): void {
|
|
const server = (this.config.server ?? {}) as Record<string, unknown>;
|
|
server.token = token;
|
|
this.config.server = server;
|
|
}
|
|
|
|
setGatewayLock(enabled: boolean): void {
|
|
const server = (this.config.server ?? {}) as Record<string, unknown>;
|
|
server.lock = enabled;
|
|
this.config.server = server;
|
|
}
|
|
|
|
setTailscaleServe(enabled: boolean): void {
|
|
const server = (this.config.server ?? {}) as Record<string, unknown>;
|
|
const tailscale = (server.tailscale ?? {}) as Record<string, unknown>;
|
|
tailscale.serve = enabled;
|
|
server.tailscale = tailscale;
|
|
this.config.server = server;
|
|
}
|
|
|
|
setMemoryEmbedding(cfg: EmbeddingConfig): void {
|
|
const memory = (this.config.memory ?? {}) as Record<string, unknown>;
|
|
const embedding: Record<string, unknown> = { enabled: true, provider: cfg.provider };
|
|
if (cfg.api_key) {embedding.api_key = cfg.api_key;}
|
|
if (cfg.endpoint) {embedding.endpoint = cfg.endpoint;}
|
|
memory.embedding = embedding;
|
|
this.config.memory = memory;
|
|
}
|
|
|
|
setSandboxEnabled(enabled: boolean): void {
|
|
this.config.sandbox = { enabled };
|
|
}
|
|
|
|
setPairingEnabled(enabled: boolean): void {
|
|
this.config.pairing = { enabled };
|
|
}
|
|
|
|
setToolProfile(profile: string): void {
|
|
this.config.tools = { profile };
|
|
}
|
|
|
|
setSensitiveMode(mode: 'deny_without_elevation' | 'confirm_without_elevation'): void {
|
|
const agents = (this.config.agents ?? {}) as Record<string, unknown>;
|
|
agents.sensitive_mode = mode;
|
|
this.config.agents = agents as SetupConfig['agents'];
|
|
}
|
|
|
|
setResearchAgentEnabled(options: ResearchAgentOptions): void {
|
|
const agentConfigs = (this.config.agent_configs ?? {}) as Record<string, Record<string, unknown>>;
|
|
const existing = (agentConfigs.research ?? {}) as Record<string, unknown>;
|
|
agentConfigs.research = {
|
|
...existing,
|
|
model_tier: options.modelTier,
|
|
tool_profile: 'messaging',
|
|
system_prompt: [
|
|
'You are a research agent. Find, verify, and synthesize information for the operator.',
|
|
'Prefer primary sources and include concrete dates and links when available.',
|
|
'Keep output structured and concise.',
|
|
].join(' '),
|
|
};
|
|
this.config.agent_configs = agentConfigs;
|
|
}
|
|
|
|
setWebhooksEnabled(secret?: string): void {
|
|
const automation = (this.config.automation ?? {}) as Record<string, unknown>;
|
|
if (secret) {
|
|
automation.webhooks = [{ name: 'default', secret, message: '{{body}}', output: { channel: 'webchat', peer: 'webhook' }, enabled: true }];
|
|
} else {
|
|
automation.webhooks = automation.webhooks ?? [];
|
|
}
|
|
this.config.automation = automation;
|
|
}
|
|
|
|
setGmailEnabled(credentialsFile: string, outputChannel: string, outputPeer: string): void {
|
|
const automation = (this.config.automation ?? {}) as Record<string, unknown>;
|
|
automation.gmail = { enabled: true, credentials_file: credentialsFile, output: { channel: outputChannel, peer: outputPeer } };
|
|
this.config.automation = automation;
|
|
}
|
|
|
|
setGcalEnabled(credentialsFile: string): void {
|
|
const automation = (this.config.automation ?? {}) as Record<string, unknown>;
|
|
automation.gcal = { enabled: true, credentials_file: credentialsFile };
|
|
this.config.automation = automation;
|
|
}
|
|
|
|
setGdocsEnabled(credentialsFile: string): void {
|
|
const automation = (this.config.automation ?? {}) as Record<string, unknown>;
|
|
automation.gdocs = { enabled: true, credentials_file: credentialsFile };
|
|
this.config.automation = automation;
|
|
}
|
|
|
|
setGdriveEnabled(credentialsFile: string): void {
|
|
const automation = (this.config.automation ?? {}) as Record<string, unknown>;
|
|
automation.gdrive = { enabled: true, credentials_file: credentialsFile };
|
|
this.config.automation = automation;
|
|
}
|
|
|
|
setGtasksEnabled(credentialsFile: string): void {
|
|
const automation = (this.config.automation ?? {}) as Record<string, unknown>;
|
|
automation.gtasks = { enabled: true, credentials_file: credentialsFile };
|
|
this.config.automation = automation;
|
|
}
|
|
|
|
setCronEnabled(): void {
|
|
const automation = (this.config.automation ?? {}) as Record<string, unknown>;
|
|
if (!automation.cron) {automation.cron = [];}
|
|
this.config.automation = automation;
|
|
}
|
|
|
|
applyOperatorPack(options: OperatorPackOptions): void {
|
|
const automation = (this.config.automation ?? {}) as Record<string, unknown>;
|
|
const backup = (this.config.backup ?? {}) as Record<string, unknown>;
|
|
const memory = (this.config.memory ?? {}) as Record<string, unknown>;
|
|
|
|
backup.enabled = true;
|
|
backup.schedule = options.backupSchedule;
|
|
backup.run_on_start = true;
|
|
backup.notify = { channel: options.outputChannel, peer: options.outputPeer };
|
|
|
|
// Personal-assistant mode defaults: proactive push and continuity-first memory cadence.
|
|
automation.delivery_mode = 'announce';
|
|
|
|
automation.heartbeat = {
|
|
enabled: true,
|
|
notify: { channel: options.outputChannel, peer: options.outputPeer },
|
|
interval: '5m',
|
|
failure_threshold: 2,
|
|
notify_cooldown: '30m',
|
|
};
|
|
|
|
automation.daily_briefing = {
|
|
enabled: true,
|
|
schedule: options.dailyBriefingSchedule,
|
|
output: { channel: options.outputChannel, peer: options.outputPeer },
|
|
dedupe_per_local_day: true,
|
|
model_tier: 'fast',
|
|
};
|
|
|
|
if (options.enableMinioSync ?? true) {
|
|
automation.minio_sync = {
|
|
enabled: true,
|
|
interval: '6h',
|
|
run_on_start: true,
|
|
notify: { channel: options.outputChannel, peer: options.outputPeer },
|
|
tasks: [
|
|
{
|
|
prefix: 'knowledge/',
|
|
namespace_base: 'global/knowledge/minio',
|
|
mode: 'append',
|
|
max_objects: 20,
|
|
max_chars_per_object: 8000,
|
|
force: false,
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
memory.daily_log = {
|
|
enabled: true,
|
|
namespace_prefix: 'daily',
|
|
};
|
|
memory.proactive_extract = {
|
|
enabled: true,
|
|
min_tool_calls: 1,
|
|
namespace: 'global',
|
|
};
|
|
|
|
this.config.automation = automation;
|
|
this.config.backup = backup;
|
|
this.config.memory = memory;
|
|
}
|
|
|
|
applyPersonalAssistantMode(options?: PersonalAssistantModeOptions): void {
|
|
const automation = (this.config.automation ?? {}) as Record<string, unknown>;
|
|
const memory = (this.config.memory ?? {}) as Record<string, unknown>;
|
|
const audio = (this.config.audio ?? {}) as Record<string, unknown>;
|
|
const talkMode = (audio.talk_mode ?? {}) as Record<string, unknown>;
|
|
const tts = (this.config.tts ?? {}) as Record<string, unknown>;
|
|
const ttsFallback = (tts.fallback ?? {}) as Record<string, unknown>;
|
|
const server = (this.config.server ?? {}) as Record<string, unknown>;
|
|
const queue = (server.queue ?? {}) as Record<string, unknown>;
|
|
|
|
automation.delivery_mode = 'announce';
|
|
memory.daily_log = {
|
|
enabled: true,
|
|
namespace_prefix: 'daily',
|
|
};
|
|
memory.proactive_extract = {
|
|
enabled: true,
|
|
min_tool_calls: 2,
|
|
namespace: 'global',
|
|
};
|
|
|
|
talkMode.enabled = options?.enableTalkMode ?? true;
|
|
talkMode.wake_phrase = 'hey flynn';
|
|
talkMode.timeout_ms = 120000;
|
|
talkMode.allow_manual_toggle = true;
|
|
audio.talk_mode = talkMode;
|
|
audio.enabled = Boolean(audio.enabled || talkMode.enabled);
|
|
|
|
tts.enabled = options?.enableTts ?? true;
|
|
ttsFallback.max_attempts = 3;
|
|
ttsFallback.failure_cooldown_ms = 60000;
|
|
tts.fallback = ttsFallback;
|
|
|
|
queue.mode = 'interrupt';
|
|
server.queue = queue;
|
|
|
|
this.config.automation = automation;
|
|
this.config.memory = memory;
|
|
this.config.audio = audio;
|
|
this.config.tts = tts;
|
|
this.config.server = server as SetupConfig['server'];
|
|
}
|
|
|
|
build(): SetupConfig {
|
|
return structuredClone(this.config) as SetupConfig;
|
|
}
|
|
|
|
toYaml(): string {
|
|
return stringify(this.config, { lineWidth: 120 });
|
|
}
|
|
}
|