refactor(mom): use SettingsManager with workspace storage

fixes #1444
This commit is contained in:
Mario Zechner 2026-03-02 19:38:07 +01:00
parent 0c23a3415e
commit 46bfa7931e
2 changed files with 25 additions and 142 deletions

View file

@ -16,7 +16,7 @@ import { existsSync, readFileSync } from "fs";
import { mkdir, writeFile } from "fs/promises"; import { mkdir, writeFile } from "fs/promises";
import { homedir } from "os"; import { homedir } from "os";
import { join } from "path"; import { join } from "path";
import { MomSettingsManager, syncLogToSessionManager } from "./context.js"; import { createMomSettingsManager, syncLogToSessionManager } from "./context.js";
import * as log from "./log.js"; import * as log from "./log.js";
import { createExecutor, type SandboxConfig } from "./sandbox.js"; import { createExecutor, type SandboxConfig } from "./sandbox.js";
import type { ChannelInfo, SlackContext, UserInfo } from "./slack.js"; import type { ChannelInfo, SlackContext, UserInfo } from "./slack.js";
@ -424,7 +424,7 @@ function createRunner(sandboxConfig: SandboxConfig, channelId: string, channelDi
// Use a fixed context.jsonl file per channel (not timestamped like coding-agent) // Use a fixed context.jsonl file per channel (not timestamped like coding-agent)
const contextFile = join(channelDir, "context.jsonl"); const contextFile = join(channelDir, "context.jsonl");
const sessionManager = SessionManager.open(contextFile, channelDir); const sessionManager = SessionManager.open(contextFile, channelDir);
const settingsManager = new MomSettingsManager(join(channelDir, "..")); const settingsManager = createMomSettingsManager(join(channelDir, ".."));
// Create AuthStorage and ModelRegistry // Create AuthStorage and ModelRegistry
// Auth stored outside workspace so agent can't access it // Auth stored outside workspace so agent can't access it
@ -469,7 +469,7 @@ function createRunner(sandboxConfig: SandboxConfig, channelId: string, channelDi
const session = new AgentSession({ const session = new AgentSession({
agent, agent,
sessionManager, sessionManager,
settingsManager: settingsManager as any, settingsManager,
cwd: process.cwd(), cwd: process.cwd(),
modelRegistry, modelRegistry,
resourceLoader, resourceLoader,

View file

@ -7,11 +7,11 @@
* *
* This module provides: * This module provides:
* - syncLogToSessionManager: Syncs messages from log.jsonl to SessionManager * - syncLogToSessionManager: Syncs messages from log.jsonl to SessionManager
* - MomSettingsManager: Simple settings for mom (compaction, retry, model preferences) * - createMomSettingsManager: Creates a SettingsManager backed by workspace settings.json
*/ */
import type { UserMessage } from "@mariozechner/pi-ai"; import type { UserMessage } from "@mariozechner/pi-ai";
import type { SessionManager, SessionMessageEntry } from "@mariozechner/pi-coding-agent"; import { type SessionManager, type SessionMessageEntry, SettingsManager } from "@mariozechner/pi-coding-agent";
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"; import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
import { dirname, join } from "path"; import { dirname, join } from "path";
@ -142,156 +142,39 @@ export function syncLogToSessionManager(
} }
// ============================================================================ // ============================================================================
// MomSettingsManager - Simple settings for mom // Settings manager for mom
// ============================================================================ // ============================================================================
export interface MomCompactionSettings { type MomSettingsStorage = Parameters<typeof SettingsManager.fromStorage>[0];
enabled: boolean;
reserveTokens: number;
keepRecentTokens: number;
}
export interface MomRetrySettings { class WorkspaceSettingsStorage implements MomSettingsStorage {
enabled: boolean;
maxRetries: number;
baseDelayMs: number;
}
export interface MomSettings {
defaultProvider?: string;
defaultModel?: string;
defaultThinkingLevel?: "off" | "minimal" | "low" | "medium" | "high";
compaction?: Partial<MomCompactionSettings>;
retry?: Partial<MomRetrySettings>;
}
const DEFAULT_COMPACTION: MomCompactionSettings = {
enabled: true,
reserveTokens: 16384,
keepRecentTokens: 20000,
};
const DEFAULT_RETRY: MomRetrySettings = {
enabled: true,
maxRetries: 3,
baseDelayMs: 2000,
};
/**
* Settings manager for mom.
* Stores settings in the workspace root directory.
*/
export class MomSettingsManager {
private settingsPath: string; private settingsPath: string;
private settings: MomSettings;
constructor(workspaceDir: string) { constructor(workspaceDir: string) {
this.settingsPath = join(workspaceDir, "settings.json"); this.settingsPath = join(workspaceDir, "settings.json");
this.settings = this.load();
} }
private load(): MomSettings { withLock(scope: "global" | "project", fn: (current: string | undefined) => string | undefined): void {
if (!existsSync(this.settingsPath)) { if (scope === "project") {
return {}; // Mom stores all settings in a single workspace file.
fn(undefined);
return;
} }
try { const current = existsSync(this.settingsPath) ? readFileSync(this.settingsPath, "utf-8") : undefined;
const content = readFileSync(this.settingsPath, "utf-8"); const next = fn(current);
return JSON.parse(content); if (next === undefined) {
} catch { return;
return {};
} }
}
private save(): void { const dir = dirname(this.settingsPath);
try { if (!existsSync(dir)) {
const dir = dirname(this.settingsPath); mkdirSync(dir, { recursive: true });
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true });
}
writeFileSync(this.settingsPath, JSON.stringify(this.settings, null, 2), "utf-8");
} catch (error) {
console.error(`Warning: Could not save settings file: ${error}`);
} }
} writeFileSync(this.settingsPath, next, "utf-8");
getCompactionSettings(): MomCompactionSettings {
return {
...DEFAULT_COMPACTION,
...this.settings.compaction,
};
}
getCompactionEnabled(): boolean {
return this.settings.compaction?.enabled ?? DEFAULT_COMPACTION.enabled;
}
setCompactionEnabled(enabled: boolean): void {
this.settings.compaction = { ...this.settings.compaction, enabled };
this.save();
}
getRetrySettings(): MomRetrySettings {
return {
...DEFAULT_RETRY,
...this.settings.retry,
};
}
getRetryEnabled(): boolean {
return this.settings.retry?.enabled ?? DEFAULT_RETRY.enabled;
}
setRetryEnabled(enabled: boolean): void {
this.settings.retry = { ...this.settings.retry, enabled };
this.save();
}
getDefaultModel(): string | undefined {
return this.settings.defaultModel;
}
getDefaultProvider(): string | undefined {
return this.settings.defaultProvider;
}
setDefaultModelAndProvider(provider: string, modelId: string): void {
this.settings.defaultProvider = provider;
this.settings.defaultModel = modelId;
this.save();
}
getDefaultThinkingLevel(): string {
return this.settings.defaultThinkingLevel || "off";
}
setDefaultThinkingLevel(level: string): void {
this.settings.defaultThinkingLevel = level as MomSettings["defaultThinkingLevel"];
this.save();
}
// Compatibility methods for AgentSession
getSteeringMode(): "all" | "one-at-a-time" {
return "one-at-a-time"; // Mom processes one message at a time
}
setSteeringMode(_mode: "all" | "one-at-a-time"): void {
// No-op for mom
}
getFollowUpMode(): "all" | "one-at-a-time" {
return "one-at-a-time"; // Mom processes one message at a time
}
setFollowUpMode(_mode: "all" | "one-at-a-time"): void {
// No-op for mom
}
getHookPaths(): string[] {
return []; // Mom doesn't use hooks
}
getHookTimeout(): number {
return 30000;
} }
} }
export function createMomSettingsManager(workspaceDir: string): SettingsManager {
return SettingsManager.fromStorage(new WorkspaceSettingsStorage(workspaceDir));
}