Compare commits
No commits in common. "419d0990bfcd04b146f4383545a2416394c3a2f3" and "6039e992bd7e19125741fc8f497cb40d7f236a18" have entirely different histories.
419d0990bf
...
6039e992bd
7 changed files with 53 additions and 186 deletions
48
package-lock.json
generated
48
package-lock.json
generated
|
|
@ -13,8 +13,7 @@
|
||||||
"discord.js": "^14.16.0",
|
"discord.js": "^14.16.0",
|
||||||
"dotenv": "^16.4.0",
|
"dotenv": "^16.4.0",
|
||||||
"pino": "^10.3.1",
|
"pino": "^10.3.1",
|
||||||
"yaml": "^2.6.0",
|
"yaml": "^2.6.0"
|
||||||
"zod": "^3.23.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/better-sqlite3": "^7.6.0",
|
"@types/better-sqlite3": "^7.6.0",
|
||||||
|
|
@ -1863,6 +1862,15 @@
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/atomic-sleep": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ast-v8-to-istanbul": {
|
"node_modules/ast-v8-to-istanbul": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz",
|
||||||
|
|
@ -1875,15 +1883,6 @@
|
||||||
"js-tokens": "^10.0.0"
|
"js-tokens": "^10.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/atomic-sleep": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
|
||||||
|
|
@ -2757,8 +2756,7 @@
|
||||||
"node_modules/help-me": {
|
"node_modules/help-me": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz",
|
||||||
"integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==",
|
"integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/html-escaper": {
|
"node_modules/html-escaper": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
|
|
@ -2887,6 +2885,13 @@
|
||||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/joycon": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/istanbul-lib-coverage": {
|
"node_modules/istanbul-lib-coverage": {
|
||||||
"version": "3.2.2",
|
"version": "3.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
|
||||||
|
|
@ -2926,13 +2931,6 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/joycon": {
|
|
||||||
"version": "3.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz",
|
|
||||||
"integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/js-tokens": {
|
"node_modules/js-tokens": {
|
||||||
"version": "10.0.0",
|
"version": "10.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz",
|
||||||
|
|
@ -4345,15 +4343,7 @@
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"node_modules/zod": {
|
|
||||||
"version": "3.25.76",
|
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
|
||||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,7 @@
|
||||||
"discord.js": "^14.16.0",
|
"discord.js": "^14.16.0",
|
||||||
"dotenv": "^16.4.0",
|
"dotenv": "^16.4.0",
|
||||||
"pino": "^10.3.1",
|
"pino": "^10.3.1",
|
||||||
"yaml": "^2.6.0",
|
"yaml": "^2.6.0"
|
||||||
"zod": "^3.23.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/better-sqlite3": "^7.6.0",
|
"@types/better-sqlite3": "^7.6.0",
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,24 @@
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import * as YAML from "yaml";
|
import * as YAML from "yaml";
|
||||||
import { ZodError } from "zod";
|
|
||||||
import { AgentYamlSchema } from "./schema";
|
|
||||||
|
|
||||||
export type { AgentYaml as AgentIdentity } from "./schema";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads agent.yaml from the workspace folder and returns the validated identity.
|
* Metadata stored in agent.yaml -- DisClaw-specific fields only.
|
||||||
|
* Identity, personality, and instructions now live in CLAUDE.md,
|
||||||
|
* which Claude Code reads automatically.
|
||||||
|
*/
|
||||||
|
export interface AgentIdentity {
|
||||||
|
name: string;
|
||||||
|
display_name: string;
|
||||||
|
role: string;
|
||||||
|
channel_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads agent.yaml from the workspace folder and returns the parsed identity.
|
||||||
* Re-read on every call so edits take effect immediately.
|
* Re-read on every call so edits take effect immediately.
|
||||||
*/
|
*/
|
||||||
export function loadAgentIdentity(workspacePath: string): import("./schema").AgentYaml {
|
export function loadAgentIdentity(workspacePath: string): AgentIdentity {
|
||||||
const yamlPath = path.join(workspacePath, "agent.yaml");
|
const yamlPath = path.join(workspacePath, "agent.yaml");
|
||||||
|
|
||||||
if (!fs.existsSync(yamlPath)) {
|
if (!fs.existsSync(yamlPath)) {
|
||||||
|
|
@ -18,20 +26,7 @@ export function loadAgentIdentity(workspacePath: string): import("./schema").Age
|
||||||
}
|
}
|
||||||
|
|
||||||
const raw = fs.readFileSync(yamlPath, "utf-8");
|
const raw = fs.readFileSync(yamlPath, "utf-8");
|
||||||
const rawParsed = YAML.parse(raw);
|
return YAML.parse(raw) as AgentIdentity;
|
||||||
|
|
||||||
try {
|
|
||||||
return AgentYamlSchema.parse(rawParsed);
|
|
||||||
} catch (err) {
|
|
||||||
if (err instanceof ZodError) {
|
|
||||||
const first = err.errors[0];
|
|
||||||
const fieldPath = first.path.length > 0 ? first.path.join(".") : "(root)";
|
|
||||||
throw new Error(
|
|
||||||
`Invalid agent.yaml at ${yamlPath}: field "${fieldPath}" -- ${first.message}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
export const AgentYamlSchema = z.object({
|
|
||||||
name: z.string().min(1),
|
|
||||||
display_name: z.string().optional(),
|
|
||||||
role: z.string().optional(),
|
|
||||||
channel_id: z.string().min(1),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type AgentYaml = z.infer<typeof AgentYamlSchema>;
|
|
||||||
|
|
||||||
export const ClaudeJsonOutputSchema = z.union([
|
|
||||||
z.object({ result: z.string() }),
|
|
||||||
z.object({ content: z.array(z.object({ type: z.string(), text: z.string().optional() })) }),
|
|
||||||
z.array(z.object({ type: z.string(), text: z.string().optional() })),
|
|
||||||
z.string(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
export type ClaudeJsonOutput = z.infer<typeof ClaudeJsonOutputSchema>;
|
|
||||||
|
|
@ -3,14 +3,19 @@ import * as os from "os";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import * as dotenv from "dotenv";
|
import * as dotenv from "dotenv";
|
||||||
import * as YAML from "yaml";
|
import * as YAML from "yaml";
|
||||||
import { ZodError } from "zod";
|
|
||||||
import { childLogger } from "../runtime/logger";
|
import { childLogger } from "../runtime/logger";
|
||||||
import { DisclawConfigSchema } from "./schema";
|
|
||||||
|
|
||||||
export type { DisclawConfig } from "./schema";
|
|
||||||
|
|
||||||
const log = childLogger("config");
|
const log = childLogger("config");
|
||||||
|
|
||||||
|
export interface DisclawConfig {
|
||||||
|
workspaces_root: string;
|
||||||
|
management_channel: string;
|
||||||
|
claude_command: string;
|
||||||
|
claude_timeout_seconds: number;
|
||||||
|
env_blocklist: string[];
|
||||||
|
max_concurrent_agents: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface EnvConfig {
|
export interface EnvConfig {
|
||||||
DISCORD_BOT_TOKEN: string;
|
DISCORD_BOT_TOKEN: string;
|
||||||
DISCORD_GUILD_ID?: string;
|
DISCORD_GUILD_ID?: string;
|
||||||
|
|
@ -73,25 +78,18 @@ export function loadDisclawConfig(rootDir: string): DisclawConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
const raw = fs.readFileSync(configPath, "utf-8");
|
const raw = fs.readFileSync(configPath, "utf-8");
|
||||||
const rawParsed = YAML.parse(raw);
|
const parsed = YAML.parse(raw) as Partial<DisclawConfig>;
|
||||||
|
|
||||||
let validated: DisclawConfig;
|
// Apply defaults
|
||||||
try {
|
const rawWorkspacesRoot = parsed.workspaces_root ?? "~/.disclaw/workspaces";
|
||||||
validated = DisclawConfigSchema.parse(rawParsed);
|
|
||||||
} catch (err) {
|
|
||||||
if (err instanceof ZodError) {
|
|
||||||
const first = err.errors[0];
|
|
||||||
const fieldPath = first.path.length > 0 ? first.path.join(".") : "(root)";
|
|
||||||
throw new Error(
|
|
||||||
`Invalid disclaw.yaml: field "${fieldPath}" -- ${first.message}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
const config: DisclawConfig = {
|
const config: DisclawConfig = {
|
||||||
...validated,
|
workspaces_root: expandWorkspacesRoot(rawWorkspacesRoot, rootDir),
|
||||||
workspaces_root: expandWorkspacesRoot(validated.workspaces_root, rootDir),
|
management_channel: parsed.management_channel ?? "disclaw",
|
||||||
|
claude_command: parsed.claude_command ?? "claude",
|
||||||
|
claude_timeout_seconds: parsed.claude_timeout_seconds ?? 120,
|
||||||
|
env_blocklist: parsed.env_blocklist ?? [],
|
||||||
|
max_concurrent_agents: parsed.max_concurrent_agents ?? 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
export const DisclawConfigSchema = z.object({
|
|
||||||
workspaces_root: z.string().min(1),
|
|
||||||
management_channel: z.string().min(1).default("disclaw"),
|
|
||||||
claude_command: z.string().min(1).default("claude"),
|
|
||||||
claude_timeout_seconds: z.number().int().positive().default(120),
|
|
||||||
max_concurrent_agents: z.number().int().positive().default(4),
|
|
||||||
env_blocklist: z.array(z.string()).default([]),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type DisclawConfig = z.infer<typeof DisclawConfigSchema>;
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
import { describe, it, expect } from "vitest";
|
|
||||||
import { ZodError } from "zod";
|
|
||||||
import { DisclawConfigSchema } from "../../src/config/schema";
|
|
||||||
import { AgentYamlSchema } from "../../src/agent/schema";
|
|
||||||
|
|
||||||
describe("DisclawConfigSchema", () => {
|
|
||||||
it("(a) parses valid disclaw.yaml data correctly", () => {
|
|
||||||
const input = {
|
|
||||||
workspaces_root: "/home/user/.disclaw/workspaces",
|
|
||||||
management_channel: "disclaw",
|
|
||||||
claude_command: "claude",
|
|
||||||
claude_timeout_seconds: 60,
|
|
||||||
max_concurrent_agents: 2,
|
|
||||||
env_blocklist: ["SECRET_KEY"],
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = DisclawConfigSchema.parse(input);
|
|
||||||
|
|
||||||
expect(result.workspaces_root).toBe("/home/user/.disclaw/workspaces");
|
|
||||||
expect(result.management_channel).toBe("disclaw");
|
|
||||||
expect(result.claude_command).toBe("claude");
|
|
||||||
expect(result.claude_timeout_seconds).toBe(60);
|
|
||||||
expect(result.max_concurrent_agents).toBe(2);
|
|
||||||
expect(result.env_blocklist).toEqual(["SECRET_KEY"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("(b) throws ZodError when workspaces_root is missing", () => {
|
|
||||||
const input = {
|
|
||||||
management_channel: "disclaw",
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(() => DisclawConfigSchema.parse(input)).toThrow(ZodError);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("(c) throws ZodError when claude_timeout_seconds is a string", () => {
|
|
||||||
const input = {
|
|
||||||
workspaces_root: "/some/path",
|
|
||||||
claude_timeout_seconds: "abc",
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(() => DisclawConfigSchema.parse(input)).toThrow(ZodError);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("(d) applies defaults when optional fields are not provided", () => {
|
|
||||||
const input = {
|
|
||||||
workspaces_root: "/some/path",
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = DisclawConfigSchema.parse(input);
|
|
||||||
|
|
||||||
expect(result.management_channel).toBe("disclaw");
|
|
||||||
expect(result.claude_command).toBe("claude");
|
|
||||||
expect(result.claude_timeout_seconds).toBe(120);
|
|
||||||
expect(result.max_concurrent_agents).toBe(4);
|
|
||||||
expect(result.env_blocklist).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("AgentYamlSchema", () => {
|
|
||||||
it("(e) parses valid agent.yaml data correctly", () => {
|
|
||||||
const input = {
|
|
||||||
name: "my-agent",
|
|
||||||
display_name: "My Agent",
|
|
||||||
role: "Backend developer",
|
|
||||||
channel_id: "1234567890",
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = AgentYamlSchema.parse(input);
|
|
||||||
|
|
||||||
expect(result.name).toBe("my-agent");
|
|
||||||
expect(result.display_name).toBe("My Agent");
|
|
||||||
expect(result.role).toBe("Backend developer");
|
|
||||||
expect(result.channel_id).toBe("1234567890");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("(f) throws ZodError when channel_id is missing", () => {
|
|
||||||
const input = {
|
|
||||||
name: "my-agent",
|
|
||||||
role: "Backend developer",
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(() => AgentYamlSchema.parse(input)).toThrow(ZodError);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Loading…
Reference in a new issue