summaryrefslogtreecommitdiffhomepage
path: root/packages/core/src/config
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-05-22 15:24:13 +0900
committerAdam Malczewski <[email protected]>2026-05-22 15:24:13 +0900
commit9ecaabd87c0e51b8a7408dabb0133a9344586859 (patch)
tree4b5809ab23948a5e3f558f3aa34c52d5038f4e19 /packages/core/src/config
parent8f1dd855f0c4c877bff8d3a4ba193432b268b1c2 (diff)
downloaddispatch-9ecaabd87c0e51b8a7408dabb0133a9344586859.tar.gz
dispatch-9ecaabd87c0e51b8a7408dabb0133a9344586859.zip
feat: agent builder, CWD support, auto-save, UI polish, unavailable tool handling
- Agent Builder: full CRUD with card grid, drag-and-drop model reorder, edit/delete - Auto-save on edit with 600ms debounce, AbortController for concurrency, fieldset disabled until name entered - Agent definitions stored as TOML with cwd field, loaded from global/project dirs - Working directory: per-tab CWD override in Chat Settings, agent default CWD, auto-create on first message - CWD validation: check-dir endpoint with ~ expansion, real-time validity indicator - Subagent CWD validated against parent's effective CWD using path.relative - Unavailable tool calls: caught gracefully, shown as tool call with error badge, model retries - UI: tab bar border radius, sidebar border removed, chat input ghost style, scroll-to-bottom rectangle - Skills dir collapse uses CSS rotation, Model Choice renamed to Chat Settings, System Prompt view removed - Reusable SkillsBrowser/ToolPermissions with external mode for Agent Builder - ModelSelector: Agent/Manual toggle, agent list, Agent Settings link - Page router, skills recursive scanning, bin/up gopass removed, docker volume mounts
Diffstat (limited to 'packages/core/src/config')
-rw-r--r--packages/core/src/config/loader.ts4
-rw-r--r--packages/core/src/config/schema.ts25
-rw-r--r--packages/core/src/config/watcher.ts14
3 files changed, 29 insertions, 14 deletions
diff --git a/packages/core/src/config/loader.ts b/packages/core/src/config/loader.ts
index 3b4d733..bccbb8f 100644
--- a/packages/core/src/config/loader.ts
+++ b/packages/core/src/config/loader.ts
@@ -28,7 +28,9 @@ export function loadConfig(dir: string): DispatchConfig {
// File doesn't exist — return empty default
return DEFAULT_CONFIG;
}
- console.warn(`dispatch: failed to parse dispatch.toml: ${err instanceof Error ? err.message : String(err)}`);
+ console.warn(
+ `dispatch: failed to parse dispatch.toml: ${err instanceof Error ? err.message : String(err)}`,
+ );
throw err;
}
diff --git a/packages/core/src/config/schema.ts b/packages/core/src/config/schema.ts
index 2cd8d55..ef10b65 100644
--- a/packages/core/src/config/schema.ts
+++ b/packages/core/src/config/schema.ts
@@ -1,8 +1,4 @@
-import type {
- ConfigError,
- DispatchConfig,
- KeyDefinition,
-} from "../types/index.js";
+import type { ConfigError, DispatchConfig, KeyDefinition } from "../types/index.js";
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -33,19 +29,28 @@ function validatePermissions(
const result: Record<string, string | Record<string, string>> = {};
for (const [key, value] of Object.entries(raw)) {
if (!isPermissionsValue(value)) {
- errors.push({ path: `${path}.${key}`, message: "must be a string or a flat string-keyed object" });
+ errors.push({
+ path: `${path}.${key}`,
+ message: "must be a string or a flat string-keyed object",
+ });
continue;
}
if (typeof value === "string") {
if (!isValidAction(value)) {
- errors.push({ path: `${path}.${key}`, message: `invalid action "${value}"; must be "allow", "deny", or "ask"` });
+ errors.push({
+ path: `${path}.${key}`,
+ message: `invalid action "${value}"; must be "allow", "deny", or "ask"`,
+ });
continue;
}
} else {
let hasError = false;
for (const [pattern, action] of Object.entries(value)) {
if (!isValidAction(action)) {
- errors.push({ path: `${path}.${key}.${pattern}`, message: `invalid action "${action}"; must be "allow", "deny", or "ask"` });
+ errors.push({
+ path: `${path}.${key}.${pattern}`,
+ message: `invalid action "${action}"; must be "allow", "deny", or "ask"`,
+ });
hasError = true;
}
}
@@ -80,7 +85,9 @@ function validateKey(raw: unknown, path: string, errors: ConfigError[]): KeyDefi
id: raw["id"] as string,
provider: raw["provider"] as string,
base_url: raw["base_url"] as string,
- ...(typeof raw["credentials_file"] === "string" ? { credentials_file: raw["credentials_file"] } as Pick<KeyDefinition, "credentials_file"> : {}),
+ ...(typeof raw["credentials_file"] === "string"
+ ? ({ credentials_file: raw["credentials_file"] } as Pick<KeyDefinition, "credentials_file">)
+ : {}),
};
}
diff --git a/packages/core/src/config/watcher.ts b/packages/core/src/config/watcher.ts
index 42b2f87..70821ed 100644
--- a/packages/core/src/config/watcher.ts
+++ b/packages/core/src/config/watcher.ts
@@ -1,5 +1,5 @@
-import { watch } from "chokidar";
import { join } from "node:path";
+import { watch } from "chokidar";
import type { DispatchConfig } from "../types/index.js";
import { loadConfig } from "./loader.js";
@@ -26,7 +26,9 @@ export function createConfigWatcher(
const config = loadConfig(dir);
onChange(config);
} catch (err) {
- console.warn(`dispatch: retaining last known config due to parse error: ${err instanceof Error ? err.message : String(err)}`);
+ console.warn(
+ `dispatch: retaining last known config due to parse error: ${err instanceof Error ? err.message : String(err)}`,
+ );
}
}, 300);
};
@@ -36,7 +38,9 @@ export function createConfigWatcher(
watcher.on("unlink", handleChange);
watcher.on("error", (err) => {
- console.warn(`dispatch: config watcher error: ${err instanceof Error ? err.message : String(err)}`);
+ console.warn(
+ `dispatch: config watcher error: ${err instanceof Error ? err.message : String(err)}`,
+ );
});
return {
@@ -46,7 +50,9 @@ export function createConfigWatcher(
debounceTimer = null;
}
watcher.close().catch((err) => {
- console.warn(`dispatch: error closing config watcher: ${err instanceof Error ? err.message : String(err)}`);
+ console.warn(
+ `dispatch: error closing config watcher: ${err instanceof Error ? err.message : String(err)}`,
+ );
});
},
};