feat: embed pi agent runtime
This commit is contained in:
@@ -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 assistant’s 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user