rol-websocket-channel 1.6.2 → 1.6.4
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/dist/index.js +56 -1
- package/dist/message-handler.js +13 -1
- package/dist/src/admin/methods/admin.js +2 -4
- package/dist/src/admin/methods/admin.test.js +5 -8
- package/dist/src/admin/methods/mem9.js +30 -9
- package/dist/src/admin/methods/models-extended.js +10 -4
- package/index.ts +69 -1
- package/message-handler.ts +16 -0
- package/package.json +1 -1
- package/src/admin/methods/admin.test.ts +7 -19
- package/src/admin/methods/admin.ts +2 -4
- package/src/admin/methods/mem9.ts +32 -9
- package/src/admin/methods/models-extended.ts +20 -5
- package/test/admin/methods/models-extended.test.ts +49 -0
- package/test/custom-message-update-ack.test.ts +77 -0
- package/test/message-handler-artifacts.test.ts +11 -0
- package/MQTT-API System.md +0 -2195
- package/MQTT-API/347/211/210/346/234/254/346/237/245/347/234/213.md +0 -2191
- package/SDK-CHANNEL-TODO.md +0 -6
package/dist/index.js
CHANGED
|
@@ -490,7 +490,8 @@ async function handleIncomingMessage(payload, account, cfg, runtime, log, mqttTo
|
|
|
490
490
|
log?.error(`[rol-websocket-channel] Failed to process message: ${err instanceof Error ? err.message : String(err)}`);
|
|
491
491
|
}
|
|
492
492
|
}
|
|
493
|
-
|
|
493
|
+
const immediateAckMessageTypes = new Set(["openclawUpdate", "pluginSelfUpdate"]);
|
|
494
|
+
export async function handleCustomMessageType(msgType, innerData, traceId, accountId, mqttTopic) {
|
|
494
495
|
const isSkillInstallFlow = msgType === "skillsInstallFromClawHub" || msgType === "skillsUpdateFromClawHub";
|
|
495
496
|
const response = {
|
|
496
497
|
type: "receiver",
|
|
@@ -503,6 +504,19 @@ async function handleCustomMessageType(msgType, innerData, traceId, accountId, m
|
|
|
503
504
|
}
|
|
504
505
|
const handlerMethod = messageHandler[msgType];
|
|
505
506
|
if (typeof handlerMethod === "function") {
|
|
507
|
+
if (immediateAckMessageTypes.has(msgType)) {
|
|
508
|
+
response.success = true;
|
|
509
|
+
response.data = {
|
|
510
|
+
ok: true,
|
|
511
|
+
action: msgType,
|
|
512
|
+
status: "running",
|
|
513
|
+
accepted: true,
|
|
514
|
+
message: `${msgType} started`,
|
|
515
|
+
};
|
|
516
|
+
publishCustomMessageResponse(response, innerData, mqttTopic);
|
|
517
|
+
void runCustomMessageHandler(msgType, handlerMethod, innerData, response, mqttTopic);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
506
520
|
try {
|
|
507
521
|
const methodResult = await handlerMethod.call(messageHandler, innerData);
|
|
508
522
|
// 兼容两种返回格式:
|
|
@@ -516,6 +530,12 @@ async function handleCustomMessageType(msgType, innerData, traceId, accountId, m
|
|
|
516
530
|
response.data = methodResult.result;
|
|
517
531
|
if (!methodResult.ok) {
|
|
518
532
|
response.error = methodResult.error?.message || "Unknown error";
|
|
533
|
+
if (methodResult.error?.code !== undefined) {
|
|
534
|
+
response.error_code = methodResult.error.code;
|
|
535
|
+
}
|
|
536
|
+
if (methodResult.error?.data !== undefined) {
|
|
537
|
+
response.error_data = methodResult.error.data;
|
|
538
|
+
}
|
|
519
539
|
if (isSkillInstallFlow) {
|
|
520
540
|
console.error(`[rol-websocket-channel] custom message failed: type=${msgType}, traceId=${traceId}, slug=${innerData?.slug ?? ""}, error=${response.error}, detail=${JSON.stringify(methodResult.error?.data ?? {})}`);
|
|
521
541
|
}
|
|
@@ -545,6 +565,41 @@ async function handleCustomMessageType(msgType, innerData, traceId, accountId, m
|
|
|
545
565
|
response.success = false;
|
|
546
566
|
response.error = `Unknown message type: ${msgType}`;
|
|
547
567
|
}
|
|
568
|
+
publishCustomMessageResponse(response, innerData, mqttTopic);
|
|
569
|
+
}
|
|
570
|
+
async function runCustomMessageHandler(msgType, handlerMethod, innerData, baseResponse, mqttTopic) {
|
|
571
|
+
const response = {
|
|
572
|
+
...baseResponse,
|
|
573
|
+
timestamp: Date.now(),
|
|
574
|
+
};
|
|
575
|
+
try {
|
|
576
|
+
const methodResult = await handlerMethod.call(messageHandler, innerData);
|
|
577
|
+
if (typeof methodResult === "object" &&
|
|
578
|
+
methodResult !== null &&
|
|
579
|
+
"ok" in methodResult) {
|
|
580
|
+
response.success = methodResult.ok;
|
|
581
|
+
response.data = methodResult.result;
|
|
582
|
+
if (!methodResult.ok) {
|
|
583
|
+
response.error = methodResult.error?.message || "Unknown error";
|
|
584
|
+
}
|
|
585
|
+
else {
|
|
586
|
+
delete response.error;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
else {
|
|
590
|
+
response.success = true;
|
|
591
|
+
response.data = methodResult;
|
|
592
|
+
delete response.error;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
catch (handlerErr) {
|
|
596
|
+
response.success = false;
|
|
597
|
+
response.data = undefined;
|
|
598
|
+
response.error = handlerErr.message;
|
|
599
|
+
}
|
|
600
|
+
publishCustomMessageResponse(response, innerData, mqttTopic);
|
|
601
|
+
}
|
|
602
|
+
function publishCustomMessageResponse(response, innerData, mqttTopic) {
|
|
548
603
|
const conn = ConnectionManager.getGlobalConnection();
|
|
549
604
|
if (conn && conn.ws && conn.ws.connected) {
|
|
550
605
|
// 根据 source_type 修改 topic 末尾的 #
|
package/dist/message-handler.js
CHANGED
|
@@ -6,7 +6,7 @@ import { getContext } from './src/shared/context.js';
|
|
|
6
6
|
import { wrapAdminCall } from './src/shared/wrapper.js';
|
|
7
7
|
import { getAgents, getConfig, setApiCoreBotConfig } from './src/admin/methods/admin.js';
|
|
8
8
|
import { createAgent, deleteAgent, listAgents, updateAgent } from './src/admin/methods/agents-extended.js';
|
|
9
|
-
import { createArtifactRecord, ensureArtifactUploaded, getArtifactContent, getArtifactPresignedPost, listArtifacts, markArtifactUploaded, refreshArtifacts } from './src/admin/methods/artifacts.js';
|
|
9
|
+
import { createArtifactRecord, ensureArtifactUploaded, findLatestArtifacts, getArtifactContent, getArtifactPresignedPost, listArtifacts, markArtifactUploaded, publishArtifact, refreshArtifacts } from './src/admin/methods/artifacts.js';
|
|
10
10
|
import { addCron, disableCron, enableCron, getCronStatus, listCron, listCronRuns, renameCron, removeCron, rescheduleCron, runCron, setCronContent, updateCronMessage, updateCronSystemEvent } from './src/admin/methods/cron.js';
|
|
11
11
|
import { listSessions } from './src/admin/methods/sessions.js';
|
|
12
12
|
import { getSession, prepareMessage, attachSkill } from './src/admin/methods/sessions-extended.js';
|
|
@@ -400,6 +400,12 @@ export class MessageHandler {
|
|
|
400
400
|
return await refreshArtifacts(data, context);
|
|
401
401
|
});
|
|
402
402
|
}
|
|
403
|
+
async artifactsFindLatest(data) {
|
|
404
|
+
return wrapAdminCall(async () => {
|
|
405
|
+
const context = getContext();
|
|
406
|
+
return await findLatestArtifacts(data, context);
|
|
407
|
+
});
|
|
408
|
+
}
|
|
403
409
|
async artifactsGetContent(data) {
|
|
404
410
|
return wrapAdminCall(async () => {
|
|
405
411
|
const context = getContext();
|
|
@@ -412,6 +418,12 @@ export class MessageHandler {
|
|
|
412
418
|
return await ensureArtifactUploaded(data, context);
|
|
413
419
|
});
|
|
414
420
|
}
|
|
421
|
+
async artifactsPublish(data) {
|
|
422
|
+
return wrapAdminCall(async () => {
|
|
423
|
+
const context = getContext();
|
|
424
|
+
return await publishArtifact(data, context);
|
|
425
|
+
});
|
|
426
|
+
}
|
|
415
427
|
async artifactsGetPresignedPost(data) {
|
|
416
428
|
return wrapAdminCall(async () => {
|
|
417
429
|
const context = getContext();
|
|
@@ -2,8 +2,6 @@ import path from 'node:path';
|
|
|
2
2
|
import { readJsonFile, writeJsonFile } from '../lib/fs.js';
|
|
3
3
|
import { JsonRpcException, JSON_RPC_ERRORS } from '../jsonrpc.js';
|
|
4
4
|
const DEFAULT_PLUGIN_ID = 'rol-websocket-channel';
|
|
5
|
-
const DEFAULT_API_CORE_BOT_BASE_URL = 'http://192.168.1.23:9092';
|
|
6
|
-
const DEFAULT_API_CORE_BOT_AUTH_TOKEN = '123';
|
|
7
5
|
export const getAgents = async (_params, context) => {
|
|
8
6
|
const configPath = path.join(context.openclawRoot, 'openclaw.json');
|
|
9
7
|
const config = await readJsonFile(configPath);
|
|
@@ -36,10 +34,10 @@ export const setApiCoreBotConfig = async (params, context) => {
|
|
|
36
34
|
const objectParams = isObject(params) ? params : {};
|
|
37
35
|
const baseUrl = typeof objectParams.baseUrl === 'string'
|
|
38
36
|
? objectParams.baseUrl.trim().replace(/\/+$/, '')
|
|
39
|
-
:
|
|
37
|
+
: '';
|
|
40
38
|
const authToken = typeof objectParams.authToken === 'string' && objectParams.authToken.trim()
|
|
41
39
|
? objectParams.authToken.trim()
|
|
42
|
-
:
|
|
40
|
+
: null;
|
|
43
41
|
if (!baseUrl) {
|
|
44
42
|
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, 'apiCoreBot.baseUrl is required');
|
|
45
43
|
}
|
|
@@ -43,15 +43,12 @@ test('setApiCoreBotConfig writes apiCoreBot endpoint without changing pairing or
|
|
|
43
43
|
assert.equal(savedConfig.plugins.entries['rol-websocket-channel'].config.pairing.pairingKeyLast4, '7a46');
|
|
44
44
|
assert.equal(savedConfig.channels['rol-websocket-channel'].config.mqttUrl, 'ws://mqtt.example.test:8083/mqtt');
|
|
45
45
|
});
|
|
46
|
-
test('setApiCoreBotConfig
|
|
46
|
+
test('setApiCoreBotConfig rejects missing baseUrl instead of writing defaults', async () => {
|
|
47
47
|
const context = await createMethodContext();
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
assert.equal(
|
|
52
|
-
assert.equal(result.apiCoreBot.authToken, '********');
|
|
53
|
-
assert.equal(savedConfig.plugins.entries['rol-websocket-channel'].config.apiCoreBot.baseUrl, 'http://192.168.1.23:9092');
|
|
54
|
-
assert.equal(savedConfig.plugins.entries['rol-websocket-channel'].config.apiCoreBot.authToken, '123');
|
|
48
|
+
const configPath = path.join(context.openclawRoot, 'openclaw.json');
|
|
49
|
+
await fs.writeFile(configPath, '{}');
|
|
50
|
+
await assert.rejects(() => setApiCoreBotConfig({}, context), /apiCoreBot\.baseUrl is required/);
|
|
51
|
+
assert.equal(await fs.readFile(configPath, 'utf8'), '{}');
|
|
55
52
|
});
|
|
56
53
|
async function createMethodContext() {
|
|
57
54
|
const projectRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'api-core-bot-config-test-'));
|
|
@@ -34,7 +34,7 @@ function resolveOpenClawBin() {
|
|
|
34
34
|
export async function installMem9(context) {
|
|
35
35
|
const config = await ensureOpenClawConfigExists(context.openclawRoot);
|
|
36
36
|
const currentState = readMem9State(config);
|
|
37
|
-
const currentEntrypoint = await findMem9RuntimeEntrypoint(context.openclawRoot);
|
|
37
|
+
const currentEntrypoint = await findMem9RuntimeEntrypoint(context.openclawRoot, config);
|
|
38
38
|
// Phase A: Plugin not installed → install only, then restart
|
|
39
39
|
if (!currentState.installed && !currentEntrypoint) {
|
|
40
40
|
await ensureOpenClawCli();
|
|
@@ -52,7 +52,7 @@ export async function installMem9(context) {
|
|
|
52
52
|
}
|
|
53
53
|
// Phase B: Installed but no key → create key, write config, restart
|
|
54
54
|
if (!currentState.configured || !currentState.apiKey) {
|
|
55
|
-
const runtimeEntrypoint = await ensureMem9RuntimeEntrypoint(context.openclawRoot);
|
|
55
|
+
const runtimeEntrypoint = await ensureMem9RuntimeEntrypoint(context.openclawRoot, config);
|
|
56
56
|
const apiKey = await createMem9Key();
|
|
57
57
|
const updated = await writeMem9Config(context.openclawRoot, apiKey);
|
|
58
58
|
const restart = await restartGateway(context.projectRoot);
|
|
@@ -73,7 +73,7 @@ export async function installMem9(context) {
|
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
75
|
// Phase C: Already configured → ensure slot/hooks/allow are correct
|
|
76
|
-
const runtimeEntrypoint = await ensureMem9RuntimeEntrypoint(context.openclawRoot);
|
|
76
|
+
const runtimeEntrypoint = await ensureMem9RuntimeEntrypoint(context.openclawRoot, config);
|
|
77
77
|
const updated = await ensureMem9SlotConfig(context.openclawRoot, currentState.apiKey);
|
|
78
78
|
const restart = await restartGateway(context.projectRoot);
|
|
79
79
|
return {
|
|
@@ -102,7 +102,7 @@ export async function reconnectMem9(key, context) {
|
|
|
102
102
|
}
|
|
103
103
|
const config = await ensureOpenClawConfigExists(context.openclawRoot);
|
|
104
104
|
const previousState = readMem9State(config);
|
|
105
|
-
const runtimeEntrypoint = await ensureMem9RuntimeEntrypoint(context.openclawRoot);
|
|
105
|
+
const runtimeEntrypoint = await ensureMem9RuntimeEntrypoint(context.openclawRoot, config);
|
|
106
106
|
const updated = await writeMem9Config(context.openclawRoot, apiKey);
|
|
107
107
|
const restart = await restartGateway(context.projectRoot);
|
|
108
108
|
return {
|
|
@@ -178,8 +178,8 @@ async function installMem9Plugin(cwd) {
|
|
|
178
178
|
});
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
|
-
export async function findMem9RuntimeEntrypoint(openclawRoot) {
|
|
182
|
-
for (const packageRoot of
|
|
181
|
+
export async function findMem9RuntimeEntrypoint(openclawRoot, config) {
|
|
182
|
+
for (const packageRoot of resolveMem9RuntimePackageRoots(openclawRoot, config)) {
|
|
183
183
|
for (const entrypoint of RUNTIME_ENTRYPOINTS.map((item) => path.join(packageRoot, item))) {
|
|
184
184
|
if (await pathExists(entrypoint)) {
|
|
185
185
|
return entrypoint;
|
|
@@ -188,17 +188,34 @@ export async function findMem9RuntimeEntrypoint(openclawRoot) {
|
|
|
188
188
|
}
|
|
189
189
|
return null;
|
|
190
190
|
}
|
|
191
|
-
async function ensureMem9RuntimeEntrypoint(openclawRoot) {
|
|
192
|
-
const entrypoint = await findMem9RuntimeEntrypoint(openclawRoot);
|
|
191
|
+
async function ensureMem9RuntimeEntrypoint(openclawRoot, config) {
|
|
192
|
+
const entrypoint = await findMem9RuntimeEntrypoint(openclawRoot, config);
|
|
193
193
|
if (entrypoint) {
|
|
194
194
|
return entrypoint;
|
|
195
195
|
}
|
|
196
|
+
const installRecord = readMem9InstallRecord(config);
|
|
197
|
+
const checkedPackageRoots = resolveMem9RuntimePackageRoots(openclawRoot, config);
|
|
196
198
|
throw new JsonRpcException(JSON_RPC_ERRORS.internalError, 'mem9 plugin is installed but missing compiled runtime output', {
|
|
197
199
|
code: 'MEM9_RUNTIME_OUTPUT_MISSING',
|
|
198
200
|
expected: RUNTIME_ENTRYPOINTS.map((item) => `./${item.replace(/\\/g, '/')}`),
|
|
199
|
-
|
|
201
|
+
checkedPackageRoots,
|
|
202
|
+
checkedEntrypoints: checkedPackageRoots.flatMap((packageRoot) => RUNTIME_ENTRYPOINTS.map((item) => path.join(packageRoot, item))),
|
|
203
|
+
installRecord
|
|
200
204
|
});
|
|
201
205
|
}
|
|
206
|
+
function resolveMem9RuntimePackageRoots(openclawRoot, config) {
|
|
207
|
+
const roots = [];
|
|
208
|
+
const installPath = pickString(readMem9InstallRecord(config)?.installPath);
|
|
209
|
+
if (installPath) {
|
|
210
|
+
roots.push(path.isAbsolute(installPath) ? installPath : path.resolve(openclawRoot, installPath));
|
|
211
|
+
}
|
|
212
|
+
for (const fallbackRoot of MEM9_PACKAGE_ROOTS.map((item) => path.join(openclawRoot, item))) {
|
|
213
|
+
if (!roots.includes(fallbackRoot)) {
|
|
214
|
+
roots.push(fallbackRoot);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return roots;
|
|
218
|
+
}
|
|
202
219
|
async function createMem9Key() {
|
|
203
220
|
let response;
|
|
204
221
|
try {
|
|
@@ -368,6 +385,10 @@ function pickString(value) {
|
|
|
368
385
|
function isRecord(value) {
|
|
369
386
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
370
387
|
}
|
|
388
|
+
function readMem9InstallRecord(config) {
|
|
389
|
+
const record = config?.plugins?.installs?.[MEM9_PLUGIN_ID];
|
|
390
|
+
return isRecord(record) ? record : null;
|
|
391
|
+
}
|
|
371
392
|
function readMem9State(config) {
|
|
372
393
|
const installed = Boolean((config.plugins?.installs && typeof config.plugins.installs === 'object' && MEM9_PLUGIN_ID in config.plugins.installs)
|
|
373
394
|
|| (config.plugins?.entries && typeof config.plugins.entries === 'object' && MEM9_PLUGIN_ID in config.plugins.entries));
|
|
@@ -32,6 +32,16 @@ export const updateModels = async (params, context) => {
|
|
|
32
32
|
const changes = [];
|
|
33
33
|
// 更新 primary model
|
|
34
34
|
if (primaryModel) {
|
|
35
|
+
const inferredProvider = inferProviderFromPrimaryModel(primaryModel);
|
|
36
|
+
if (!inferredProvider) {
|
|
37
|
+
throwModelError('MODEL_PRIMARY_INVALID', 'primaryModel must be in provider/model format');
|
|
38
|
+
}
|
|
39
|
+
if (modelProvider && modelProvider !== inferredProvider) {
|
|
40
|
+
throwModelError('MODEL_PROVIDER_MISMATCH', 'modelProvider does not match primaryModel');
|
|
41
|
+
}
|
|
42
|
+
if (!isAllowedModel(config, primaryModel)) {
|
|
43
|
+
throwModelError('MODEL_NOT_ALLOWED', 'model is not in allowed model options');
|
|
44
|
+
}
|
|
35
45
|
if (!config.agents)
|
|
36
46
|
config.agents = {};
|
|
37
47
|
if (!config.agents.defaults)
|
|
@@ -39,10 +49,6 @@ export const updateModels = async (params, context) => {
|
|
|
39
49
|
if (!config.agents.defaults.model)
|
|
40
50
|
config.agents.defaults.model = {};
|
|
41
51
|
config.agents.defaults.model.primary = primaryModel;
|
|
42
|
-
const inferredProvider = inferProviderFromPrimaryModel(primaryModel);
|
|
43
|
-
if (modelProvider && modelProvider !== inferredProvider) {
|
|
44
|
-
throwModelError('MODEL_PROVIDER_MISMATCH', 'modelProvider does not match primaryModel');
|
|
45
|
-
}
|
|
46
52
|
updated = true;
|
|
47
53
|
changes.push(`Updated primary model to: ${primaryModel}`);
|
|
48
54
|
}
|
package/index.ts
CHANGED
|
@@ -625,7 +625,9 @@ async function handleIncomingMessage(
|
|
|
625
625
|
}
|
|
626
626
|
}
|
|
627
627
|
|
|
628
|
-
|
|
628
|
+
const immediateAckMessageTypes = new Set(["openclawUpdate", "pluginSelfUpdate"]);
|
|
629
|
+
|
|
630
|
+
export async function handleCustomMessageType(
|
|
629
631
|
msgType: string,
|
|
630
632
|
innerData: any,
|
|
631
633
|
traceId: string,
|
|
@@ -648,6 +650,21 @@ async function handleCustomMessageType(
|
|
|
648
650
|
|
|
649
651
|
const handlerMethod = (messageHandler as any)[msgType];
|
|
650
652
|
if (typeof handlerMethod === "function") {
|
|
653
|
+
if (immediateAckMessageTypes.has(msgType)) {
|
|
654
|
+
response.success = true;
|
|
655
|
+
response.data = {
|
|
656
|
+
ok: true,
|
|
657
|
+
action: msgType,
|
|
658
|
+
status: "running",
|
|
659
|
+
accepted: true,
|
|
660
|
+
message: `${msgType} started`,
|
|
661
|
+
};
|
|
662
|
+
publishCustomMessageResponse(response, innerData, mqttTopic);
|
|
663
|
+
|
|
664
|
+
void runCustomMessageHandler(msgType, handlerMethod, innerData, response, mqttTopic);
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
|
|
651
668
|
try {
|
|
652
669
|
const methodResult = await handlerMethod.call(messageHandler, innerData);
|
|
653
670
|
|
|
@@ -664,6 +681,12 @@ async function handleCustomMessageType(
|
|
|
664
681
|
response.data = methodResult.result;
|
|
665
682
|
if (!methodResult.ok) {
|
|
666
683
|
response.error = methodResult.error?.message || "Unknown error";
|
|
684
|
+
if (methodResult.error?.code !== undefined) {
|
|
685
|
+
response.error_code = methodResult.error.code;
|
|
686
|
+
}
|
|
687
|
+
if (methodResult.error?.data !== undefined) {
|
|
688
|
+
response.error_data = methodResult.error.data;
|
|
689
|
+
}
|
|
667
690
|
if (isSkillInstallFlow) {
|
|
668
691
|
console.error(
|
|
669
692
|
`[rol-websocket-channel] custom message failed: type=${msgType}, traceId=${traceId}, slug=${innerData?.slug ?? ""}, error=${response.error}, detail=${JSON.stringify(methodResult.error?.data ?? {})}`,
|
|
@@ -698,6 +721,51 @@ async function handleCustomMessageType(
|
|
|
698
721
|
response.error = `Unknown message type: ${msgType}`;
|
|
699
722
|
}
|
|
700
723
|
|
|
724
|
+
publishCustomMessageResponse(response, innerData, mqttTopic);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
async function runCustomMessageHandler(
|
|
728
|
+
msgType: string,
|
|
729
|
+
handlerMethod: Function,
|
|
730
|
+
innerData: any,
|
|
731
|
+
baseResponse: any,
|
|
732
|
+
mqttTopic: string,
|
|
733
|
+
): Promise<void> {
|
|
734
|
+
const response = {
|
|
735
|
+
...baseResponse,
|
|
736
|
+
timestamp: Date.now(),
|
|
737
|
+
};
|
|
738
|
+
|
|
739
|
+
try {
|
|
740
|
+
const methodResult = await handlerMethod.call(messageHandler, innerData);
|
|
741
|
+
|
|
742
|
+
if (
|
|
743
|
+
typeof methodResult === "object" &&
|
|
744
|
+
methodResult !== null &&
|
|
745
|
+
"ok" in methodResult
|
|
746
|
+
) {
|
|
747
|
+
response.success = methodResult.ok;
|
|
748
|
+
response.data = methodResult.result;
|
|
749
|
+
if (!methodResult.ok) {
|
|
750
|
+
response.error = methodResult.error?.message || "Unknown error";
|
|
751
|
+
} else {
|
|
752
|
+
delete response.error;
|
|
753
|
+
}
|
|
754
|
+
} else {
|
|
755
|
+
response.success = true;
|
|
756
|
+
response.data = methodResult;
|
|
757
|
+
delete response.error;
|
|
758
|
+
}
|
|
759
|
+
} catch (handlerErr: any) {
|
|
760
|
+
response.success = false;
|
|
761
|
+
response.data = undefined;
|
|
762
|
+
response.error = handlerErr.message;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
publishCustomMessageResponse(response, innerData, mqttTopic);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
function publishCustomMessageResponse(response: any, innerData: any, mqttTopic: string): void {
|
|
701
769
|
const conn = ConnectionManager.getGlobalConnection();
|
|
702
770
|
if (conn && conn.ws && conn.ws.connected) {
|
|
703
771
|
// 根据 source_type 修改 topic 末尾的 #
|
package/message-handler.ts
CHANGED
|
@@ -10,10 +10,12 @@ import { createAgent, deleteAgent, listAgents, updateAgent } from './src/admin/m
|
|
|
10
10
|
import {
|
|
11
11
|
createArtifactRecord,
|
|
12
12
|
ensureArtifactUploaded,
|
|
13
|
+
findLatestArtifacts,
|
|
13
14
|
getArtifactContent,
|
|
14
15
|
getArtifactPresignedPost,
|
|
15
16
|
listArtifacts,
|
|
16
17
|
markArtifactUploaded,
|
|
18
|
+
publishArtifact,
|
|
17
19
|
refreshArtifacts
|
|
18
20
|
} from './src/admin/methods/artifacts.js';
|
|
19
21
|
import {
|
|
@@ -493,6 +495,13 @@ export class MessageHandler {
|
|
|
493
495
|
});
|
|
494
496
|
}
|
|
495
497
|
|
|
498
|
+
async artifactsFindLatest(data: any): Promise<any> {
|
|
499
|
+
return wrapAdminCall(async () => {
|
|
500
|
+
const context = getContext();
|
|
501
|
+
return await findLatestArtifacts(data, context);
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
496
505
|
async artifactsGetContent(data: any): Promise<any> {
|
|
497
506
|
return wrapAdminCall(async () => {
|
|
498
507
|
const context = getContext();
|
|
@@ -507,6 +516,13 @@ export class MessageHandler {
|
|
|
507
516
|
});
|
|
508
517
|
}
|
|
509
518
|
|
|
519
|
+
async artifactsPublish(data: any): Promise<any> {
|
|
520
|
+
return wrapAdminCall(async () => {
|
|
521
|
+
const context = getContext();
|
|
522
|
+
return await publishArtifact(data, context);
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
|
|
510
526
|
async artifactsGetPresignedPost(data: any): Promise<any> {
|
|
511
527
|
return wrapAdminCall(async () => {
|
|
512
528
|
const context = getContext();
|
package/package.json
CHANGED
|
@@ -67,28 +67,16 @@ test('setApiCoreBotConfig writes apiCoreBot endpoint without changing pairing or
|
|
|
67
67
|
);
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
-
test('setApiCoreBotConfig
|
|
70
|
+
test('setApiCoreBotConfig rejects missing baseUrl instead of writing defaults', async () => {
|
|
71
71
|
const context = await createMethodContext();
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const result = await setApiCoreBotConfig({}, context) as {
|
|
75
|
-
apiCoreBot: {
|
|
76
|
-
baseUrl: string;
|
|
77
|
-
authToken: string;
|
|
78
|
-
};
|
|
79
|
-
};
|
|
80
|
-
const savedConfig = JSON.parse(await fs.readFile(path.join(context.openclawRoot, 'openclaw.json'), 'utf8'));
|
|
72
|
+
const configPath = path.join(context.openclawRoot, 'openclaw.json');
|
|
73
|
+
await fs.writeFile(configPath, '{}');
|
|
81
74
|
|
|
82
|
-
assert.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
savedConfig.plugins.entries['rol-websocket-channel'].config.apiCoreBot.baseUrl,
|
|
86
|
-
'http://192.168.1.23:9092'
|
|
87
|
-
);
|
|
88
|
-
assert.equal(
|
|
89
|
-
savedConfig.plugins.entries['rol-websocket-channel'].config.apiCoreBot.authToken,
|
|
90
|
-
'123'
|
|
75
|
+
await assert.rejects(
|
|
76
|
+
() => setApiCoreBotConfig({}, context),
|
|
77
|
+
/apiCoreBot\.baseUrl is required/
|
|
91
78
|
);
|
|
79
|
+
assert.equal(await fs.readFile(configPath, 'utf8'), '{}');
|
|
92
80
|
});
|
|
93
81
|
|
|
94
82
|
async function createMethodContext(): Promise<MethodContext> {
|
|
@@ -5,8 +5,6 @@ import { JsonRpcException, JSON_RPC_ERRORS } from '../jsonrpc.js';
|
|
|
5
5
|
import type { JsonValue, MethodHandler } from '../types.js';
|
|
6
6
|
|
|
7
7
|
const DEFAULT_PLUGIN_ID = 'rol-websocket-channel';
|
|
8
|
-
const DEFAULT_API_CORE_BOT_BASE_URL = 'http://192.168.1.23:9092';
|
|
9
|
-
const DEFAULT_API_CORE_BOT_AUTH_TOKEN = '123';
|
|
10
8
|
|
|
11
9
|
interface OpenClawConfig {
|
|
12
10
|
agents?: {
|
|
@@ -63,10 +61,10 @@ export const setApiCoreBotConfig: MethodHandler = async (params, context): Promi
|
|
|
63
61
|
const objectParams = isObject(params) ? params : {};
|
|
64
62
|
const baseUrl = typeof objectParams.baseUrl === 'string'
|
|
65
63
|
? objectParams.baseUrl.trim().replace(/\/+$/, '')
|
|
66
|
-
:
|
|
64
|
+
: '';
|
|
67
65
|
const authToken = typeof objectParams.authToken === 'string' && objectParams.authToken.trim()
|
|
68
66
|
? objectParams.authToken.trim()
|
|
69
|
-
:
|
|
67
|
+
: null;
|
|
70
68
|
|
|
71
69
|
if (!baseUrl) {
|
|
72
70
|
throw new JsonRpcException(
|
|
@@ -53,7 +53,7 @@ function resolveOpenClawBin(): string {
|
|
|
53
53
|
export async function installMem9(context: MethodContext): Promise<JsonValue> {
|
|
54
54
|
const config = await ensureOpenClawConfigExists(context.openclawRoot);
|
|
55
55
|
const currentState = readMem9State(config);
|
|
56
|
-
const currentEntrypoint = await findMem9RuntimeEntrypoint(context.openclawRoot);
|
|
56
|
+
const currentEntrypoint = await findMem9RuntimeEntrypoint(context.openclawRoot, config);
|
|
57
57
|
|
|
58
58
|
// Phase A: Plugin not installed → install only, then restart
|
|
59
59
|
if (!currentState.installed && !currentEntrypoint) {
|
|
@@ -74,7 +74,7 @@ export async function installMem9(context: MethodContext): Promise<JsonValue> {
|
|
|
74
74
|
|
|
75
75
|
// Phase B: Installed but no key → create key, write config, restart
|
|
76
76
|
if (!currentState.configured || !currentState.apiKey) {
|
|
77
|
-
const runtimeEntrypoint = await ensureMem9RuntimeEntrypoint(context.openclawRoot);
|
|
77
|
+
const runtimeEntrypoint = await ensureMem9RuntimeEntrypoint(context.openclawRoot, config);
|
|
78
78
|
const apiKey = await createMem9Key();
|
|
79
79
|
const updated = await writeMem9Config(context.openclawRoot, apiKey);
|
|
80
80
|
const restart = await restartGateway(context.projectRoot);
|
|
@@ -97,7 +97,7 @@ export async function installMem9(context: MethodContext): Promise<JsonValue> {
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
// Phase C: Already configured → ensure slot/hooks/allow are correct
|
|
100
|
-
const runtimeEntrypoint = await ensureMem9RuntimeEntrypoint(context.openclawRoot);
|
|
100
|
+
const runtimeEntrypoint = await ensureMem9RuntimeEntrypoint(context.openclawRoot, config);
|
|
101
101
|
const updated = await ensureMem9SlotConfig(context.openclawRoot, currentState.apiKey!);
|
|
102
102
|
const restart = await restartGateway(context.projectRoot);
|
|
103
103
|
|
|
@@ -130,7 +130,7 @@ export async function reconnectMem9(key: string, context: MethodContext): Promis
|
|
|
130
130
|
|
|
131
131
|
const config = await ensureOpenClawConfigExists(context.openclawRoot);
|
|
132
132
|
const previousState = readMem9State(config);
|
|
133
|
-
const runtimeEntrypoint = await ensureMem9RuntimeEntrypoint(context.openclawRoot);
|
|
133
|
+
const runtimeEntrypoint = await ensureMem9RuntimeEntrypoint(context.openclawRoot, config);
|
|
134
134
|
const updated = await writeMem9Config(context.openclawRoot, apiKey);
|
|
135
135
|
const restart = await restartGateway(context.projectRoot);
|
|
136
136
|
|
|
@@ -225,8 +225,8 @@ async function installMem9Plugin(cwd: string): Promise<void> {
|
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
-
export async function findMem9RuntimeEntrypoint(openclawRoot: string): Promise<string | null> {
|
|
229
|
-
for (const packageRoot of
|
|
228
|
+
export async function findMem9RuntimeEntrypoint(openclawRoot: string, config?: OpenClawConfig): Promise<string | null> {
|
|
229
|
+
for (const packageRoot of resolveMem9RuntimePackageRoots(openclawRoot, config)) {
|
|
230
230
|
for (const entrypoint of RUNTIME_ENTRYPOINTS.map((item) => path.join(packageRoot, item))) {
|
|
231
231
|
if (await pathExists(entrypoint)) {
|
|
232
232
|
return entrypoint;
|
|
@@ -236,22 +236,40 @@ export async function findMem9RuntimeEntrypoint(openclawRoot: string): Promise<s
|
|
|
236
236
|
return null;
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
-
async function ensureMem9RuntimeEntrypoint(openclawRoot: string): Promise<string> {
|
|
240
|
-
const entrypoint = await findMem9RuntimeEntrypoint(openclawRoot);
|
|
239
|
+
async function ensureMem9RuntimeEntrypoint(openclawRoot: string, config?: OpenClawConfig): Promise<string> {
|
|
240
|
+
const entrypoint = await findMem9RuntimeEntrypoint(openclawRoot, config);
|
|
241
241
|
if (entrypoint) {
|
|
242
242
|
return entrypoint;
|
|
243
243
|
}
|
|
244
|
+
const installRecord = readMem9InstallRecord(config);
|
|
245
|
+
const checkedPackageRoots = resolveMem9RuntimePackageRoots(openclawRoot, config);
|
|
244
246
|
throw new JsonRpcException(
|
|
245
247
|
JSON_RPC_ERRORS.internalError,
|
|
246
248
|
'mem9 plugin is installed but missing compiled runtime output',
|
|
247
249
|
{
|
|
248
250
|
code: 'MEM9_RUNTIME_OUTPUT_MISSING',
|
|
249
251
|
expected: RUNTIME_ENTRYPOINTS.map((item) => `./${item.replace(/\\/g, '/')}`),
|
|
250
|
-
|
|
252
|
+
checkedPackageRoots,
|
|
253
|
+
checkedEntrypoints: checkedPackageRoots.flatMap((packageRoot) => RUNTIME_ENTRYPOINTS.map((item) => path.join(packageRoot, item))),
|
|
254
|
+
installRecord
|
|
251
255
|
}
|
|
252
256
|
);
|
|
253
257
|
}
|
|
254
258
|
|
|
259
|
+
function resolveMem9RuntimePackageRoots(openclawRoot: string, config?: OpenClawConfig): string[] {
|
|
260
|
+
const roots: string[] = [];
|
|
261
|
+
const installPath = pickString(readMem9InstallRecord(config)?.installPath);
|
|
262
|
+
if (installPath) {
|
|
263
|
+
roots.push(path.isAbsolute(installPath) ? installPath : path.resolve(openclawRoot, installPath));
|
|
264
|
+
}
|
|
265
|
+
for (const fallbackRoot of MEM9_PACKAGE_ROOTS.map((item) => path.join(openclawRoot, item))) {
|
|
266
|
+
if (!roots.includes(fallbackRoot)) {
|
|
267
|
+
roots.push(fallbackRoot);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return roots;
|
|
271
|
+
}
|
|
272
|
+
|
|
255
273
|
async function createMem9Key(): Promise<string> {
|
|
256
274
|
let response: Response;
|
|
257
275
|
try {
|
|
@@ -447,6 +465,11 @@ function isRecord(value: unknown): value is Record<string, any> {
|
|
|
447
465
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
448
466
|
}
|
|
449
467
|
|
|
468
|
+
function readMem9InstallRecord(config?: OpenClawConfig): Record<string, any> | null {
|
|
469
|
+
const record = config?.plugins?.installs?.[MEM9_PLUGIN_ID];
|
|
470
|
+
return isRecord(record) ? record : null;
|
|
471
|
+
}
|
|
472
|
+
|
|
450
473
|
function readMem9State(config: OpenClawConfig): {
|
|
451
474
|
installed: boolean;
|
|
452
475
|
configured: boolean;
|
|
@@ -62,18 +62,33 @@ export const updateModels: MethodHandler = async (
|
|
|
62
62
|
|
|
63
63
|
// 更新 primary model
|
|
64
64
|
if (primaryModel) {
|
|
65
|
-
if (!config.agents) config.agents = {};
|
|
66
|
-
if (!config.agents.defaults) config.agents.defaults = {};
|
|
67
|
-
if (!config.agents.defaults.model) config.agents.defaults.model = {};
|
|
68
|
-
|
|
69
|
-
config.agents.defaults.model.primary = primaryModel;
|
|
70
65
|
const inferredProvider = inferProviderFromPrimaryModel(primaryModel);
|
|
66
|
+
if (!inferredProvider) {
|
|
67
|
+
throwModelError(
|
|
68
|
+
'MODEL_PRIMARY_INVALID',
|
|
69
|
+
'primaryModel must be in provider/model format'
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
71
73
|
if (modelProvider && modelProvider !== inferredProvider) {
|
|
72
74
|
throwModelError(
|
|
73
75
|
'MODEL_PROVIDER_MISMATCH',
|
|
74
76
|
'modelProvider does not match primaryModel'
|
|
75
77
|
);
|
|
76
78
|
}
|
|
79
|
+
|
|
80
|
+
if (!isAllowedModel(config, primaryModel)) {
|
|
81
|
+
throwModelError(
|
|
82
|
+
'MODEL_NOT_ALLOWED',
|
|
83
|
+
'model is not in allowed model options'
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!config.agents) config.agents = {};
|
|
88
|
+
if (!config.agents.defaults) config.agents.defaults = {};
|
|
89
|
+
if (!config.agents.defaults.model) config.agents.defaults.model = {};
|
|
90
|
+
|
|
91
|
+
config.agents.defaults.model.primary = primaryModel;
|
|
77
92
|
updated = true;
|
|
78
93
|
changes.push(`Updated primary model to: ${primaryModel}`);
|
|
79
94
|
}
|