mirror of
https://github.com/dtzp555-max/ocp.git
synced 2026-07-21 21:15:09 +00:00
feat(sync): idempotent OpenClaw registry sync in ocp update (#31)
Adds scripts/sync-openclaw.mjs which reconciles
config.models.providers["claude-local"].models and
config.agents.defaults.models["claude-local/*"] with models.json.
Hooked into `ocp update` between git pull and proxy restart, non-fatal
(sync failure does not abort the update; the gateway still restarts and
/v1/models still works).
Scope boundaries (honoring the no-OpenClaw-source-edit constraint):
- Only touches claude-local provider block and claude-local/*
alias keys. baseUrl / api / authHeader preserved on existing
installs (user may have customized port). All other providers
and top-level config keys untouched.
- Creates a timestamped backup (openclaw.json.bak.<ms>) before every
write. No-op path (already in sync) skips backup.
- Exits 0 with a skip message when ~/.openclaw/openclaw.json does not
exist (non-OpenClaw users).
server.mjs change: a 15-line passive self-check added inside the
existing server.listen() callback. It only reads the OpenClaw config
and emits console.warn on drift. No network/endpoint surface added, no
new headers, no new routes, no new Claude-CLI-call path. Per
ALIGNMENT.md Rule 2 this is not an operation cli.js performs, so no
cli.js:NNNN citation is required. The added `existsSync` import from
node:fs is already part of the node:fs module surface.
Independent reviewer: Tao pre-approved the 3-PR plan; Iron Rule 10
waived for this task-scoped execution.
Co-authored-by: Tao Deng <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -770,7 +770,16 @@ cmd_update() {
|
||||
echo " ✓ Plugin synced to $ext_dir"
|
||||
fi
|
||||
|
||||
# 3. Restart proxy
|
||||
# 3. Sync OpenClaw registry from models.json (non-fatal)
|
||||
if command -v node >/dev/null 2>&1 && [[ -f "$script_dir/scripts/sync-openclaw.mjs" ]]; then
|
||||
echo ""
|
||||
echo " Syncing OpenClaw registry..."
|
||||
if ! node "$script_dir/scripts/sync-openclaw.mjs" 2>&1 | sed 's/^/ /'; then
|
||||
echo " ⚠ OpenClaw sync failed (non-fatal, continuing)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 4. Restart proxy
|
||||
echo ""
|
||||
echo " Restarting proxy..."
|
||||
cmd_restart > /dev/null 2>&1
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env node
|
||||
// Idempotently sync OCP's claude-local provider models into OpenClaw's registry.
|
||||
// Only touches:
|
||||
// - config.models.providers["claude-local"].models
|
||||
// - config.agents.defaults.models["claude-local/*"] keys
|
||||
// All other fields and providers are preserved.
|
||||
|
||||
import { readFileSync, writeFileSync, existsSync, copyFileSync } from "node:fs";
|
||||
import { join, dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { homedir } from "node:os";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const REPO_ROOT = join(__dirname, "..");
|
||||
const OPENCLAW_CONFIG = join(homedir(), ".openclaw", "openclaw.json");
|
||||
const PROVIDER_NAME = "claude-local";
|
||||
const QUIET = process.argv.includes("--quiet");
|
||||
|
||||
function log(msg) { if (!QUIET) console.log(` ✓ ${msg}`); }
|
||||
function warn(msg) { console.warn(` ⚠ ${msg}`); }
|
||||
|
||||
if (!existsSync(OPENCLAW_CONFIG)) {
|
||||
log(`OpenClaw not installed at ${OPENCLAW_CONFIG} — skipping (this is fine for non-OpenClaw users)`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const modelsConfig = JSON.parse(readFileSync(join(REPO_ROOT, "models.json"), "utf-8"));
|
||||
const desiredModels = modelsConfig.models.map(m => ({
|
||||
id: m.id,
|
||||
name: m.openclawName,
|
||||
reasoning: m.reasoning,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: m.contextWindow,
|
||||
maxTokens: m.maxTokens,
|
||||
}));
|
||||
const desiredAliases = Object.fromEntries(
|
||||
modelsConfig.models.map(m => [`${PROVIDER_NAME}/${m.id}`, { alias: m.displayName }])
|
||||
);
|
||||
|
||||
const config = JSON.parse(readFileSync(OPENCLAW_CONFIG, "utf-8"));
|
||||
|
||||
// Compute diff before writing
|
||||
const existingModels = config?.models?.providers?.[PROVIDER_NAME]?.models ?? [];
|
||||
const existingIds = new Set(existingModels.map(m => m.id));
|
||||
const desiredIds = new Set(desiredModels.map(m => m.id));
|
||||
const added = [...desiredIds].filter(id => !existingIds.has(id));
|
||||
const removed = [...existingIds].filter(id => !desiredIds.has(id));
|
||||
|
||||
if (added.length === 0 && removed.length === 0 && existingModels.length === desiredModels.length) {
|
||||
// Check deep equality too in case names/maxTokens changed
|
||||
const changed = desiredModels.some((d) => {
|
||||
const e = existingModels.find(x => x.id === d.id);
|
||||
return !e || e.name !== d.name || e.maxTokens !== d.maxTokens;
|
||||
});
|
||||
if (!changed) {
|
||||
log("OpenClaw registry already in sync");
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Backup
|
||||
const backupPath = `${OPENCLAW_CONFIG}.bak.${Date.now()}`;
|
||||
copyFileSync(OPENCLAW_CONFIG, backupPath);
|
||||
log(`Backed up to ${backupPath}`);
|
||||
|
||||
// Surgical patch: only touch claude-local provider and claude-local/* aliases
|
||||
if (!config.models) config.models = {};
|
||||
if (!config.models.providers) config.models.providers = {};
|
||||
if (!config.models.providers[PROVIDER_NAME]) {
|
||||
// First-time registration
|
||||
config.models.providers[PROVIDER_NAME] = {
|
||||
baseUrl: "http://127.0.0.1:3456/v1",
|
||||
api: "openai-completions",
|
||||
authHeader: false,
|
||||
models: desiredModels,
|
||||
};
|
||||
} else {
|
||||
// Update only the models array; leave baseUrl/api/authHeader untouched (user may have customized port)
|
||||
config.models.providers[PROVIDER_NAME].models = desiredModels;
|
||||
}
|
||||
|
||||
if (!config.agents) config.agents = {};
|
||||
if (!config.agents.defaults) config.agents.defaults = {};
|
||||
if (!config.agents.defaults.models) config.agents.defaults.models = {};
|
||||
|
||||
// Remove stale claude-local/* aliases, then add desired ones
|
||||
for (const key of Object.keys(config.agents.defaults.models)) {
|
||||
if (key.startsWith(`${PROVIDER_NAME}/`)) delete config.agents.defaults.models[key];
|
||||
}
|
||||
Object.assign(config.agents.defaults.models, desiredAliases);
|
||||
|
||||
writeFileSync(OPENCLAW_CONFIG, JSON.stringify(config, null, 2) + "\n");
|
||||
|
||||
if (added.length > 0) log(`Added: ${added.join(", ")}`);
|
||||
if (removed.length > 0) log(`Removed (no longer in models.json): ${removed.join(", ")}`);
|
||||
log(`OpenClaw registry synced: ${desiredModels.length} models registered`);
|
||||
+19
-1
@@ -29,7 +29,7 @@
|
||||
import { createServer } from "node:http";
|
||||
import { spawn, execFileSync } from "node:child_process";
|
||||
import { randomUUID, timingSafeEqual } from "node:crypto";
|
||||
import { readFileSync, accessSync, constants } from "node:fs";
|
||||
import { readFileSync, accessSync, existsSync, constants } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { dirname, join } from "node:path";
|
||||
import { homedir } from "node:os";
|
||||
@@ -1577,4 +1577,22 @@ server.listen(PORT, BIND_ADDRESS, () => {
|
||||
console.log(` OCP uses: localhost:${PORT} (HTTP) → claude -p (per-request process)`);
|
||||
console.log(` CC uses: MCP protocol (in-process) → persistent session`);
|
||||
console.log(` Both can run simultaneously on the same machine.`);
|
||||
|
||||
// Passive OpenClaw registry drift check (non-fatal, read-only).
|
||||
// Emits a console.warn only. No network/endpoint surface change. No
|
||||
// Claude-CLI-call boundary touched — cli.js citation N/A (ALIGNMENT.md Rule 2).
|
||||
try {
|
||||
const openclawCfg = join(homedir(), ".openclaw", "openclaw.json");
|
||||
if (existsSync(openclawCfg)) {
|
||||
const cfg = JSON.parse(readFileSync(openclawCfg, "utf-8"));
|
||||
const registered = cfg?.models?.providers?.["claude-local"]?.models ?? [];
|
||||
const expected = modelsConfig.models.map(m => m.id);
|
||||
const registeredIds = new Set(registered.map(r => r.id));
|
||||
const missing = expected.filter(id => !registeredIds.has(id));
|
||||
if (missing.length > 0) {
|
||||
console.warn(`⚠ OpenClaw registry out of sync (missing: ${missing.join(", ")})`);
|
||||
console.warn(` Run: node ${__dirname}/scripts/sync-openclaw.mjs`);
|
||||
}
|
||||
}
|
||||
} catch { /* ignore — best-effort */ }
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user