feat: embed pi agent runtime

This commit is contained in:
Peter Steinberger
2025-12-17 11:29:04 +01:00
parent c5867b2876
commit fece42ce0a
42 changed files with 2076 additions and 4009 deletions

View File

@@ -1,10 +1,13 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { CONFIG_DIR, resolveUserPath } from "../utils.js";
import { resolveUserPath } from "../utils.js";
export const DEFAULT_AGENT_WORKSPACE_DIR = path.join(CONFIG_DIR, "workspace");
export const DEFAULT_AGENT_WORKSPACE_DIR = path.join(os.homedir(), "clawd");
export const DEFAULT_AGENTS_FILENAME = "AGENTS.md";
export const DEFAULT_SOUL_FILENAME = "SOUL.md";
export const DEFAULT_TOOLS_FILENAME = "TOOLS.md";
const DEFAULT_AGENTS_TEMPLATE = `# AGENTS.md — Clawdis Workspace
@@ -20,21 +23,47 @@ This folder is the assistants working directory.
- Customize this file with additional instructions for your assistant.
`;
export async function ensureAgentWorkspace(params?: {
dir?: string;
ensureAgentsFile?: boolean;
}): Promise<{ dir: string; agentsPath?: string }> {
const rawDir = params?.dir?.trim()
? params.dir.trim()
: DEFAULT_AGENT_WORKSPACE_DIR;
const dir = resolveUserPath(rawDir);
await fs.mkdir(dir, { recursive: true });
const DEFAULT_SOUL_TEMPLATE = `# SOUL.md — Persona & Boundaries
if (!params?.ensureAgentsFile) return { dir };
Describe who the assistant is, tone, and boundaries.
const agentsPath = path.join(dir, DEFAULT_AGENTS_FILENAME);
- Keep replies concise and direct.
- Ask clarifying questions when needed.
- Never send streaming/partial replies to external messaging surfaces.
`;
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.
## Examples
### imsg
- Send an iMessage/SMS: describe who/what, confirm before sending.
- Prefer short messages; avoid sending secrets.
### sag
- Text-to-speech: specify voice, target speaker/room, and whether to stream.
Add whatever else you want the assistant to know about your local toolchain.
`;
export type WorkspaceBootstrapFileName =
| typeof DEFAULT_AGENTS_FILENAME
| typeof DEFAULT_SOUL_FILENAME
| typeof DEFAULT_TOOLS_FILENAME;
export type WorkspaceBootstrapFile = {
name: WorkspaceBootstrapFileName;
path: string;
content?: string;
missing: boolean;
};
async function writeFileIfMissing(filePath: string, content: string) {
try {
await fs.writeFile(agentsPath, DEFAULT_AGENTS_TEMPLATE, {
await fs.writeFile(filePath, content, {
encoding: "utf-8",
flag: "wx",
});
@@ -42,5 +71,72 @@ export async function ensureAgentWorkspace(params?: {
const anyErr = err as { code?: string };
if (anyErr.code !== "EEXIST") throw err;
}
return { dir, agentsPath };
}
export async function ensureAgentWorkspace(params?: {
dir?: string;
ensureBootstrapFiles?: boolean;
}): Promise<{
dir: string;
agentsPath?: string;
soulPath?: string;
toolsPath?: string;
}> {
const rawDir = params?.dir?.trim()
? params.dir.trim()
: DEFAULT_AGENT_WORKSPACE_DIR;
const dir = resolveUserPath(rawDir);
await fs.mkdir(dir, { recursive: true });
if (!params?.ensureBootstrapFiles) return { dir };
const agentsPath = path.join(dir, DEFAULT_AGENTS_FILENAME);
const soulPath = path.join(dir, DEFAULT_SOUL_FILENAME);
const toolsPath = path.join(dir, DEFAULT_TOOLS_FILENAME);
await writeFileIfMissing(agentsPath, DEFAULT_AGENTS_TEMPLATE);
await writeFileIfMissing(soulPath, DEFAULT_SOUL_TEMPLATE);
await writeFileIfMissing(toolsPath, DEFAULT_TOOLS_TEMPLATE);
return { dir, agentsPath, soulPath, toolsPath };
}
export async function loadWorkspaceBootstrapFiles(
dir: string,
): Promise<WorkspaceBootstrapFile[]> {
const resolvedDir = resolveUserPath(dir);
const entries: Array<{
name: WorkspaceBootstrapFileName;
filePath: string;
}> = [
{
name: DEFAULT_AGENTS_FILENAME,
filePath: path.join(resolvedDir, DEFAULT_AGENTS_FILENAME),
},
{
name: DEFAULT_SOUL_FILENAME,
filePath: path.join(resolvedDir, DEFAULT_SOUL_FILENAME),
},
{
name: DEFAULT_TOOLS_FILENAME,
filePath: path.join(resolvedDir, DEFAULT_TOOLS_FILENAME),
},
];
const result: WorkspaceBootstrapFile[] = [];
for (const entry of entries) {
try {
const content = await fs.readFile(entry.filePath, "utf-8");
result.push({
name: entry.name,
path: entry.filePath,
content,
missing: false,
});
} catch {
result.push({ name: entry.name, path: entry.filePath, missing: true });
}
}
return result;
}