- 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>
184 lines
6.2 KiB
JavaScript
184 lines
6.2 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Install / uninstall the contractor-agent plugin into OpenClaw.
|
|
*
|
|
* Usage:
|
|
* node scripts/install.mjs --install
|
|
* node scripts/install.mjs --uninstall
|
|
*/
|
|
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import os from "node:os";
|
|
import { execSync } from "node:child_process";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const PROJECT_ROOT = path.resolve(__dirname, "..");
|
|
const PLUGIN_ID = "contractor-agent";
|
|
const PLUGIN_INSTALL_DIR = path.join(os.homedir(), ".openclaw", "plugins", PLUGIN_ID);
|
|
const OPENCLAW_CONFIG = path.join(os.homedir(), ".openclaw", "openclaw.json");
|
|
const BRIDGE_PORT = 18800;
|
|
const BRIDGE_API_KEY = "contractor-bridge-local";
|
|
|
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
|
|
function readConfig() {
|
|
return JSON.parse(fs.readFileSync(OPENCLAW_CONFIG, "utf8"));
|
|
}
|
|
|
|
function writeConfig(cfg) {
|
|
fs.writeFileSync(OPENCLAW_CONFIG, JSON.stringify(cfg, null, 2) + "\n", "utf8");
|
|
}
|
|
|
|
function setIfMissing(obj, key, value) {
|
|
if (obj[key] === undefined || obj[key] === null) obj[key] = value;
|
|
}
|
|
|
|
// ── Install ───────────────────────────────────────────────────────────────────
|
|
|
|
function install() {
|
|
console.log(`[install] Installing ${PLUGIN_ID}...`);
|
|
|
|
// 1. Copy plugin files to ~/.openclaw/plugins/contractor-agent/
|
|
if (fs.existsSync(PLUGIN_INSTALL_DIR)) {
|
|
fs.rmSync(PLUGIN_INSTALL_DIR, { recursive: true });
|
|
}
|
|
fs.mkdirSync(PLUGIN_INSTALL_DIR, { recursive: true });
|
|
|
|
// Copy plugin/ contents to the install root.
|
|
// plugin/ contains index.ts, openclaw.plugin.json, package.json, and subdirs.
|
|
const pluginDir = path.join(PROJECT_ROOT, "plugin");
|
|
fs.cpSync(pluginDir, PLUGIN_INSTALL_DIR, { recursive: true });
|
|
|
|
// Copy services/ (standalone processes used by the plugin) into the install root.
|
|
const servicesDir = path.join(PROJECT_ROOT, "services");
|
|
if (fs.existsSync(servicesDir)) {
|
|
fs.cpSync(servicesDir, path.join(PLUGIN_INSTALL_DIR, "services"), { recursive: true });
|
|
}
|
|
|
|
// 2. Install npm dependencies inside the plugin dir
|
|
console.log(`[install] Installing npm dependencies...`);
|
|
execSync("npm install --omit=dev --no-audit --no-fund", {
|
|
cwd: PLUGIN_INSTALL_DIR,
|
|
stdio: "inherit",
|
|
});
|
|
|
|
// 3. Update openclaw.json
|
|
const cfg = readConfig();
|
|
|
|
// Add provider
|
|
cfg.models = cfg.models ?? {};
|
|
cfg.models.providers = cfg.models.providers ?? {};
|
|
cfg.models.providers[PLUGIN_ID] = {
|
|
baseUrl: `http://127.0.0.1:${BRIDGE_PORT}/v1`,
|
|
apiKey: BRIDGE_API_KEY,
|
|
api: "openai-completions",
|
|
models: [
|
|
{
|
|
id: "contractor-claude-bridge",
|
|
name: "Contractor Claude Bridge",
|
|
reasoning: false,
|
|
input: ["text"],
|
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
contextWindow: 200000,
|
|
maxTokens: 16000,
|
|
},
|
|
{
|
|
id: "contractor-gemini-bridge",
|
|
name: "Contractor Gemini Bridge",
|
|
reasoning: false,
|
|
input: ["text"],
|
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
contextWindow: 200000,
|
|
maxTokens: 16000,
|
|
},
|
|
],
|
|
};
|
|
|
|
// Add to plugin allow list
|
|
cfg.plugins = cfg.plugins ?? {};
|
|
cfg.plugins.allow = cfg.plugins.allow ?? [];
|
|
if (!cfg.plugins.allow.includes(PLUGIN_ID)) {
|
|
cfg.plugins.allow.push(PLUGIN_ID);
|
|
}
|
|
|
|
// Add load path
|
|
cfg.plugins.load = cfg.plugins.load ?? {};
|
|
cfg.plugins.load.paths = cfg.plugins.load.paths ?? [];
|
|
if (!cfg.plugins.load.paths.includes(PLUGIN_INSTALL_DIR)) {
|
|
cfg.plugins.load.paths.push(PLUGIN_INSTALL_DIR);
|
|
}
|
|
|
|
// Add plugin entry
|
|
cfg.plugins.entries = cfg.plugins.entries ?? {};
|
|
cfg.plugins.entries[PLUGIN_ID] = cfg.plugins.entries[PLUGIN_ID] ?? {};
|
|
cfg.plugins.entries[PLUGIN_ID].enabled = true;
|
|
|
|
// Set default config — setIfMissing so user values are preserved
|
|
const pluginCfg = cfg.plugins.entries[PLUGIN_ID].config ?? {};
|
|
setIfMissing(pluginCfg, "bridgePort", BRIDGE_PORT);
|
|
setIfMissing(pluginCfg, "bridgeApiKey", BRIDGE_API_KEY);
|
|
setIfMissing(pluginCfg, "permissionMode", "bypassPermissions");
|
|
cfg.plugins.entries[PLUGIN_ID].config = pluginCfg;
|
|
|
|
writeConfig(cfg);
|
|
|
|
console.log(`[install] Done. Restart the gateway to activate:`);
|
|
console.log(` openclaw gateway restart`);
|
|
console.log(` openclaw contractor-agents add --agent-id <id> --workspace <path> --contractor claude`);
|
|
}
|
|
|
|
// ── Uninstall ─────────────────────────────────────────────────────────────────
|
|
|
|
function uninstall() {
|
|
console.log(`[uninstall] Removing ${PLUGIN_ID}...`);
|
|
|
|
const cfg = readConfig();
|
|
|
|
// Remove provider
|
|
if (cfg.models?.providers?.[PLUGIN_ID]) {
|
|
delete cfg.models.providers[PLUGIN_ID];
|
|
}
|
|
|
|
// Remove from allow list
|
|
if (Array.isArray(cfg.plugins?.allow)) {
|
|
cfg.plugins.allow = cfg.plugins.allow.filter((id) => id !== PLUGIN_ID);
|
|
}
|
|
|
|
// Remove load path
|
|
if (Array.isArray(cfg.plugins?.load?.paths)) {
|
|
cfg.plugins.load.paths = cfg.plugins.load.paths.filter(
|
|
(p) => p !== PLUGIN_INSTALL_DIR,
|
|
);
|
|
}
|
|
|
|
// Remove plugin entry
|
|
if (cfg.plugins?.entries?.[PLUGIN_ID]) {
|
|
delete cfg.plugins.entries[PLUGIN_ID];
|
|
}
|
|
|
|
writeConfig(cfg);
|
|
|
|
// Remove installed files
|
|
if (fs.existsSync(PLUGIN_INSTALL_DIR)) {
|
|
fs.rmSync(PLUGIN_INSTALL_DIR, { recursive: true });
|
|
console.log(`[uninstall] Removed ${PLUGIN_INSTALL_DIR}`);
|
|
}
|
|
|
|
console.log(`[uninstall] Done. Restart the gateway:`);
|
|
console.log(` openclaw gateway restart`);
|
|
}
|
|
|
|
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
|
|
const arg = process.argv[2];
|
|
if (arg === "--install") {
|
|
install();
|
|
} else if (arg === "--uninstall") {
|
|
uninstall();
|
|
} else {
|
|
console.error("Usage: node scripts/install.mjs --install | --uninstall");
|
|
process.exit(1);
|
|
}
|