diff --git a/apps/server/src/app.test.ts b/apps/server/src/app.test.ts index 7d6f3a7f..18944de0 100644 --- a/apps/server/src/app.test.ts +++ b/apps/server/src/app.test.ts @@ -5,12 +5,6 @@ import { createRuntimeApp } from "./app"; import { globalEventBus } from "./events/global-event-bus"; const mockRuntime = { - configService: { - getSnapshot: mock(async () => ({ config: { provider: {}, profiles: {} }, revision: "test", modelRuntimeRevision: "test", configPath: "/test", restartRequiredSections: [] })), - getModelRuntimeCatalog: mock(() => ({ revision: "test", providers: [], profileDefaults: {} })), - getProviderAdapterCatalog: mock(() => []), - save: mock(async () => ({ config: { provider: {}, profiles: {} }, revision: "test", modelRuntimeRevision: "test", configPath: "/test", restartRequiredSections: [] })), - }, listAgentDescriptors: mock(() => []), subscribeSessionEvents: mock(() => () => undefined), subscribeHitlEvents: mock(() => () => undefined), diff --git a/apps/server/src/app.ts b/apps/server/src/app.ts index cef76075..83d04482 100644 --- a/apps/server/src/app.ts +++ b/apps/server/src/app.ts @@ -7,7 +7,6 @@ import { } from "@archcode/agent-core"; import type { GlobalSSEEvent } from "@archcode/protocol"; import { errorHandler } from "./error-handler"; -import { createConfigRoutes } from "./routes/config"; import { createAgentsRoutes } from "./routes/agents"; import { createCompressionRoutes } from "./routes/compression"; import { createDirectoriesRoutes } from "./routes/directories"; @@ -83,7 +82,6 @@ export function createRuntimeApp( const files = createFilesRoutes(serverRuntime); const directories = createDirectoriesRoutes(); const mcp = createMcpRoutes(serverRuntime); - const config = createConfigRoutes(serverRuntime.configService); app.route("/api", globalWork); app.route("/api/projects", projects); @@ -98,7 +96,6 @@ export function createRuntimeApp( app.route("/api/events", globalEvents); app.route("/api/projects", files); app.route("/api/mcp", mcp); - app.route("/api/config", config); app.route("/api/sessions", new Hono()); app.route("/api/agents", agents); app.route("/api/files", new Hono()); diff --git a/apps/server/src/boot.ts b/apps/server/src/boot.ts index 0f960239..56ecbea8 100644 --- a/apps/server/src/boot.ts +++ b/apps/server/src/boot.ts @@ -45,10 +45,14 @@ export async function bootServer( ...(options.version ? { version: options.version } : {}), }, }); - for (const instruction of host.setupInstructions(url)) { - logger.info("server.setup.required", { message: instruction }); + for (const instruction of host.terminalInstructions(url)) { + logger.info("server.terminal_action.required", { message: instruction }); } + // The listener is deliberately published before Runtime activation begins. + // Runtime startup may be slow or fail; the control plane remains responsive. + host.startRuntimeActivation(); + if (Bun.env[ENV_OPEN_BROWSER]) { // Browser opening will be implemented when the web UI workflow is ready. } diff --git a/apps/server/src/errors.ts b/apps/server/src/errors.ts index 09c946b2..35bf4bcd 100644 --- a/apps/server/src/errors.ts +++ b/apps/server/src/errors.ts @@ -27,6 +27,7 @@ export type ServerErrorCode = | "PROJECT_TODO_DISCUSSION_UNAUTHORIZED" | "CONFIG_REVISION_CONFLICT" | "CONFIG_VALIDATION_ERROR" + | "CONFIG_RECOVERY_CONFLICT" | "TOOL_OUTPUT_FORBIDDEN" | "TOOL_OUTPUT_NOT_FOUND" | "TOOL_OUTPUT_EXPIRED" @@ -55,7 +56,14 @@ export type ServerErrorCode = | "UPDATE_INSTALL_FAILED" | "UPDATE_NOT_AVAILABLE" | "UPDATE_RESTART_UNAVAILABLE" - | "UPDATE_RUNTIME_BUSY"; + | "UPDATE_RUNTIME_BUSY" + | "RUNTIME_BUSY" + | "RUNTIME_CLEANUP_INCOMPLETE" + | "EMPTY_PROJECT_SLUGS" + | "DUPLICATE_PROJECT_SLUG" + | "PROJECT_NOT_REGISTERED" + | "PROJECT_HAS_NO_ISSUES" + | "DELETE_TARGET_UNSAFE"; export class ServerError extends Error { constructor( diff --git a/apps/server/src/main.ts b/apps/server/src/main.ts index 6d61b96d..d207babf 100755 --- a/apps/server/src/main.ts +++ b/apps/server/src/main.ts @@ -3,6 +3,8 @@ import { createConsoleLogger, createRuntime, type Logger, + ProjectRegistry, + RuntimeDataService, ServerConfigService, } from "@archcode/agent-core"; import { @@ -46,6 +48,10 @@ async function main( const configService = new ServerConfigService(); const compiled = isCompiledRuntime(); const restartController = new ServerRestartController(); + const projectRegistry = new ProjectRegistry({ + logger: logger.child({ module: "projects.registry" }), + }); + const runtimeDataService = new RuntimeDataService({ projectRegistry }); const updateService = new UpdateService({ currentVersion: options.version ?? "development", executablePath: process.execPath, @@ -71,6 +77,8 @@ async function main( version: options.version, updateService, restartController, + projectRegistry, + runtimeDataService, }); await bootServer(host, { port: options.port, diff --git a/apps/server/src/routes/config-recovery.test.ts b/apps/server/src/routes/config-recovery.test.ts new file mode 100644 index 00000000..e58cef45 --- /dev/null +++ b/apps/server/src/routes/config-recovery.test.ts @@ -0,0 +1,75 @@ +import { describe, expect, test } from "bun:test"; +import { safeConfigRecoveryStatus } from "./config-recovery"; + +describe("safe Config Recovery diagnostics", () => { + test("removes dynamic Config keys and raw validation values", () => { + const text = JSON.stringify(safeConfigRecoveryStatus( + "/Users/test/.archcode/config.json", + [{ + path: "provider.raw-secret-provider.options.raw-secret-option", + message: "Unsupported provider package \"raw-secret-package\"", + }, { + path: "provider.raw-secret-provider.models.raw-secret-model.variants.raw-secret-variant.temperature", + message: "raw-secret-value is invalid", + }, { + path: "mcp.servers.raw-secret-server.headers.raw-secret-header", + message: "raw-secret-header-value is invalid", + }, { + path: "memory.raw-secret-field", + message: "raw-secret-memory-value is invalid", + }], + )); + + expect(text).not.toContain("raw-secret"); + expect(JSON.parse(text)).toEqual({ + configPath: "/Users/test/.archcode/config.json", + removableItems: [], + issues: [{ + path: "provider..options.