@yahaha-studio/kichi-forwarder 0.0.1-alpha.25
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/.claude/settings.local.json +18 -0
- package/.github/workflows/static.yml +43 -0
- package/index.ts +845 -0
- package/openclaw.plugin.json +25 -0
- package/package.json +24 -0
- package/skills/kichi-forwarder/.nojekyll +0 -0
- package/skills/kichi-forwarder/SKILL.md +243 -0
- package/skills/kichi-forwarder/references/error.md +10 -0
- package/skills/kichi-forwarder/references/heartbeat.md +83 -0
- package/skills/kichi-forwarder/references/install.md +51 -0
- package/src/config.ts +9 -0
- package/src/service.ts +434 -0
- package/src/types.ts +210 -0
package/src/service.ts
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
import WebSocket from "ws";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
import type { Logger } from "openclaw/plugin-sdk";
|
|
7
|
+
import type {
|
|
8
|
+
ClockAction,
|
|
9
|
+
ClockConfig,
|
|
10
|
+
ClockPayload,
|
|
11
|
+
KichiConnectionStatus,
|
|
12
|
+
CreateNotesBoardNotePayload,
|
|
13
|
+
CreateNotesBoardNoteResultPayload,
|
|
14
|
+
KichiForwarderConfig,
|
|
15
|
+
KichiIdentity,
|
|
16
|
+
PoseType,
|
|
17
|
+
QueryNotesBoardPayload,
|
|
18
|
+
QueryNotesBoardResultPayload,
|
|
19
|
+
StatusPayload,
|
|
20
|
+
} from "./types.js";
|
|
21
|
+
|
|
22
|
+
const IDENTITY_DIR = path.join(os.homedir(), ".openclaw", "kichi-world");
|
|
23
|
+
const IDENTITY_PATH = path.join(IDENTITY_DIR, "identity.json");
|
|
24
|
+
const MAX_NOTEBOARD_TEXT_LENGTH = 200;
|
|
25
|
+
|
|
26
|
+
export class KichiForwarderService {
|
|
27
|
+
private ws: WebSocket | null = null;
|
|
28
|
+
private stopped = false;
|
|
29
|
+
private reconnectTimeout: NodeJS.Timeout | null = null;
|
|
30
|
+
private identity: KichiIdentity | null = null;
|
|
31
|
+
private joinResolve: ((authKey: string) => void) | null = null;
|
|
32
|
+
private pendingRequests = new Map<
|
|
33
|
+
string,
|
|
34
|
+
{
|
|
35
|
+
expectedType: string;
|
|
36
|
+
resolve: (value: unknown) => void;
|
|
37
|
+
reject: (error: Error) => void;
|
|
38
|
+
timeout: NodeJS.Timeout;
|
|
39
|
+
}
|
|
40
|
+
>();
|
|
41
|
+
|
|
42
|
+
constructor(private config: KichiForwarderConfig, private logger: Logger) {}
|
|
43
|
+
|
|
44
|
+
async start(): Promise<void> {
|
|
45
|
+
if (!this.config.enabled) return;
|
|
46
|
+
this.identity = this.loadIdentity();
|
|
47
|
+
this.stopped = false;
|
|
48
|
+
this.connect();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async stop(): Promise<void> {
|
|
52
|
+
this.stopped = true;
|
|
53
|
+
if (this.reconnectTimeout) clearTimeout(this.reconnectTimeout);
|
|
54
|
+
this.rejectPendingRequests("Kichi websocket stopped");
|
|
55
|
+
this.ws?.close();
|
|
56
|
+
this.ws = null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async join(
|
|
60
|
+
mateId: string,
|
|
61
|
+
botName: string,
|
|
62
|
+
bio: string,
|
|
63
|
+
): Promise<string | null> {
|
|
64
|
+
return new Promise((resolve) => {
|
|
65
|
+
this.identity = { mateId };
|
|
66
|
+
this.joinResolve = resolve;
|
|
67
|
+
const sendJoin = () =>
|
|
68
|
+
this.ws?.send(JSON.stringify({ type: "join", mateId, botName, bio }));
|
|
69
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
70
|
+
sendJoin();
|
|
71
|
+
} else {
|
|
72
|
+
this.ws?.once("open", sendJoin);
|
|
73
|
+
}
|
|
74
|
+
setTimeout(() => { if (this.joinResolve) { this.joinResolve = null; resolve(null); } }, 10000);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private connect(): void {
|
|
79
|
+
if (this.stopped) return;
|
|
80
|
+
this.ws = new WebSocket(this.config.wsUrl);
|
|
81
|
+
this.ws.on("open", () => {
|
|
82
|
+
this.logger.info(`Connected to ${this.config.wsUrl}`);
|
|
83
|
+
// Automatically send rejoin when a valid identity is available.
|
|
84
|
+
this.sendRejoinPayload();
|
|
85
|
+
});
|
|
86
|
+
this.ws.on("message", (data) => this.handleMessage(data.toString()));
|
|
87
|
+
this.ws.on("close", () => {
|
|
88
|
+
this.ws = null;
|
|
89
|
+
this.rejectPendingRequests("Kichi websocket closed");
|
|
90
|
+
if (!this.stopped) {
|
|
91
|
+
this.reconnectTimeout = setTimeout(() => {
|
|
92
|
+
this.reconnectTimeout = null;
|
|
93
|
+
this.connect();
|
|
94
|
+
}, 2000);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
this.ws.on("error", () => {});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private handleMessage(data: string): void {
|
|
101
|
+
try {
|
|
102
|
+
const msg = JSON.parse(data);
|
|
103
|
+
this.tryResolvePendingRequest(msg);
|
|
104
|
+
if (msg.type === "join_ack" && msg.authKey && this.identity) {
|
|
105
|
+
this.identity.authKey = msg.authKey;
|
|
106
|
+
this.saveIdentity();
|
|
107
|
+
this.logger.info(`Joined as ${this.identity.mateId}`);
|
|
108
|
+
this.joinResolve?.(msg.authKey);
|
|
109
|
+
this.joinResolve = null;
|
|
110
|
+
} else if (msg.type === "rejoin_failed" || msg.type === "auth_error") {
|
|
111
|
+
// AuthKey invalid/expired, clear it
|
|
112
|
+
this.logger.warn(`Auth failed: ${msg.reason || "unknown"}`);
|
|
113
|
+
this.clearAuthKey();
|
|
114
|
+
} else if (msg.type === "leave_ack") {
|
|
115
|
+
this.logger.info("Left Kichi world");
|
|
116
|
+
}
|
|
117
|
+
} catch (e) {
|
|
118
|
+
this.logger.warn(`Failed to parse message: ${e}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private tryResolvePendingRequest(msg: { type?: unknown; requestId?: unknown }): void {
|
|
123
|
+
const requestId = typeof msg.requestId === "string" ? msg.requestId : "";
|
|
124
|
+
if (!requestId) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const pending = this.pendingRequests.get(requestId);
|
|
128
|
+
if (!pending) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (msg.type !== pending.expectedType) {
|
|
132
|
+
pending.reject(
|
|
133
|
+
new Error(
|
|
134
|
+
`Unexpected response type for request ${requestId}: ${String(msg.type)} (expected ${pending.expectedType})`,
|
|
135
|
+
),
|
|
136
|
+
);
|
|
137
|
+
clearTimeout(pending.timeout);
|
|
138
|
+
this.pendingRequests.delete(requestId);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
clearTimeout(pending.timeout);
|
|
142
|
+
this.pendingRequests.delete(requestId);
|
|
143
|
+
pending.resolve(msg);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private rejectPendingRequests(reason: string): void {
|
|
147
|
+
for (const [requestId, pending] of this.pendingRequests.entries()) {
|
|
148
|
+
clearTimeout(pending.timeout);
|
|
149
|
+
pending.reject(new Error(`${reason} (${requestId})`));
|
|
150
|
+
}
|
|
151
|
+
this.pendingRequests.clear();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private requireIdentity(): { mateId: string; authKey: string } | null {
|
|
155
|
+
if (!this.identity?.mateId || !this.identity?.authKey) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
mateId: this.identity.mateId,
|
|
160
|
+
authKey: this.identity.authKey,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private sendRequest<TResponse extends { type?: unknown; requestId?: unknown }>(
|
|
165
|
+
payload: { type: string; requestId?: string },
|
|
166
|
+
expectedType: string,
|
|
167
|
+
timeoutMs = 10000,
|
|
168
|
+
): Promise<TResponse> {
|
|
169
|
+
if (this.ws?.readyState !== WebSocket.OPEN) {
|
|
170
|
+
return Promise.reject(new Error("Kichi websocket is not connected"));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const requestId = payload.requestId?.trim() || randomUUID();
|
|
174
|
+
const outboundPayload = { ...payload, requestId };
|
|
175
|
+
|
|
176
|
+
return new Promise<TResponse>((resolve, reject) => {
|
|
177
|
+
const timeout = setTimeout(() => {
|
|
178
|
+
this.pendingRequests.delete(requestId);
|
|
179
|
+
reject(new Error(`Timed out waiting for ${expectedType}`));
|
|
180
|
+
}, timeoutMs);
|
|
181
|
+
|
|
182
|
+
this.pendingRequests.set(requestId, {
|
|
183
|
+
expectedType,
|
|
184
|
+
timeout,
|
|
185
|
+
resolve: (value) => resolve(value as TResponse),
|
|
186
|
+
reject,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
this.ws?.send(JSON.stringify(outboundPayload));
|
|
191
|
+
} catch (error) {
|
|
192
|
+
clearTimeout(timeout);
|
|
193
|
+
this.pendingRequests.delete(requestId);
|
|
194
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private loadIdentity(): KichiIdentity | null {
|
|
200
|
+
try {
|
|
201
|
+
if (!fs.existsSync(IDENTITY_PATH)) return null;
|
|
202
|
+
const data = JSON.parse(fs.readFileSync(IDENTITY_PATH, "utf-8"));
|
|
203
|
+
const mateId = typeof data.mateId === "string" && data.mateId ? data.mateId : null;
|
|
204
|
+
if (mateId) {
|
|
205
|
+
return {
|
|
206
|
+
mateId,
|
|
207
|
+
authKey: typeof data.authKey === "string" ? data.authKey : undefined,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
return null;
|
|
211
|
+
} catch (e) {
|
|
212
|
+
this.logger.warn(`Failed to load identity: ${e}`);
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private saveIdentity(): void {
|
|
218
|
+
if (!this.identity?.mateId) return;
|
|
219
|
+
try {
|
|
220
|
+
if (!fs.existsSync(IDENTITY_DIR)) fs.mkdirSync(IDENTITY_DIR, { recursive: true, mode: 0o700 });
|
|
221
|
+
fs.writeFileSync(IDENTITY_PATH, JSON.stringify(this.identity, null, 2), { mode: 0o600 });
|
|
222
|
+
} catch (e) {
|
|
223
|
+
this.logger.error(`Failed to save identity: ${e}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private clearAuthKey(): void {
|
|
228
|
+
if (!this.identity) return;
|
|
229
|
+
this.identity.authKey = undefined;
|
|
230
|
+
this.saveIdentity();
|
|
231
|
+
this.logger.info("AuthKey cleared");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private sendRejoinPayload(): boolean {
|
|
235
|
+
if (!this.identity?.mateId || !this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN) {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
this.ws.send(
|
|
240
|
+
JSON.stringify({ type: "rejoin", mateId: this.identity.mateId, authKey: this.identity.authKey }),
|
|
241
|
+
);
|
|
242
|
+
this.logger.debug(`Sent rejoin for ${this.identity.mateId}`);
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
sendStatus(poseType: PoseType | "", action: string, bubble: string, log: string): void {
|
|
247
|
+
if (!this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN) return;
|
|
248
|
+
const payload: StatusPayload = {
|
|
249
|
+
type: "status",
|
|
250
|
+
mateId: this.identity.mateId,
|
|
251
|
+
authKey: this.identity.authKey,
|
|
252
|
+
poseType,
|
|
253
|
+
action,
|
|
254
|
+
bubble,
|
|
255
|
+
log,
|
|
256
|
+
};
|
|
257
|
+
this.ws.send(JSON.stringify(payload));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
sendClock(action: ClockAction, clock?: ClockConfig, requestId?: string): boolean {
|
|
261
|
+
if (!this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN) return false;
|
|
262
|
+
if (action === "set" && !clock) return false;
|
|
263
|
+
|
|
264
|
+
const basePayload = {
|
|
265
|
+
type: "clock" as const,
|
|
266
|
+
mateId: this.identity.mateId,
|
|
267
|
+
authKey: this.identity.authKey,
|
|
268
|
+
...(requestId ? { requestId } : {}),
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const payload: ClockPayload =
|
|
272
|
+
action === "set"
|
|
273
|
+
? {
|
|
274
|
+
...basePayload,
|
|
275
|
+
action,
|
|
276
|
+
clock,
|
|
277
|
+
}
|
|
278
|
+
: {
|
|
279
|
+
...basePayload,
|
|
280
|
+
action,
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
this.ws.send(JSON.stringify(payload));
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async queryNotesBoard(requestId?: string): Promise<QueryNotesBoardResultPayload> {
|
|
288
|
+
const identity = this.requireIdentity();
|
|
289
|
+
if (!identity) {
|
|
290
|
+
throw new Error("Missing Kichi identity");
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const payload: QueryNotesBoardPayload = {
|
|
294
|
+
type: "query_notes_board",
|
|
295
|
+
requestId: requestId?.trim() || randomUUID(),
|
|
296
|
+
mateId: identity.mateId,
|
|
297
|
+
authKey: identity.authKey,
|
|
298
|
+
};
|
|
299
|
+
return this.sendRequest<QueryNotesBoardResultPayload>(payload, "query_notes_board_result");
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async createNotesBoardNote(
|
|
303
|
+
propId: string,
|
|
304
|
+
data: string,
|
|
305
|
+
requestId?: string,
|
|
306
|
+
): Promise<CreateNotesBoardNoteResultPayload> {
|
|
307
|
+
const identity = this.requireIdentity();
|
|
308
|
+
if (!identity) {
|
|
309
|
+
throw new Error("Missing Kichi identity");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (data.trim().length > MAX_NOTEBOARD_TEXT_LENGTH) {
|
|
313
|
+
throw new Error(`Note content must be ${MAX_NOTEBOARD_TEXT_LENGTH} characters or fewer`);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const payload: CreateNotesBoardNotePayload = {
|
|
317
|
+
type: "create_notes_board_note",
|
|
318
|
+
requestId: requestId?.trim() || randomUUID(),
|
|
319
|
+
mateId: identity.mateId,
|
|
320
|
+
authKey: identity.authKey,
|
|
321
|
+
propId,
|
|
322
|
+
data,
|
|
323
|
+
};
|
|
324
|
+
return this.sendRequest<CreateNotesBoardNoteResultPayload>(
|
|
325
|
+
payload,
|
|
326
|
+
"create_notes_board_note_result",
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
isConnected(): boolean { return this.ws?.readyState === WebSocket.OPEN && !!this.identity?.authKey; }
|
|
331
|
+
|
|
332
|
+
hasValidIdentity(): boolean { return !!this.identity?.mateId && !!this.identity?.authKey; }
|
|
333
|
+
|
|
334
|
+
requestRejoin(): { accepted: boolean; mode: "sent" | "waiting_open" | "reconnecting" | "unavailable"; message: string } {
|
|
335
|
+
if (!this.identity?.mateId || !this.identity?.authKey) {
|
|
336
|
+
return {
|
|
337
|
+
accepted: false,
|
|
338
|
+
mode: "unavailable",
|
|
339
|
+
message: "Missing authKey. Run kichi_join first.",
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
344
|
+
const sent = this.sendRejoinPayload();
|
|
345
|
+
return {
|
|
346
|
+
accepted: sent,
|
|
347
|
+
mode: sent ? "sent" : "unavailable",
|
|
348
|
+
message: sent ? "Rejoin payload sent." : "Unable to send rejoin payload.",
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (this.ws?.readyState === WebSocket.CONNECTING) {
|
|
353
|
+
return {
|
|
354
|
+
accepted: true,
|
|
355
|
+
mode: "waiting_open",
|
|
356
|
+
message: "WebSocket is connecting. Rejoin will be sent automatically on open.",
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (this.stopped || !this.config.enabled) {
|
|
361
|
+
return {
|
|
362
|
+
accepted: false,
|
|
363
|
+
mode: "unavailable",
|
|
364
|
+
message: "Service is not running.",
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (this.reconnectTimeout) {
|
|
369
|
+
clearTimeout(this.reconnectTimeout);
|
|
370
|
+
this.reconnectTimeout = null;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
this.connect();
|
|
374
|
+
return {
|
|
375
|
+
accepted: true,
|
|
376
|
+
mode: "reconnecting",
|
|
377
|
+
message: "Reconnect started. Rejoin will be sent automatically on open.",
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
getConnectionStatus(): KichiConnectionStatus {
|
|
382
|
+
return {
|
|
383
|
+
enabled: this.config.enabled,
|
|
384
|
+
wsUrl: this.config.wsUrl,
|
|
385
|
+
connected: this.isConnected(),
|
|
386
|
+
websocketState: this.getWebsocketState(),
|
|
387
|
+
hasIdentity: !!this.identity?.mateId,
|
|
388
|
+
mateId: this.identity?.mateId,
|
|
389
|
+
hasAuthKey: !!this.identity?.authKey,
|
|
390
|
+
pendingRequestCount: this.pendingRequests.size,
|
|
391
|
+
reconnectScheduled: !!this.reconnectTimeout,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
private getWebsocketState(): KichiConnectionStatus["websocketState"] {
|
|
396
|
+
if (!this.ws) {
|
|
397
|
+
return "idle";
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (this.ws.readyState === WebSocket.CONNECTING) {
|
|
401
|
+
return "connecting";
|
|
402
|
+
}
|
|
403
|
+
if (this.ws.readyState === WebSocket.OPEN) {
|
|
404
|
+
return "open";
|
|
405
|
+
}
|
|
406
|
+
if (this.ws.readyState === WebSocket.CLOSING) {
|
|
407
|
+
return "closing";
|
|
408
|
+
}
|
|
409
|
+
return "closed";
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async leave(): Promise<boolean> {
|
|
413
|
+
if (!this.identity?.mateId || !this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN) return false;
|
|
414
|
+
return new Promise((resolve) => {
|
|
415
|
+
const handler = (data: WebSocket.Data) => {
|
|
416
|
+
try {
|
|
417
|
+
const msg = JSON.parse(data.toString());
|
|
418
|
+
if (msg.type === "leave_ack") {
|
|
419
|
+
this.ws?.off("message", handler);
|
|
420
|
+
this.clearAuthKey();
|
|
421
|
+
resolve(true);
|
|
422
|
+
}
|
|
423
|
+
} catch (e) {
|
|
424
|
+
this.logger.warn(`Failed to parse leave response: ${e}`);
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
this.ws!.on("message", handler);
|
|
428
|
+
this.ws!.send(
|
|
429
|
+
JSON.stringify({ type: "leave", mateId: this.identity!.mateId, authKey: this.identity!.authKey }),
|
|
430
|
+
);
|
|
431
|
+
setTimeout(() => { this.ws?.off("message", handler); resolve(false); }, 10000);
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
export type KichiForwarderConfig = {
|
|
2
|
+
wsUrl: string;
|
|
3
|
+
enabled: boolean;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export type PoseType = "stand" | "sit" | "lay" | "floor";
|
|
7
|
+
|
|
8
|
+
export type ActionResult = {
|
|
9
|
+
poseType: PoseType;
|
|
10
|
+
action: string;
|
|
11
|
+
bubble: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type KichiRuntimeConfig = {
|
|
15
|
+
actions: Record<PoseType, string[]>;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Backward-compatible alias for older imports.
|
|
19
|
+
export type SkillsConfig = KichiRuntimeConfig;
|
|
20
|
+
|
|
21
|
+
export type KichiIdentity = {
|
|
22
|
+
mateId: string;
|
|
23
|
+
authKey?: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type KichiConnectionStatus = {
|
|
27
|
+
enabled: boolean;
|
|
28
|
+
wsUrl: string;
|
|
29
|
+
connected: boolean;
|
|
30
|
+
websocketState: "idle" | "connecting" | "open" | "closing" | "closed";
|
|
31
|
+
hasIdentity: boolean;
|
|
32
|
+
mateId?: string;
|
|
33
|
+
hasAuthKey: boolean;
|
|
34
|
+
pendingRequestCount: number;
|
|
35
|
+
reconnectScheduled: boolean;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type KichiErrorResult = {
|
|
39
|
+
success: false;
|
|
40
|
+
errorCode?: string;
|
|
41
|
+
error?: string;
|
|
42
|
+
message?: string;
|
|
43
|
+
dailyLimit?: number;
|
|
44
|
+
remaining?: number;
|
|
45
|
+
resetAtUtc?: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type JoinPayload = {
|
|
49
|
+
type: "join";
|
|
50
|
+
mateId: string;
|
|
51
|
+
botName: string;
|
|
52
|
+
bio: string;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type JoinAckPayload = {
|
|
56
|
+
type: "join_ack";
|
|
57
|
+
authKey: string;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export type LeavePayload = {
|
|
61
|
+
type: "leave";
|
|
62
|
+
mateId: string;
|
|
63
|
+
authKey: string;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export type StatusPayload = {
|
|
67
|
+
type: "status";
|
|
68
|
+
mateId: string;
|
|
69
|
+
authKey: string;
|
|
70
|
+
poseType: PoseType | "";
|
|
71
|
+
action: string;
|
|
72
|
+
bubble: string;
|
|
73
|
+
log: string;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export type ClockAction = "set" | "stop";
|
|
77
|
+
|
|
78
|
+
export type ClockMode = "pomodoro" | "countDown" | "countUp";
|
|
79
|
+
|
|
80
|
+
export type PomodoroPhase = "kichiing" | "shortBreak" | "longBreak";
|
|
81
|
+
|
|
82
|
+
export type PomodoroClock = {
|
|
83
|
+
mode: "pomodoro";
|
|
84
|
+
running: boolean;
|
|
85
|
+
kichiSeconds: number;
|
|
86
|
+
shortBreakSeconds: number;
|
|
87
|
+
longBreakSeconds: number;
|
|
88
|
+
sessionCount: number;
|
|
89
|
+
currentSession: number;
|
|
90
|
+
phase: PomodoroPhase;
|
|
91
|
+
remainingSeconds: number;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export type CountDownClock = {
|
|
95
|
+
mode: "countDown";
|
|
96
|
+
running: boolean;
|
|
97
|
+
durationSeconds: number;
|
|
98
|
+
remainingSeconds: number;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export type CountUpClock = {
|
|
102
|
+
mode: "countUp";
|
|
103
|
+
running: boolean;
|
|
104
|
+
elapsedSeconds: number;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export type ClockConfig = PomodoroClock | CountDownClock | CountUpClock;
|
|
108
|
+
|
|
109
|
+
type ClockPayloadBase = {
|
|
110
|
+
type: "clock";
|
|
111
|
+
mateId: string;
|
|
112
|
+
authKey: string;
|
|
113
|
+
requestId?: string;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export type ClockSetPayload = ClockPayloadBase & {
|
|
117
|
+
action: "set";
|
|
118
|
+
clock: ClockConfig;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export type ClockControlPayload = ClockPayloadBase & {
|
|
122
|
+
action: Exclude<ClockAction, "set">;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export type ClockPayload = ClockSetPayload | ClockControlPayload;
|
|
126
|
+
|
|
127
|
+
export type QueryNotesBoardNote = {
|
|
128
|
+
creatorName: string;
|
|
129
|
+
isFromOwner: boolean;
|
|
130
|
+
isCreatedByCurrentMate: boolean;
|
|
131
|
+
createTime: string;
|
|
132
|
+
updateTime: string;
|
|
133
|
+
data: string;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export type QueryNotesBoard = {
|
|
137
|
+
propId: string;
|
|
138
|
+
noteCount: number;
|
|
139
|
+
latestActivityAt: string;
|
|
140
|
+
notes: QueryNotesBoardNote[];
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export type QueryNotesBoardPayload = {
|
|
144
|
+
type: "query_notes_board";
|
|
145
|
+
requestId: string;
|
|
146
|
+
mateId: string;
|
|
147
|
+
authKey: string;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export type QueryNotesBoardSuccessPayload = {
|
|
151
|
+
type: "query_notes_board_result";
|
|
152
|
+
requestId: string;
|
|
153
|
+
success: true;
|
|
154
|
+
mateId: string;
|
|
155
|
+
spaceId: string;
|
|
156
|
+
dailyLimit: number;
|
|
157
|
+
remaining: number;
|
|
158
|
+
resetAtUtc: string;
|
|
159
|
+
boards: QueryNotesBoard[];
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export type QueryNotesBoardFailurePayload = {
|
|
163
|
+
type: "query_notes_board_result";
|
|
164
|
+
requestId: string;
|
|
165
|
+
} & KichiErrorResult;
|
|
166
|
+
|
|
167
|
+
export type QueryNotesBoardResultPayload =
|
|
168
|
+
| QueryNotesBoardSuccessPayload
|
|
169
|
+
| QueryNotesBoardFailurePayload;
|
|
170
|
+
|
|
171
|
+
export type CreateNotesBoardNotePayload = {
|
|
172
|
+
type: "create_notes_board_note";
|
|
173
|
+
requestId: string;
|
|
174
|
+
mateId: string;
|
|
175
|
+
authKey: string;
|
|
176
|
+
propId: string;
|
|
177
|
+
data: string;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
export type CreateNotesBoardNote = {
|
|
181
|
+
id: string;
|
|
182
|
+
ownerName: string;
|
|
183
|
+
createTime: string;
|
|
184
|
+
data: string;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
type NotesBoardMutationSuccessPayloadBase = {
|
|
188
|
+
requestId: string;
|
|
189
|
+
success: true;
|
|
190
|
+
mateId: string;
|
|
191
|
+
spaceId?: string;
|
|
192
|
+
propId: string;
|
|
193
|
+
dailyLimit?: number;
|
|
194
|
+
remaining?: number;
|
|
195
|
+
resetAtUtc?: string;
|
|
196
|
+
note: CreateNotesBoardNote;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
export type CreateNotesBoardNoteSuccessPayload = NotesBoardMutationSuccessPayloadBase & {
|
|
200
|
+
type: "create_notes_board_note_result";
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
export type CreateNotesBoardNoteFailurePayload = {
|
|
204
|
+
type: "create_notes_board_note_result";
|
|
205
|
+
requestId: string;
|
|
206
|
+
} & KichiErrorResult;
|
|
207
|
+
|
|
208
|
+
export type CreateNotesBoardNoteResultPayload =
|
|
209
|
+
| CreateNotesBoardNoteSuccessPayload
|
|
210
|
+
| CreateNotesBoardNoteFailurePayload;
|