@yahaha-studio/kichi-forwarder 0.1.0-beta.8 → 0.1.1-beta.1

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/src/service.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import WebSocket from "ws";
2
2
  import * as fs from "fs";
3
- import os from "node:os";
4
3
  import * as path from "path";
5
4
  import { randomUUID } from "node:crypto";
6
5
  import type { Logger } from "openclaw/plugin-sdk";
@@ -27,9 +26,6 @@ import type {
27
26
  StatusPayload,
28
27
  } from "./types.js";
29
28
 
30
- const KICHI_WORLD_DIR = path.join(os.homedir(), ".openclaw", "kichi-world");
31
- const HOSTS_DIR = path.join(KICHI_WORLD_DIR, "hosts");
32
- const STATE_PATH = path.join(KICHI_WORLD_DIR, "state.json");
33
29
  const MAX_NOTEBOARD_TEXT_LENGTH = 200;
34
30
  const DEFAULT_LLM_RUNTIME_ENABLED = true;
35
31
 
@@ -53,6 +49,11 @@ export type LeaveResult =
53
49
  }
54
50
  | AckFailureResult;
55
51
 
52
+ type KichiForwarderServiceOptions = {
53
+ agentId: string;
54
+ runtimeDir: string;
55
+ };
56
+
56
57
  export class KichiForwarderService {
57
58
  private ws: WebSocket | null = null;
58
59
  private stopped = false;
@@ -70,9 +71,12 @@ export class KichiForwarderService {
70
71
  }
71
72
  >();
72
73
 
73
- constructor(private logger: Logger) {}
74
+ constructor(
75
+ private logger: Logger,
76
+ private options: KichiForwarderServiceOptions,
77
+ ) {}
74
78
 
75
- async start(): Promise<void> {
79
+ start(): void {
76
80
  this.host = this.loadCurrentHost();
77
81
  this.identity = this.host ? this.loadIdentity() : null;
78
82
  this.stopped = false;
@@ -80,10 +84,10 @@ export class KichiForwarderService {
80
84
  this.connect();
81
85
  return;
82
86
  }
83
- this.logger.info("Kichi host is not configured yet; waiting for kichi_switch_host");
87
+ this.log("debug", "host is not configured yet; waiting for kichi_switch_host");
84
88
  }
85
89
 
86
- async stop(): Promise<void> {
90
+ stop(): void {
87
91
  this.stopped = true;
88
92
  this.clearReconnectTimeout();
89
93
  this.rejectPendingRequests("Kichi websocket stopped");
@@ -270,10 +274,26 @@ export class KichiForwarderService {
270
274
 
271
275
  hasValidIdentity(): boolean { return !!this.identity?.avatarId && !!this.identity?.authKey; }
272
276
 
277
+ isLlmRuntimeEnabled(): boolean {
278
+ return this.readStateFile()?.llmRuntimeEnabled ?? DEFAULT_LLM_RUNTIME_ENABLED;
279
+ }
280
+
273
281
  getCurrentHost(): string {
274
282
  return this.host ?? "";
275
283
  }
276
284
 
285
+ getAgentId(): string {
286
+ return this.options.agentId;
287
+ }
288
+
289
+ getRuntimeDir(): string {
290
+ return this.options.runtimeDir;
291
+ }
292
+
293
+ getStatePath(): string {
294
+ return path.join(this.options.runtimeDir, "state.json");
295
+ }
296
+
277
297
  getIdentityPath(): string {
278
298
  if (!this.host) {
279
299
  return "";
@@ -336,6 +356,9 @@ export class KichiForwarderService {
336
356
  getConnectionStatus(): KichiConnectionStatus {
337
357
  const host = this.host ?? undefined;
338
358
  return {
359
+ agentId: this.options.agentId,
360
+ runtimeDir: this.getRuntimeDir(),
361
+ statePath: this.getStatePath(),
339
362
  ...(host ? {
340
363
  host,
341
364
  wsUrl: this.getWsUrl(),
@@ -372,7 +395,7 @@ export class KichiForwarderService {
372
395
  resolve({ success: true });
373
396
  }
374
397
  } catch (e) {
375
- this.logger.warn(`Failed to parse leave response: ${e}`);
398
+ this.log("warn", `failed to parse leave response: ${e}`);
376
399
  }
377
400
  };
378
401
  this.ws!.on("message", handler);
@@ -395,7 +418,7 @@ export class KichiForwarderService {
395
418
 
396
419
  ws.on("open", () => {
397
420
  if (this.ws !== ws) return;
398
- this.logger.info(`Connected to ${wsUrl} (${this.host})`);
421
+ this.log("info", `connected to ${wsUrl} (${this.host})`);
399
422
  this.sendRejoinPayload();
400
423
  });
401
424
 
@@ -420,7 +443,7 @@ export class KichiForwarderService {
420
443
  }
421
444
 
422
445
  private handleMessage(data: string): void {
423
- this.logger.debug(`[kichi ws recv] ${data}`);
446
+ this.log("debug", `ws recv ${data}`);
424
447
  try {
425
448
  const msg = JSON.parse(data);
426
449
  this.tryResolvePendingRequest(msg);
@@ -428,7 +451,7 @@ export class KichiForwarderService {
428
451
  const joinAck = msg as JoinAckPayload;
429
452
  if (joinAck.success === false || !joinAck.authKey) {
430
453
  const failure = this.buildAckFailure(joinAck, "Join failed");
431
- this.logger.warn(`Join failed: ${failure.error}`);
454
+ this.log("warn", `join failed: ${failure.error}`);
432
455
  this.joinResolve?.(failure);
433
456
  this.joinResolve = null;
434
457
  return;
@@ -437,24 +460,24 @@ export class KichiForwarderService {
437
460
  if (this.identity) {
438
461
  this.identity.authKey = joinAck.authKey;
439
462
  this.saveIdentity();
440
- this.logger.info(`Joined as ${this.identity.avatarId}`);
463
+ this.log("info", `joined as ${this.identity.avatarId}`);
441
464
  }
442
465
  this.joinResolve?.({ success: true, authKey: joinAck.authKey });
443
466
  this.joinResolve = null;
444
467
  } else if (msg.type === "rejoin_failed" || msg.type === "auth_error") {
445
- this.logger.warn(`Auth failed: ${msg.reason || "unknown"}`);
468
+ this.log("warn", `auth failed: ${msg.reason || "unknown"}`);
446
469
  this.clearAuthKey();
447
470
  } else if (msg.type === "leave_ack") {
448
471
  const leaveAck = msg as LeaveAckPayload;
449
472
  if (leaveAck.success === false) {
450
473
  const failure = this.buildAckFailure(leaveAck, "Leave failed");
451
- this.logger.warn(`Leave failed: ${failure.error}`);
474
+ this.log("warn", `leave failed: ${failure.error}`);
452
475
  } else {
453
- this.logger.info("Left Kichi world");
476
+ this.log("info", "left Kichi world");
454
477
  }
455
478
  }
456
479
  } catch (e) {
457
- this.logger.warn(`Failed to parse message: ${e}`);
480
+ this.log("warn", `failed to parse message: ${e}`);
458
481
  }
459
482
  }
460
483
 
@@ -571,7 +594,7 @@ export class KichiForwarderService {
571
594
  }
572
595
  return null;
573
596
  } catch (e) {
574
- this.logger.warn(`Failed to load identity: ${e}`);
597
+ this.log("warn", `failed to load identity: ${e}`);
575
598
  return null;
576
599
  }
577
600
  }
@@ -584,7 +607,7 @@ export class KichiForwarderService {
584
607
  if (!fs.existsSync(identityDir)) fs.mkdirSync(identityDir, { recursive: true, mode: 0o700 });
585
608
  fs.writeFileSync(identityPath, JSON.stringify(this.identity, null, 2), { mode: 0o600 });
586
609
  } catch (e) {
587
- this.logger.error(`Failed to save identity: ${e}`);
610
+ this.log("error", `failed to save identity: ${e}`);
588
611
  }
589
612
  }
590
613
 
@@ -592,7 +615,7 @@ export class KichiForwarderService {
592
615
  if (!this.identity) return;
593
616
  this.identity.authKey = undefined;
594
617
  this.saveIdentity();
595
- this.logger.info("AuthKey cleared");
618
+ this.log("info", "authKey cleared");
596
619
  }
597
620
 
598
621
  private sendRejoinPayload(): boolean {
@@ -603,7 +626,7 @@ export class KichiForwarderService {
603
626
  this.ws.send(
604
627
  JSON.stringify({ type: "rejoin", avatarId: this.identity.avatarId, authKey: this.identity.authKey }),
605
628
  );
606
- this.logger.debug(`Sent rejoin for ${this.identity.avatarId}`);
629
+ this.log("debug", `sent rejoin for ${this.identity.avatarId}`);
607
630
  return true;
608
631
  }
609
632
 
@@ -628,7 +651,7 @@ export class KichiForwarderService {
628
651
  if (!this.host) {
629
652
  throw new Error("No Kichi host configured");
630
653
  }
631
- return path.join(HOSTS_DIR, encodeURIComponent(this.host));
654
+ return path.join(this.options.runtimeDir, "hosts", encodeURIComponent(this.host));
632
655
  }
633
656
 
634
657
  private getWsUrl(): string {
@@ -647,14 +670,15 @@ export class KichiForwarderService {
647
670
 
648
671
  private loadCurrentHost(): string | null {
649
672
  try {
650
- if (!fs.existsSync(STATE_PATH)) {
673
+ const statePath = this.getStatePath();
674
+ if (!fs.existsSync(statePath)) {
651
675
  return null;
652
676
  }
653
- const data = JSON.parse(fs.readFileSync(STATE_PATH, "utf-8")) as { currentHost?: unknown };
677
+ const data = JSON.parse(fs.readFileSync(statePath, "utf-8")) as { currentHost?: unknown };
654
678
  if (typeof data.currentHost === "string" && data.currentHost.trim()) {
655
679
  return data.currentHost;
656
680
  }
657
- throw new Error(`Invalid currentHost value in ${STATE_PATH}`);
681
+ throw new Error(`Invalid currentHost value in ${statePath}`);
658
682
  } catch (error) {
659
683
  throw new Error(`Failed to load current host: ${error}`);
660
684
  }
@@ -666,17 +690,18 @@ export class KichiForwarderService {
666
690
  currentHost: host,
667
691
  llmRuntimeEnabled: previousState?.llmRuntimeEnabled ?? DEFAULT_LLM_RUNTIME_ENABLED,
668
692
  };
669
- fs.mkdirSync(KICHI_WORLD_DIR, { recursive: true, mode: 0o700 });
670
- fs.writeFileSync(STATE_PATH, JSON.stringify(nextState, null, 2), { mode: 0o600 });
693
+ fs.mkdirSync(this.options.runtimeDir, { recursive: true, mode: 0o700 });
694
+ fs.writeFileSync(this.getStatePath(), JSON.stringify(nextState, null, 2), { mode: 0o600 });
671
695
  }
672
696
 
673
697
  private readStateFile(): Partial<KichiState> | null {
674
- if (!fs.existsSync(STATE_PATH)) {
698
+ const statePath = this.getStatePath();
699
+ if (!fs.existsSync(statePath)) {
675
700
  return null;
676
701
  }
677
- const data = JSON.parse(fs.readFileSync(STATE_PATH, "utf-8")) as unknown;
702
+ const data = JSON.parse(fs.readFileSync(statePath, "utf-8")) as unknown;
678
703
  if (!data || typeof data !== "object") {
679
- throw new Error(`Invalid state payload in ${STATE_PATH}`);
704
+ throw new Error(`Invalid state payload in ${statePath}`);
680
705
  }
681
706
  return data as Partial<KichiState>;
682
707
  }
@@ -699,4 +724,26 @@ export class KichiForwarderService {
699
724
  this.joinResolve({ success: false, error: reason });
700
725
  this.joinResolve = null;
701
726
  }
727
+
728
+ private logPrefix(): string {
729
+ return `[kichi:${this.options.agentId}]`;
730
+ }
731
+
732
+ private log(level: "debug" | "info" | "warn" | "error", message: string): void {
733
+ const formatted = `${this.logPrefix()} ${message}`;
734
+ switch (level) {
735
+ case "debug":
736
+ this.logger.debug(formatted);
737
+ return;
738
+ case "info":
739
+ this.logger.info(formatted);
740
+ return;
741
+ case "warn":
742
+ this.logger.warn(formatted);
743
+ return;
744
+ case "error":
745
+ this.logger.error(formatted);
746
+ return;
747
+ }
748
+ }
702
749
  }
package/src/types.ts CHANGED
@@ -47,6 +47,9 @@ export type KichiIdentity = {
47
47
  };
48
48
 
49
49
  export type KichiConnectionStatus = {
50
+ agentId?: string;
51
+ runtimeDir?: string;
52
+ statePath?: string;
50
53
  host?: string;
51
54
  wsUrl?: string;
52
55
  identityPath?: string;