diff --git a/src/bridge/bootstrap.ts b/src/bridge/bootstrap.ts index 579e481..3f1bc8c 100644 --- a/src/bridge/bootstrap.ts +++ b/src/bridge/bootstrap.ts @@ -4,6 +4,8 @@ export type BootstrapInput = { workspace: string; /** Skills XML block extracted from the OpenClaw system prompt, if any */ skillsBlock?: string; + /** Subset of OpenClaw context files present in the workspace (for persona/identity) */ + workspaceContextFiles?: string[]; }; /** @@ -12,6 +14,10 @@ export type BootstrapInput = { * Must NOT be re-injected on every turn. */ export function buildBootstrap(input: BootstrapInput): string { + const contextFiles = input.workspaceContextFiles ?? []; + const hasSoul = contextFiles.includes("SOUL.md"); + const hasIdentity = contextFiles.includes("IDENTITY.md"); + const lines = [ `You are operating as a contractor agent inside OpenClaw.`, ``, @@ -32,6 +38,38 @@ export function buildBootstrap(input: BootstrapInput): string { `- If a task is unclear, ask one focused clarifying question.`, ]; + // Inject persona/identity context from OpenClaw workspace files. + // These files define who this agent is and how it should behave. + if (hasSoul || hasIdentity) { + lines.push(``, `## Persona & Identity`); + if (hasSoul) { + lines.push( + `Read ${input.workspace}/SOUL.md now (using the Read tool) and embody the persona and tone it describes.`, + `SOUL.md defines your values, communication style, and boundaries — treat it as authoritative.`, + ); + } + if (hasIdentity) { + lines.push( + `Read ${input.workspace}/IDENTITY.md now (using the Read tool) to understand your name, creature type, and personal vibe.`, + `If IDENTITY.md is empty/template-only, you may fill it in as you develop your identity.`, + ); + } + } + + // Memory system note: OpenClaw uses workspace/memory/*.md + MEMORY.md. + // Claude Code also maintains per-project auto-memory at ~/.claude/projects//memory/. + // Both are active; Claude Code's auto-memory is loaded automatically each session. + // OpenClaw memory tools (memory_search, memory_get) are available as MCP tools + // but require bridge-side implementations to execute (not yet wired). + if (contextFiles.includes("MEMORY.md") || contextFiles.some(f => f.startsWith("memory/"))) { + lines.push( + ``, + `## Memory`, + `OpenClaw memory files exist in ${input.workspace}/memory/ and ${input.workspace}/MEMORY.md.`, + `Use the Read tool to access these directly. For writing new memories, write to ${input.workspace}/memory/.md.`, + ); + } + if (input.skillsBlock) { lines.push( ``, diff --git a/src/bridge/input-filter.ts b/src/bridge/input-filter.ts index 8bc4d52..0020274 100644 --- a/src/bridge/input-filter.ts +++ b/src/bridge/input-filter.ts @@ -1,3 +1,5 @@ +import fs from "node:fs"; +import path from "node:path"; import type { BridgeInboundRequest, OpenAIMessage } from "../types/model.js"; function messageText(m: OpenAIMessage): string { @@ -33,6 +35,8 @@ export type RequestContext = { workspace: string; /** Raw ... XML block from the system prompt */ skillsBlock: string; + /** OpenClaw context files present in the workspace (SOUL.md, IDENTITY.md, etc.) */ + workspaceContextFiles: string[]; }; /** @@ -67,9 +71,25 @@ export function extractRequestContext(req: BridgeInboundRequest): RequestContext ? skillsMatch[0].replace(/~\//g, `${home}/`) : ""; + // Detect which OpenClaw context files are present in the workspace. + // These tell us what persona/memory files to surface to Claude. + const workspace = repoMatch?.[1] ?? ""; + const CONTEXT_FILES = ["SOUL.md", "IDENTITY.md", "MEMORY.md", "AGENTS.md", "USER.md"]; + const workspaceContextFiles: string[] = []; + if (workspace) { + for (const f of CONTEXT_FILES) { + if (fs.existsSync(path.join(workspace, f))) workspaceContextFiles.push(f); + } + // Also check for memory/ directory + if (fs.existsSync(path.join(workspace, "memory"))) { + workspaceContextFiles.push("memory/"); + } + } + return { agentId: agentMatch?.[1] ?? "", - workspace: repoMatch?.[1] ?? "", + workspace, skillsBlock, + workspaceContextFiles, }; } diff --git a/src/bridge/server.ts b/src/bridge/server.ts index 3da9b94..fdd72a4 100644 --- a/src/bridge/server.ts +++ b/src/bridge/server.ts @@ -104,7 +104,7 @@ export function createBridgeServer(config: BridgeServerConfig): http.Server { // Extract agent ID and workspace from the system prompt's Runtime line. // OpenClaw does NOT send agent/session info as HTTP headers — it's in the system prompt. - const { agentId: parsedAgentId, workspace: parsedWorkspace, skillsBlock } = extractRequestContext(body); + const { agentId: parsedAgentId, workspace: parsedWorkspace, skillsBlock, workspaceContextFiles } = extractRequestContext(body); const latestMessage = extractLatestUserMessage(body); if (!latestMessage) { @@ -139,7 +139,7 @@ export function createBridgeServer(config: BridgeServerConfig): http.Server { // Build prompt: bootstrap on first turn, bare message on subsequent turns const isFirstTurn = !claudeSessionId; const prompt = isFirstTurn - ? `${buildBootstrap({ agentId, openclawSessionKey: sessionKey, workspace, skillsBlock: skillsBlock || undefined })}\n\n---\n\n${latestMessage}` + ? `${buildBootstrap({ agentId, openclawSessionKey: sessionKey, workspace, skillsBlock: skillsBlock || undefined, workspaceContextFiles })}\n\n---\n\n${latestMessage}` : latestMessage; // Start SSE response