chore: rename project to clawdbot

This commit is contained in:
Peter Steinberger
2026-01-04 14:32:47 +00:00
parent d48dc71fa4
commit 246adaa119
841 changed files with 4590 additions and 4328 deletions

View File

@@ -4,17 +4,17 @@ import { CONFIG_DIR, resolveUserPath } from "../utils.js";
const DEFAULT_AGENT_DIR = path.join(CONFIG_DIR, "agent");
export function resolveClawdisAgentDir(): string {
export function resolveClawdbotAgentDir(): string {
const override =
process.env.CLAWDIS_AGENT_DIR?.trim() ||
process.env.CLAWDBOT_AGENT_DIR?.trim() ||
process.env.PI_CODING_AGENT_DIR?.trim() ||
DEFAULT_AGENT_DIR;
return resolveUserPath(override);
}
export function ensureClawdisAgentEnv(): string {
const dir = resolveClawdisAgentDir();
if (!process.env.CLAWDIS_AGENT_DIR) process.env.CLAWDIS_AGENT_DIR = dir;
export function ensureClawdbotAgentEnv(): string {
const dir = resolveClawdbotAgentDir();
if (!process.env.CLAWDBOT_AGENT_DIR) process.env.CLAWDBOT_AGENT_DIR = dir;
if (!process.env.PI_CODING_AGENT_DIR) process.env.PI_CODING_AGENT_DIR = dir;
return dir;
}

View File

@@ -1,6 +1,6 @@
import { describe, expect, it, vi } from "vitest";
import { createClawdisTools } from "./clawdis-tools.js";
import { createClawdbotTools } from "./clawdbot-tools.js";
describe("gateway tool", () => {
it("schedules SIGUSR1 restart", async () => {
@@ -8,7 +8,7 @@ describe("gateway tool", () => {
const kill = vi.spyOn(process, "kill").mockImplementation(() => true);
try {
const tool = createClawdisTools().find(
const tool = createClawdbotTools().find(
(candidate) => candidate.name === "gateway",
);
expect(tool).toBeDefined();

View File

@@ -10,7 +10,7 @@ vi.mock("../media/image-ops.js", () => ({
resizeToJpeg: vi.fn(async () => Buffer.from("jpeg")),
}));
import { createClawdisTools } from "./clawdis-tools.js";
import { createClawdbotTools } from "./clawdbot-tools.js";
describe("nodes camera_snap", () => {
beforeEach(() => {
@@ -35,7 +35,7 @@ describe("nodes camera_snap", () => {
throw new Error(`unexpected method: ${String(method)}`);
});
const tool = createClawdisTools().find(
const tool = createClawdbotTools().find(
(candidate) => candidate.name === "nodes",
);
if (!tool) throw new Error("missing nodes tool");
@@ -75,7 +75,7 @@ describe("nodes camera_snap", () => {
throw new Error(`unexpected method: ${String(method)}`);
});
const tool = createClawdisTools().find(
const tool = createClawdbotTools().find(
(candidate) => candidate.name === "nodes",
);
if (!tool) throw new Error("missing nodes tool");

View File

@@ -16,7 +16,7 @@ vi.mock("../config/config.js", () => ({
resolveGatewayPort: () => 18789,
}));
import { createClawdisTools } from "./clawdis-tools.js";
import { createClawdbotTools } from "./clawdbot-tools.js";
describe("sessions tools", () => {
it("sessions_list filters kinds and includes messages", async () => {
@@ -67,7 +67,7 @@ describe("sessions tools", () => {
return {};
});
const tool = createClawdisTools().find(
const tool = createClawdbotTools().find(
(candidate) => candidate.name === "sessions_list",
);
expect(tool).toBeDefined();
@@ -106,7 +106,7 @@ describe("sessions tools", () => {
return {};
});
const tool = createClawdisTools().find(
const tool = createClawdbotTools().find(
(candidate) => candidate.name === "sessions_history",
);
expect(tool).toBeDefined();
@@ -190,7 +190,7 @@ describe("sessions tools", () => {
return {};
});
const tool = createClawdisTools({
const tool = createClawdbotTools({
agentSessionKey: requesterKey,
agentSurface: "discord",
}).find((candidate) => candidate.name === "sessions_send");
@@ -340,7 +340,7 @@ describe("sessions tools", () => {
return {};
});
const tool = createClawdisTools({
const tool = createClawdbotTools({
agentSessionKey: requesterKey,
agentSurface: "discord",
}).find((candidate) => candidate.name === "sessions_send");

View File

@@ -10,7 +10,7 @@ import { createSessionsListTool } from "./tools/sessions-list-tool.js";
import { createSessionsSendTool } from "./tools/sessions-send-tool.js";
import { createSlackTool } from "./tools/slack-tool.js";
export function createClawdisTools(options?: {
export function createClawdbotTools(options?: {
browserControlUrl?: string;
agentSessionKey?: string;
agentSurface?: string;

View File

@@ -2,8 +2,8 @@
// the agent reports a model id. This includes custom models.json entries.
import { loadConfig } from "../config/config.js";
import { resolveClawdisAgentDir } from "./agent-paths.js";
import { ensureClawdisModelsJson } from "./models-config.js";
import { resolveClawdbotAgentDir } from "./agent-paths.js";
import { ensureClawdbotModelsJson } from "./models-config.js";
type ModelEntry = { id: string; contextWindow?: number };
@@ -14,8 +14,8 @@ const loadPromise = (async () => {
"@mariozechner/pi-coding-agent"
);
const cfg = loadConfig();
await ensureClawdisModelsJson(cfg);
const agentDir = resolveClawdisAgentDir();
await ensureClawdbotModelsJson(cfg);
const agentDir = resolveClawdbotAgentDir();
const authStorage = discoverAuthStorage(agentDir);
const modelRegistry = discoverModels(authStorage, agentDir);
const models = modelRegistry.getAll() as ModelEntry[];

View File

@@ -1,6 +1,6 @@
import { type ClawdisConfig, loadConfig } from "../config/config.js";
import { resolveClawdisAgentDir } from "./agent-paths.js";
import { ensureClawdisModelsJson } from "./models-config.js";
import { type ClawdbotConfig, loadConfig } from "../config/config.js";
import { resolveClawdbotAgentDir } from "./agent-paths.js";
import { ensureClawdbotModelsJson } from "./models-config.js";
export type ModelCatalogEntry = {
id: string;
@@ -25,7 +25,7 @@ export function resetModelCatalogCacheForTest() {
}
export async function loadModelCatalog(params?: {
config?: ClawdisConfig;
config?: ClawdbotConfig;
useCache?: boolean;
}): Promise<ModelCatalogEntry[]> {
if (params?.useCache === false) {
@@ -39,8 +39,8 @@ export async function loadModelCatalog(params?: {
const models: ModelCatalogEntry[] = [];
try {
const cfg = params?.config ?? loadConfig();
await ensureClawdisModelsJson(cfg);
const agentDir = resolveClawdisAgentDir();
await ensureClawdbotModelsJson(cfg);
const agentDir = resolveClawdbotAgentDir();
const authStorage = piSdk.discoverAuthStorage(agentDir);
const registry = piSdk.discoverModels(authStorage, agentDir) as
| {

View File

@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import type { ClawdisConfig } from "../config/config.js";
import type { ClawdbotConfig } from "../config/config.js";
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "./defaults.js";
import { resolveConfiguredModelRef } from "./model-selection.js";
@@ -8,7 +8,7 @@ describe("resolveConfiguredModelRef", () => {
it("parses provider/model from agent.model", () => {
const cfg = {
agent: { model: "openai/gpt-4.1-mini" },
} satisfies ClawdisConfig;
} satisfies ClawdbotConfig;
const resolved = resolveConfiguredModelRef({
cfg,
@@ -22,7 +22,7 @@ describe("resolveConfiguredModelRef", () => {
it("falls back to anthropic when agent.model omits provider", () => {
const cfg = {
agent: { model: "claude-opus-4-5" },
} satisfies ClawdisConfig;
} satisfies ClawdbotConfig;
const resolved = resolveConfiguredModelRef({
cfg,
@@ -37,7 +37,7 @@ describe("resolveConfiguredModelRef", () => {
});
it("falls back to defaults when agent.model is missing", () => {
const cfg = {} satisfies ClawdisConfig;
const cfg = {} satisfies ClawdbotConfig;
const resolved = resolveConfiguredModelRef({
cfg,
@@ -59,7 +59,7 @@ describe("resolveConfiguredModelRef", () => {
Opus: "anthropic/claude-opus-4-5",
},
},
} satisfies ClawdisConfig;
} satisfies ClawdbotConfig;
const resolved = resolveConfiguredModelRef({
cfg,

View File

@@ -1,4 +1,4 @@
import type { ClawdisConfig } from "../config/config.js";
import type { ClawdbotConfig } from "../config/config.js";
import type { ModelCatalogEntry } from "./model-catalog.js";
export type ModelRef = {
@@ -38,7 +38,7 @@ export function parseModelRef(
}
export function buildModelAliasIndex(params: {
cfg: ClawdisConfig;
cfg: ClawdbotConfig;
defaultProvider: string;
}): ModelAliasIndex {
const rawAliases = params.cfg.agent?.modelAliases ?? {};
@@ -84,7 +84,7 @@ export function resolveModelRefFromString(params: {
}
export function resolveConfiguredModelRef(params: {
cfg: ClawdisConfig;
cfg: ClawdbotConfig;
defaultProvider: string;
defaultModel: string;
}): ModelRef {
@@ -108,7 +108,7 @@ export function resolveConfiguredModelRef(params: {
}
export function buildAllowedModelSet(params: {
cfg: ClawdisConfig;
cfg: ClawdbotConfig;
catalog: ModelCatalogEntry[];
defaultProvider: string;
}): {
@@ -156,7 +156,7 @@ export function buildAllowedModelSet(params: {
}
export function resolveThinkingDefault(params: {
cfg: ClawdisConfig;
cfg: ClawdbotConfig;
provider: string;
model: string;
catalog?: ModelCatalogEntry[];

View File

@@ -3,10 +3,10 @@ import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { ClawdisConfig } from "../config/config.js";
import type { ClawdbotConfig } from "../config/config.js";
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
const base = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-models-"));
const base = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-models-"));
const previousHome = process.env.HOME;
process.env.HOME = base;
try {
@@ -17,7 +17,7 @@ async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
}
}
const MODELS_CONFIG: ClawdisConfig = {
const MODELS_CONFIG: ClawdbotConfig = {
models: {
providers: {
"custom-proxy": {
@@ -55,12 +55,12 @@ describe("models config", () => {
it("writes models.json for configured providers", async () => {
await withTempHome(async () => {
vi.resetModules();
const { ensureClawdisModelsJson } = await import("./models-config.js");
const { resolveClawdisAgentDir } = await import("./agent-paths.js");
const { ensureClawdbotModelsJson } = await import("./models-config.js");
const { resolveClawdbotAgentDir } = await import("./agent-paths.js");
await ensureClawdisModelsJson(MODELS_CONFIG);
await ensureClawdbotModelsJson(MODELS_CONFIG);
const modelPath = path.join(resolveClawdisAgentDir(), "models.json");
const modelPath = path.join(resolveClawdbotAgentDir(), "models.json");
const raw = await fs.readFile(modelPath, "utf8");
const parsed = JSON.parse(raw) as {
providers: Record<string, { baseUrl?: string }>;
@@ -75,10 +75,10 @@ describe("models config", () => {
it("merges providers by default", async () => {
await withTempHome(async () => {
vi.resetModules();
const { ensureClawdisModelsJson } = await import("./models-config.js");
const { resolveClawdisAgentDir } = await import("./agent-paths.js");
const { ensureClawdbotModelsJson } = await import("./models-config.js");
const { resolveClawdbotAgentDir } = await import("./agent-paths.js");
const agentDir = resolveClawdisAgentDir();
const agentDir = resolveClawdbotAgentDir();
await fs.mkdir(agentDir, { recursive: true });
await fs.writeFile(
path.join(agentDir, "models.json"),
@@ -110,7 +110,7 @@ describe("models config", () => {
"utf8",
);
await ensureClawdisModelsJson(MODELS_CONFIG);
await ensureClawdbotModelsJson(MODELS_CONFIG);
const raw = await fs.readFile(path.join(agentDir, "models.json"), "utf8");
const parsed = JSON.parse(raw) as {

View File

@@ -1,13 +1,13 @@
import fs from "node:fs/promises";
import path from "node:path";
import { type ClawdisConfig, loadConfig } from "../config/config.js";
import { type ClawdbotConfig, loadConfig } from "../config/config.js";
import {
ensureClawdisAgentEnv,
resolveClawdisAgentDir,
ensureClawdbotAgentEnv,
resolveClawdbotAgentDir,
} from "./agent-paths.js";
type ModelsConfig = NonNullable<ClawdisConfig["models"]>;
type ModelsConfig = NonNullable<ClawdbotConfig["models"]>;
const DEFAULT_MODE: NonNullable<ModelsConfig["mode"]> = "merge";
@@ -24,17 +24,17 @@ async function readJson(pathname: string): Promise<unknown> {
}
}
export async function ensureClawdisModelsJson(
config?: ClawdisConfig,
export async function ensureClawdbotModelsJson(
config?: ClawdbotConfig,
): Promise<{ agentDir: string; wrote: boolean }> {
const cfg = config ?? loadConfig();
const providers = cfg.models?.providers;
if (!providers || Object.keys(providers).length === 0) {
return { agentDir: resolveClawdisAgentDir(), wrote: false };
return { agentDir: resolveClawdbotAgentDir(), wrote: false };
}
const mode = cfg.models?.mode ?? DEFAULT_MODE;
const agentDir = ensureClawdisAgentEnv();
const agentDir = ensureClawdbotAgentEnv();
const targetPath = path.join(agentDir, "models.json");
let mergedProviders = providers;

View File

@@ -12,12 +12,12 @@ describe("buildEmbeddedSandboxInfo", () => {
const sandbox = {
enabled: true,
sessionKey: "session:test",
workspaceDir: "/tmp/clawdis-sandbox",
containerName: "clawdis-sbx-test",
workspaceDir: "/tmp/clawdbot-sandbox",
containerName: "clawdbot-sbx-test",
containerWorkdir: "/workspace",
docker: {
image: "clawdis-sandbox:bookworm-slim",
containerPrefix: "clawdis-sbx-",
image: "clawdbot-sandbox:bookworm-slim",
containerPrefix: "clawdbot-sbx-",
workdir: "/workspace",
readOnlyRoot: true,
tmpfs: ["/tmp"],
@@ -33,13 +33,13 @@ describe("buildEmbeddedSandboxInfo", () => {
browser: {
controlUrl: "http://localhost:9222",
noVncUrl: "http://localhost:6080",
containerName: "clawdis-sbx-browser-test",
containerName: "clawdbot-sbx-browser-test",
},
} satisfies SandboxContext;
expect(buildEmbeddedSandboxInfo(sandbox)).toEqual({
enabled: true,
workspaceDir: "/tmp/clawdis-sandbox",
workspaceDir: "/tmp/clawdbot-sandbox",
browserControlUrl: "http://localhost:9222",
browserNoVncUrl: "http://localhost:6080",
});

View File

@@ -24,7 +24,7 @@ import {
} from "@mariozechner/pi-coding-agent";
import type { ThinkLevel, VerboseLevel } from "../auto-reply/thinking.js";
import { formatToolAggregate } from "../auto-reply/tool-meta.js";
import type { ClawdisConfig } from "../config/config.js";
import type { ClawdbotConfig } from "../config/config.js";
import { getMachineDisplayName } from "../infra/machine-name.js";
import { createSubsystemLogger } from "../logging.js";
import { splitMediaFromOutput } from "../media/parse.js";
@@ -33,10 +33,10 @@ import {
enqueueCommandInLane,
} from "../process/command-queue.js";
import { CONFIG_DIR, resolveUserPath } from "../utils.js";
import { resolveClawdisAgentDir } from "./agent-paths.js";
import { resolveClawdbotAgentDir } from "./agent-paths.js";
import type { BashElevatedDefaults } from "./bash-tools.js";
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "./defaults.js";
import { ensureClawdisModelsJson } from "./models-config.js";
import { ensureClawdbotModelsJson } from "./models-config.js";
import {
buildBootstrapContextFiles,
ensureSessionHeader,
@@ -48,7 +48,7 @@ import {
subscribeEmbeddedPiSession,
} from "./pi-embedded-subscribe.js";
import { extractAssistantText } from "./pi-embedded-utils.js";
import { createClawdisCodingTools } from "./pi-tools.js";
import { createClawdbotCodingTools } from "./pi-tools.js";
import { resolveSandboxContext } from "./sandbox.js";
import {
applySkillEnvOverrides,
@@ -139,9 +139,9 @@ export function buildEmbeddedSandboxInfo(
};
}
function resolveClawdisOAuthPath(): string {
function resolveClawdbotOAuthPath(): string {
const overrideDir =
process.env.CLAWDIS_OAUTH_DIR?.trim() || DEFAULT_OAUTH_DIR;
process.env.CLAWDBOT_OAUTH_DIR?.trim() || DEFAULT_OAUTH_DIR;
return path.join(resolveUserPath(overrideDir), OAUTH_FILENAME);
}
@@ -212,7 +212,7 @@ function importLegacyOAuthIfNeeded(destPath: string): void {
function ensureOAuthStorage(): void {
if (oauthStorageConfigured) return;
oauthStorageConfigured = true;
const oauthPath = resolveClawdisOAuthPath();
const oauthPath = resolveClawdbotOAuthPath();
importLegacyOAuthIfNeeded(oauthPath);
}
@@ -298,7 +298,7 @@ export function resolveEmbeddedSessionLane(key: string) {
}
function mapThinkingLevel(level?: ThinkLevel): ThinkingLevel {
// pi-agent-core supports "xhigh" too; Clawdis doesn't surface it for now.
// pi-agent-core supports "xhigh" too; Clawdbot doesn't surface it for now.
if (!level) return "off";
return level;
}
@@ -313,7 +313,7 @@ function resolveModel(
authStorage: ReturnType<typeof discoverAuthStorage>;
modelRegistry: ReturnType<typeof discoverModels>;
} {
const resolvedAgentDir = agentDir ?? resolveClawdisAgentDir();
const resolvedAgentDir = agentDir ?? resolveClawdbotAgentDir();
const authStorage = discoverAuthStorage(resolvedAgentDir);
const modelRegistry = discoverModels(authStorage, resolvedAgentDir);
const model = modelRegistry.find(provider, modelId) as Model<Api> | null;
@@ -341,7 +341,7 @@ async function getApiKeyForModel(
const envKey = getEnvApiKey(model.provider);
if (envKey) return envKey;
if (isOAuthProvider(model.provider)) {
const oauthPath = resolveClawdisOAuthPath();
const oauthPath = resolveClawdbotOAuthPath();
const storage = loadOAuthStorageAt(oauthPath);
if (storage) {
try {
@@ -384,7 +384,7 @@ export async function runEmbeddedPiAgent(params: {
surface?: string;
sessionFile: string;
workspaceDir: string;
config?: ClawdisConfig;
config?: ClawdbotConfig;
skillsSnapshot?: SkillSnapshot;
prompt: string;
provider?: string;
@@ -436,8 +436,8 @@ export async function runEmbeddedPiAgent(params: {
const provider =
(params.provider ?? DEFAULT_PROVIDER).trim() || DEFAULT_PROVIDER;
const modelId = (params.model ?? DEFAULT_MODEL).trim() || DEFAULT_MODEL;
await ensureClawdisModelsJson(params.config);
const agentDir = resolveClawdisAgentDir();
await ensureClawdbotModelsJson(params.config);
const agentDir = resolveClawdbotAgentDir();
const { model, error, authStorage, modelRegistry } = resolveModel(
provider,
modelId,
@@ -496,7 +496,7 @@ export async function runEmbeddedPiAgent(params: {
await loadWorkspaceBootstrapFiles(resolvedWorkspace);
const contextFiles = buildBootstrapContextFiles(bootstrapFiles);
const promptSkills = resolvePromptSkills(skillsSnapshot, skillEntries);
const tools = createClawdisCodingTools({
const tools = createClawdbotCodingTools({
bash: {
...params.config?.agent?.bash,
elevated: params.bashElevated,

View File

@@ -4,11 +4,11 @@ import path from "node:path";
import sharp from "sharp";
import { describe, expect, it } from "vitest";
import { createClawdisCodingTools } from "./pi-tools.js";
import { createClawdbotCodingTools } from "./pi-tools.js";
describe("createClawdisCodingTools", () => {
describe("createClawdbotCodingTools", () => {
it("merges properties for union tool schemas", () => {
const tools = createClawdisCodingTools();
const tools = createClawdbotCodingTools();
const browser = tools.find((tool) => tool.name === "browser");
expect(browser).toBeDefined();
const parameters = browser?.parameters as {
@@ -24,7 +24,7 @@ describe("createClawdisCodingTools", () => {
});
it("preserves union action values in merged schema", () => {
const tools = createClawdisCodingTools();
const tools = createClawdbotCodingTools();
const toolNames = ["browser", "canvas", "nodes", "cron", "gateway"];
for (const name of toolNames) {
@@ -75,33 +75,33 @@ describe("createClawdisCodingTools", () => {
});
it("includes bash and process tools", () => {
const tools = createClawdisCodingTools();
const tools = createClawdbotCodingTools();
expect(tools.some((tool) => tool.name === "bash")).toBe(true);
expect(tools.some((tool) => tool.name === "process")).toBe(true);
});
it("scopes discord tool to discord surface", () => {
const other = createClawdisCodingTools({ surface: "whatsapp" });
const other = createClawdbotCodingTools({ surface: "whatsapp" });
expect(other.some((tool) => tool.name === "discord")).toBe(false);
const discord = createClawdisCodingTools({ surface: "discord" });
const discord = createClawdbotCodingTools({ surface: "discord" });
expect(discord.some((tool) => tool.name === "discord")).toBe(true);
});
it("scopes slack tool to slack surface", () => {
const other = createClawdisCodingTools({ surface: "whatsapp" });
const other = createClawdbotCodingTools({ surface: "whatsapp" });
expect(other.some((tool) => tool.name === "slack")).toBe(false);
const slack = createClawdisCodingTools({ surface: "slack" });
const slack = createClawdbotCodingTools({ surface: "slack" });
expect(slack.some((tool) => tool.name === "slack")).toBe(true);
});
it("keeps read tool image metadata intact", async () => {
const tools = createClawdisCodingTools();
const tools = createClawdbotCodingTools();
const readTool = tools.find((tool) => tool.name === "read");
expect(readTool).toBeDefined();
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-read-"));
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-read-"));
try {
const imagePath = path.join(tmpDir, "sample.png");
const png = await sharp({
@@ -137,14 +137,14 @@ describe("createClawdisCodingTools", () => {
});
it("returns text content without image blocks for text files", async () => {
const tools = createClawdisCodingTools();
const tools = createClawdbotCodingTools();
const readTool = tools.find((tool) => tool.name === "read");
expect(readTool).toBeDefined();
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-read-"));
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-read-"));
try {
const textPath = path.join(tmpDir, "sample.txt");
const contents = "Hello from clawdis read tool.";
const contents = "Hello from clawdbot read tool.";
await fs.writeFile(textPath, contents, "utf8");
const result = await readTool?.execute("tool-2", {
@@ -171,12 +171,12 @@ describe("createClawdisCodingTools", () => {
const sandbox = {
enabled: true,
sessionKey: "sandbox:test",
workspaceDir: path.join(os.tmpdir(), "clawdis-sandbox"),
containerName: "clawdis-sbx-test",
workspaceDir: path.join(os.tmpdir(), "clawdbot-sandbox"),
containerName: "clawdbot-sbx-test",
containerWorkdir: "/workspace",
docker: {
image: "clawdis-sandbox:bookworm-slim",
containerPrefix: "clawdis-sbx-",
image: "clawdbot-sandbox:bookworm-slim",
containerPrefix: "clawdbot-sbx-",
workdir: "/workspace",
readOnlyRoot: true,
tmpfs: [],
@@ -190,7 +190,7 @@ describe("createClawdisCodingTools", () => {
deny: ["browser"],
},
};
const tools = createClawdisCodingTools({ sandbox });
const tools = createClawdbotCodingTools({ sandbox });
expect(tools.some((tool) => tool.name === "bash")).toBe(true);
expect(tools.some((tool) => tool.name === "read")).toBe(false);
expect(tools.some((tool) => tool.name === "browser")).toBe(false);

View File

@@ -16,7 +16,7 @@ import {
createProcessTool,
type ProcessToolDefaults,
} from "./bash-tools.js";
import { createClawdisTools } from "./clawdis-tools.js";
import { createClawdbotTools } from "./clawdbot-tools.js";
import type { SandboxContext, SandboxToolPolicy } from "./sandbox.js";
import { assertSandboxPath } from "./sandbox-paths.js";
import { sanitizeToolResultImages } from "./tool-images.js";
@@ -332,7 +332,7 @@ function wrapSandboxPathGuard(tool: AnyAgentTool, root: string): AnyAgentTool {
function createSandboxedReadTool(root: string) {
const base = createReadTool(root);
return wrapSandboxPathGuard(createClawdisReadTool(base), root);
return wrapSandboxPathGuard(createClawdbotReadTool(base), root);
}
function createSandboxedWriteTool(root: string) {
@@ -409,7 +409,7 @@ function createWhatsAppLoginTool(): AnyAgentTool {
};
}
function createClawdisReadTool(base: AnyAgentTool): AnyAgentTool {
function createClawdbotReadTool(base: AnyAgentTool): AnyAgentTool {
return {
...base,
execute: async (toolCallId, params, signal) => {
@@ -447,7 +447,7 @@ function shouldIncludeSlackTool(surface?: string): boolean {
return normalized === "slack" || normalized.startsWith("slack:");
}
export function createClawdisCodingTools(options?: {
export function createClawdbotCodingTools(options?: {
bash?: BashToolDefaults & ProcessToolDefaults;
surface?: string;
sandbox?: SandboxContext | null;
@@ -460,7 +460,7 @@ export function createClawdisCodingTools(options?: {
if (tool.name === readTool.name) {
return sandboxRoot
? [createSandboxedReadTool(sandboxRoot)]
: [createClawdisReadTool(tool)];
: [createClawdbotReadTool(tool)];
}
if (tool.name === bashToolName) return [];
if (sandboxRoot && (tool.name === "write" || tool.name === "edit")) {
@@ -493,7 +493,7 @@ export function createClawdisCodingTools(options?: {
bashTool as unknown as AnyAgentTool,
processTool as unknown as AnyAgentTool,
createWhatsAppLoginTool(),
...createClawdisTools({
...createClawdbotTools({
browserControlUrl: sandbox?.browser?.controlUrl,
agentSessionKey: options?.sessionKey,
agentSurface: options?.surface,

View File

@@ -0,0 +1,97 @@
import { describe, expect, it } from "vitest";
import {
buildSandboxCreateArgs,
type SandboxDockerConfig,
} from "./sandbox.js";
describe("buildSandboxCreateArgs", () => {
it("includes hardening and resource flags", () => {
const cfg: SandboxDockerConfig = {
image: "clawdbot-sandbox:bookworm-slim",
containerPrefix: "clawdbot-sbx-",
workdir: "/workspace",
readOnlyRoot: true,
tmpfs: ["/tmp"],
network: "none",
user: "1000:1000",
capDrop: ["ALL"],
env: { LANG: "C.UTF-8" },
pidsLimit: 256,
memory: "512m",
memorySwap: 1024,
cpus: 1.5,
ulimits: {
nofile: { soft: 1024, hard: 2048 },
nproc: 128,
core: "0",
},
seccompProfile: "/tmp/seccomp.json",
apparmorProfile: "clawdbot-sandbox",
dns: ["1.1.1.1"],
extraHosts: ["internal.service:10.0.0.5"],
};
const args = buildSandboxCreateArgs({
name: "clawdbot-sbx-test",
cfg,
sessionKey: "main",
createdAtMs: 1700000000000,
labels: { "clawdbot.sandboxBrowser": "1" },
});
expect(args).toEqual(
expect.arrayContaining([
"create",
"--name",
"clawdbot-sbx-test",
"--label",
"clawdbot.sandbox=1",
"--label",
"clawdbot.sessionKey=main",
"--label",
"clawdbot.createdAtMs=1700000000000",
"--label",
"clawdbot.sandboxBrowser=1",
"--read-only",
"--tmpfs",
"/tmp",
"--network",
"none",
"--user",
"1000:1000",
"--cap-drop",
"ALL",
"--security-opt",
"no-new-privileges",
"--security-opt",
"seccomp=/tmp/seccomp.json",
"--security-opt",
"apparmor=clawdbot-sandbox",
"--dns",
"1.1.1.1",
"--add-host",
"internal.service:10.0.0.5",
"--pids-limit",
"256",
"--memory",
"512m",
"--memory-swap",
"1024",
"--cpus",
"1.5",
]),
);
const ulimitValues: string[] = [];
for (let i = 0; i < args.length; i += 1) {
if (args[i] === "--ulimit") {
const value = args[i + 1];
if (value) ulimitValues.push(value);
}
}
expect(ulimitValues).toEqual(
expect.arrayContaining(["nofile=1024:2048", "nproc=128", "core=0"]),
);
});
});

View File

@@ -14,8 +14,8 @@ import {
resolveProfile,
} from "../browser/config.js";
import { DEFAULT_CLAWD_BROWSER_COLOR } from "../browser/constants.js";
import type { ClawdisConfig } from "../config/config.js";
import { STATE_DIR_CLAWDIS } from "../config/config.js";
import type { ClawdbotConfig } from "../config/config.js";
import { STATE_DIR_CLAWDBOT } from "../config/config.js";
import { defaultRuntime } from "../runtime.js";
import { resolveUserPath } from "../utils.js";
import {
@@ -56,6 +56,18 @@ export type SandboxDockerConfig = {
capDrop: string[];
env?: Record<string, string>;
setupCommand?: string;
pidsLimit?: number;
memory?: string | number;
memorySwap?: string | number;
cpus?: number;
ulimits?: Record<
string,
string | number | { soft?: number; hard?: number }
>;
seccompProfile?: string;
apparmorProfile?: string;
dns?: string[];
extraHosts?: string[];
};
export type SandboxPruneConfig = {
@@ -92,11 +104,11 @@ export type SandboxContext = {
const DEFAULT_SANDBOX_WORKSPACE_ROOT = path.join(
os.homedir(),
".clawdis",
".clawdbot",
"sandboxes",
);
const DEFAULT_SANDBOX_IMAGE = "clawdis-sandbox:bookworm-slim";
const DEFAULT_SANDBOX_CONTAINER_PREFIX = "clawdis-sbx-";
const DEFAULT_SANDBOX_IMAGE = "clawdbot-sandbox:bookworm-slim";
const DEFAULT_SANDBOX_CONTAINER_PREFIX = "clawdbot-sbx-";
const DEFAULT_SANDBOX_WORKDIR = "/workspace";
const DEFAULT_SANDBOX_IDLE_HOURS = 24;
const DEFAULT_SANDBOX_MAX_AGE_DAYS = 7;
@@ -109,13 +121,13 @@ const DEFAULT_TOOL_DENY = [
"discord",
"gateway",
];
const DEFAULT_SANDBOX_BROWSER_IMAGE = "clawdis-sandbox-browser:bookworm-slim";
const DEFAULT_SANDBOX_BROWSER_PREFIX = "clawdis-sbx-browser-";
const DEFAULT_SANDBOX_BROWSER_IMAGE = "clawdbot-sandbox-browser:bookworm-slim";
const DEFAULT_SANDBOX_BROWSER_PREFIX = "clawdbot-sbx-browser-";
const DEFAULT_SANDBOX_BROWSER_CDP_PORT = 9222;
const DEFAULT_SANDBOX_BROWSER_VNC_PORT = 5900;
const DEFAULT_SANDBOX_BROWSER_NOVNC_PORT = 6080;
const SANDBOX_STATE_DIR = path.join(STATE_DIR_CLAWDIS, "sandbox");
const SANDBOX_STATE_DIR = path.join(STATE_DIR_CLAWDBOT, "sandbox");
const SANDBOX_REGISTRY_PATH = path.join(SANDBOX_STATE_DIR, "containers.json");
const SANDBOX_BROWSER_REGISTRY_PATH = path.join(
SANDBOX_STATE_DIR,
@@ -170,7 +182,7 @@ function isToolAllowed(policy: SandboxToolPolicy, name: string) {
return allow.includes(name.toLowerCase());
}
function defaultSandboxConfig(cfg?: ClawdisConfig): SandboxConfig {
function defaultSandboxConfig(cfg?: ClawdbotConfig): SandboxConfig {
const agent = cfg?.agent?.sandbox;
return {
mode: agent?.mode ?? "off",
@@ -183,11 +195,20 @@ function defaultSandboxConfig(cfg?: ClawdisConfig): SandboxConfig {
workdir: agent?.docker?.workdir ?? DEFAULT_SANDBOX_WORKDIR,
readOnlyRoot: agent?.docker?.readOnlyRoot ?? true,
tmpfs: agent?.docker?.tmpfs ?? ["/tmp", "/var/tmp", "/run"],
network: agent?.docker?.network ?? "bridge",
network: agent?.docker?.network ?? "none",
user: agent?.docker?.user,
capDrop: agent?.docker?.capDrop ?? ["ALL"],
env: agent?.docker?.env ?? { LANG: "C.UTF-8" },
setupCommand: agent?.docker?.setupCommand,
pidsLimit: agent?.docker?.pidsLimit,
memory: agent?.docker?.memory,
memorySwap: agent?.docker?.memorySwap,
cpus: agent?.docker?.cpus,
ulimits: agent?.docker?.ulimits,
seccompProfile: agent?.docker?.seccompProfile,
apparmorProfile: agent?.docker?.apparmorProfile,
dns: agent?.docker?.dns,
extraHosts: agent?.docker?.extraHosts,
},
browser: {
enabled: agent?.browser?.enabled ?? false,
@@ -428,6 +449,88 @@ async function ensureSandboxWorkspace(workspaceDir: string, seedFrom?: string) {
await ensureAgentWorkspace({ dir: workspaceDir, ensureBootstrapFiles: true });
}
function normalizeDockerLimit(value?: string | number) {
if (value === undefined || value === null) return undefined;
if (typeof value === "number") {
return Number.isFinite(value) ? String(value) : undefined;
}
const trimmed = value.trim();
return trimmed ? trimmed : undefined;
}
function formatUlimitValue(
name: string,
value: string | number | { soft?: number; hard?: number },
) {
if (!name.trim()) return null;
if (typeof value === "number" || typeof value === "string") {
const raw = String(value).trim();
return raw ? `${name}=${raw}` : null;
}
const soft =
typeof value.soft === "number" ? Math.max(0, value.soft) : undefined;
const hard =
typeof value.hard === "number" ? Math.max(0, value.hard) : undefined;
if (soft === undefined && hard === undefined) return null;
if (soft === undefined) return `${name}=${hard}`;
if (hard === undefined) return `${name}=${soft}`;
return `${name}=${soft}:${hard}`;
}
export function buildSandboxCreateArgs(params: {
name: string;
cfg: SandboxDockerConfig;
sessionKey: string;
createdAtMs?: number;
labels?: Record<string, string>;
}) {
const createdAtMs = params.createdAtMs ?? Date.now();
const args = ["create", "--name", params.name];
args.push("--label", "clawdbot.sandbox=1");
args.push("--label", `clawdbot.sessionKey=${params.sessionKey}`);
args.push("--label", `clawdbot.createdAtMs=${createdAtMs}`);
for (const [key, value] of Object.entries(params.labels ?? {})) {
if (key && value) args.push("--label", `${key}=${value}`);
}
if (params.cfg.readOnlyRoot) args.push("--read-only");
for (const entry of params.cfg.tmpfs) {
args.push("--tmpfs", entry);
}
if (params.cfg.network) args.push("--network", params.cfg.network);
if (params.cfg.user) args.push("--user", params.cfg.user);
for (const cap of params.cfg.capDrop) {
args.push("--cap-drop", cap);
}
args.push("--security-opt", "no-new-privileges");
if (params.cfg.seccompProfile) {
args.push("--security-opt", `seccomp=${params.cfg.seccompProfile}`);
}
if (params.cfg.apparmorProfile) {
args.push("--security-opt", `apparmor=${params.cfg.apparmorProfile}`);
}
for (const entry of params.cfg.dns ?? []) {
if (entry.trim()) args.push("--dns", entry);
}
for (const entry of params.cfg.extraHosts ?? []) {
if (entry.trim()) args.push("--add-host", entry);
}
if (typeof params.cfg.pidsLimit === "number" && params.cfg.pidsLimit > 0) {
args.push("--pids-limit", String(params.cfg.pidsLimit));
}
const memory = normalizeDockerLimit(params.cfg.memory);
if (memory) args.push("--memory", memory);
const memorySwap = normalizeDockerLimit(params.cfg.memorySwap);
if (memorySwap) args.push("--memory-swap", memorySwap);
if (typeof params.cfg.cpus === "number" && params.cfg.cpus > 0) {
args.push("--cpus", String(params.cfg.cpus));
}
for (const [name, value] of Object.entries(params.cfg.ulimits ?? {})) {
const formatted = formatUlimitValue(name, value);
if (formatted) args.push("--ulimit", formatted);
}
return args;
}
async function createSandboxContainer(params: {
name: string;
cfg: SandboxDockerConfig;
@@ -437,20 +540,11 @@ async function createSandboxContainer(params: {
const { name, cfg, workspaceDir, sessionKey } = params;
await ensureDockerImage(cfg.image);
const args = ["create", "--name", name];
args.push("--label", "clawdis.sandbox=1");
args.push("--label", `clawdis.sessionKey=${sessionKey}`);
args.push("--label", `clawdis.createdAtMs=${Date.now()}`);
if (cfg.readOnlyRoot) args.push("--read-only");
for (const entry of cfg.tmpfs) {
args.push("--tmpfs", entry);
}
if (cfg.network) args.push("--network", cfg.network);
if (cfg.user) args.push("--user", cfg.user);
for (const cap of cfg.capDrop) {
args.push("--cap-drop", cap);
}
args.push("--security-opt", "no-new-privileges");
const args = buildSandboxCreateArgs({
name,
cfg,
sessionKey,
});
args.push("--workdir", cfg.workdir);
args.push("-v", `${workspaceDir}:${cfg.workdir}`);
args.push(cfg.image, "sleep", "infinity");
@@ -547,22 +641,12 @@ async function ensureSandboxBrowser(params: {
const state = await dockerContainerState(containerName);
if (!state.exists) {
await ensureSandboxBrowserImage(params.cfg.browser.image);
const args = ["create", "--name", containerName];
args.push("--label", "clawdis.sandbox=1");
args.push("--label", "clawdis.sandboxBrowser=1");
args.push("--label", `clawdis.sessionKey=${params.sessionKey}`);
args.push("--label", `clawdis.createdAtMs=${Date.now()}`);
if (params.cfg.docker.readOnlyRoot) args.push("--read-only");
for (const entry of params.cfg.docker.tmpfs) {
args.push("--tmpfs", entry);
}
if (params.cfg.docker.network)
args.push("--network", params.cfg.docker.network);
if (params.cfg.docker.user) args.push("--user", params.cfg.docker.user);
for (const cap of params.cfg.docker.capDrop) {
args.push("--cap-drop", cap);
}
args.push("--security-opt", "no-new-privileges");
const args = buildSandboxCreateArgs({
name: containerName,
cfg: params.cfg.docker,
sessionKey: params.sessionKey,
labels: { "clawdbot.sandboxBrowser": "1" },
});
args.push("-v", `${params.workspaceDir}:${params.cfg.docker.workdir}`);
args.push("-p", `127.0.0.1::${params.cfg.browser.cdpPort}`);
if (params.cfg.browser.enableNoVnc && !params.cfg.browser.headless) {
@@ -570,19 +654,19 @@ async function ensureSandboxBrowser(params: {
}
args.push(
"-e",
`CLAWDIS_BROWSER_HEADLESS=${params.cfg.browser.headless ? "1" : "0"}`,
`CLAWDBOT_BROWSER_HEADLESS=${params.cfg.browser.headless ? "1" : "0"}`,
);
args.push(
"-e",
`CLAWDIS_BROWSER_ENABLE_NOVNC=${
`CLAWDBOT_BROWSER_ENABLE_NOVNC=${
params.cfg.browser.enableNoVnc ? "1" : "0"
}`,
);
args.push("-e", `CLAWDIS_BROWSER_CDP_PORT=${params.cfg.browser.cdpPort}`);
args.push("-e", `CLAWDIS_BROWSER_VNC_PORT=${params.cfg.browser.vncPort}`);
args.push("-e", `CLAWDBOT_BROWSER_CDP_PORT=${params.cfg.browser.cdpPort}`);
args.push("-e", `CLAWDBOT_BROWSER_VNC_PORT=${params.cfg.browser.vncPort}`);
args.push(
"-e",
`CLAWDIS_BROWSER_NOVNC_PORT=${params.cfg.browser.noVncPort}`,
`CLAWDBOT_BROWSER_NOVNC_PORT=${params.cfg.browser.noVncPort}`,
);
args.push(params.cfg.browser.image);
await execDocker(args);
@@ -739,7 +823,7 @@ async function maybePruneSandboxes(cfg: SandboxConfig) {
}
export async function resolveSandboxContext(params: {
config?: ClawdisConfig;
config?: ClawdbotConfig;
sessionKey?: string;
workspaceDir?: string;
}): Promise<SandboxContext | null> {

View File

@@ -1,7 +1,7 @@
import fs from "node:fs";
import path from "node:path";
import type { ClawdisConfig } from "../config/config.js";
import type { ClawdbotConfig } from "../config/config.js";
import { runCommandWithTimeout } from "../process/exec.js";
import { resolveUserPath } from "../utils.js";
import {
@@ -18,7 +18,7 @@ export type SkillInstallRequest = {
skillName: string;
installId: string;
timeoutMs?: number;
config?: ClawdisConfig;
config?: ClawdbotConfig;
};
export type SkillInstallResult = {
@@ -73,7 +73,7 @@ function findInstallSpec(
entry: SkillEntry,
installId: string,
): SkillInstallSpec | undefined {
const specs = entry.clawdis?.install ?? [];
const specs = entry.clawdbot?.install ?? [];
for (const [index, spec] of specs.entries()) {
if (resolveInstallId(spec, index) === installId) return spec;
}

View File

@@ -1,6 +1,6 @@
import path from "node:path";
import type { ClawdisConfig } from "../config/config.js";
import type { ClawdbotConfig } from "../config/config.js";
import { CONFIG_DIR } from "../utils.js";
import {
hasBinary,
@@ -66,7 +66,7 @@ export type SkillStatusReport = {
};
function resolveSkillKey(entry: SkillEntry): string {
return entry.clawdis?.skillKey ?? entry.skill.name;
return entry.clawdbot?.skillKey ?? entry.skill.name;
}
function selectPreferredInstallSpec(
@@ -95,7 +95,7 @@ function normalizeInstallOptions(
entry: SkillEntry,
prefs: SkillsInstallPreferences,
): SkillInstallOption[] {
const install = entry.clawdis?.install ?? [];
const install = entry.clawdbot?.install ?? [];
if (install.length === 0) return [];
const preferred = selectPreferredInstallSpec(install, prefs);
if (!preferred) return [];
@@ -131,7 +131,7 @@ function normalizeInstallOptions(
function buildSkillStatus(
entry: SkillEntry,
config?: ClawdisConfig,
config?: ClawdbotConfig,
prefs?: SkillsInstallPreferences,
): SkillStatusEntry {
const skillKey = resolveSkillKey(entry);
@@ -139,19 +139,19 @@ function buildSkillStatus(
const disabled = skillConfig?.enabled === false;
const allowBundled = resolveBundledAllowlist(config);
const blockedByAllowlist = !isBundledSkillAllowed(entry, allowBundled);
const always = entry.clawdis?.always === true;
const emoji = entry.clawdis?.emoji ?? entry.frontmatter.emoji;
const always = entry.clawdbot?.always === true;
const emoji = entry.clawdbot?.emoji ?? entry.frontmatter.emoji;
const homepageRaw =
entry.clawdis?.homepage ??
entry.clawdbot?.homepage ??
entry.frontmatter.homepage ??
entry.frontmatter.website ??
entry.frontmatter.url;
const homepage = homepageRaw?.trim() ? homepageRaw.trim() : undefined;
const requiredBins = entry.clawdis?.requires?.bins ?? [];
const requiredEnv = entry.clawdis?.requires?.env ?? [];
const requiredConfig = entry.clawdis?.requires?.config ?? [];
const requiredOs = entry.clawdis?.os ?? [];
const requiredBins = entry.clawdbot?.requires?.bins ?? [];
const requiredEnv = entry.clawdbot?.requires?.env ?? [];
const requiredConfig = entry.clawdbot?.requires?.config ?? [];
const requiredOs = entry.clawdbot?.os ?? [];
const missingBins = requiredBins.filter((bin) => !hasBinary(bin));
const missingOs =
@@ -163,7 +163,7 @@ function buildSkillStatus(
for (const envName of requiredEnv) {
if (process.env[envName]) continue;
if (skillConfig?.env?.[envName]) continue;
if (skillConfig?.apiKey && entry.clawdis?.primaryEnv === envName) {
if (skillConfig?.apiKey && entry.clawdbot?.primaryEnv === envName) {
continue;
}
missingEnv.push(envName);
@@ -204,7 +204,7 @@ function buildSkillStatus(
filePath: entry.skill.filePath,
baseDir: entry.skill.baseDir,
skillKey,
primaryEnv: entry.clawdis?.primaryEnv,
primaryEnv: entry.clawdbot?.primaryEnv,
emoji,
homepage,
always,
@@ -229,7 +229,7 @@ function buildSkillStatus(
export function buildWorkspaceSkillStatus(
workspaceDir: string,
opts?: {
config?: ClawdisConfig;
config?: ClawdbotConfig;
managedSkillsDir?: string;
entries?: SkillEntry[];
},

View File

@@ -37,7 +37,7 @@ ${body ?? `# ${name}\n`}
describe("buildWorkspaceSkillsPrompt", () => {
it("returns empty prompt when skills dirs are missing", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const prompt = buildWorkspaceSkillsPrompt(workspaceDir, {
managedSkillsDir: path.join(workspaceDir, ".managed"),
@@ -48,7 +48,7 @@ describe("buildWorkspaceSkillsPrompt", () => {
});
it("loads bundled skills when present", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const bundledDir = path.join(workspaceDir, ".bundled");
const bundledSkillDir = path.join(bundledDir, "peekaboo");
@@ -69,7 +69,7 @@ describe("buildWorkspaceSkillsPrompt", () => {
});
it("loads extra skill folders from config (lowest precedence)", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const extraDir = path.join(workspaceDir, ".extra");
const bundledDir = path.join(workspaceDir, ".bundled");
const managedDir = path.join(workspaceDir, ".managed");
@@ -112,7 +112,7 @@ describe("buildWorkspaceSkillsPrompt", () => {
});
it("loads skills from workspace skills/", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const skillDir = path.join(workspaceDir, "skills", "demo-skill");
await writeSkill({
@@ -131,7 +131,7 @@ describe("buildWorkspaceSkillsPrompt", () => {
});
it("filters skills based on env/config gates", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const skillDir = path.join(workspaceDir, "skills", "nano-banana-pro");
const originalEnv = process.env.GEMINI_API_KEY;
delete process.env.GEMINI_API_KEY;
@@ -142,7 +142,7 @@ describe("buildWorkspaceSkillsPrompt", () => {
name: "nano-banana-pro",
description: "Generates images",
metadata:
'{"clawdis":{"requires":{"env":["GEMINI_API_KEY"]},"primaryEnv":"GEMINI_API_KEY"}}',
'{"clawdbot":{"requires":{"env":["GEMINI_API_KEY"]},"primaryEnv":"GEMINI_API_KEY"}}',
body: "# Nano Banana\n",
});
@@ -166,7 +166,7 @@ describe("buildWorkspaceSkillsPrompt", () => {
});
it("prefers workspace skills over managed skills", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const managedDir = path.join(workspaceDir, ".managed");
const bundledDir = path.join(workspaceDir, ".bundled");
const managedSkillDir = path.join(managedDir, "demo-skill");
@@ -204,7 +204,7 @@ describe("buildWorkspaceSkillsPrompt", () => {
});
it("gates by bins, config, and always", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const skillsDir = path.join(workspaceDir, "skills");
const binDir = path.join(workspaceDir, "bin");
const originalPath = process.env.PATH;
@@ -213,32 +213,32 @@ describe("buildWorkspaceSkillsPrompt", () => {
dir: path.join(skillsDir, "bin-skill"),
name: "bin-skill",
description: "Needs a bin",
metadata: '{"clawdis":{"requires":{"bins":["fakebin"]}}}',
metadata: '{"clawdbot":{"requires":{"bins":["fakebin"]}}}',
});
await writeSkill({
dir: path.join(skillsDir, "anybin-skill"),
name: "anybin-skill",
description: "Needs any bin",
metadata: '{"clawdis":{"requires":{"anyBins":["missingbin","fakebin"]}}}',
metadata: '{"clawdbot":{"requires":{"anyBins":["missingbin","fakebin"]}}}',
});
await writeSkill({
dir: path.join(skillsDir, "config-skill"),
name: "config-skill",
description: "Needs config",
metadata: '{"clawdis":{"requires":{"config":["browser.enabled"]}}}',
metadata: '{"clawdbot":{"requires":{"config":["browser.enabled"]}}}',
});
await writeSkill({
dir: path.join(skillsDir, "always-skill"),
name: "always-skill",
description: "Always on",
metadata: '{"clawdis":{"always":true,"requires":{"env":["MISSING"]}}}',
metadata: '{"clawdbot":{"always":true,"requires":{"env":["MISSING"]}}}',
});
await writeSkill({
dir: path.join(skillsDir, "env-skill"),
name: "env-skill",
description: "Needs env",
metadata:
'{"clawdis":{"requires":{"env":["ENV_KEY"]},"primaryEnv":"ENV_KEY"}}',
'{"clawdbot":{"requires":{"env":["ENV_KEY"]},"primaryEnv":"ENV_KEY"}}',
});
try {
@@ -275,13 +275,13 @@ describe("buildWorkspaceSkillsPrompt", () => {
});
it("uses skillKey for config lookups", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const skillDir = path.join(workspaceDir, "skills", "alias-skill");
await writeSkill({
dir: skillDir,
name: "alias-skill",
description: "Uses skillKey",
metadata: '{"clawdis":{"skillKey":"alias"}}',
metadata: '{"clawdbot":{"skillKey":"alias"}}',
});
const prompt = buildWorkspaceSkillsPrompt(workspaceDir, {
@@ -292,7 +292,7 @@ describe("buildWorkspaceSkillsPrompt", () => {
});
it("applies bundled allowlist without affecting workspace skills", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const bundledDir = path.join(workspaceDir, ".bundled");
const bundledSkillDir = path.join(bundledDir, "peekaboo");
const workspaceSkillDir = path.join(workspaceDir, "skills", "demo-skill");
@@ -323,7 +323,7 @@ describe("buildWorkspaceSkillsPrompt", () => {
describe("loadWorkspaceSkillEntries", () => {
it("handles an empty managed skills dir without throwing", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const managedDir = path.join(workspaceDir, ".managed");
await fs.mkdir(managedDir, { recursive: true });
@@ -338,7 +338,7 @@ describe("loadWorkspaceSkillEntries", () => {
describe("buildWorkspaceSkillSnapshot", () => {
it("returns an empty snapshot when skills dirs are missing", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const snapshot = buildWorkspaceSkillSnapshot(workspaceDir, {
managedSkillsDir: path.join(workspaceDir, ".managed"),
@@ -352,7 +352,7 @@ describe("buildWorkspaceSkillSnapshot", () => {
describe("buildWorkspaceSkillStatus", () => {
it("reports missing requirements and install options", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const skillDir = path.join(workspaceDir, "skills", "status-skill");
await writeSkill({
@@ -360,7 +360,7 @@ describe("buildWorkspaceSkillStatus", () => {
name: "status-skill",
description: "Needs setup",
metadata:
'{"clawdis":{"requires":{"bins":["fakebin"],"env":["ENV_KEY"],"config":["browser.enabled"]},"install":[{"id":"brew","kind":"brew","formula":"fakebin","bins":["fakebin"],"label":"Install fakebin"}]}}',
'{"clawdbot":{"requires":{"bins":["fakebin"],"env":["ENV_KEY"],"config":["browser.enabled"]},"install":[{"id":"brew","kind":"brew","formula":"fakebin","bins":["fakebin"],"label":"Install fakebin"}]}}',
});
const report = buildWorkspaceSkillStatus(workspaceDir, {
@@ -378,14 +378,14 @@ describe("buildWorkspaceSkillStatus", () => {
});
it("respects OS-gated skills", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const skillDir = path.join(workspaceDir, "skills", "os-skill");
await writeSkill({
dir: skillDir,
name: "os-skill",
description: "Darwin only",
metadata: '{"clawdis":{"os":["darwin"]}}',
metadata: '{"clawdbot":{"os":["darwin"]}}',
});
const report = buildWorkspaceSkillStatus(workspaceDir, {
@@ -404,10 +404,10 @@ describe("buildWorkspaceSkillStatus", () => {
});
it("marks bundled skills blocked by allowlist", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const bundledDir = path.join(workspaceDir, ".bundled");
const bundledSkillDir = path.join(bundledDir, "peekaboo");
const originalBundled = process.env.CLAWDIS_BUNDLED_SKILLS_DIR;
const originalBundled = process.env.CLAWDBOT_BUNDLED_SKILLS_DIR;
await writeSkill({
dir: bundledSkillDir,
@@ -417,7 +417,7 @@ describe("buildWorkspaceSkillStatus", () => {
});
try {
process.env.CLAWDIS_BUNDLED_SKILLS_DIR = bundledDir;
process.env.CLAWDBOT_BUNDLED_SKILLS_DIR = bundledDir;
const report = buildWorkspaceSkillStatus(workspaceDir, {
managedSkillsDir: path.join(workspaceDir, ".managed"),
config: { skills: { allowBundled: ["other-skill"] } },
@@ -429,9 +429,9 @@ describe("buildWorkspaceSkillStatus", () => {
expect(skill?.eligible).toBe(false);
} finally {
if (originalBundled === undefined) {
delete process.env.CLAWDIS_BUNDLED_SKILLS_DIR;
delete process.env.CLAWDBOT_BUNDLED_SKILLS_DIR;
} else {
process.env.CLAWDIS_BUNDLED_SKILLS_DIR = originalBundled;
process.env.CLAWDBOT_BUNDLED_SKILLS_DIR = originalBundled;
}
}
});
@@ -439,14 +439,14 @@ describe("buildWorkspaceSkillStatus", () => {
describe("applySkillEnvOverrides", () => {
it("sets and restores env vars", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const skillDir = path.join(workspaceDir, "skills", "env-skill");
await writeSkill({
dir: skillDir,
name: "env-skill",
description: "Needs env",
metadata:
'{"clawdis":{"requires":{"env":["ENV_KEY"]},"primaryEnv":"ENV_KEY"}}',
'{"clawdbot":{"requires":{"env":["ENV_KEY"]},"primaryEnv":"ENV_KEY"}}',
});
const entries = loadWorkspaceSkillEntries(workspaceDir, {
@@ -474,14 +474,14 @@ describe("applySkillEnvOverrides", () => {
});
it("applies env overrides from snapshots", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const skillDir = path.join(workspaceDir, "skills", "env-skill");
await writeSkill({
dir: skillDir,
name: "env-skill",
description: "Needs env",
metadata:
'{"clawdis":{"requires":{"env":["ENV_KEY"]},"primaryEnv":"ENV_KEY"}}',
'{"clawdbot":{"requires":{"env":["ENV_KEY"]},"primaryEnv":"ENV_KEY"}}',
});
const snapshot = buildWorkspaceSkillSnapshot(workspaceDir, {

View File

@@ -8,7 +8,7 @@ import {
type Skill,
} from "@mariozechner/pi-coding-agent";
import type { ClawdisConfig, SkillConfig } from "../config/config.js";
import type { ClawdbotConfig, SkillConfig } from "../config/config.js";
import { CONFIG_DIR, resolveUserPath } from "../utils.js";
export type SkillInstallSpec = {
@@ -21,7 +21,7 @@ export type SkillInstallSpec = {
module?: string;
};
export type ClawdisSkillMetadata = {
export type ClawdbotSkillMetadata = {
always?: boolean;
skillKey?: string;
primaryEnv?: string;
@@ -47,7 +47,7 @@ type ParsedSkillFrontmatter = Record<string, string>;
export type SkillEntry = {
skill: Skill;
frontmatter: ParsedSkillFrontmatter;
clawdis?: ClawdisSkillMetadata;
clawdbot?: ClawdbotSkillMetadata;
};
export type SkillSnapshot = {
@@ -57,7 +57,7 @@ export type SkillSnapshot = {
};
function resolveBundledSkillsDir(): string | undefined {
const override = process.env.CLAWDIS_BUNDLED_SKILLS_DIR?.trim();
const override = process.env.CLAWDBOT_BUNDLED_SKILLS_DIR?.trim();
if (override) return override;
// bun --compile: ship a sibling `skills/` next to the executable.
@@ -173,7 +173,7 @@ const DEFAULT_CONFIG_VALUES: Record<string, boolean> = {
};
export function resolveSkillsInstallPreferences(
config?: ClawdisConfig,
config?: ClawdbotConfig,
): SkillsInstallPreferences {
const raw = config?.skills?.install;
const preferBrew = raw?.preferBrew ?? true;
@@ -195,7 +195,7 @@ export function resolveRuntimePlatform(): string {
}
export function resolveConfigPath(
config: ClawdisConfig | undefined,
config: ClawdbotConfig | undefined,
pathStr: string,
) {
const parts = pathStr.split(".").filter(Boolean);
@@ -208,7 +208,7 @@ export function resolveConfigPath(
}
export function isConfigPathTruthy(
config: ClawdisConfig | undefined,
config: ClawdbotConfig | undefined,
pathStr: string,
): boolean {
const value = resolveConfigPath(config, pathStr);
@@ -219,7 +219,7 @@ export function isConfigPathTruthy(
}
export function resolveSkillConfig(
config: ClawdisConfig | undefined,
config: ClawdbotConfig | undefined,
skillKey: string,
): SkillConfig | undefined {
const skills = config?.skills?.entries;
@@ -237,7 +237,7 @@ function normalizeAllowlist(input: unknown): string[] | undefined {
}
function isBundledSkill(entry: SkillEntry): boolean {
return entry.skill.source === "clawdis-bundled";
return entry.skill.source === "clawdbot-bundled";
}
export function isBundledSkillAllowed(
@@ -265,44 +265,44 @@ export function hasBinary(bin: string): boolean {
return false;
}
function resolveClawdisMetadata(
function resolveClawdbotMetadata(
frontmatter: ParsedSkillFrontmatter,
): ClawdisSkillMetadata | undefined {
): ClawdbotSkillMetadata | undefined {
const raw = getFrontmatterValue(frontmatter, "metadata");
if (!raw) return undefined;
try {
const parsed = JSON.parse(raw) as { clawdis?: unknown };
const parsed = JSON.parse(raw) as { clawdbot?: unknown };
if (!parsed || typeof parsed !== "object") return undefined;
const clawdis = (parsed as { clawdis?: unknown }).clawdis;
if (!clawdis || typeof clawdis !== "object") return undefined;
const clawdisObj = clawdis as Record<string, unknown>;
const clawdbot = (parsed as { clawdbot?: unknown }).clawdbot;
if (!clawdbot || typeof clawdbot !== "object") return undefined;
const clawdbotObj = clawdbot as Record<string, unknown>;
const requiresRaw =
typeof clawdisObj.requires === "object" && clawdisObj.requires !== null
? (clawdisObj.requires as Record<string, unknown>)
typeof clawdbotObj.requires === "object" && clawdbotObj.requires !== null
? (clawdbotObj.requires as Record<string, unknown>)
: undefined;
const installRaw = Array.isArray(clawdisObj.install)
? (clawdisObj.install as unknown[])
const installRaw = Array.isArray(clawdbotObj.install)
? (clawdbotObj.install as unknown[])
: [];
const install = installRaw
.map((entry) => parseInstallSpec(entry))
.filter((entry): entry is SkillInstallSpec => Boolean(entry));
const osRaw = normalizeStringList(clawdisObj.os);
const osRaw = normalizeStringList(clawdbotObj.os);
return {
always:
typeof clawdisObj.always === "boolean" ? clawdisObj.always : undefined,
typeof clawdbotObj.always === "boolean" ? clawdbotObj.always : undefined,
emoji:
typeof clawdisObj.emoji === "string" ? clawdisObj.emoji : undefined,
typeof clawdbotObj.emoji === "string" ? clawdbotObj.emoji : undefined,
homepage:
typeof clawdisObj.homepage === "string"
? clawdisObj.homepage
typeof clawdbotObj.homepage === "string"
? clawdbotObj.homepage
: undefined,
skillKey:
typeof clawdisObj.skillKey === "string"
? clawdisObj.skillKey
typeof clawdbotObj.skillKey === "string"
? clawdbotObj.skillKey
: undefined,
primaryEnv:
typeof clawdisObj.primaryEnv === "string"
? clawdisObj.primaryEnv
typeof clawdbotObj.primaryEnv === "string"
? clawdbotObj.primaryEnv
: undefined,
os: osRaw.length > 0 ? osRaw : undefined,
requires: requiresRaw
@@ -321,53 +321,53 @@ function resolveClawdisMetadata(
}
function resolveSkillKey(skill: Skill, entry?: SkillEntry): string {
return entry?.clawdis?.skillKey ?? skill.name;
return entry?.clawdbot?.skillKey ?? skill.name;
}
function shouldIncludeSkill(params: {
entry: SkillEntry;
config?: ClawdisConfig;
config?: ClawdbotConfig;
}): boolean {
const { entry, config } = params;
const skillKey = resolveSkillKey(entry.skill, entry);
const skillConfig = resolveSkillConfig(config, skillKey);
const allowBundled = normalizeAllowlist(config?.skills?.allowBundled);
const osList = entry.clawdis?.os ?? [];
const osList = entry.clawdbot?.os ?? [];
if (skillConfig?.enabled === false) return false;
if (!isBundledSkillAllowed(entry, allowBundled)) return false;
if (osList.length > 0 && !osList.includes(resolveRuntimePlatform())) {
return false;
}
if (entry.clawdis?.always === true) {
if (entry.clawdbot?.always === true) {
return true;
}
const requiredBins = entry.clawdis?.requires?.bins ?? [];
const requiredBins = entry.clawdbot?.requires?.bins ?? [];
if (requiredBins.length > 0) {
for (const bin of requiredBins) {
if (!hasBinary(bin)) return false;
}
}
const requiredAnyBins = entry.clawdis?.requires?.anyBins ?? [];
const requiredAnyBins = entry.clawdbot?.requires?.anyBins ?? [];
if (requiredAnyBins.length > 0) {
const anyFound = requiredAnyBins.some((bin) => hasBinary(bin));
if (!anyFound) return false;
}
const requiredEnv = entry.clawdis?.requires?.env ?? [];
const requiredEnv = entry.clawdbot?.requires?.env ?? [];
if (requiredEnv.length > 0) {
for (const envName of requiredEnv) {
if (process.env[envName]) continue;
if (skillConfig?.env?.[envName]) continue;
if (skillConfig?.apiKey && entry.clawdis?.primaryEnv === envName) {
if (skillConfig?.apiKey && entry.clawdbot?.primaryEnv === envName) {
continue;
}
return false;
}
}
const requiredConfig = entry.clawdis?.requires?.config ?? [];
const requiredConfig = entry.clawdbot?.requires?.config ?? [];
if (requiredConfig.length > 0) {
for (const configPath of requiredConfig) {
if (!isConfigPathTruthy(config, configPath)) return false;
@@ -379,14 +379,14 @@ function shouldIncludeSkill(params: {
function filterSkillEntries(
entries: SkillEntry[],
config?: ClawdisConfig,
config?: ClawdbotConfig,
): SkillEntry[] {
return entries.filter((entry) => shouldIncludeSkill({ entry, config }));
}
export function applySkillEnvOverrides(params: {
skills: SkillEntry[];
config?: ClawdisConfig;
config?: ClawdbotConfig;
}) {
const { skills, config } = params;
const updates: Array<{ key: string; prev: string | undefined }> = [];
@@ -404,7 +404,7 @@ export function applySkillEnvOverrides(params: {
}
}
const primaryEnv = entry.clawdis?.primaryEnv;
const primaryEnv = entry.clawdbot?.primaryEnv;
if (primaryEnv && skillConfig.apiKey && !process.env[primaryEnv]) {
updates.push({ key: primaryEnv, prev: process.env[primaryEnv] });
process.env[primaryEnv] = skillConfig.apiKey;
@@ -421,7 +421,7 @@ export function applySkillEnvOverrides(params: {
export function applySkillEnvOverridesFromSnapshot(params: {
snapshot?: SkillSnapshot;
config?: ClawdisConfig;
config?: ClawdbotConfig;
}) {
const { snapshot, config } = params;
if (!snapshot) return () => {};
@@ -463,7 +463,7 @@ export function applySkillEnvOverridesFromSnapshot(params: {
function loadSkillEntries(
workspaceDir: string,
opts?: {
config?: ClawdisConfig;
config?: ClawdbotConfig;
managedSkillsDir?: string;
bundledSkillsDir?: string;
},
@@ -494,23 +494,23 @@ function loadSkillEntries(
const bundledSkills = bundledSkillsDir
? loadSkills({
dir: bundledSkillsDir,
source: "clawdis-bundled",
source: "clawdbot-bundled",
})
: [];
const extraSkills = extraDirs.flatMap((dir) => {
const resolved = resolveUserPath(dir);
return loadSkills({
dir: resolved,
source: "clawdis-extra",
source: "clawdbot-extra",
});
});
const managedSkills = loadSkills({
dir: managedSkillsDir,
source: "clawdis-managed",
source: "clawdbot-managed",
});
const workspaceSkills = loadSkills({
dir: workspaceSkillsDir,
source: "clawdis-workspace",
source: "clawdbot-workspace",
});
const merged = new Map<string, Skill>();
@@ -532,7 +532,7 @@ function loadSkillEntries(
return {
skill,
frontmatter,
clawdis: resolveClawdisMetadata(frontmatter),
clawdbot: resolveClawdbotMetadata(frontmatter),
};
},
);
@@ -542,7 +542,7 @@ function loadSkillEntries(
export function buildWorkspaceSkillSnapshot(
workspaceDir: string,
opts?: {
config?: ClawdisConfig;
config?: ClawdbotConfig;
managedSkillsDir?: string;
bundledSkillsDir?: string;
entries?: SkillEntry[];
@@ -555,7 +555,7 @@ export function buildWorkspaceSkillSnapshot(
prompt: formatSkillsForPrompt(resolvedSkills),
skills: eligible.map((entry) => ({
name: entry.skill.name,
primaryEnv: entry.clawdis?.primaryEnv,
primaryEnv: entry.clawdbot?.primaryEnv,
})),
resolvedSkills,
};
@@ -564,7 +564,7 @@ export function buildWorkspaceSkillSnapshot(
export function buildWorkspaceSkillsPrompt(
workspaceDir: string,
opts?: {
config?: ClawdisConfig;
config?: ClawdbotConfig;
managedSkillsDir?: string;
bundledSkillsDir?: string;
entries?: SkillEntry[];
@@ -578,7 +578,7 @@ export function buildWorkspaceSkillsPrompt(
export function loadWorkspaceSkillEntries(
workspaceDir: string,
opts?: {
config?: ClawdisConfig;
config?: ClawdbotConfig;
managedSkillsDir?: string;
bundledSkillsDir?: string;
},
@@ -588,12 +588,12 @@ export function loadWorkspaceSkillEntries(
export function filterWorkspaceSkillEntries(
entries: SkillEntry[],
config?: ClawdisConfig,
config?: ClawdbotConfig,
): SkillEntry[] {
return filterSkillEntries(entries, config);
}
export function resolveBundledAllowlist(
config?: ClawdisConfig,
config?: ClawdbotConfig,
): string[] | undefined {
return normalizeAllowlist(config?.skills?.allowBundled);
}

View File

@@ -58,7 +58,7 @@ export function buildAgentSystemPromptAppend(params: {
if (runtimeInfo?.model) runtimeLines.push(`Model: ${runtimeInfo.model}`);
const lines = [
"You are Clawd, a personal assistant running inside Clawdis.",
"You are Clawd, a personal assistant running inside Clawdbot.",
"",
"## Tooling",
"Pi lists the standard tools above. This runtime enables:",
@@ -101,11 +101,11 @@ export function buildAgentSystemPromptAppend(params: {
ownerLine ?? "",
ownerLine ? "" : "",
"## Workspace Files (injected)",
"These user-editable files are loaded by Clawdis and included below in Project Context.",
"These user-editable files are loaded by Clawdbot and included below in Project Context.",
"",
"## Messaging Safety",
"Never send streaming/partial replies to external messaging surfaces; only final replies should be delivered there.",
"Clawdis handles message transport automatically; respond normally and your reply will be delivered to the current chat.",
"Clawdbot handles message transport automatically; respond normally and your reply will be delivered to the current chat.",
"",
"## Reply Tags",
"To request a native reply/quote on supported surfaces, include one tag in your reply:",
@@ -126,7 +126,7 @@ export function buildAgentSystemPromptAppend(params: {
"## Heartbeats",
'If you receive a heartbeat poll (a user message containing just "HEARTBEAT"), and there is nothing that needs attention, reply exactly:',
"HEARTBEAT_OK",
'Clawdis treats a leading/trailing "HEARTBEAT_OK" as a heartbeat ack (and may discard it).',
'Clawdbot treats a leading/trailing "HEARTBEAT_OK" as a heartbeat ack (and may discard it).',
'If something needs attention, do NOT include "HEARTBEAT_OK"; reply with the alert text instead.',
"",
"## Runtime",

View File

@@ -6,7 +6,7 @@ type ToolContentBlock = AgentToolResult<unknown>["content"][number];
type ImageContentBlock = Extract<ToolContentBlock, { type: "image" }>;
type TextContentBlock = Extract<ToolContentBlock, { type: "text" }>;
// Anthropic Messages API limitations (observed in Clawdis sessions):
// Anthropic Messages API limitations (observed in Clawdbot sessions):
// - Images over ~2000px per side can fail in multi-image requests.
// - Images over 5MB are rejected by the API.
//

View File

@@ -196,7 +196,7 @@ function resolveBrowserBaseUrl(controlUrl?: string) {
const resolved = resolveBrowserConfig(cfg.browser);
if (!resolved.enabled && !controlUrl?.trim()) {
throw new Error(
"Browser control is disabled. Set browser.enabled=true in ~/.clawdis/clawdis.json.",
"Browser control is disabled. Set browser.enabled=true in ~/.clawdbot/clawdbot.json.",
);
}
const url = controlUrl?.trim() ? controlUrl.trim() : resolved.controlUrl;

View File

@@ -1,6 +1,6 @@
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
import type {
ClawdisConfig,
ClawdbotConfig,
DiscordActionConfig,
} from "../../config/config.js";
import { readStringParam } from "./common.js";
@@ -51,7 +51,7 @@ type ActionGate = (
export async function handleDiscordAction(
params: Record<string, unknown>,
cfg: ClawdisConfig,
cfg: ClawdbotConfig,
): Promise<AgentToolResult<unknown>> {
const action = readStringParam(params, "action", { required: true });
const isActionEnabled: ActionGate = (key, defaultValue = true) => {

View File

@@ -1,4 +1,4 @@
import type { ClawdisConfig } from "../../config/config.js";
import type { ClawdbotConfig } from "../../config/config.js";
export type SessionKind = "main" | "group" | "cron" | "hook" | "node" | "other";
@@ -7,7 +7,7 @@ function normalizeKey(value?: string) {
return trimmed ? trimmed : undefined;
}
export function resolveMainSessionAlias(cfg: ClawdisConfig) {
export function resolveMainSessionAlias(cfg: ClawdbotConfig) {
const mainKey = normalizeKey(cfg.session?.mainKey) ?? "main";
const scope = cfg.session?.scope ?? "per-sender";
const alias = scope === "global" ? "global" : mainKey;

View File

@@ -1,4 +1,4 @@
import type { ClawdisConfig } from "../../config/config.js";
import type { ClawdbotConfig } from "../../config/config.js";
const ANNOUNCE_SKIP_TOKEN = "ANNOUNCE_SKIP";
const REPLY_SKIP_TOKEN = "REPLY_SKIP";
@@ -123,7 +123,7 @@ export function isReplySkip(text?: string) {
return (text ?? "").trim() === REPLY_SKIP_TOKEN;
}
export function resolvePingPongTurns(cfg?: ClawdisConfig) {
export function resolvePingPongTurns(cfg?: ClawdbotConfig) {
const raw = cfg?.session?.agentToAgent?.maxPingPongTurns;
const fallback = DEFAULT_PING_PONG_TURNS;
if (typeof raw !== "number" || !Number.isFinite(raw)) return fallback;

View File

@@ -1,6 +1,6 @@
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
import type { ClawdisConfig, SlackActionConfig } from "../../config/config.js";
import type { ClawdbotConfig, SlackActionConfig } from "../../config/config.js";
import {
deleteSlackMessage,
editSlackMessage,
@@ -33,7 +33,7 @@ type ActionGate = (
export async function handleSlackAction(
params: Record<string, unknown>,
cfg: ClawdisConfig,
cfg: ClawdbotConfig,
): Promise<AgentToolResult<unknown>> {
const action = readStringParam(params, "action", { required: true });
const isActionEnabled: ActionGate = (key, defaultValue = true) => {

View File

@@ -6,7 +6,7 @@ import { ensureAgentWorkspace } from "./workspace.js";
describe("ensureAgentWorkspace", () => {
it("creates directory and bootstrap files when missing", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-ws-"));
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-ws-"));
const nested = path.join(dir, "nested");
const result = await ensureAgentWorkspace({
dir: nested,
@@ -30,7 +30,7 @@ describe("ensureAgentWorkspace", () => {
});
it("does not overwrite existing AGENTS.md", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-ws-"));
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-ws-"));
const agentsPath = path.join(dir, "AGENTS.md");
await fs.writeFile(agentsPath, "custom", "utf-8");
await ensureAgentWorkspace({ dir, ensureBootstrapFiles: true });

View File

@@ -13,7 +13,7 @@ export const DEFAULT_IDENTITY_FILENAME = "IDENTITY.md";
export const DEFAULT_USER_FILENAME = "USER.md";
export const DEFAULT_BOOTSTRAP_FILENAME = "BOOTSTRAP.md";
const DEFAULT_AGENTS_TEMPLATE = `# AGENTS.md - Clawdis Workspace
const DEFAULT_AGENTS_TEMPLATE = `# AGENTS.md - Clawdbot Workspace
This folder is the assistant's working directory.
@@ -58,7 +58,7 @@ Describe who the assistant is, tone, and boundaries.
const DEFAULT_TOOLS_TEMPLATE = `# TOOLS.md - User Tool Notes (editable)
This file is for *your* notes about external tools and conventions.
It does not define which tools exist; Clawdis provides built-in tools internally.
It does not define which tools exist; Clawdbot provides built-in tools internally.
## Examples
@@ -108,7 +108,7 @@ After the user chooses, update:
- Timezone (optional)
- Notes
3) ~/.clawdis/clawdis.json
3) ~/.clawdbot/clawdbot.json
Set identity.name, identity.theme, identity.emoji to match IDENTITY.md.
## Cleanup