Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions apps/server/src/app.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
3 changes: 0 additions & 3 deletions apps/server/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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);
Expand All @@ -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());
Expand Down
8 changes: 6 additions & 2 deletions apps/server/src/boot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
}
Expand Down
10 changes: 9 additions & 1 deletion apps/server/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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(
Expand Down
8 changes: 8 additions & 0 deletions apps/server/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {
createConsoleLogger,
createRuntime,
type Logger,
ProjectRegistry,
RuntimeDataService,
ServerConfigService,
} from "@archcode/agent-core";
import {
Expand Down Expand Up @@ -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,
Expand All @@ -71,6 +77,8 @@ async function main(
version: options.version,
updateService,
restartController,
projectRegistry,
runtimeDataService,
});
await bootServer(host, {
port: options.port,
Expand Down
75 changes: 75 additions & 0 deletions apps/server/src/routes/config-recovery.test.ts
Original file line number Diff line number Diff line change
@@ -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.<provider>.options.<option>",
message: "A configured provider package is not supported by this ArchCode release.",
}, {
path: "provider.<provider>.models.<model>.variants.<variant>",
message: "This value does not match the current ArchCode configuration format.",
}, {
path: "mcp.servers.<server>.headers.<header>",
message: "This value does not match the current ArchCode configuration format.",
}, {
path: "memory.<field>",
message: "This value does not match the current ArchCode configuration format.",
}],
});
});

test("exposes only opaque removable identities and bounded impact descriptions", () => {
const text = JSON.stringify(safeConfigRecoveryStatus(
"/Users/test/.archcode/config.json",
[],
{
revision: "revision-sentinel-safe",
items: [{
id: "abcdefghijklmnopqrstuv",
path: ["provider", "raw-secret-provider", "models", "raw-secret-model"],
}, {
id: "zyxwvutsrqponmlkjihgfe",
path: ["mcp", "servers", "raw-secret-server"],
}],
},
));

expect(text).not.toContain("raw-secret");
expect(JSON.parse(text)).toMatchObject({
revision: "revision-sentinel-safe",
removableItems: [{
id: "abcdefghijklmnopqrstuv",
label: "Invalid model entry",
path: "provider.<provider>.models.<model>",
impact: expect.stringContaining("Other models remain configured"),
}, {
id: "zyxwvutsrqponmlkjihgfe",
label: "Invalid MCP server",
path: "mcp.servers.<server>",
impact: expect.stringContaining("Other MCP servers remain configured"),
}],
});
});
});
Loading