refactor: restructure to plugin/ + services/ layout and add per-turn bootstrap injection
- Migrate src/ → plugin/ (plugin/core/, plugin/web/, plugin/commands/)
and src/mcp/ → services/ per OpenClaw plugin dev spec
- Add Gemini CLI backend (plugin/core/gemini/sdk-adapter.ts) with GEMINI.md
system-prompt injection
- Inject bootstrap as stateless system prompt on every turn instead of
first turn only: Claude via --system-prompt, Gemini via workspace/GEMINI.md;
eliminates isFirstTurn branch, keeps skills in sync with OpenClaw snapshots
- Fix session-map-store defensive parsing (sessions ?? []) to handle bare {}
reset files without crashing on .find()
- Add docs/TEST_FLOW.md with E2E test scenarios and expected outcomes
- Add docs/claude/BRIDGE_MODEL_FINDINGS.md with contractor-probe results
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
58
plugin/core/openclaw/agent-config-writer.ts
Normal file
58
plugin/core/openclaw/agent-config-writer.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
type OpenClawConfig = Record<string, unknown>;
|
||||
type AgentEntry = Record<string, unknown>;
|
||||
|
||||
const CLAUDE_CONTRACTOR_MODEL = "contractor-agent/contractor-claude-bridge";
|
||||
const GEMINI_CONTRACTOR_MODEL = "contractor-agent/contractor-gemini-bridge";
|
||||
|
||||
function readConfig(): { config: OpenClawConfig; configPath: string } {
|
||||
const configPath = path.join(os.homedir(), ".openclaw", "openclaw.json");
|
||||
const raw = fs.readFileSync(configPath, "utf8");
|
||||
return { config: JSON.parse(raw) as OpenClawConfig, configPath };
|
||||
}
|
||||
|
||||
function writeConfig(configPath: string, config: OpenClawConfig): void {
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that an agent is configured with the contractor bridge model.
|
||||
* Called after createBaseAgent() — the model is already set by `openclaw agents add`,
|
||||
* so this is just a sanity check that throws if something went wrong.
|
||||
*
|
||||
* We intentionally do NOT write a custom `runtime.type` — OpenClaw's schema only
|
||||
* allows "embedded" or "acp", and contractor agents are identified by their model.
|
||||
*/
|
||||
function markContractorAgent(agentId: string, expectedModel: string, configPath: string, config: OpenClawConfig): void {
|
||||
const agents = (config.agents as { list?: AgentEntry[] } | undefined)?.list;
|
||||
if (!agents) throw new Error("agents.list not found in openclaw.json");
|
||||
|
||||
const agent = agents.find((a) => a.id === agentId);
|
||||
if (!agent) throw new Error(`agent ${agentId} not found in openclaw.json`);
|
||||
|
||||
if (agent.model !== expectedModel) {
|
||||
throw new Error(
|
||||
`agent ${agentId} model is "${String(agent.model)}", expected "${expectedModel}"`,
|
||||
);
|
||||
}
|
||||
|
||||
// Ensure tools are enabled so OpenClaw includes tool definitions in every
|
||||
// chat completions request — needed for the MCP proxy to expose them to Claude/Gemini.
|
||||
if (!agent.tools) {
|
||||
agent.tools = { profile: "full" };
|
||||
writeConfig(configPath, config);
|
||||
}
|
||||
}
|
||||
|
||||
export function markAgentAsClaudeContractor(agentId: string, _workspace: string): void {
|
||||
const { config, configPath } = readConfig();
|
||||
markContractorAgent(agentId, CLAUDE_CONTRACTOR_MODEL, configPath, config);
|
||||
}
|
||||
|
||||
export function markAgentAsGeminiContractor(agentId: string, _workspace: string): void {
|
||||
const { config, configPath } = readConfig();
|
||||
markContractorAgent(agentId, GEMINI_CONTRACTOR_MODEL, configPath, config);
|
||||
}
|
||||
Reference in New Issue
Block a user