summaryrefslogtreecommitdiffhomepage
path: root/packages/core/src/config
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-02 21:10:09 +0900
committerAdam Malczewski <[email protected]>2026-06-02 21:10:09 +0900
commitd9f53727845dface3e6d8a84ba2270b1de55482b (patch)
tree6d42f0a0fbda15057296992e78c4b4e12046f9ed /packages/core/src/config
parent80212bfb009eaf71a4743310dee6ed08b8f7e1da (diff)
parent9d8cf7005ba4c0bb8ade0775f54c2557aa1c5683 (diff)
downloaddispatch-d9f53727845dface3e6d8a84ba2270b1de55482b.tar.gz
dispatch-d9f53727845dface3e6d8a84ba2270b1de55482b.zip
Merge branch 'dev' into feat/cs-code-search-tool
# Conflicts: # packages/api/src/agent-manager.ts # packages/api/tests/agent-manager.test.ts # packages/frontend/src/lib/components/ToolPermissions.svelte # packages/frontend/src/lib/settings.svelte.ts
Diffstat (limited to 'packages/core/src/config')
-rw-r--r--packages/core/src/config/schema.ts107
1 files changed, 106 insertions, 1 deletions
diff --git a/packages/core/src/config/schema.ts b/packages/core/src/config/schema.ts
index a459a4d..304ee10 100644
--- a/packages/core/src/config/schema.ts
+++ b/packages/core/src/config/schema.ts
@@ -1,4 +1,9 @@
-import type { ConfigError, DispatchConfig, KeyDefinition } from "../types/index.js";
+import type {
+ ConfigError,
+ DispatchConfig,
+ KeyDefinition,
+ LspServerConfig,
+} from "../types/index.js";
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -100,6 +105,99 @@ function validateKey(raw: unknown, path: string, errors: ConfigError[]): KeyDefi
};
}
+function isStringArray(value: unknown): value is string[] {
+ return Array.isArray(value) && value.every((v) => typeof v === "string");
+}
+
+function validateLspServer(
+ raw: unknown,
+ path: string,
+ errors: ConfigError[],
+): LspServerConfig | null {
+ if (!isRecord(raw)) {
+ errors.push({ path, message: "must be an object" });
+ return null;
+ }
+
+ const disabled = raw.disabled === true;
+
+ // `command` is required and must be a non-empty string array unless the
+ // entry is explicitly disabled (a disabled entry is skipped wholesale).
+ if (!disabled) {
+ if (!isStringArray(raw.command) || raw.command.length === 0) {
+ errors.push({
+ path: `${path}.command`,
+ message: "must be a non-empty array of strings",
+ });
+ return null;
+ }
+ // `extensions` is required for custom servers — without it the client
+ // cannot know which files should activate the server.
+ if (!isStringArray(raw.extensions) || raw.extensions.length === 0) {
+ errors.push({
+ path: `${path}.extensions`,
+ message: 'must be a non-empty array of strings (e.g. [".luau"])',
+ });
+ return null;
+ }
+ } else {
+ // Disabled entries still must not carry a malformed command/extensions
+ // if present, but we do not require them.
+ if (raw.command !== undefined && !isStringArray(raw.command)) {
+ errors.push({ path: `${path}.command`, message: "must be an array of strings" });
+ return null;
+ }
+ if (raw.extensions !== undefined && !isStringArray(raw.extensions)) {
+ errors.push({ path: `${path}.extensions`, message: "must be an array of strings" });
+ return null;
+ }
+ }
+
+ if (raw.env !== undefined && !isStringRecord(raw.env)) {
+ errors.push({
+ path: `${path}.env`,
+ message: "must be a flat string-keyed object",
+ });
+ return null;
+ }
+
+ if (raw.initialization !== undefined && !isRecord(raw.initialization)) {
+ errors.push({
+ path: `${path}.initialization`,
+ message: "must be an object",
+ });
+ return null;
+ }
+
+ const server: LspServerConfig = {
+ command: (raw.command as string[] | undefined) ?? [],
+ extensions: (raw.extensions as string[] | undefined) ?? [],
+ ...(isStringRecord(raw.env) ? { env: raw.env } : {}),
+ ...(isRecord(raw.initialization)
+ ? { initialization: raw.initialization as Record<string, unknown> }
+ : {}),
+ ...(disabled ? { disabled: true } : {}),
+ };
+ return server;
+}
+
+function validateLsp(
+ raw: unknown,
+ path: string,
+ errors: ConfigError[],
+): Record<string, LspServerConfig> | undefined {
+ if (!isRecord(raw)) {
+ errors.push({ path, message: "must be an object" });
+ return undefined;
+ }
+ const result: Record<string, LspServerConfig> = {};
+ for (const [id, value] of Object.entries(raw)) {
+ const server = validateLspServer(value, `${path}.${id}`, errors);
+ if (server) result[id] = server;
+ }
+ return Object.keys(result).length > 0 ? result : undefined;
+}
+
export function validateConfig(raw: unknown): { config: DispatchConfig; errors: ConfigError[] } {
const errors: ConfigError[] = [];
@@ -125,9 +223,16 @@ export function validateConfig(raw: unknown): { config: DispatchConfig; errors:
}
}
+ // lsp (optional)
+ let lsp: Record<string, LspServerConfig> | undefined;
+ if (raw.lsp !== undefined) {
+ lsp = validateLsp(raw.lsp, "lsp", errors);
+ }
+
const config: DispatchConfig = {
permissions,
...(keys !== undefined && { keys }),
+ ...(lsp !== undefined && { lsp }),
};
return { config, errors };