feat: support per-agent extraWorkspaceFiles config
This commit is contained in:
parent
f5ff4363b3
commit
ca09994c09
@ -20,6 +20,7 @@ type ResolvedAgentConfig = {
|
|||||||
workspace?: string;
|
workspace?: string;
|
||||||
agentDir?: string;
|
agentDir?: string;
|
||||||
model?: AgentEntry["model"];
|
model?: AgentEntry["model"];
|
||||||
|
extraWorkspaceFiles?: string[];
|
||||||
memorySearch?: AgentEntry["memorySearch"];
|
memorySearch?: AgentEntry["memorySearch"];
|
||||||
humanDelay?: AgentEntry["humanDelay"];
|
humanDelay?: AgentEntry["humanDelay"];
|
||||||
heartbeat?: AgentEntry["heartbeat"];
|
heartbeat?: AgentEntry["heartbeat"];
|
||||||
@ -103,6 +104,9 @@ export function resolveAgentConfig(
|
|||||||
typeof entry.model === "string" || (entry.model && typeof entry.model === "object")
|
typeof entry.model === "string" || (entry.model && typeof entry.model === "object")
|
||||||
? entry.model
|
? entry.model
|
||||||
: undefined,
|
: undefined,
|
||||||
|
extraWorkspaceFiles: Array.isArray(entry.extraWorkspaceFiles)
|
||||||
|
? entry.extraWorkspaceFiles
|
||||||
|
: undefined,
|
||||||
memorySearch: entry.memorySearch,
|
memorySearch: entry.memorySearch,
|
||||||
humanDelay: entry.humanDelay,
|
humanDelay: entry.humanDelay,
|
||||||
heartbeat: entry.heartbeat,
|
heartbeat: entry.heartbeat,
|
||||||
|
|||||||
@ -3,12 +3,13 @@ import path from "node:path";
|
|||||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
import { resolveBootstrapContextForRun, resolveBootstrapFilesForRun } from "./bootstrap-files.js";
|
import { resolveBootstrapContextForRun, resolveBootstrapFilesForRun } from "./bootstrap-files.js";
|
||||||
import { makeTempWorkspace } from "../test-helpers/workspace.js";
|
import { makeTempWorkspace, writeWorkspaceFile } from "../test-helpers/workspace.js";
|
||||||
import {
|
import {
|
||||||
clearInternalHooks,
|
clearInternalHooks,
|
||||||
registerInternalHook,
|
registerInternalHook,
|
||||||
type AgentBootstrapHookContext,
|
type AgentBootstrapHookContext,
|
||||||
} from "../hooks/internal-hooks.js";
|
} from "../hooks/internal-hooks.js";
|
||||||
|
import type { ClawdbotConfig } from "../config/config.js";
|
||||||
|
|
||||||
describe("resolveBootstrapFilesForRun", () => {
|
describe("resolveBootstrapFilesForRun", () => {
|
||||||
beforeEach(() => clearInternalHooks());
|
beforeEach(() => clearInternalHooks());
|
||||||
@ -60,3 +61,72 @@ describe("resolveBootstrapContextForRun", () => {
|
|||||||
expect(extra?.content).toBe("extra");
|
expect(extra?.content).toBe("extra");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("extraWorkspaceFiles per-agent config", () => {
|
||||||
|
beforeEach(() => clearInternalHooks());
|
||||||
|
afterEach(() => clearInternalHooks());
|
||||||
|
|
||||||
|
it("uses per-agent extraWorkspaceFiles over defaults", async () => {
|
||||||
|
const workspaceDir = await makeTempWorkspace("clawdbot-bootstrap-");
|
||||||
|
await writeWorkspaceFile({ dir: workspaceDir, name: "AGENT_SPECIFIC.md", content: "agent" });
|
||||||
|
await writeWorkspaceFile({ dir: workspaceDir, name: "DEFAULT.md", content: "default" });
|
||||||
|
|
||||||
|
const config: ClawdbotConfig = {
|
||||||
|
agents: {
|
||||||
|
defaults: { extraWorkspaceFiles: ["DEFAULT.md"] },
|
||||||
|
list: [{ id: "test-agent", extraWorkspaceFiles: ["AGENT_SPECIFIC.md"] }],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const files = await resolveBootstrapFilesForRun({
|
||||||
|
workspaceDir,
|
||||||
|
config,
|
||||||
|
agentId: "test-agent",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Per-agent config should be used, not defaults
|
||||||
|
expect(files.some((f) => f.name === "AGENT_SPECIFIC.md")).toBe(true);
|
||||||
|
expect(files.some((f) => f.name === "DEFAULT.md")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to defaults when agent has no extraWorkspaceFiles", async () => {
|
||||||
|
const workspaceDir = await makeTempWorkspace("clawdbot-bootstrap-");
|
||||||
|
await writeWorkspaceFile({ dir: workspaceDir, name: "DEFAULT.md", content: "default" });
|
||||||
|
|
||||||
|
const config: ClawdbotConfig = {
|
||||||
|
agents: {
|
||||||
|
defaults: { extraWorkspaceFiles: ["DEFAULT.md"] },
|
||||||
|
list: [{ id: "other-agent" }],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const files = await resolveBootstrapFilesForRun({
|
||||||
|
workspaceDir,
|
||||||
|
config,
|
||||||
|
agentId: "other-agent",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(files.some((f) => f.name === "DEFAULT.md")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows per-agent to disable extras with empty array", async () => {
|
||||||
|
const workspaceDir = await makeTempWorkspace("clawdbot-bootstrap-");
|
||||||
|
await writeWorkspaceFile({ dir: workspaceDir, name: "DEFAULT.md", content: "default" });
|
||||||
|
|
||||||
|
const config: ClawdbotConfig = {
|
||||||
|
agents: {
|
||||||
|
defaults: { extraWorkspaceFiles: ["DEFAULT.md"] },
|
||||||
|
list: [{ id: "no-extras", extraWorkspaceFiles: [] }],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const files = await resolveBootstrapFilesForRun({
|
||||||
|
workspaceDir,
|
||||||
|
config,
|
||||||
|
agentId: "no-extras",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Empty array should override defaults - no extra files
|
||||||
|
expect(files.some((f) => f.name === "DEFAULT.md")).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import type { MoltbotConfig } from "../config/config.js";
|
import type { MoltbotConfig } from "../config/config.js";
|
||||||
|
import { resolveAgentConfig } from "./agent-scope.js";
|
||||||
import { applyBootstrapHookOverrides } from "./bootstrap-hooks.js";
|
import { applyBootstrapHookOverrides } from "./bootstrap-hooks.js";
|
||||||
import {
|
import {
|
||||||
filterBootstrapFilesForSession,
|
filterBootstrapFilesForSession,
|
||||||
@ -24,7 +25,11 @@ export async function resolveBootstrapFilesForRun(params: {
|
|||||||
agentId?: string;
|
agentId?: string;
|
||||||
}): Promise<WorkspaceBootstrapFile[]> {
|
}): Promise<WorkspaceBootstrapFile[]> {
|
||||||
const sessionKey = params.sessionKey ?? params.sessionId;
|
const sessionKey = params.sessionKey ?? params.sessionId;
|
||||||
const extraFiles = params.config?.agents?.defaults?.extraWorkspaceFiles;
|
// Per-agent extraWorkspaceFiles takes precedence over global defaults
|
||||||
|
const agentConfig =
|
||||||
|
params.agentId && params.config ? resolveAgentConfig(params.config, params.agentId) : undefined;
|
||||||
|
const extraFiles =
|
||||||
|
agentConfig?.extraWorkspaceFiles ?? params.config?.agents?.defaults?.extraWorkspaceFiles;
|
||||||
const bootstrapFiles = filterBootstrapFilesForSession(
|
const bootstrapFiles = filterBootstrapFilesForSession(
|
||||||
await loadWorkspaceBootstrapFiles(params.workspaceDir, { extraFiles }),
|
await loadWorkspaceBootstrapFiles(params.workspaceDir, { extraFiles }),
|
||||||
sessionKey,
|
sessionKey,
|
||||||
|
|||||||
@ -124,6 +124,7 @@ const FIELD_LABELS: Record<string, string> = {
|
|||||||
"diagnostics.cacheTrace.includePrompt": "Cache Trace Include Prompt",
|
"diagnostics.cacheTrace.includePrompt": "Cache Trace Include Prompt",
|
||||||
"diagnostics.cacheTrace.includeSystem": "Cache Trace Include System",
|
"diagnostics.cacheTrace.includeSystem": "Cache Trace Include System",
|
||||||
"agents.list.*.identity.avatar": "Identity Avatar",
|
"agents.list.*.identity.avatar": "Identity Avatar",
|
||||||
|
"agents.list.*.extraWorkspaceFiles": "Extra Workspace Files",
|
||||||
"gateway.remote.url": "Remote Gateway URL",
|
"gateway.remote.url": "Remote Gateway URL",
|
||||||
"gateway.remote.sshTarget": "Remote Gateway SSH Target",
|
"gateway.remote.sshTarget": "Remote Gateway SSH Target",
|
||||||
"gateway.remote.sshIdentity": "Remote Gateway SSH Identity",
|
"gateway.remote.sshIdentity": "Remote Gateway SSH Identity",
|
||||||
@ -570,6 +571,8 @@ const FIELD_HELP: Record<string, string> = {
|
|||||||
"plugins.installs.*.installedAt": "ISO timestamp of last install/update.",
|
"plugins.installs.*.installedAt": "ISO timestamp of last install/update.",
|
||||||
"agents.list.*.identity.avatar":
|
"agents.list.*.identity.avatar":
|
||||||
"Agent avatar (workspace-relative path, http(s) URL, or data URI).",
|
"Agent avatar (workspace-relative path, http(s) URL, or data URI).",
|
||||||
|
"agents.list.*.extraWorkspaceFiles":
|
||||||
|
"Additional workspace files to auto-inject for this agent (overrides agents.defaults.extraWorkspaceFiles). Paths are relative to the workspace directory.",
|
||||||
"agents.defaults.model.primary": "Primary model (provider/model).",
|
"agents.defaults.model.primary": "Primary model (provider/model).",
|
||||||
"agents.defaults.model.fallbacks":
|
"agents.defaults.model.fallbacks":
|
||||||
"Ordered fallback models (provider/model). Used when the primary model fails.",
|
"Ordered fallback models (provider/model). Used when the primary model fails.",
|
||||||
|
|||||||
@ -24,6 +24,8 @@ export type AgentConfig = {
|
|||||||
workspace?: string;
|
workspace?: string;
|
||||||
agentDir?: string;
|
agentDir?: string;
|
||||||
model?: AgentModelConfig;
|
model?: AgentModelConfig;
|
||||||
|
/** Extra workspace files to inject alongside the default bootstrap files (paths relative to workspace). */
|
||||||
|
extraWorkspaceFiles?: string[];
|
||||||
memorySearch?: MemorySearchConfig;
|
memorySearch?: MemorySearchConfig;
|
||||||
/** Human-like delay between block replies for this agent. */
|
/** Human-like delay between block replies for this agent. */
|
||||||
humanDelay?: HumanDelayConfig;
|
humanDelay?: HumanDelayConfig;
|
||||||
|
|||||||
@ -421,6 +421,8 @@ export const AgentEntrySchema = z
|
|||||||
workspace: z.string().optional(),
|
workspace: z.string().optional(),
|
||||||
agentDir: z.string().optional(),
|
agentDir: z.string().optional(),
|
||||||
model: AgentModelSchema.optional(),
|
model: AgentModelSchema.optional(),
|
||||||
|
/** Extra workspace files to inject alongside the default bootstrap files (paths relative to workspace). */
|
||||||
|
extraWorkspaceFiles: z.array(z.string()).optional(),
|
||||||
memorySearch: MemorySearchSchema,
|
memorySearch: MemorySearchSchema,
|
||||||
humanDelay: HumanDelaySchema.optional(),
|
humanDelay: HumanDelaySchema.optional(),
|
||||||
heartbeat: HeartbeatSchema,
|
heartbeat: HeartbeatSchema,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user