mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-17 04:02:21 +00:00
Merge remote-tracking branch 'origin/main' into feat/no-extensions-flag
This commit is contained in:
commit
893e00579d
5 changed files with 123 additions and 9 deletions
|
|
@ -313,7 +313,7 @@ async function loadExtensionWithBun(
|
||||||
setFlagValue,
|
setFlagValue,
|
||||||
} = createExtensionAPI(handlers, tools, cwd, extensionPath, eventBus, sharedUI);
|
} = createExtensionAPI(handlers, tools, cwd, extensionPath, eventBus, sharedUI);
|
||||||
|
|
||||||
factory(api);
|
await factory(api);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
extension: {
|
extension: {
|
||||||
|
|
@ -401,7 +401,7 @@ async function loadExtension(
|
||||||
setFlagValue,
|
setFlagValue,
|
||||||
} = createExtensionAPI(handlers, tools, cwd, extensionPath, eventBus, sharedUI);
|
} = createExtensionAPI(handlers, tools, cwd, extensionPath, eventBus, sharedUI);
|
||||||
|
|
||||||
factory(api);
|
await factory(api);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
extension: {
|
extension: {
|
||||||
|
|
@ -436,13 +436,13 @@ async function loadExtension(
|
||||||
/**
|
/**
|
||||||
* Create a LoadedExtension from an inline factory function.
|
* Create a LoadedExtension from an inline factory function.
|
||||||
*/
|
*/
|
||||||
export function loadExtensionFromFactory(
|
export async function loadExtensionFromFactory(
|
||||||
factory: ExtensionFactory,
|
factory: ExtensionFactory,
|
||||||
cwd: string,
|
cwd: string,
|
||||||
eventBus: EventBus,
|
eventBus: EventBus,
|
||||||
sharedUI: { ui: ExtensionUIContext; hasUI: boolean },
|
sharedUI: { ui: ExtensionUIContext; hasUI: boolean },
|
||||||
name = "<inline>",
|
name = "<inline>",
|
||||||
): LoadedExtension {
|
): Promise<LoadedExtension> {
|
||||||
const handlers = new Map<string, HandlerFn[]>();
|
const handlers = new Map<string, HandlerFn[]>();
|
||||||
const tools = new Map<string, RegisteredTool>();
|
const tools = new Map<string, RegisteredTool>();
|
||||||
const {
|
const {
|
||||||
|
|
@ -464,7 +464,7 @@ export function loadExtensionFromFactory(
|
||||||
setFlagValue,
|
setFlagValue,
|
||||||
} = createExtensionAPI(handlers, tools, cwd, name, eventBus, sharedUI);
|
} = createExtensionAPI(handlers, tools, cwd, name, eventBus, sharedUI);
|
||||||
|
|
||||||
factory(api);
|
await factory(api);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
path: name,
|
path: name,
|
||||||
|
|
|
||||||
|
|
@ -649,8 +649,8 @@ export interface ExtensionAPI {
|
||||||
events: EventBus;
|
events: EventBus;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Extension factory function type. */
|
/** Extension factory function type. Supports both sync and async initialization. */
|
||||||
export type ExtensionFactory = (pi: ExtensionAPI) => void;
|
export type ExtensionFactory = (pi: ExtensionAPI) => void | Promise<void>;
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Loaded Extension Types
|
// Loaded Extension Types
|
||||||
|
|
|
||||||
|
|
@ -488,7 +488,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
||||||
};
|
};
|
||||||
for (let i = 0; i < options.extensions.length; i++) {
|
for (let i = 0; i < options.extensions.length; i++) {
|
||||||
const factory = options.extensions[i];
|
const factory = options.extensions[i];
|
||||||
const loaded = loadExtensionFromFactory(factory, cwd, eventBus, uiHolder, `<inline-${i}>`);
|
const loaded = await loadExtensionFromFactory(factory, cwd, eventBus, uiHolder, `<inline-${i}>`);
|
||||||
extensionsResult.extensions.push(loaded);
|
extensionsResult.extensions.push(loaded);
|
||||||
}
|
}
|
||||||
// Extend setUIContext to update inline extensions too
|
// Extend setUIContext to update inline extensions too
|
||||||
|
|
|
||||||
|
|
@ -178,7 +178,13 @@ export class SettingsManager {
|
||||||
mkdirSync(dir, { recursive: true });
|
mkdirSync(dir, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save only global settings (project settings are read-only)
|
// Re-read current file to preserve any settings added externally while running
|
||||||
|
const currentFileSettings = SettingsManager.loadFromFile(this.settingsPath);
|
||||||
|
// Merge: file settings as base, globalSettings (in-memory changes) as overrides
|
||||||
|
const mergedSettings = deepMergeSettings(currentFileSettings, this.globalSettings);
|
||||||
|
this.globalSettings = mergedSettings;
|
||||||
|
|
||||||
|
// Save merged settings (project settings are read-only)
|
||||||
writeFileSync(this.settingsPath, JSON.stringify(this.globalSettings, null, 2), "utf-8");
|
writeFileSync(this.settingsPath, JSON.stringify(this.globalSettings, null, 2), "utf-8");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Warning: Could not save settings file: ${error}`);
|
console.error(`Warning: Could not save settings file: ${error}`);
|
||||||
|
|
|
||||||
108
packages/coding-agent/test/settings-manager.test.ts
Normal file
108
packages/coding-agent/test/settings-manager.test.ts
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
||||||
|
import { join } from "path";
|
||||||
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
import { SettingsManager } from "../src/core/settings-manager.js";
|
||||||
|
|
||||||
|
describe("SettingsManager", () => {
|
||||||
|
const testDir = join(process.cwd(), "test-settings-tmp");
|
||||||
|
const agentDir = join(testDir, "agent");
|
||||||
|
const projectDir = join(testDir, "project");
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Clean up and create fresh directories
|
||||||
|
if (existsSync(testDir)) {
|
||||||
|
rmSync(testDir, { recursive: true });
|
||||||
|
}
|
||||||
|
mkdirSync(agentDir, { recursive: true });
|
||||||
|
mkdirSync(join(projectDir, ".pi"), { recursive: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (existsSync(testDir)) {
|
||||||
|
rmSync(testDir, { recursive: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("preserves externally added settings", () => {
|
||||||
|
it("should preserve enabledModels when changing thinking level", () => {
|
||||||
|
// Create initial settings file
|
||||||
|
const settingsPath = join(agentDir, "settings.json");
|
||||||
|
writeFileSync(
|
||||||
|
settingsPath,
|
||||||
|
JSON.stringify({
|
||||||
|
theme: "dark",
|
||||||
|
defaultModel: "claude-sonnet",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create SettingsManager (simulates pi starting up)
|
||||||
|
const manager = SettingsManager.create(projectDir, agentDir);
|
||||||
|
|
||||||
|
// Simulate user editing settings.json externally to add enabledModels
|
||||||
|
const currentSettings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
||||||
|
currentSettings.enabledModels = ["claude-opus-4-5", "gpt-5.2-codex"];
|
||||||
|
writeFileSync(settingsPath, JSON.stringify(currentSettings, null, 2));
|
||||||
|
|
||||||
|
// User changes thinking level via Shift+Tab
|
||||||
|
manager.setDefaultThinkingLevel("high");
|
||||||
|
|
||||||
|
// Verify enabledModels is preserved
|
||||||
|
const savedSettings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
||||||
|
expect(savedSettings.enabledModels).toEqual(["claude-opus-4-5", "gpt-5.2-codex"]);
|
||||||
|
expect(savedSettings.defaultThinkingLevel).toBe("high");
|
||||||
|
expect(savedSettings.theme).toBe("dark");
|
||||||
|
expect(savedSettings.defaultModel).toBe("claude-sonnet");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should preserve custom settings when changing theme", () => {
|
||||||
|
const settingsPath = join(agentDir, "settings.json");
|
||||||
|
writeFileSync(
|
||||||
|
settingsPath,
|
||||||
|
JSON.stringify({
|
||||||
|
defaultModel: "claude-sonnet",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const manager = SettingsManager.create(projectDir, agentDir);
|
||||||
|
|
||||||
|
// User adds custom settings externally
|
||||||
|
const currentSettings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
||||||
|
currentSettings.shellPath = "/bin/zsh";
|
||||||
|
currentSettings.extensions = ["/path/to/extension.ts"];
|
||||||
|
writeFileSync(settingsPath, JSON.stringify(currentSettings, null, 2));
|
||||||
|
|
||||||
|
// User changes theme
|
||||||
|
manager.setTheme("light");
|
||||||
|
|
||||||
|
// Verify all settings preserved
|
||||||
|
const savedSettings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
||||||
|
expect(savedSettings.shellPath).toBe("/bin/zsh");
|
||||||
|
expect(savedSettings.extensions).toEqual(["/path/to/extension.ts"]);
|
||||||
|
expect(savedSettings.theme).toBe("light");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should let in-memory changes override file changes for same key", () => {
|
||||||
|
const settingsPath = join(agentDir, "settings.json");
|
||||||
|
writeFileSync(
|
||||||
|
settingsPath,
|
||||||
|
JSON.stringify({
|
||||||
|
theme: "dark",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const manager = SettingsManager.create(projectDir, agentDir);
|
||||||
|
|
||||||
|
// User externally sets thinking level to "low"
|
||||||
|
const currentSettings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
||||||
|
currentSettings.defaultThinkingLevel = "low";
|
||||||
|
writeFileSync(settingsPath, JSON.stringify(currentSettings, null, 2));
|
||||||
|
|
||||||
|
// But then changes it via UI to "high"
|
||||||
|
manager.setDefaultThinkingLevel("high");
|
||||||
|
|
||||||
|
// In-memory change should win
|
||||||
|
const savedSettings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
||||||
|
expect(savedSettings.defaultThinkingLevel).toBe("high");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue