@yahaha-studio/kichi-forwarder 0.0.1-alpha.40 → 0.0.1-alpha.42

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
@@ -602,7 +602,18 @@ const plugin = {
602
602
  return { success: false, error: tagsError };
603
603
  }
604
604
  const result = await service?.join(avatarId, botName, bio, tags ?? []);
605
- return result ? { success: true, authKey: result } : { success: false, error: "Failed" };
605
+ if (!result) {
606
+ return { success: false, error: "Kichi service is not initialized" };
607
+ }
608
+ if (result.success) {
609
+ return { success: true, authKey: result.authKey };
610
+ }
611
+ return {
612
+ success: false,
613
+ error: result.error,
614
+ ...(result.errorCode ? { errorCode: result.errorCode } : {}),
615
+ ...(result.errorMessage ? { errorMessage: result.errorMessage } : {}),
616
+ };
606
617
  },
607
618
  });
608
619
 
@@ -631,7 +642,18 @@ const plugin = {
631
642
  parameters: { type: "object", properties: {} },
632
643
  execute: async () => {
633
644
  const result = await service?.leave();
634
- return result ? { success: true } : { success: false, error: "Failed or not connected" };
645
+ if (!result) {
646
+ return { success: false, error: "Kichi service is not initialized" };
647
+ }
648
+ if (result.success) {
649
+ return { success: true };
650
+ }
651
+ return {
652
+ success: false,
653
+ error: result.error,
654
+ ...(result.errorCode ? { errorCode: result.errorCode } : {}),
655
+ ...(result.errorMessage ? { errorMessage: result.errorMessage } : {}),
656
+ };
635
657
  },
636
658
  });
637
659
 
@@ -8,18 +8,7 @@
8
8
  "configSchema": {
9
9
  "type": "object",
10
10
  "additionalProperties": false,
11
- "properties": {
12
- "wsUrl": {
13
- "type": "string"
14
- },
15
- "enabled": {
16
- "type": "boolean",
17
- "default": true
18
- }
19
- }
11
+ "properties": {}
20
12
  },
21
- "uiHints": {
22
- "wsUrl": { "label": "WebSocket URL", "placeholder": "ws://host:port/ws/openclaw" },
23
- "enabled": { "label": "Enable Plugin" }
24
- }
13
+ "uiHints": {}
25
14
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yahaha-studio/kichi-forwarder",
3
- "version": "0.0.1-alpha.40",
3
+ "version": "0.0.1-alpha.42",
4
4
  "description": "Forward OpenClaw agent events to external WebSocket server for visualization",
5
5
  "type": "module",
6
6
  "main": "index.ts",
package/src/config.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import type { KichiForwarderConfig } from "./types.js";
2
2
 
