rol-websocket-channel 1.4.2 → 1.4.8
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/{MQTT-API /346/226/260/345/242/236/346/226/207/344/273/266/345/212/237/350/203/275.md" → MQTT-API 5-6.md } +89 -1
- package/dist/index.js +617 -617
- package/dist/message-handler.js +515 -503
- package/dist/src/admin/cli.js +43 -43
- package/dist/src/admin/jsonrpc.js +60 -60
- package/dist/src/admin/lib/fs.js +30 -30
- package/dist/src/admin/lib/paths.js +80 -80
- package/dist/src/admin/methods/admin.js +60 -60
- package/dist/src/admin/methods/agents-extended.js +251 -251
- package/dist/src/admin/methods/artifacts.js +736 -642
- package/dist/src/admin/methods/artifacts.test.js +210 -191
- package/dist/src/admin/methods/cron.js +250 -250
- package/dist/src/admin/methods/index.js +104 -102
- package/dist/src/admin/methods/mem9.js +309 -270
- package/dist/src/admin/methods/mem9.test.js +34 -0
- package/dist/src/admin/methods/memory.js +363 -363
- package/dist/src/admin/methods/models-extended.js +190 -190
- package/dist/src/admin/methods/models.js +195 -195
- package/dist/src/admin/methods/pairing.js +268 -268
- package/dist/src/admin/methods/sessions-extended.js +215 -215
- package/dist/src/admin/methods/sessions.js +75 -75
- package/dist/src/admin/methods/skills-extended.js +157 -157
- package/dist/src/admin/methods/skills-toggle.js +183 -183
- package/dist/src/admin/methods/skills.js +528 -528
- package/dist/src/admin/methods/system.js +271 -180
- package/dist/src/admin/methods/usage.js +1170 -1170
- package/dist/src/admin/types.js +1 -1
- package/dist/src/mqtt/connection-manager.js +209 -209
- package/dist/src/mqtt/index.js +5 -5
- package/dist/src/mqtt/mqtt-client.js +110 -110
- package/dist/src/mqtt/mqtt.test.js +418 -418
- package/dist/src/mqtt/types.js +2 -2
- package/dist/src/shared/context.js +24 -24
- package/dist/src/shared/wrapper.js +23 -23
- package/message-handler.ts +15 -1
- package/openclaw.plugin.json +73 -0
- package/package.json +1 -1
- package/src/admin/methods/artifacts.test.ts +35 -0
- package/src/admin/methods/artifacts.ts +140 -2
- package/src/admin/methods/index.ts +3 -1
- package/src/admin/methods/mem9.test.ts +39 -0
- package/src/admin/methods/mem9.ts +48 -1
- package/src/admin/methods/system.ts +129 -1
|
@@ -1,251 +1,251 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { pathExists, readJsonFile, writeJsonFile } from '../lib/fs.js';
|
|
4
|
-
import { JsonRpcException, JSON_RPC_ERRORS } from '../jsonrpc.js';
|
|
5
|
-
const ALLOWED_AGENT_UPDATES = {
|
|
6
|
-
'name': true,
|
|
7
|
-
'workspace': true,
|
|
8
|
-
'agentDir': true,
|
|
9
|
-
'model.primary': true,
|
|
10
|
-
'skills': true,
|
|
11
|
-
'tools.profile': true,
|
|
12
|
-
'behavior': true,
|
|
13
|
-
};
|
|
14
|
-
export const listAgents = async (_params, context) => {
|
|
15
|
-
const configPath = path.join(context.openclawRoot, 'openclaw.json');
|
|
16
|
-
const config = await loadConfig(context.openclawRoot);
|
|
17
|
-
const items = normalizeAgentList(config);
|
|
18
|
-
return {
|
|
19
|
-
sourceConfigFile: configPath,
|
|
20
|
-
count: items.length,
|
|
21
|
-
items
|
|
22
|
-
};
|
|
23
|
-
};
|
|
24
|
-
export const createAgent = async (params, context) => {
|
|
25
|
-
const objectParams = expectObject(params);
|
|
26
|
-
const rawAgentId = typeof objectParams.agentId === 'string'
|
|
27
|
-
? objectParams.agentId
|
|
28
|
-
: typeof objectParams.id === 'string'
|
|
29
|
-
? objectParams.id
|
|
30
|
-
: undefined;
|
|
31
|
-
const agentId = sanitizeAgentId(expectString(rawAgentId, 'agentId'));
|
|
32
|
-
const configPath = path.join(context.openclawRoot, 'openclaw.json');
|
|
33
|
-
const config = await loadConfig(context.openclawRoot);
|
|
34
|
-
const agents = ensureAgentList(config);
|
|
35
|
-
if (findNamedAgent(agents, agentId)) {
|
|
36
|
-
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, `Agent already exists: ${agentId}`);
|
|
37
|
-
}
|
|
38
|
-
const workspace = typeof objectParams.workspace === 'string' && objectParams.workspace.trim().length > 0
|
|
39
|
-
? objectParams.workspace.trim()
|
|
40
|
-
: path.join(context.openclawRoot, 'workspace', agentId);
|
|
41
|
-
const agentDir = typeof objectParams.agentDir === 'string' && objectParams.agentDir.trim().length > 0
|
|
42
|
-
? objectParams.agentDir.trim()
|
|
43
|
-
: path.join(context.openclawRoot, 'agents', agentId, 'agent');
|
|
44
|
-
const created = {
|
|
45
|
-
id: agentId,
|
|
46
|
-
name: typeof objectParams.name === 'string' && objectParams.name.trim().length > 0
|
|
47
|
-
? objectParams.name.trim()
|
|
48
|
-
: agentId,
|
|
49
|
-
workspace,
|
|
50
|
-
agentDir
|
|
51
|
-
};
|
|
52
|
-
const modelPrimary = typeof objectParams.modelPrimary === 'string' && objectParams.modelPrimary.trim().length > 0
|
|
53
|
-
? objectParams.modelPrimary.trim()
|
|
54
|
-
: null;
|
|
55
|
-
const modelProvider = typeof objectParams.modelProvider === 'string' && objectParams.modelProvider.trim().length > 0
|
|
56
|
-
? objectParams.modelProvider.trim()
|
|
57
|
-
: null;
|
|
58
|
-
if (modelPrimary) {
|
|
59
|
-
const inferredProvider = inferProviderFromPrimaryModel(modelPrimary);
|
|
60
|
-
if (modelProvider && modelProvider !== inferredProvider) {
|
|
61
|
-
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, 'modelProvider does not match modelPrimary');
|
|
62
|
-
}
|
|
63
|
-
created.model = { ...(created.model ?? {}), primary: modelPrimary };
|
|
64
|
-
}
|
|
65
|
-
else if (modelProvider) {
|
|
66
|
-
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, 'modelProvider cannot be set without modelPrimary; provider is derived from model.primary');
|
|
67
|
-
}
|
|
68
|
-
if (Array.isArray(objectParams.skills)) {
|
|
69
|
-
created.skills = objectParams.skills.filter((item) => typeof item === 'string');
|
|
70
|
-
}
|
|
71
|
-
if (typeof objectParams.toolsProfile === 'string' && objectParams.toolsProfile.trim().length > 0) {
|
|
72
|
-
created.tools = { ...(created.tools ?? {}), profile: objectParams.toolsProfile.trim() };
|
|
73
|
-
}
|
|
74
|
-
if (isObject(objectParams.behavior)) {
|
|
75
|
-
created.behavior = objectParams.behavior;
|
|
76
|
-
}
|
|
77
|
-
agents.push(created);
|
|
78
|
-
await fs.mkdir(workspace, { recursive: true });
|
|
79
|
-
await fs.mkdir(agentDir, { recursive: true });
|
|
80
|
-
await writeJsonFile(configPath, config);
|
|
81
|
-
return {
|
|
82
|
-
success: true,
|
|
83
|
-
configFile: configPath,
|
|
84
|
-
created
|
|
85
|
-
};
|
|
86
|
-
};
|
|
87
|
-
export const deleteAgent = async (params, context) => {
|
|
88
|
-
const objectParams = expectObject(params);
|
|
89
|
-
const rawAgentId = typeof objectParams.agentId === 'string'
|
|
90
|
-
? objectParams.agentId
|
|
91
|
-
: typeof objectParams.id === 'string'
|
|
92
|
-
? objectParams.id
|
|
93
|
-
: undefined;
|
|
94
|
-
const agentId = sanitizeAgentId(expectString(rawAgentId, 'agentId'));
|
|
95
|
-
const purgeFiles = objectParams.purgeFiles === true;
|
|
96
|
-
const configPath = path.join(context.openclawRoot, 'openclaw.json');
|
|
97
|
-
const config = await loadConfig(context.openclawRoot);
|
|
98
|
-
const agents = ensureAgentList(config);
|
|
99
|
-
const index = agents.findIndex((agent) => isMatchingAgent(agent, agentId));
|
|
100
|
-
if (index === -1) {
|
|
101
|
-
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, `Agent not found: ${agentId}`);
|
|
102
|
-
}
|
|
103
|
-
const [removed] = agents.splice(index, 1);
|
|
104
|
-
await writeJsonFile(configPath, config);
|
|
105
|
-
const removedPaths = [];
|
|
106
|
-
if (purgeFiles) {
|
|
107
|
-
const workspaceRoot = path.normalize(path.join(context.openclawRoot, 'workspace'));
|
|
108
|
-
const agentsRoot = path.normalize(path.join(context.openclawRoot, 'agents'));
|
|
109
|
-
const purgeCandidates = [
|
|
110
|
-
typeof removed.workspace === 'string' ? removed.workspace : null,
|
|
111
|
-
typeof removed.agentDir === 'string' ? path.dirname(removed.agentDir) : null
|
|
112
|
-
].filter((value) => Boolean(value));
|
|
113
|
-
for (const candidate of purgeCandidates) {
|
|
114
|
-
const normalized = path.normalize(candidate);
|
|
115
|
-
const insideWorkspace = normalized === workspaceRoot || normalized.startsWith(`${workspaceRoot}${path.sep}`);
|
|
116
|
-
const insideAgents = normalized === agentsRoot || normalized.startsWith(`${agentsRoot}${path.sep}`);
|
|
117
|
-
if (!insideWorkspace && !insideAgents) {
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
if (await pathExists(candidate)) {
|
|
121
|
-
await fs.rm(candidate, { recursive: true, force: true });
|
|
122
|
-
removedPaths.push(candidate);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return {
|
|
127
|
-
success: true,
|
|
128
|
-
configFile: configPath,
|
|
129
|
-
removed,
|
|
130
|
-
purgeFiles,
|
|
131
|
-
removedPaths
|
|
132
|
-
};
|
|
133
|
-
};
|
|
134
|
-
export const updateAgent = async (params, context) => {
|
|
135
|
-
const objectParams = isObject(params) ? params : {};
|
|
136
|
-
const updates = isObject(objectParams.updates) ? objectParams.updates : null;
|
|
137
|
-
const rawAgentId = typeof objectParams.agentId === 'string'
|
|
138
|
-
? objectParams.agentId
|
|
139
|
-
: typeof objectParams.agentName === 'string'
|
|
140
|
-
? objectParams.agentName
|
|
141
|
-
: typeof objectParams.id === 'string'
|
|
142
|
-
? objectParams.id
|
|
143
|
-
: 'defaults';
|
|
144
|
-
const agentId = typeof rawAgentId === 'string' && rawAgentId !== 'defaults'
|
|
145
|
-
? sanitizeAgentId(rawAgentId)
|
|
146
|
-
: rawAgentId;
|
|
147
|
-
if (!updates) {
|
|
148
|
-
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, 'Missing required parameter: updates (object with field paths and values)');
|
|
149
|
-
}
|
|
150
|
-
const configPath = path.join(context.openclawRoot, 'openclaw.json');
|
|
151
|
-
const config = await loadConfig(context.openclawRoot);
|
|
152
|
-
const target = resolveAgentTarget(config, agentId);
|
|
153
|
-
const changes = [];
|
|
154
|
-
for (const [fieldPath, value] of Object.entries(updates)) {
|
|
155
|
-
if (fieldPath === 'model.provider') {
|
|
156
|
-
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, 'model.provider is derived from model.primary and cannot be updated directly');
|
|
157
|
-
}
|
|
158
|
-
if (!ALLOWED_AGENT_UPDATES[fieldPath]) {
|
|
159
|
-
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, `Field not allowed for update: ${fieldPath}. Allowed fields: ${Object.keys(ALLOWED_AGENT_UPDATES).join(', ')}`);
|
|
160
|
-
}
|
|
161
|
-
setNestedValue(target, fieldPath, value);
|
|
162
|
-
changes.push(`Updated ${fieldPath} to: ${JSON.stringify(value)}`);
|
|
163
|
-
}
|
|
164
|
-
await writeJsonFile(configPath, config);
|
|
165
|
-
return {
|
|
166
|
-
success: true,
|
|
167
|
-
configFile: configPath,
|
|
168
|
-
agentId,
|
|
169
|
-
changes,
|
|
170
|
-
updatedConfig: target
|
|
171
|
-
};
|
|
172
|
-
};
|
|
173
|
-
function setNestedValue(obj, pathExpression, value) {
|
|
174
|
-
const keys = pathExpression.split('.');
|
|
175
|
-
let current = obj;
|
|
176
|
-
for (let index = 0; index < keys.length - 1; index += 1) {
|
|
177
|
-
const key = keys[index];
|
|
178
|
-
if (!current[key] || typeof current[key] !== 'object') {
|
|
179
|
-
current[key] = {};
|
|
180
|
-
}
|
|
181
|
-
current = current[key];
|
|
182
|
-
}
|
|
183
|
-
current[keys[keys.length - 1]] = value;
|
|
184
|
-
}
|
|
185
|
-
function inferProviderFromPrimaryModel(primary) {
|
|
186
|
-
const separatorIndex = primary.indexOf('/');
|
|
187
|
-
return separatorIndex > 0 ? primary.slice(0, separatorIndex) : null;
|
|
188
|
-
}
|
|
189
|
-
function isObject(value) {
|
|
190
|
-
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
191
|
-
}
|
|
192
|
-
function expectObject(value) {
|
|
193
|
-
if (!value || Array.isArray(value) || typeof value !== 'object') {
|
|
194
|
-
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, 'Params must be an object');
|
|
195
|
-
}
|
|
196
|
-
return value;
|
|
197
|
-
}
|
|
198
|
-
function expectString(value, fieldName) {
|
|
199
|
-
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
200
|
-
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, `Field '${fieldName}' must be a non-empty string`);
|
|
201
|
-
}
|
|
202
|
-
return value.trim();
|
|
203
|
-
}
|
|
204
|
-
async function loadConfig(openclawRoot) {
|
|
205
|
-
return await readJsonFile(path.join(openclawRoot, 'openclaw.json'));
|
|
206
|
-
}
|
|
207
|
-
function ensureAgentList(config) {
|
|
208
|
-
if (!config.agents)
|
|
209
|
-
config.agents = {};
|
|
210
|
-
if (!Array.isArray(config.agents.list))
|
|
211
|
-
config.agents.list = [];
|
|
212
|
-
return config.agents.list;
|
|
213
|
-
}
|
|
214
|
-
function normalizeAgentList(config) {
|
|
215
|
-
const defaults = {
|
|
216
|
-
id: 'defaults',
|
|
217
|
-
name: 'defaults',
|
|
218
|
-
...(config.agents?.defaults ?? {})
|
|
219
|
-
};
|
|
220
|
-
const named = Array.isArray(config.agents?.list) ? config.agents.list : [];
|
|
221
|
-
return [defaults, ...named];
|
|
222
|
-
}
|
|
223
|
-
function resolveAgentTarget(config, agentName) {
|
|
224
|
-
if (agentName === 'defaults') {
|
|
225
|
-
if (!config.agents)
|
|
226
|
-
config.agents = {};
|
|
227
|
-
if (!config.agents.defaults)
|
|
228
|
-
config.agents.defaults = {};
|
|
229
|
-
return config.agents.defaults;
|
|
230
|
-
}
|
|
231
|
-
const agents = ensureAgentList(config);
|
|
232
|
-
const found = findNamedAgent(agents, agentName);
|
|
233
|
-
if (!found) {
|
|
234
|
-
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, `Agent not found: ${agentName}`);
|
|
235
|
-
}
|
|
236
|
-
return found;
|
|
237
|
-
}
|
|
238
|
-
function findNamedAgent(agents, agentName) {
|
|
239
|
-
return agents.find((agent) => isMatchingAgent(agent, agentName)) ?? null;
|
|
240
|
-
}
|
|
241
|
-
function isMatchingAgent(agent, agentName) {
|
|
242
|
-
return agent.id === agentName || agent.name === agentName;
|
|
243
|
-
}
|
|
244
|
-
function sanitizeAgentId(value) {
|
|
245
|
-
return value
|
|
246
|
-
.trim()
|
|
247
|
-
.replace(/[^a-zA-Z0-9._-]+/g, '-')
|
|
248
|
-
.replace(/-+/g, '-')
|
|
249
|
-
.replace(/^-|-$/g, '')
|
|
250
|
-
.toLowerCase();
|
|
251
|
-
}
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { pathExists, readJsonFile, writeJsonFile } from '../lib/fs.js';
|
|
4
|
+
import { JsonRpcException, JSON_RPC_ERRORS } from '../jsonrpc.js';
|
|
5
|
+
const ALLOWED_AGENT_UPDATES = {
|
|
6
|
+
'name': true,
|
|
7
|
+
'workspace': true,
|
|
8
|
+
'agentDir': true,
|
|
9
|
+
'model.primary': true,
|
|
10
|
+
'skills': true,
|
|
11
|
+
'tools.profile': true,
|
|
12
|
+
'behavior': true,
|
|
13
|
+
};
|
|
14
|
+
export const listAgents = async (_params, context) => {
|
|
15
|
+
const configPath = path.join(context.openclawRoot, 'openclaw.json');
|
|
16
|
+
const config = await loadConfig(context.openclawRoot);
|
|
17
|
+
const items = normalizeAgentList(config);
|
|
18
|
+
return {
|
|
19
|
+
sourceConfigFile: configPath,
|
|
20
|
+
count: items.length,
|
|
21
|
+
items
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
export const createAgent = async (params, context) => {
|
|
25
|
+
const objectParams = expectObject(params);
|
|
26
|
+
const rawAgentId = typeof objectParams.agentId === 'string'
|
|
27
|
+
? objectParams.agentId
|
|
28
|
+
: typeof objectParams.id === 'string'
|
|
29
|
+
? objectParams.id
|
|
30
|
+
: undefined;
|
|
31
|
+
const agentId = sanitizeAgentId(expectString(rawAgentId, 'agentId'));
|
|
32
|
+
const configPath = path.join(context.openclawRoot, 'openclaw.json');
|
|
33
|
+
const config = await loadConfig(context.openclawRoot);
|
|
34
|
+
const agents = ensureAgentList(config);
|
|
35
|
+
if (findNamedAgent(agents, agentId)) {
|
|
36
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, `Agent already exists: ${agentId}`);
|
|
37
|
+
}
|
|
38
|
+
const workspace = typeof objectParams.workspace === 'string' && objectParams.workspace.trim().length > 0
|
|
39
|
+
? objectParams.workspace.trim()
|
|
40
|
+
: path.join(context.openclawRoot, 'workspace', agentId);
|
|
41
|
+
const agentDir = typeof objectParams.agentDir === 'string' && objectParams.agentDir.trim().length > 0
|
|
42
|
+
? objectParams.agentDir.trim()
|
|
43
|
+
: path.join(context.openclawRoot, 'agents', agentId, 'agent');
|
|
44
|
+
const created = {
|
|
45
|
+
id: agentId,
|
|
46
|
+
name: typeof objectParams.name === 'string' && objectParams.name.trim().length > 0
|
|
47
|
+
? objectParams.name.trim()
|
|
48
|
+
: agentId,
|
|
49
|
+
workspace,
|
|
50
|
+
agentDir
|
|
51
|
+
};
|
|
52
|
+
const modelPrimary = typeof objectParams.modelPrimary === 'string' && objectParams.modelPrimary.trim().length > 0
|
|
53
|
+
? objectParams.modelPrimary.trim()
|
|
54
|
+
: null;
|
|
55
|
+
const modelProvider = typeof objectParams.modelProvider === 'string' && objectParams.modelProvider.trim().length > 0
|
|
56
|
+
? objectParams.modelProvider.trim()
|
|
57
|
+
: null;
|
|
58
|
+
if (modelPrimary) {
|
|
59
|
+
const inferredProvider = inferProviderFromPrimaryModel(modelPrimary);
|
|
60
|
+
if (modelProvider && modelProvider !== inferredProvider) {
|
|
61
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, 'modelProvider does not match modelPrimary');
|
|
62
|
+
}
|
|
63
|
+
created.model = { ...(created.model ?? {}), primary: modelPrimary };
|
|
64
|
+
}
|
|
65
|
+
else if (modelProvider) {
|
|
66
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, 'modelProvider cannot be set without modelPrimary; provider is derived from model.primary');
|
|
67
|
+
}
|
|
68
|
+
if (Array.isArray(objectParams.skills)) {
|
|
69
|
+
created.skills = objectParams.skills.filter((item) => typeof item === 'string');
|
|
70
|
+
}
|
|
71
|
+
if (typeof objectParams.toolsProfile === 'string' && objectParams.toolsProfile.trim().length > 0) {
|
|
72
|
+
created.tools = { ...(created.tools ?? {}), profile: objectParams.toolsProfile.trim() };
|
|
73
|
+
}
|
|
74
|
+
if (isObject(objectParams.behavior)) {
|
|
75
|
+
created.behavior = objectParams.behavior;
|
|
76
|
+
}
|
|
77
|
+
agents.push(created);
|
|
78
|
+
await fs.mkdir(workspace, { recursive: true });
|
|
79
|
+
await fs.mkdir(agentDir, { recursive: true });
|
|
80
|
+
await writeJsonFile(configPath, config);
|
|
81
|
+
return {
|
|
82
|
+
success: true,
|
|
83
|
+
configFile: configPath,
|
|
84
|
+
created
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
export const deleteAgent = async (params, context) => {
|
|
88
|
+
const objectParams = expectObject(params);
|
|
89
|
+
const rawAgentId = typeof objectParams.agentId === 'string'
|
|
90
|
+
? objectParams.agentId
|
|
91
|
+
: typeof objectParams.id === 'string'
|
|
92
|
+
? objectParams.id
|
|
93
|
+
: undefined;
|
|
94
|
+
const agentId = sanitizeAgentId(expectString(rawAgentId, 'agentId'));
|
|
95
|
+
const purgeFiles = objectParams.purgeFiles === true;
|
|
96
|
+
const configPath = path.join(context.openclawRoot, 'openclaw.json');
|
|
97
|
+
const config = await loadConfig(context.openclawRoot);
|
|
98
|
+
const agents = ensureAgentList(config);
|
|
99
|
+
const index = agents.findIndex((agent) => isMatchingAgent(agent, agentId));
|
|
100
|
+
if (index === -1) {
|
|
101
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, `Agent not found: ${agentId}`);
|
|
102
|
+
}
|
|
103
|
+
const [removed] = agents.splice(index, 1);
|
|
104
|
+
await writeJsonFile(configPath, config);
|
|
105
|
+
const removedPaths = [];
|
|
106
|
+
if (purgeFiles) {
|
|
107
|
+
const workspaceRoot = path.normalize(path.join(context.openclawRoot, 'workspace'));
|
|
108
|
+
const agentsRoot = path.normalize(path.join(context.openclawRoot, 'agents'));
|
|
109
|
+
const purgeCandidates = [
|
|
110
|
+
typeof removed.workspace === 'string' ? removed.workspace : null,
|
|
111
|
+
typeof removed.agentDir === 'string' ? path.dirname(removed.agentDir) : null
|
|
112
|
+
].filter((value) => Boolean(value));
|
|
113
|
+
for (const candidate of purgeCandidates) {
|
|
114
|
+
const normalized = path.normalize(candidate);
|
|
115
|
+
const insideWorkspace = normalized === workspaceRoot || normalized.startsWith(`${workspaceRoot}${path.sep}`);
|
|
116
|
+
const insideAgents = normalized === agentsRoot || normalized.startsWith(`${agentsRoot}${path.sep}`);
|
|
117
|
+
if (!insideWorkspace && !insideAgents) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (await pathExists(candidate)) {
|
|
121
|
+
await fs.rm(candidate, { recursive: true, force: true });
|
|
122
|
+
removedPaths.push(candidate);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
success: true,
|
|
128
|
+
configFile: configPath,
|
|
129
|
+
removed,
|
|
130
|
+
purgeFiles,
|
|
131
|
+
removedPaths
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
export const updateAgent = async (params, context) => {
|
|
135
|
+
const objectParams = isObject(params) ? params : {};
|
|
136
|
+
const updates = isObject(objectParams.updates) ? objectParams.updates : null;
|
|
137
|
+
const rawAgentId = typeof objectParams.agentId === 'string'
|
|
138
|
+
? objectParams.agentId
|
|
139
|
+
: typeof objectParams.agentName === 'string'
|
|
140
|
+
? objectParams.agentName
|
|
141
|
+
: typeof objectParams.id === 'string'
|
|
142
|
+
? objectParams.id
|
|
143
|
+
: 'defaults';
|
|
144
|
+
const agentId = typeof rawAgentId === 'string' && rawAgentId !== 'defaults'
|
|
145
|
+
? sanitizeAgentId(rawAgentId)
|
|
146
|
+
: rawAgentId;
|
|
147
|
+
if (!updates) {
|
|
148
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, 'Missing required parameter: updates (object with field paths and values)');
|
|
149
|
+
}
|
|
150
|
+
const configPath = path.join(context.openclawRoot, 'openclaw.json');
|
|
151
|
+
const config = await loadConfig(context.openclawRoot);
|
|
152
|
+
const target = resolveAgentTarget(config, agentId);
|
|
153
|
+
const changes = [];
|
|
154
|
+
for (const [fieldPath, value] of Object.entries(updates)) {
|
|
155
|
+
if (fieldPath === 'model.provider') {
|
|
156
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, 'model.provider is derived from model.primary and cannot be updated directly');
|
|
157
|
+
}
|
|
158
|
+
if (!ALLOWED_AGENT_UPDATES[fieldPath]) {
|
|
159
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, `Field not allowed for update: ${fieldPath}. Allowed fields: ${Object.keys(ALLOWED_AGENT_UPDATES).join(', ')}`);
|
|
160
|
+
}
|
|
161
|
+
setNestedValue(target, fieldPath, value);
|
|
162
|
+
changes.push(`Updated ${fieldPath} to: ${JSON.stringify(value)}`);
|
|
163
|
+
}
|
|
164
|
+
await writeJsonFile(configPath, config);
|
|
165
|
+
return {
|
|
166
|
+
success: true,
|
|
167
|
+
configFile: configPath,
|
|
168
|
+
agentId,
|
|
169
|
+
changes,
|
|
170
|
+
updatedConfig: target
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
|
+
function setNestedValue(obj, pathExpression, value) {
|
|
174
|
+
const keys = pathExpression.split('.');
|
|
175
|
+
let current = obj;
|
|
176
|
+
for (let index = 0; index < keys.length - 1; index += 1) {
|
|
177
|
+
const key = keys[index];
|
|
178
|
+
if (!current[key] || typeof current[key] !== 'object') {
|
|
179
|
+
current[key] = {};
|
|
180
|
+
}
|
|
181
|
+
current = current[key];
|
|
182
|
+
}
|
|
183
|
+
current[keys[keys.length - 1]] = value;
|
|
184
|
+
}
|
|
185
|
+
function inferProviderFromPrimaryModel(primary) {
|
|
186
|
+
const separatorIndex = primary.indexOf('/');
|
|
187
|
+
return separatorIndex > 0 ? primary.slice(0, separatorIndex) : null;
|
|
188
|
+
}
|
|
189
|
+
function isObject(value) {
|
|
190
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
191
|
+
}
|
|
192
|
+
function expectObject(value) {
|
|
193
|
+
if (!value || Array.isArray(value) || typeof value !== 'object') {
|
|
194
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, 'Params must be an object');
|
|
195
|
+
}
|
|
196
|
+
return value;
|
|
197
|
+
}
|
|
198
|
+
function expectString(value, fieldName) {
|
|
199
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
200
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, `Field '${fieldName}' must be a non-empty string`);
|
|
201
|
+
}
|
|
202
|
+
return value.trim();
|
|
203
|
+
}
|
|
204
|
+
async function loadConfig(openclawRoot) {
|
|
205
|
+
return await readJsonFile(path.join(openclawRoot, 'openclaw.json'));
|
|
206
|
+
}
|
|
207
|
+
function ensureAgentList(config) {
|
|
208
|
+
if (!config.agents)
|
|
209
|
+
config.agents = {};
|
|
210
|
+
if (!Array.isArray(config.agents.list))
|
|
211
|
+
config.agents.list = [];
|
|
212
|
+
return config.agents.list;
|
|
213
|
+
}
|
|
214
|
+
function normalizeAgentList(config) {
|
|
215
|
+
const defaults = {
|
|
216
|
+
id: 'defaults',
|
|
217
|
+
name: 'defaults',
|
|
218
|
+
...(config.agents?.defaults ?? {})
|
|
219
|
+
};
|
|
220
|
+
const named = Array.isArray(config.agents?.list) ? config.agents.list : [];
|
|
221
|
+
return [defaults, ...named];
|
|
222
|
+
}
|
|
223
|
+
function resolveAgentTarget(config, agentName) {
|
|
224
|
+
if (agentName === 'defaults') {
|
|
225
|
+
if (!config.agents)
|
|
226
|
+
config.agents = {};
|
|
227
|
+
if (!config.agents.defaults)
|
|
228
|
+
config.agents.defaults = {};
|
|
229
|
+
return config.agents.defaults;
|
|
230
|
+
}
|
|
231
|
+
const agents = ensureAgentList(config);
|
|
232
|
+
const found = findNamedAgent(agents, agentName);
|
|
233
|
+
if (!found) {
|
|
234
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, `Agent not found: ${agentName}`);
|
|
235
|
+
}
|
|
236
|
+
return found;
|
|
237
|
+
}
|
|
238
|
+
function findNamedAgent(agents, agentName) {
|
|
239
|
+
return agents.find((agent) => isMatchingAgent(agent, agentName)) ?? null;
|
|
240
|
+
}
|
|
241
|
+
function isMatchingAgent(agent, agentName) {
|
|
242
|
+
return agent.id === agentName || agent.name === agentName;
|
|
243
|
+
}
|
|
244
|
+
function sanitizeAgentId(value) {
|
|
245
|
+
return value
|
|
246
|
+
.trim()
|
|
247
|
+
.replace(/[^a-zA-Z0-9._-]+/g, '-')
|
|
248
|
+
.replace(/-+/g, '-')
|
|
249
|
+
.replace(/^-|-$/g, '')
|
|
250
|
+
.toLowerCase();
|
|
251
|
+
}
|