@clawdreyhepburn/carapace 0.4.2 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/openclaw.plugin.json +55 -15
- package/package.json +1 -1
- package/src/index.ts +97 -0
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "carapace",
|
|
3
3
|
"name": "Carapace",
|
|
4
4
|
"description": "Immutable policy boundaries for MCP tool access. Your agent's exoskeleton.",
|
|
5
|
-
"version": "0.4.
|
|
5
|
+
"version": "0.4.3",
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
|
8
8
|
"additionalProperties": true,
|
|
@@ -20,13 +20,31 @@
|
|
|
20
20
|
"properties": {
|
|
21
21
|
"transport": {
|
|
22
22
|
"type": "string",
|
|
23
|
-
"enum": [
|
|
23
|
+
"enum": [
|
|
24
|
+
"stdio",
|
|
25
|
+
"http",
|
|
26
|
+
"sse"
|
|
27
|
+
],
|
|
24
28
|
"default": "stdio"
|
|
25
29
|
},
|
|
26
|
-
"command": {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
+
"command": {
|
|
31
|
+
"type": "string"
|
|
32
|
+
},
|
|
33
|
+
"args": {
|
|
34
|
+
"type": "array",
|
|
35
|
+
"items": {
|
|
36
|
+
"type": "string"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"env": {
|
|
40
|
+
"type": "object",
|
|
41
|
+
"additionalProperties": {
|
|
42
|
+
"type": "string"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"url": {
|
|
46
|
+
"type": "string"
|
|
47
|
+
}
|
|
30
48
|
}
|
|
31
49
|
}
|
|
32
50
|
},
|
|
@@ -37,7 +55,10 @@
|
|
|
37
55
|
},
|
|
38
56
|
"defaultPolicy": {
|
|
39
57
|
"type": "string",
|
|
40
|
-
"enum": [
|
|
58
|
+
"enum": [
|
|
59
|
+
"deny-all",
|
|
60
|
+
"allow-all"
|
|
61
|
+
],
|
|
41
62
|
"default": "allow-all",
|
|
42
63
|
"description": "Default policy for tools. allow-all (default) keeps everything working — use the GUI to restrict. deny-all requires explicit permits."
|
|
43
64
|
},
|
|
@@ -74,13 +95,32 @@
|
|
|
74
95
|
}
|
|
75
96
|
},
|
|
76
97
|
"uiHints": {
|
|
77
|
-
"guiPort": {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
"
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
"
|
|
98
|
+
"guiPort": {
|
|
99
|
+
"label": "GUI Port",
|
|
100
|
+
"placeholder": "19820"
|
|
101
|
+
},
|
|
102
|
+
"policyDir": {
|
|
103
|
+
"label": "Policy Directory"
|
|
104
|
+
},
|
|
105
|
+
"defaultPolicy": {
|
|
106
|
+
"label": "Default Policy for New Tools"
|
|
107
|
+
},
|
|
108
|
+
"verify": {
|
|
109
|
+
"label": "Enable Formal Verification"
|
|
110
|
+
},
|
|
111
|
+
"proxy.enabled": {
|
|
112
|
+
"label": "Enable LLM Proxy"
|
|
113
|
+
},
|
|
114
|
+
"proxy.port": {
|
|
115
|
+
"label": "Proxy Port",
|
|
116
|
+
"placeholder": "19821"
|
|
117
|
+
},
|
|
118
|
+
"proxy.upstream": {
|
|
119
|
+
"label": "Upstream API URL"
|
|
120
|
+
},
|
|
121
|
+
"proxy.apiKey": {
|
|
122
|
+
"label": "Upstream API Key",
|
|
123
|
+
"sensitive": true
|
|
124
|
+
}
|
|
85
125
|
}
|
|
86
126
|
}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -228,6 +228,41 @@ export default function register(api: OpenClawPluginApi) {
|
|
|
228
228
|
writeFileSync(configPath, JSON.stringify(cfg, null, 2) + "\n", "utf-8");
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
// Also patch the per-agent models.json (this is what OpenClaw actually reads at runtime)
|
|
232
|
+
// openclaw.json models.providers gets merged INTO models.json on restart,
|
|
233
|
+
// but if someone restores openclaw.json from backup, models.json keeps the stale baseUrl.
|
|
234
|
+
// So we patch both files for safety.
|
|
235
|
+
const agentModelsPath = join(homedir(), ".openclaw", "agents", "main", "agent", "models.json");
|
|
236
|
+
if (existsSync(agentModelsPath)) {
|
|
237
|
+
try {
|
|
238
|
+
const agentModels = JSON.parse(readFileSync(agentModelsPath, "utf-8"));
|
|
239
|
+
let agentModelsChanged = false;
|
|
240
|
+
if (!agentModels.providers) agentModels.providers = {};
|
|
241
|
+
for (const provider of providers) {
|
|
242
|
+
if (!agentModels.providers[provider]) agentModels.providers[provider] = {};
|
|
243
|
+
if (agentModels.providers[provider].baseUrl !== proxyUrl) {
|
|
244
|
+
if (agentModels.providers[provider].baseUrl && agentModels.providers[provider].baseUrl !== proxyUrl) {
|
|
245
|
+
agentModels.providers[provider]._originalBaseUrl = agentModels.providers[provider].baseUrl;
|
|
246
|
+
}
|
|
247
|
+
agentModels.providers[provider].baseUrl = proxyUrl;
|
|
248
|
+
agentModelsChanged = true;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (agentModelsChanged) {
|
|
252
|
+
// Backup models.json before modifying
|
|
253
|
+
const modelsBackup = agentModelsPath + ".carapace-backup";
|
|
254
|
+
if (!existsSync(modelsBackup)) {
|
|
255
|
+
const { copyFileSync } = require("node:fs");
|
|
256
|
+
copyFileSync(agentModelsPath, modelsBackup);
|
|
257
|
+
}
|
|
258
|
+
writeFileSync(agentModelsPath, JSON.stringify(agentModels, null, 2) + "\n", "utf-8");
|
|
259
|
+
patched.push(...providers.map(p => `models.json:${p}`));
|
|
260
|
+
}
|
|
261
|
+
} catch (e: any) {
|
|
262
|
+
logger.warn(`Failed to patch agent models.json: ${e.message}`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
231
266
|
return { patched, alreadySet };
|
|
232
267
|
}
|
|
233
268
|
|
|
@@ -708,6 +743,68 @@ export default function register(api: OpenClawPluginApi) {
|
|
|
708
743
|
if (cfg.models && Object.keys(cfg.models).length === 0) delete cfg.models;
|
|
709
744
|
}
|
|
710
745
|
|
|
746
|
+
// Also clean up the per-agent models.json
|
|
747
|
+
const agentModelsPath = join(homedir(), ".openclaw", "agents", "main", "agent", "models.json");
|
|
748
|
+
if (existsSync(agentModelsPath)) {
|
|
749
|
+
try {
|
|
750
|
+
const agentModels = JSON.parse(readFileSync(agentModelsPath, "utf-8"));
|
|
751
|
+
let modelsChanged = false;
|
|
752
|
+
if (agentModels.providers) {
|
|
753
|
+
for (const [name, provCfg] of Object.entries(agentModels.providers)) {
|
|
754
|
+
if ((provCfg as any)?.baseUrl === proxyUrl) {
|
|
755
|
+
if ((provCfg as any)._originalBaseUrl) {
|
|
756
|
+
(provCfg as any).baseUrl = (provCfg as any)._originalBaseUrl;
|
|
757
|
+
delete (provCfg as any)._originalBaseUrl;
|
|
758
|
+
} else {
|
|
759
|
+
delete (provCfg as any).baseUrl;
|
|
760
|
+
}
|
|
761
|
+
if (Object.keys(provCfg as any).length === 0) delete agentModels.providers[name];
|
|
762
|
+
modelsChanged = true;
|
|
763
|
+
console.log(` ✅ Cleaned proxy baseUrl from models.json for ${name}`);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
if (modelsChanged) {
|
|
768
|
+
writeFileSync(agentModelsPath, JSON.stringify(agentModels, null, 2) + "\n", "utf-8");
|
|
769
|
+
changed = true;
|
|
770
|
+
}
|
|
771
|
+
} catch (e: any) {
|
|
772
|
+
console.log(` ⚠️ Could not clean models.json: ${e.message}`);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// Clean up per-agent models.json (this is what actually routes API calls)
|
|
777
|
+
const agentModelsPath = join(homedir(), ".openclaw", "agents", "main", "agent", "models.json");
|
|
778
|
+
if (existsSync(agentModelsPath)) {
|
|
779
|
+
try {
|
|
780
|
+
const agentModels = JSON.parse(readFileSync(agentModelsPath, "utf-8"));
|
|
781
|
+
let modelsChanged = false;
|
|
782
|
+
if (agentModels.providers) {
|
|
783
|
+
for (const [name, provCfg] of Object.entries(agentModels.providers)) {
|
|
784
|
+
if ((provCfg as any)?.baseUrl === proxyUrl) {
|
|
785
|
+
if ((provCfg as any)._originalBaseUrl) {
|
|
786
|
+
(provCfg as any).baseUrl = (provCfg as any)._originalBaseUrl;
|
|
787
|
+
delete (provCfg as any)._originalBaseUrl;
|
|
788
|
+
} else {
|
|
789
|
+
delete (provCfg as any).baseUrl;
|
|
790
|
+
}
|
|
791
|
+
// Remove empty provider entries (but keep ones with other config)
|
|
792
|
+
const remaining = Object.keys(provCfg as any).filter(k => k !== 'models' || (provCfg as any).models?.length > 0);
|
|
793
|
+
if (remaining.length === 0) delete agentModels.providers[name];
|
|
794
|
+
modelsChanged = true;
|
|
795
|
+
console.log(` ✅ Cleaned proxy baseUrl from models.json (${name})`);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
if (modelsChanged) {
|
|
800
|
+
writeFileSync(agentModelsPath, JSON.stringify(agentModels, null, 2) + "\n", "utf-8");
|
|
801
|
+
changed = true;
|
|
802
|
+
}
|
|
803
|
+
} catch (e: any) {
|
|
804
|
+
console.log(` ⚠️ Could not clean models.json: ${e.message}`);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
711
808
|
// Disable the plugin entry (don't delete — user might want to re-enable)
|
|
712
809
|
if (cfg.plugins?.entries?.carapace?.enabled) {
|
|
713
810
|
cfg.plugins.entries.carapace.enabled = false;
|