rol-websocket-channel 1.1.3 → 1.1.5

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/index.ts CHANGED
@@ -536,6 +536,7 @@ async function handleCustomMessageType(
536
536
  accountId: string,
537
537
  mqttTopic: string,
538
538
  ): Promise<void> {
539
+ const isSkillInstallFlow = msgType === "skillsInstallFromClawHub" || msgType === "skillsUpdateFromClawHub";
539
540
  const response: any = {
540
541
  type: "receiver",
541
542
  trace_id: traceId,
@@ -543,6 +544,12 @@ async function handleCustomMessageType(
543
544
  timestamp: Date.now(),
544
545
  };
545
546
 
547
+ if (isSkillInstallFlow) {
548
+ console.log(
549
+ `[rol-websocket-channel] custom message start: type=${msgType}, traceId=${traceId}, slug=${innerData?.slug ?? ""}, accountId=${accountId}, topic=${mqttTopic}`,
550
+ );
551
+ }
552
+
546
553
  const handlerMethod = (messageHandler as any)[msgType];
547
554
  if (typeof handlerMethod === "function") {
548
555
  try {
@@ -561,15 +568,34 @@ async function handleCustomMessageType(
561
568
  response.data = methodResult.result;
562
569
  if (!methodResult.ok) {
563
570
  response.error = methodResult.error?.message || "Unknown error";
571
+ if (isSkillInstallFlow) {
572
+ console.error(
573
+ `[rol-websocket-channel] custom message failed: type=${msgType}, traceId=${traceId}, slug=${innerData?.slug ?? ""}, error=${response.error}, detail=${JSON.stringify(methodResult.error?.data ?? {})}`,
574
+ );
575
+ }
576
+ } else if (isSkillInstallFlow) {
577
+ console.log(
578
+ `[rol-websocket-channel] custom message success: type=${msgType}, traceId=${traceId}, slug=${innerData?.slug ?? ""}`,
579
+ );
564
580
  }
565
581
  } else {
566
582
  // 旧格式:直接返回数据
567
583
  response.success = true;
568
584
  response.data = methodResult;
585
+ if (isSkillInstallFlow) {
586
+ console.log(
587
+ `[rol-websocket-channel] custom message success: type=${msgType}, traceId=${traceId}, slug=${innerData?.slug ?? ""}`,
588
+ );
589
+ }
569
590
  }
570
591
  } catch (handlerErr: any) {
571
592
  response.success = false;
572
593
  response.error = handlerErr.message;
594
+ if (isSkillInstallFlow) {
595
+ console.error(
596
+ `[rol-websocket-channel] custom message threw: type=${msgType}, traceId=${traceId}, slug=${innerData?.slug ?? ""}, error=${handlerErr?.message ?? String(handlerErr)}`,
597
+ );
598
+ }
573
599
  }
574
600
  } else {
575
601
  response.success = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rol-websocket-channel",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "Unified OpenClaw plugin: MQTT Channel + Admin Bridge for remote management",
5
5
  "license": "MIT",
6
6
  "author": "nixgnehc",
@@ -21,6 +21,9 @@ export const getSession: MethodHandler = async (
21
21
  context
22
22
  ): Promise<JsonValue> => {
23
23
  const objectParams = isObject(params) ? params : {};
24
+ const agentId = typeof objectParams.agentId === 'string' && objectParams.agentId.trim()
25
+ ? objectParams.agentId.trim()
26
+ : undefined;
24
27
  const sessionId = typeof objectParams.sessionId === 'string' ? objectParams.sessionId : null;
25
28
  const requestedLimit = typeof objectParams.limit === 'number' ? objectParams.limit : MAX_SESSION_MESSAGES;
26
29
  const requestedOffset = typeof objectParams.offset === 'number' ? objectParams.offset : 0;
@@ -34,11 +37,11 @@ export const getSession: MethodHandler = async (
34
37
  );
35
38
  }
36
39
 
37
- const session = await findSessionRecord(context, sessionId);
40
+ const session = await findSessionRecord(context, sessionId, agentId);
38
41
  if (!session) {
39
42
  throw new JsonRpcException(
40
43
  JSON_RPC_ERRORS.invalidParams,
41
- `Session not found: ${sessionId}`
44
+ agentId ? `Session not found: ${sessionId} for agent ${agentId}` : `Session not found: ${sessionId}`
42
45
  );
43
46
  }
44
47
 
@@ -52,6 +55,7 @@ export const getSession: MethodHandler = async (
52
55
  const messages = await readSessionMessages(sessionFile, limit, offset);
53
56
 
54
57
  return {
58
+ agentId: session.agentId,
55
59
  agentName: session.agentName,
56
60
  sessionId: session.sessionId ?? sessionId,
57
61
  sessionKey: session.sessionKey,
@@ -76,6 +80,9 @@ export const prepareMessage: MethodHandler = async (
76
80
  context
77
81
  ): Promise<JsonValue> => {
78
82
  const objectParams = isObject(params) ? params : {};
83
+ const agentId = typeof objectParams.agentId === 'string' && objectParams.agentId.trim()
84
+ ? objectParams.agentId.trim()
85
+ : undefined;
79
86
  const sessionId = typeof objectParams.sessionId === 'string' ? objectParams.sessionId : null;
80
87
  const message = typeof objectParams.message === 'string' ? objectParams.message : null;
81
88
  const attachedSkills = Array.isArray(objectParams.attachedSkills) ? objectParams.attachedSkills : [];
@@ -94,15 +101,16 @@ export const prepareMessage: MethodHandler = async (
94
101
  );
95
102
  }
96
103
 
97
- const session = await findSessionRecord(context, sessionId);
104
+ const session = await findSessionRecord(context, sessionId, agentId);
98
105
  if (!session) {
99
106
  throw new JsonRpcException(
100
107
  JSON_RPC_ERRORS.invalidParams,
101
- `Session not found: ${sessionId}`
108
+ agentId ? `Session not found: ${sessionId} for agent ${agentId}` : `Session not found: ${sessionId}`
102
109
  );
103
110
  }
104
111
 
105
112
  const messageData: any = {
113
+ agentId: session.agentId,
106
114
  agentName: session.agentName,
107
115
  sessionId: session.sessionId ?? sessionId,
108
116
  sessionKey: session.sessionKey,
@@ -26,6 +26,7 @@ interface SessionsIndexEntry {
26
26
  type SessionsIndex = Record<string, SessionsIndexEntry>;
27
27
 
28
28
  export interface AgentSessionRecord {
29
+ agentId: string;
29
30
  agentName: string;
30
31
  sessionKey: string;
31
32
  sessionId: string | null;
@@ -41,17 +42,24 @@ export interface AgentSessionRecord {
41
42
  }
42
43
 
43
44
  export const listSessions: MethodHandler = async (
44
- _params,
45
+ params,
45
46
  context
46
47
  ): Promise<JsonValue> => {
47
- const items = await listAllAgentSessions(context);
48
+ const objectParams = isObject(params) ? params : {};
49
+ const agentId = typeof objectParams.agentId === 'string' && objectParams.agentId.trim()
50
+ ? objectParams.agentId.trim()
51
+ : undefined;
52
+ const items = await listAllAgentSessions(context, agentId);
48
53
  return {
49
54
  count: items.length,
50
55
  items
51
56
  };
52
57
  };
53
58
 
54
- export async function listAllAgentSessions(context: MethodContext): Promise<AgentSessionRecord[]> {
59
+ export async function listAllAgentSessions(
60
+ context: MethodContext,
61
+ agentId?: string
62
+ ): Promise<AgentSessionRecord[]> {
55
63
  const agentsRoot = path.join(context.openclawRoot, 'agents');
56
64
  if (!(await pathExists(agentsRoot))) {
57
65
  return [];
@@ -66,6 +74,10 @@ export async function listAllAgentSessions(context: MethodContext): Promise<Agen
66
74
  }
67
75
 
68
76
  const agentName = agentEntry.name;
77
+ if (agentId && agentName !== agentId) {
78
+ continue;
79
+ }
80
+
69
81
  const sessionsPath = path.join(agentsRoot, agentName, 'sessions', 'sessions.json');
70
82
  if (!(await pathExists(sessionsPath))) {
71
83
  continue;
@@ -75,6 +87,7 @@ export async function listAllAgentSessions(context: MethodContext): Promise<Agen
75
87
  for (const [sessionKey, entry] of Object.entries(sessions)) {
76
88
  const sessionId = entry.sessionId ?? extractSessionIdFromFile(entry.sessionFile) ?? null;
77
89
  items.push({
90
+ agentId: agentName,
78
91
  agentName,
79
92
  sessionKey,
80
93
  sessionId,
@@ -98,20 +111,26 @@ export async function listAllAgentSessions(context: MethodContext): Promise<Agen
98
111
 
99
112
  export async function findSessionRecord(
100
113
  context: MethodContext,
101
- sessionIdOrKey: string
114
+ sessionIdOrKey: string,
115
+ agentId?: string
102
116
  ): Promise<AgentSessionRecord | null> {
103
- const sessions = await listAllAgentSessions(context);
117
+ const sessions = await listAllAgentSessions(context, agentId);
104
118
  return sessions.find((item) => item.sessionId === sessionIdOrKey || item.sessionKey === sessionIdOrKey) ?? null;
105
119
  }
106
120
 
107
121
  export async function resolveSessionFile(
108
122
  context: MethodContext,
109
- sessionIdOrKey: string
123
+ sessionIdOrKey: string,
124
+ agentId?: string
110
125
  ): Promise<string | null> {
111
- const session = await findSessionRecord(context, sessionIdOrKey);
126
+ const session = await findSessionRecord(context, sessionIdOrKey, agentId);
112
127
  return session?.sessionFilePath ?? null;
113
128
  }
114
129
 
130
+ function isObject(value: JsonValue | undefined): value is Record<string, JsonValue> {
131
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
132
+ }
133
+
115
134
  function extractSessionIdFromFile(sessionFile: string | undefined): string | null {
116
135
  if (!sessionFile) {
117
136
  return null;
@@ -143,7 +143,7 @@ export const searchClawHubSkills: MethodHandler = async (params, context): Promi
143
143
  const objectParams = isObject(params) ? params : {};
144
144
  const query = typeof objectParams.query === 'string' ? objectParams.query.trim() : '';
145
145
  const args = query ? ['skills', 'search', query, '--json'] : ['skills', 'search', '--json'];
146
- const result = await runOpenClawSkillCommand(args, context.projectRoot);
146
+ const result = await runOpenClawSkillCommand(args, context.openclawRoot, context.openclawRoot);
147
147
 
148
148
  return {
149
149
  ok: true,
@@ -155,7 +155,7 @@ export const searchClawHubSkills: MethodHandler = async (params, context): Promi
155
155
  export const installSkillFromClawHub: MethodHandler = async (params, context): Promise<JsonValue> => {
156
156
  const objectParams = expectObject(params) as unknown as ClawHubSkillParams;
157
157
  const slug = expectString(objectParams.slug, 'slug');
158
- const result = await runOpenClawSkillCommand(['skills', 'install', slug], context.projectRoot);
158
+ const result = await runOpenClawSkillCommand(['skills', 'install', slug], context.openclawRoot, context.openclawRoot);
159
159
 
160
160
  return {
161
161
  ok: true,
@@ -167,7 +167,7 @@ export const installSkillFromClawHub: MethodHandler = async (params, context): P
167
167
  export const updateSkillFromClawHub: MethodHandler = async (params, context): Promise<JsonValue> => {
168
168
  const objectParams = expectObject(params) as unknown as ClawHubSkillParams;
169
169
  const slug = expectString(objectParams.slug, 'slug');
170
- const result = await runOpenClawSkillCommand(['skills', 'update', slug], context.projectRoot);
170
+ const result = await runOpenClawSkillCommand(['skills', 'update', slug], context.openclawRoot, context.openclawRoot);
171
171
 
172
172
  return {
173
173
  ok: true,
@@ -233,27 +233,43 @@ async function npmPack(packageSpec: string, cwd: string): Promise<string> {
233
233
 
234
234
  async function runOpenClawSkillCommand(
235
235
  args: string[],
236
- cwd: string
236
+ cwd: string,
237
+ openclawRoot?: string
237
238
  ): Promise<{ stdout: string; stderr: string; parsed: JsonValue | null }> {
238
239
  const command = process.env.OPENCLAW_BIN || 'openclaw';
239
- const options = buildOpenClawExecOptions(cwd);
240
+ const options = buildOpenClawExecOptions(cwd, openclawRoot);
241
+ const openclawBin = process.env.OPENCLAW_BIN || '';
242
+ const openclawHome = options.env?.OPENCLAW_HOME || '';
243
+ const home = options.env?.HOME || '';
244
+
245
+ console.log(
246
+ `[skills] exec start: command=${command}, args=${JSON.stringify(args)}, cwd=${cwd}, OPENCLAW_BIN=${openclawBin}, OPENCLAW_HOME=${openclawHome}, HOME=${home}`
247
+ );
240
248
 
241
249
  try {
242
250
  const { stdout, stderr } = await execFileAsync(command, args, options);
251
+ console.log(
252
+ `[skills] exec success: command=${command}, args=${JSON.stringify(args)}, stdoutLength=${stdout.length}, stderrLength=${stderr.length}`
253
+ );
243
254
  return {
244
255
  stdout,
245
256
  stderr,
246
257
  parsed: parseJsonOutput(stdout)
247
258
  };
248
259
  } catch (err: any) {
260
+ const stdout = typeof err?.stdout === 'string' ? err.stdout : '';
261
+ const stderr = typeof err?.stderr === 'string' ? err.stderr : '';
262
+ console.error(
263
+ `[skills] exec failed: command=${command}, args=${JSON.stringify(args)}, cwd=${cwd}, OPENCLAW_BIN=${openclawBin}, OPENCLAW_HOME=${openclawHome}, HOME=${home}, stdout=${JSON.stringify(stdout)}, stderr=${JSON.stringify(stderr)}`
264
+ );
249
265
  throw new JsonRpcException(
250
266
  JSON_RPC_ERRORS.internalError,
251
267
  `OpenClaw skill command failed: ${err instanceof Error ? err.message : String(err)}`,
252
268
  {
253
269
  command,
254
270
  args,
255
- stdout: typeof err?.stdout === 'string' ? err.stdout : '',
256
- stderr: typeof err?.stderr === 'string' ? err.stderr : ''
271
+ stdout,
272
+ stderr
257
273
  }
258
274
  );
259
275
  }
@@ -274,7 +290,7 @@ function parseJsonOutput(stdout: string): JsonValue | null {
274
290
 
275
291
  async function queryOpenClawSkills(context: MethodContext): Promise<unknown[]> {
276
292
  const command = process.env.OPENCLAW_BIN || 'openclaw';
277
- const options = buildOpenClawExecOptions(context.projectRoot);
293
+ const options = buildOpenClawExecOptions(context.openclawRoot, context.openclawRoot);
278
294
 
279
295
  try {
280
296
  const { stdout } = await execFileAsync(command, ['skills', 'list', '--json'], options);
@@ -300,15 +316,45 @@ async function queryOpenClawSkills(context: MethodContext): Promise<unknown[]> {
300
316
  }
301
317
  }
302
318
 
303
- function buildOpenClawExecOptions(cwd: string): { cwd: string; shell?: boolean } {
319
+ function buildOpenClawExecOptions(
320
+ cwd: string,
321
+ openclawRoot?: string
322
+ ): { cwd: string; shell?: boolean; env: NodeJS.ProcessEnv } {
323
+ const env: NodeJS.ProcessEnv = { ...process.env };
324
+ const openclawHome = resolveOpenClawHomeForCli(openclawRoot);
325
+ if (openclawHome) {
326
+ env.OPENCLAW_HOME = openclawHome;
327
+ }
328
+ if (openclawRoot && path.basename(path.resolve(openclawRoot)) === '.openclaw') {
329
+ const home = path.dirname(path.resolve(openclawRoot));
330
+ env.HOME = home;
331
+ if (process.platform === 'win32') {
332
+ env.USERPROFILE = home;
333
+ }
334
+ }
335
+
304
336
  if (process.platform === 'win32') {
305
337
  return {
306
338
  cwd,
307
- shell: true
339
+ shell: true,
340
+ env
308
341
  };
309
342
  }
310
343
 
311
- return { cwd };
344
+ return { cwd, env };
345
+ }
346
+
347
+ function resolveOpenClawHomeForCli(openclawRoot?: string): string | undefined {
348
+ if (!openclawRoot) {
349
+ return process.env.OPENCLAW_HOME;
350
+ }
351
+
352
+ const resolvedRoot = path.resolve(openclawRoot);
353
+ if (path.basename(resolvedRoot) === '.openclaw') {
354
+ return path.dirname(resolvedRoot);
355
+ }
356
+
357
+ return process.env.OPENCLAW_HOME;
312
358
  }
313
359
 
314
360
  function normalizeCliSkill(