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

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
 
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.41",
4
4
  "description": "Forward OpenClaw agent events to external WebSocket server for visualization",
5
5
  "type": "module",
6
6
  "main": "index.ts",
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
  {
@@ -64,7 +86,7 @@ export class KichiForwarderService {
64
86
  botName: string,
65
87
  bio: string,
66
88
  tags: string[],
67
- ): Promise<string | null> {
89
+ ): Promise<JoinResult> {
68
90
  return new Promise((resolve) => {
69
91
  this.identity = { avatarId };
70
92
  this.joinResolve = resolve;
@@ -75,7 +97,12 @@ export class KichiForwarderService {
75
97
  } else {
76
98
  this.ws?.once("open", sendJoin);
77
99
  }
78
- setTimeout(() => { if (this.joinResolve) { this.joinResolve = null; resolve(null); } }, 10000);
100
+ setTimeout(() => {
101
+ if (this.joinResolve) {
102
+ this.joinResolve = null;
103
+ resolve({ success: false, error: "Timed out waiting for join_ack" });
104
+ }
105
+ }, 10000);
79
106
  });
80
107
  }
81
108
 
@@ -102,27 +129,64 @@ export class KichiForwarderService {
102
129
  }
103
130
 
104
131
  private handleMessage(data: string): void {
132
+ this.logger.debug(`[kichi ws recv] ${data}`);
105
133
  try {
106
134
  const msg = JSON.parse(data);
107
135
  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);
136
+ if (msg.type === "join_ack") {
137
+ const joinAck = msg as JoinAckPayload;
138
+ if (joinAck.success === false || !joinAck.authKey) {
139
+ const failure = this.buildAckFailure(joinAck, "Join failed");
140
+ this.logger.warn(`Join failed: ${failure.error}`);
141
+ this.joinResolve?.(failure);
142
+ this.joinResolve = null;
143
+ return;
144
+ }
145
+
146
+ if (this.identity) {
147
+ this.identity.authKey = joinAck.authKey;
148
+ this.saveIdentity();
149
+ this.logger.info(`Joined as ${this.identity.avatarId}`);
150
+ }
151
+ this.joinResolve?.({ success: true, authKey: joinAck.authKey });
113
152
  this.joinResolve = null;
114
153
  } else if (msg.type === "rejoin_failed" || msg.type === "auth_error") {
115
154
  // AuthKey invalid/expired, clear it
116
155
  this.logger.warn(`Auth failed: ${msg.reason || "unknown"}`);
117
156
  this.clearAuthKey();
118
157
  } else if (msg.type === "leave_ack") {
119
- this.logger.info("Left Kichi world");
158
+ const leaveAck = msg as LeaveAckPayload;
159
+ if (leaveAck.success === false) {
160
+ const failure = this.buildAckFailure(leaveAck, "Leave failed");
161
+ this.logger.warn(`Leave failed: ${failure.error}`);
162
+ } else {
163
+ this.logger.info("Left Kichi world");
164
+ }
120
165
  }
121
166
  } catch (e) {
122
167
  this.logger.warn(`Failed to parse message: ${e}`);
123
168
  }
124
169
  }
125
170
 
171
+ private buildAckFailure(
172
+ msg: { errorCode?: unknown; errorMessage?: unknown },
173
+ fallbackError: string,
174
+ ): AckFailureResult {
175
+ const errorCode =
176
+ typeof msg.errorCode === "string" && msg.errorCode.trim().length > 0 ? msg.errorCode : undefined;
177
+ const errorMessage =
178
+ typeof msg.errorMessage === "string" && msg.errorMessage.trim().length > 0
179
+ ? msg.errorMessage
180
+ : undefined;
181
+
182
+ return {
183
+ success: false,
184
+ error: errorMessage ?? (errorCode ? `${fallbackError} (${errorCode})` : fallbackError),
185
+ errorCode,
186
+ errorMessage,
187
+ };
188
+ }
189
+
126
190
  private tryResolvePendingRequest(msg: { type?: unknown; requestId?: unknown }): void {
127
191
  const requestId = typeof msg.requestId === "string" ? msg.requestId : "";
128
192
  if (!requestId) {
@@ -448,16 +512,24 @@ export class KichiForwarderService {
448
512
  return "closed";
449
513
  }
450
514
 
451
- async leave(): Promise<boolean> {
452
- if (!this.identity?.avatarId || !this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN) return false;
515
+ async leave(): Promise<LeaveResult> {
516
+ if (!this.identity?.avatarId || !this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN) {
517
+ return { success: false, error: "Failed or not connected" };
518
+ }
519
+
453
520
  return new Promise((resolve) => {
454
521
  const handler = (data: WebSocket.Data) => {
455
522
  try {
456
523
  const msg = JSON.parse(data.toString());
457
524
  if (msg.type === "leave_ack") {
458
525
  this.ws?.off("message", handler);
526
+ const leaveAck = msg as LeaveAckPayload;
527
+ if (leaveAck.success === false) {
528
+ resolve(this.buildAckFailure(leaveAck, "Leave failed"));
529
+ return;
530
+ }
459
531
  this.clearAuthKey();
460
- resolve(true);
532
+ resolve({ success: true });
461
533
  }
462
534
  } catch (e) {
463
535
  this.logger.warn(`Failed to parse leave response: ${e}`);
@@ -467,7 +539,10 @@ export class KichiForwarderService {
467
539
  this.ws!.send(
468
540
  JSON.stringify({ type: "leave", avatarId: this.identity!.avatarId, authKey: this.identity!.authKey }),
469
541
  );
470
- setTimeout(() => { this.ws?.off("message", handler); resolve(false); }, 10000);
542
+ setTimeout(() => {
543
+ this.ws?.off("message", handler);
544
+ resolve({ success: false, error: "Timed out waiting for leave_ack" });
545
+ }, 10000);
471
546
  });
472
547
  }
473
548
  }
package/src/types.ts CHANGED
@@ -69,7 +69,17 @@ export type JoinPayload = {
69
69
 
70
70
  export type JoinAckPayload = {
71
71
  type: "join_ack";
72
- authKey: string;
72
+ authKey?: string;
73
+ success?: boolean;
74
+ errorCode?: string;
75
+ errorMessage?: string;
76
+ };
77
+
78
+ export type LeaveAckPayload = {
79
+ type: "leave_ack";
80
+ success?: boolean;
81
+ errorCode?: string;
82
+ errorMessage?: string;
73
83
  };
74
84
 
75
85
  export type LeavePayload = {