3
- export function parse(value: unknown): KichiForwarderConfig {
4
- const config = (value ?? {}) as Partial<KichiForwarderConfig>;
3
+ const FIXED_WS_URL = "ws://43.106.148.251:48870/ws/openclaw";
4
+
5
+ export function parse(_value: unknown): KichiForwarderConfig {
5
6
  return {
6
- wsUrl: config.wsUrl ?? "ws://43.106.148.251:48870/ws/openclaw",
7
- enabled: config.enabled ?? true,
7
+ wsUrl: FIXED_WS_URL,
8
8
  };
9
9
  }
package/src/service.ts CHANGED
@@ -11,11 +11,13 @@ import type {
11
11
  CreateMusicAlbumPayload,
12
12
  HookNotifyPayload,
13
13
  HookNotifyType,
14
+ JoinAckPayload,
14
15
  JoinPayload,
15
16
  KichiConnectionStatus,
16
17
  CreateNotesBoardNotePayload,
17
18
  KichiForwarderConfig,
18
19
  KichiIdentity,
20
+ LeaveAckPayload,
19
21
  PoseType,
20
22
  QueryStatusPayload,
21
23
  QueryStatusResultPayload,
@@ -26,12 +28,32 @@ const IDENTITY_DIR = path.join(os.homedir(), ".openclaw", "kichi-world");
26
28
  const IDENTITY_PATH = path.join(IDENTITY_DIR, "identity.json");
27
29
  const MAX_NOTEBOARD_TEXT_LENGTH = 200;
28
30
 
31
+ type AckFailureResult = {
32
+ success: false;
33
+ error: string;
34
+ errorCode?: string;
35
+ errorMessage?: string;
36
+ };
37
+
38
+ export type JoinResult =
39
+ | {
40
+ success: true;
41
+ authKey: string;
42
+ }
43
+ | AckFailureResult;
44
+
45
+ export type LeaveResult =
46
+ | {
47
+ success: true;
48
+ }
49
+ | AckFailureResult;
50
+
29
51
  export class KichiForwarderService {
30
52
  private ws: WebSocket | null = null;
31
53
  private stopped = false;
32
54
  private reconnectTimeout: NodeJS.Timeout | null = null;
33
55
  private identity: KichiIdentity | null = null;
34
- private joinResolve: ((authKey: string) => void) | null = null;
56
+ private joinResolve: ((result: JoinResult) => void) | null = null;
35
57
  private pendingRequests = new Map<
36
58
  string,
37
59
  {
@@ -45,7 +67,6 @@ export class KichiForwarderService {
45
67
  constructor(private config: KichiForwarderConfig, private logger: Logger) {}
46
68
 
47
69
  async start(): Promise<void> {
48
- if (!this.config.enabled) return;
49
70
  this.identity = this.loadIdentity();
50
71
  this.stopped = false;
51
72
  this.connect();
@@ -64,7 +85,7 @@ export class KichiForwarderService {
64
85
  botName: string,
65
86
  bio: string,
66
87
  tags: string[],
67
- ): Promise<string | null> {
88
+ ): Promise<JoinResult> {
68
89
  return new Promise((resolve) => {
69
90
  this.identity = { avatarId };
70
91
  this.joinResolve = resolve;
@@ -75,7 +96,12 @@ export class KichiForwarderService {
75
96
  } else {
76
97
  this.ws?.once("open", sendJoin);
77
98
  }
78
- setTimeout(() => { if (this.joinResolve) { this.joinResolve = null; resolve(null); } }, 10000);
99
+ setTimeout(() => {
100
+ if (this.joinResolve) {
101
+ this.joinResolve = null;
102
+ resolve({ success: false, error: "Timed out waiting for join_ack" });
103
+ }
104
+ }, 10000);
79
105
  });
80
106
  }
81
107
 
@@ -102,27 +128,64 @@ export class KichiForwarderService {
102
128
  }
103
129
 
104
130
  private handleMessage(data: string): void {
131
+ this.logger.debug(`[kichi ws recv] ${data}`);
105
132
  try {
106
133
  const msg = JSON.parse(data);
107
134
  this.tryResolvePendingRequest(msg);
108
- if (msg.type === "join_ack" && msg.authKey && this.identity) {
109
- this.identity.authKey = msg.authKey;
110
- this.saveIdentity();
111
- this.logger.info(`Joined as ${this.identity.avatarId}`);
112
- this.joinResolve?.(msg.authKey);
135
+ if (msg.type === "join_ack") {
136
+ const joinAck = msg as JoinAckPayload;
137
+ if (joinAck.success === false || !joinAck.authKey) {
138
+ const failure = this.buildAckFailure(joinAck, "Join failed");
139
+ this.logger.warn(`Join failed: ${failure.error}`);
140
+ this.joinResolve?.(failure);
141
+ this.joinResolve = null;
142
+ return;
143
+ }
144
+
145
+ if (this.identity) {
146
+ this.identity.authKey = joinAck.authKey;
147
+ this.saveIdentity();
148
+ this.logger.info(`Joined as ${this.identity.avatarId}`);
149
+ }
150
+ this.joinResolve?.({ success: true, authKey: joinAck.authKey });
113
151
  this.joinResolve = null;
114
152
  } else if (msg.type === "rejoin_failed" || msg.type === "auth_error") {
115
153
  // AuthKey invalid/expired, clear it
116
154
  this.logger.warn(`Auth failed: ${msg.reason || "unknown"}`);
117
155
  this.clearAuthKey();
118
156
  } else if (msg.type === "leave_ack") {
119
- this.logger.info("Left Kichi world");
157
+ const leaveAck = msg as LeaveAckPayload;
158
+ if (leaveAck.success === false) {
159
+ const failure = this.buildAckFailure(leaveAck, "Leave failed");
160
+ this.logger.warn(`Leave failed: ${failure.error}`);
161
+ } else {
162
+ this.logger.info("Left Kichi world");
163
+ }
120
164
  }
121
165
  } catch (e) {
122
166
  this.logger.warn(`Failed to parse message: ${e}`);
123
167
  }
124
168
  }
125
169
 
170
+ private buildAckFailure(
171
+ msg: { errorCode?: unknown; errorMessage?: unknown },
172
+ fallbackError: string,
173
+ ): AckFailureResult {
174
+ const errorCode =
175
+ typeof msg.errorCode === "string" && msg.errorCode.trim().length > 0 ? msg.errorCode : undefined;
176
+ const errorMessage =
177
+ typeof msg.errorMessage === "string" && msg.errorMessage.trim().length > 0
178
+ ? msg.errorMessage
179
+ : undefined;
180
+
181
+ return {
182
+ success: false,
183
+ error: errorMessage ?? (errorCode ? `${fallbackError} (${errorCode})` : fallbackError),
184
+ errorCode,
185
+ errorMessage,
186
+ };
187
+ }
188
+
126
189
  private tryResolvePendingRequest(msg: { type?: unknown; requestId?: unknown }): void {
127
190
  const requestId = typeof msg.requestId === "string" ? msg.requestId : "";
128
191
  if (!requestId) {
@@ -396,7 +459,7 @@ export class KichiForwarderService {
396
459
  };
397
460
  }
398
461
 
399
- if (this.stopped || !this.config.enabled) {
462
+ if (this.stopped) {
400
463
  return {
401
464
  accepted: false,
402
465
  mode: "unavailable",
@@ -419,7 +482,6 @@ export class KichiForwarderService {
419
482
 
420
483
  getConnectionStatus(): KichiConnectionStatus {
421
484
  return {
422
- enabled: this.config.enabled,
423
485
  wsUrl: this.config.wsUrl,
424
486
  connected: this.isConnected(),
425
487
  websocketState: this.getWebsocketState(),
@@ -448,16 +510,24 @@ export class KichiForwarderService {
448
510
  return "closed";
449
511
  }
450
512
 
451
- async leave(): Promise<boolean> {
452
- if (!this.identity?.avatarId || !this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN) return false;
513
+ async leave(): Promise<LeaveResult> {
514
+ if (!this.identity?.avatarId || !this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN) {
515
+ return { success: false, error: "Failed or not connected" };
516
+ }
517
+
453
518
  return new Promise((resolve) => {
454
519
  const handler = (data: WebSocket.Data) => {
455
520
  try {
456
521
  const msg = JSON.parse(data.toString());
457
522
  if (msg.type === "leave_ack") {
458
523
  this.ws?.off("message", handler);
524
+ const leaveAck = msg as LeaveAckPayload;
525
+ if (leaveAck.success === false) {
526
+ resolve(this.buildAckFailure(leaveAck, "Leave failed"));
527
+ return;
528
+ }
459
529
  this.clearAuthKey();
460
- resolve(true);
530
+ resolve({ success: true });
461
531
  }
462
532
  } catch (e) {
463
533
  this.logger.warn(`Failed to parse leave response: ${e}`);
@@ -467,7 +537,10 @@ export class KichiForwarderService {
467
537
  this.ws!.send(
468
538
  JSON.stringify({ type: "leave", avatarId: this.identity!.avatarId, authKey: this.identity!.authKey }),
469
539
  );
470
- setTimeout(() => { this.ws?.off("message", handler); resolve(false); }, 10000);
540
+ setTimeout(() => {
541
+ this.ws?.off("message", handler);
542
+ resolve({ success: false, error: "Timed out waiting for leave_ack" });
543
+ }, 10000);
471
544
  });
472
545
  }
473
546
  }
package/src/types.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  export type KichiForwarderConfig = {
2
2
  wsUrl: string;
3
- enabled: boolean;
4
3
  };
5
4
 
6
5
  export type PoseType = "stand" | "sit" | "lay" | "floor";
@@ -38,7 +37,6 @@ export type KichiIdentity = {
38
37
  };
39
38
 
40
39
  export type KichiConnectionStatus = {
41
- enabled: boolean;
42
40
  wsUrl: string;
43
41
  connected: boolean;
44
42
  websocketState: "idle" | "connecting" | "open" | "closing" | "closed";
@@ -69,7 +67,17 @@ export type JoinPayload = {
69
67
 
70
68
  export type JoinAckPayload = {
71
69
  type: "join_ack";
72
- authKey: string;
70
+ authKey?: string;
71
+ success?: boolean;
72
+ errorCode?: string;
73
+ errorMessage?: string;
74
+ };
75
+
76
+ export type LeaveAckPayload = {
77
+ type: "leave_ack";
78
+ success?: boolean;
79
+ errorCode?: string;
80
+ errorMessage?: string;
73
81
  };
74
82
 
75
83
  export type LeavePayload = {