src / runtime.ts
src / runtime.ts
import { mkdir } from "node:fs/promises";
import { join } from "node:path";
import type { LMStudioClient, ToolsProviderController } from "@lmstudio/sdk";
import { AgentOrchestrator } from "./agents/orchestrator";
import { AgentRunStore } from "./agents/runStore";
import { BrowserService } from "./browser/service";
import { ArtifactStore } from "./core/artifacts";
import { Journal } from "./core/journal";
import { InternalStorage } from "./core/internalStorage";
import { configSchematics, splitConfigList } from "./config";
import { NoteStore } from "./notes/noteStore";
import { ApprovalStore } from "./policy/approvalStore";
import { permissionModeFromLevel, type PermissionMode } from "./policy/permissions";
import { ProcessService } from "./execution/processRunner";
import { ResearchStore } from "./research/store";
import { WebResearchService, type SearchProvider } from "./research/web";
import { TaskBoardStore } from "./tasks/taskStore";
import { WorkspaceBoundary } from "./workspace/boundary";
import { TransactionManager } from "./workspace/transactions";
import { resolveWorkspaceRoot } from "./workspaceRoot";
export interface RuntimeSettings {
permissionMode: PermissionMode;
allowDestructiveEdits: boolean;
maxReadBytes: number;
maxEditableBytes: number;
maxToolResultChars: number;
processExecutionEnabled: boolean;
/** Whether sub-agent runs may execute commands at all (= processExecutionEnabled && agentAllowCommands). */
agentCommandsEnabled: boolean;
internalAgentsEnabled: boolean;
taskBoardsEnabled: boolean;
webResearchEnabled: boolean;
webSearchProvider: SearchProvider;
agentAllowWeb: boolean;
vcsToolsEnabled: boolean;
browserEnabled: boolean;
}
export interface PluginRuntime {
client: LMStudioClient;
boundary: WorkspaceBoundary;
storage: InternalStorage;
agenticDir: string;
artifacts: ArtifactStore;
journal: Journal;
transactions: TransactionManager;
processes: ProcessService;
runStore: AgentRunStore;
tasks: TaskBoardStore;
web: WebResearchService;
research: ResearchStore;
browser: BrowserService;
agents: AgentOrchestrator;
approvals: ApprovalStore;
notes: NoteStore;
settings: RuntimeSettings;
}
function searchProvider(value: string): SearchProvider {
const provider = value.trim().toLowerCase();
return provider === "duckduckgo" ||
provider === "wikipedia" ||
provider === "searxng"
? provider
: "auto";
}
export async function createRuntime(
ctl: ToolsProviderController,
): Promise<PluginRuntime> {
const config = ctl.getPluginConfig(configSchematics);
const root = resolveWorkspaceRoot(ctl, config.get("defaultWorkspacePath"));
await mkdir(root, { recursive: true });
const boundary = await WorkspaceBoundary.create(root, {
protectedPatterns: splitConfigList(config.get("protectedPatterns")),
});
const storage = new InternalStorage(boundary);
const agenticDir = await storage.initialize();
const journal = new Journal(storage);
const artifacts = new ArtifactStore(storage);
const permissionMode = permissionModeFromLevel(config.get("permissionMode"));
const settings: RuntimeSettings = {
permissionMode,
allowDestructiveEdits: config.get("allowDestructiveEdits"),
maxReadBytes: config.get("maxReadBytes"),
maxEditableBytes: config.get("maxEditableBytes"),
maxToolResultChars: config.get("maxToolResultChars"),
processExecutionEnabled: config.get("processExecutionEnabled"),
agentCommandsEnabled:
config.get("processExecutionEnabled") && config.get("agentAllowCommands"),
internalAgentsEnabled: config.get("internalAgentsEnabled"),
taskBoardsEnabled: config.get("taskBoardsEnabled"),
webResearchEnabled: config.get("webResearchEnabled"),
webSearchProvider: searchProvider(config.get("webSearchProvider")),
agentAllowWeb: config.get("agentAllowWeb"),
vcsToolsEnabled: config.get("vcsToolsEnabled"),
browserEnabled: config.get("browserEnabled"),
};
const transactions = new TransactionManager(
boundary,
storage,
journal,
settings.maxEditableBytes,
);
const processes = new ProcessService(boundary, storage, journal, {
allowedExecutables: splitConfigList(config.get("allowedExecutables")),
inheritEnvironment: config.get("inheritProcessEnvironment"),
maxTimeoutSeconds: config.get("maxCommandSeconds"),
maxPreviewChars: config.get("maxCommandPreviewChars"),
maxArtifactBytes: config.get("maxCommandArtifactBytes"),
});
const runStore = new AgentRunStore(storage);
const tasks = new TaskBoardStore(storage);
const research = new ResearchStore(storage);
const web = new WebResearchService({
timeoutMs: config.get("webTimeoutSeconds") * 1000,
maxResponseBytes: config.get("maxWebResponseBytes"),
maxTextChars: config.get("maxWebTextChars"),
allowPrivateNetwork: config.get("allowPrivateNetworkWeb"),
allowedDomains: splitConfigList(config.get("webAllowedDomains")),
...(config.get("searxngBaseUrl").trim()
? { searxngBaseUrl: config.get("searxngBaseUrl").trim() }
: {}),
});
const browser = new BrowserService(web, {
enabled: settings.browserEnabled,
...(config.get("browserExecutablePath").trim()
? { executablePath: config.get("browserExecutablePath").trim() }
: {}),
headless: config.get("browserHeadless"),
allowEvaluate: config.get("browserAllowEvaluate"),
maxSessions: 4,
navigationTimeoutMs: config.get("webTimeoutSeconds") * 1000,
maxTextChars: config.get("maxWebTextChars"),
});
const client = ctl.client as LMStudioClient;
const agents = new AgentOrchestrator(
client,
boundary,
runStore,
transactions,
processes,
tasks,
web,
research,
journal,
{
defaultModelId: config.get("agentModelId").trim() || undefined,
defaultCommitEdits: settings.permissionMode === "auto",
destructiveEditsEnabled: settings.allowDestructiveEdits,
commandsEnabled: settings.agentCommandsEnabled,
webEnabled: settings.webResearchEnabled && settings.agentAllowWeb,
defaultSearchProvider: settings.webSearchProvider,
maxPasses: config.get("agentMaxPasses"),
roundsPerPass: config.get("agentRoundsPerPass"),
maxToolCalls: config.get("agentMaxToolCalls"),
maxReadBytes: settings.maxReadBytes,
maxResultChars: settings.maxToolResultChars,
},
);
const approvals = new ApprovalStore(storage, journal);
const notes = new NoteStore(storage);
await Promise.all([
artifacts.initialize(),
transactions.initialize(),
processes.initialize(),
runStore.initialize(),
tasks.initialize(),
research.initialize(),
approvals.initialize(),
notes.initialize(),
]);
return {
client,
boundary,
storage,
agenticDir,
artifacts,
journal,
transactions,
processes,
runStore,
tasks,
web,
research,
browser,
agents,
approvals,
notes,
settings,
};
}
import { mkdir } from "node:fs/promises";
import { join } from "node:path";
import type { LMStudioClient, ToolsProviderController } from "@lmstudio/sdk";
import { AgentOrchestrator } from "./agents/orchestrator";
import { AgentRunStore } from "./agents/runStore";
import { BrowserService } from "./browser/service";
import { ArtifactStore } from "./core/artifacts";
import { Journal } from "./core/journal";
import { InternalStorage } from "./core/internalStorage";
import { configSchematics, splitConfigList } from "./config";
import { NoteStore } from "./notes/noteStore";
import { ApprovalStore } from "./policy/approvalStore";
import { permissionModeFromLevel, type PermissionMode } from "./policy/permissions";
import { ProcessService } from "./execution/processRunner";
import { ResearchStore } from "./research/store";
import { WebResearchService, type SearchProvider } from "./research/web";
import { TaskBoardStore } from "./tasks/taskStore";
import { WorkspaceBoundary } from "./workspace/boundary";
import { TransactionManager } from "./workspace/transactions";
import { resolveWorkspaceRoot } from "./workspaceRoot";
export interface RuntimeSettings {
permissionMode: PermissionMode;
allowDestructiveEdits: boolean;
maxReadBytes: number;
maxEditableBytes: number;
maxToolResultChars: number;
processExecutionEnabled: boolean;
/** Whether sub-agent runs may execute commands at all (= processExecutionEnabled && agentAllowCommands). */
agentCommandsEnabled: boolean;
internalAgentsEnabled: boolean;
taskBoardsEnabled: boolean;
webResearchEnabled: boolean;
webSearchProvider: SearchProvider;
agentAllowWeb: boolean;
vcsToolsEnabled: boolean;
browserEnabled: boolean;
}
export interface PluginRuntime {
client: LMStudioClient;
boundary: WorkspaceBoundary;
storage: InternalStorage;
agenticDir: string;
artifacts: ArtifactStore;
journal: Journal;
transactions: TransactionManager;
processes: ProcessService;
runStore: AgentRunStore;
tasks: TaskBoardStore;
web: WebResearchService;
research: ResearchStore;
browser: BrowserService;
agents: AgentOrchestrator;
approvals: ApprovalStore;
notes: NoteStore;
settings: RuntimeSettings;
}
function searchProvider(value: string): SearchProvider {
const provider = value.trim().toLowerCase();
return provider === "duckduckgo" ||
provider === "wikipedia" ||
provider === "searxng"
? provider
: "auto";
}
export async function createRuntime(
ctl: ToolsProviderController,
): Promise<PluginRuntime> {
const config = ctl.getPluginConfig(configSchematics);
const root = resolveWorkspaceRoot(ctl, config.get("defaultWorkspacePath"));
await mkdir(root, { recursive: true });
const boundary = await WorkspaceBoundary.create(root, {
protectedPatterns: splitConfigList(config.get("protectedPatterns")),
});
const storage = new InternalStorage(boundary);
const agenticDir = await storage.initialize();
const journal = new Journal(storage);
const artifacts = new ArtifactStore(storage);
const permissionMode = permissionModeFromLevel(config.get("permissionMode"));
const settings: RuntimeSettings = {
permissionMode,
allowDestructiveEdits: config.get("allowDestructiveEdits"),
maxReadBytes: config.get("maxReadBytes"),
maxEditableBytes: config.get("maxEditableBytes"),
maxToolResultChars: config.get("maxToolResultChars"),
processExecutionEnabled: config.get("processExecutionEnabled"),
agentCommandsEnabled:
config.get("processExecutionEnabled") && config.get("agentAllowCommands"),
internalAgentsEnabled: config.get("internalAgentsEnabled"),
taskBoardsEnabled: config.get("taskBoardsEnabled"),
webResearchEnabled: config.get("webResearchEnabled"),
webSearchProvider: searchProvider(config.get("webSearchProvider")),
agentAllowWeb: config.get("agentAllowWeb"),
vcsToolsEnabled: config.get("vcsToolsEnabled"),
browserEnabled: config.get("browserEnabled"),
};
const transactions = new TransactionManager(
boundary,
storage,
journal,
settings.maxEditableBytes,
);
const processes = new ProcessService(boundary, storage, journal, {
allowedExecutables: splitConfigList(config.get("allowedExecutables")),
inheritEnvironment: config.get("inheritProcessEnvironment"),
maxTimeoutSeconds: config.get("maxCommandSeconds"),
maxPreviewChars: config.get("maxCommandPreviewChars"),
maxArtifactBytes: config.get("maxCommandArtifactBytes"),
});
const runStore = new AgentRunStore(storage);
const tasks = new TaskBoardStore(storage);
const research = new ResearchStore(storage);
const web = new WebResearchService({
timeoutMs: config.get("webTimeoutSeconds") * 1000,
maxResponseBytes: config.get("maxWebResponseBytes"),
maxTextChars: config.get("maxWebTextChars"),
allowPrivateNetwork: config.get("allowPrivateNetworkWeb"),
allowedDomains: splitConfigList(config.get("webAllowedDomains")),
...(config.get("searxngBaseUrl").trim()
? { searxngBaseUrl: config.get("searxngBaseUrl").trim() }
: {}),
});
const browser = new BrowserService(web, {
enabled: settings.browserEnabled,
...(config.get("browserExecutablePath").trim()
? { executablePath: config.get("browserExecutablePath").trim() }
: {}),
headless: config.get("browserHeadless"),
allowEvaluate: config.get("browserAllowEvaluate"),
maxSessions: 4,
navigationTimeoutMs: config.get("webTimeoutSeconds") * 1000,
maxTextChars: config.get("maxWebTextChars"),
});
const client = ctl.client as LMStudioClient;
const agents = new AgentOrchestrator(
client,
boundary,
runStore,
transactions,
processes,
tasks,
web,
research,
journal,
{
defaultModelId: config.get("agentModelId").trim() || undefined,
defaultCommitEdits: settings.permissionMode === "auto",
destructiveEditsEnabled: settings.allowDestructiveEdits,
commandsEnabled: settings.agentCommandsEnabled,
webEnabled: settings.webResearchEnabled && settings.agentAllowWeb,
defaultSearchProvider: settings.webSearchProvider,
maxPasses: config.get("agentMaxPasses"),
roundsPerPass: config.get("agentRoundsPerPass"),
maxToolCalls: config.get("agentMaxToolCalls"),
maxReadBytes: settings.maxReadBytes,
maxResultChars: settings.maxToolResultChars,
},
);
const approvals = new ApprovalStore(storage, journal);
const notes = new NoteStore(storage);
await Promise.all([
artifacts.initialize(),
transactions.initialize(),
processes.initialize(),
runStore.initialize(),
tasks.initialize(),
research.initialize(),
approvals.initialize(),
notes.initialize(),
]);
return {
client,
boundary,
storage,
agenticDir,
artifacts,
journal,
transactions,
processes,
runStore,
tasks,
web,
research,
browser,
agents,
approvals,
notes,
settings,
};
}