@yahaha-studio/kichi-forwarder 0.1.2-beta.14 → 0.1.2-beta.16

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/dist/index.js CHANGED
@@ -845,6 +845,7 @@ function buildKichiPrompt() {
845
845
  "Kichi avatar control and status sync are available via `kichi_action` and `kichi_clock`.",
846
846
  "If the user gives a direct Kichi pose or action request, fulfill it with `kichi_action` and set `verify: true` so you can confirm the avatar actually applied the pose. If the result contains a warning about a fallback, tell the user what actually happened instead of assuming success.",
847
847
  "Write the visible reply as a natural user-facing response. Keep `kichi_action`, `kichi_clock`, and sync steps internal and absent from the visible reply.",
848
+ "If the user asks OpenClaw to send a selfie, base the visual appearance on the avatar details in IDENTITY.",
848
849
  "",
849
850
  "kichi_action timing (all required when sync is active):",
850
851
  "1. Task start: call BEFORE your first tool call OR before composing a multi-paragraph reply. For most work, start from a sit pose unless the user asked for a different pose or the task clearly fits another pose better.",
@@ -6,6 +6,7 @@ const MAX_NOTEBOARD_TEXT_LENGTH = 200;
6
6
  const DEFAULT_LLM_RUNTIME_ENABLED = true;
7
7
  const DEFAULT_GLANCE_DURATION_SECONDS = 1.8;
8
8
  const JOIN_SOURCE_FILE_NAME = "join-source.json";
9
+ const SMS_STATE_FILE_NAME = "sms-state.json";
9
10
  export class KichiForwarderService {
10
11
  logger;
11
12
  options;
@@ -488,6 +489,7 @@ export class KichiForwarderService {
488
489
  if (this.identity) {
489
490
  this.identity.authKey = joinAck.authKey;
490
491
  this.saveIdentity();
492
+ this.updateSmsLastActiveAt();
491
493
  this.log("info", `joined as ${this.identity.avatarId}`);
492
494
  }
493
495
  this.joinResolve?.({ success: true, authKey: joinAck.authKey });
@@ -665,6 +667,9 @@ export class KichiForwarderService {
665
667
  }
666
668
  return path.join(this.options.runtimeDir, "hosts", encodeURIComponent(this.host));
667
669
  }
670
+ getSmsStatePath() {
671
+ return path.join(this.options.runtimeDir, SMS_STATE_FILE_NAME);
672
+ }
668
673
  getKichiWorldRootDir() {
669
674
  return path.dirname(path.dirname(this.options.runtimeDir));
670
675
  }
@@ -693,6 +698,25 @@ export class KichiForwarderService {
693
698
  fs.mkdirSync(this.options.runtimeDir, { recursive: true, mode: 0o700 });
694
699
  fs.writeFileSync(this.getStatePath(), JSON.stringify(nextState, null, 2), { mode: 0o600 });
695
700
  }
701
+ updateSmsLastActiveAt() {
702
+ try {
703
+ const now = new Date();
704
+ const previousState = this.readSmsStateFile();
705
+ const nextState = {
706
+ date: now.toISOString().slice(0, 10),
707
+ totalSent: 0,
708
+ windows: { morning: 0, afternoon: 0, evening: 0 },
709
+ lastTypes: [],
710
+ ...previousState,
711
+ lastActiveAt: now.toISOString(),
712
+ };
713
+ fs.mkdirSync(this.options.runtimeDir, { recursive: true, mode: 0o700 });
714
+ fs.writeFileSync(this.getSmsStatePath(), JSON.stringify(nextState, null, 2), { mode: 0o600 });
715
+ }
716
+ catch (e) {
717
+ this.log("error", `failed to update sms state: ${e}`);
718
+ }
719
+ }
696
720
  readStateFile() {
697
721
  const statePath = this.getStatePath();
698
722
  if (!fs.existsSync(statePath)) {
@@ -704,6 +728,17 @@ export class KichiForwarderService {
704
728
  }
705
729
  return data;
706
730
  }
731
+ readSmsStateFile() {
732
+ const smsStatePath = this.getSmsStatePath();
733
+ if (!fs.existsSync(smsStatePath)) {
734
+ return null;
735
+ }
736
+ const data = JSON.parse(fs.readFileSync(smsStatePath, "utf-8"));
737
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
738
+ throw new Error(`Invalid SMS state payload in ${smsStatePath}`);
739
+ }
740
+ return data;
741
+ }
707
742
  clearReconnectTimeout() {
708
743
  if (!this.reconnectTimeout)
709
744
  return;
package/index.ts CHANGED
@@ -1049,6 +1049,7 @@ function buildKichiPrompt(): string {
1049
1049
  "Kichi avatar control and status sync are available via `kichi_action` and `kichi_clock`.",
1050
1050
  "If the user gives a direct Kichi pose or action request, fulfill it with `kichi_action` and set `verify: true` so you can confirm the avatar actually applied the pose. If the result contains a warning about a fallback, tell the user what actually happened instead of assuming success.",
1051
1051
  "Write the visible reply as a natural user-facing response. Keep `kichi_action`, `kichi_clock`, and sync steps internal and absent from the visible reply.",
1052
+ "If the user asks OpenClaw to send a selfie, base the visual appearance on the avatar details in IDENTITY.",
1052
1053
  "",
1053
1054
  "kichi_action timing (all required when sync is active):",
1054
1055
  "1. Task start: call BEFORE your first tool call OR before composing a multi-paragraph reply. For most work, start from a sit pose unless the user asked for a different pose or the task clearly fits another pose better.",
@@ -2,7 +2,7 @@
2
2
  "id": "kichi-forwarder",
3
3
  "name": "Kichi Forwarder",
4
4
  "description": "Native OpenClaw plugin for Kichi World with direct avatar control, status sync, timers, notes, and music tools",
5
- "version": "0.1.2-beta.14",
5
+ "version": "0.1.2-beta.16",
6
6
  "author": "OpenClaw",
7
7
  "skills": ["./skills/kichi-forwarder"],
8
8
  "contracts": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yahaha-studio/kichi-forwarder",
3
- "version": "0.1.2-beta.14",
3
+ "version": "0.1.2-beta.16",
4
4
  "description": "Native OpenClaw plugin for Kichi World with direct avatar control, status sync, timers, notes, and music tools",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/service.ts CHANGED
@@ -38,6 +38,19 @@ const MAX_NOTEBOARD_TEXT_LENGTH = 200;
38
38
  const DEFAULT_LLM_RUNTIME_ENABLED = true;
39
39
  const DEFAULT_GLANCE_DURATION_SECONDS = 1.8;
40
40
  const JOIN_SOURCE_FILE_NAME = "join-source.json";
41
+ const SMS_STATE_FILE_NAME = "sms-state.json";
42
+
43
+ type SmsState = {
44
+ lastActiveAt: string;
45
+ date: string;
46
+ totalSent: number;
47
+ windows: {
48
+ morning: number;
49
+ afternoon: number;
50
+ evening: number;
51
+ };
52
+ lastTypes: string[];
53
+ };
41
54
 
42
55
  type AckFailureResult = {
43
56
  success: false;
@@ -625,6 +638,7 @@ export class KichiForwarderService {
625
638
  if (this.identity) {
626
639
  this.identity.authKey = joinAck.authKey;
627
640
  this.saveIdentity();
641
+ this.updateSmsLastActiveAt();
628
642
  this.log("info", `joined as ${this.identity.avatarId}`);
629
643
  }
630
644
  this.joinResolve?.({ success: true, authKey: joinAck.authKey });
@@ -824,6 +838,10 @@ export class KichiForwarderService {
824
838
  return path.join(this.options.runtimeDir, "hosts", encodeURIComponent(this.host));
825
839
  }
826
840
 
841
+ private getSmsStatePath(): string {
842
+ return path.join(this.options.runtimeDir, SMS_STATE_FILE_NAME);
843
+ }
844
+
827
845
  private getKichiWorldRootDir(): string {
828
846
  return path.dirname(path.dirname(this.options.runtimeDir));
829
847
  }
@@ -856,6 +874,25 @@ export class KichiForwarderService {
856
874
  fs.writeFileSync(this.getStatePath(), JSON.stringify(nextState, null, 2), { mode: 0o600 });
857
875
  }
858
876
 
877
+ private updateSmsLastActiveAt(): void {
878
+ try {
879
+ const now = new Date();
880
+ const previousState = this.readSmsStateFile();
881
+ const nextState: SmsState = {
882
+ date: now.toISOString().slice(0, 10),
883
+ totalSent: 0,
884
+ windows: { morning: 0, afternoon: 0, evening: 0 },
885
+ lastTypes: [],
886
+ ...previousState,
887
+ lastActiveAt: now.toISOString(),
888
+ };
889
+ fs.mkdirSync(this.options.runtimeDir, { recursive: true, mode: 0o700 });
890
+ fs.writeFileSync(this.getSmsStatePath(), JSON.stringify(nextState, null, 2), { mode: 0o600 });
891
+ } catch (e) {
892
+ this.log("error", `failed to update sms state: ${e}`);
893
+ }
894
+ }
895
+
859
896
  private readStateFile(): Partial<KichiState> | null {
860
897
  const statePath = this.getStatePath();
861
898
  if (!fs.existsSync(statePath)) {
@@ -868,6 +905,18 @@ export class KichiForwarderService {
868
905
  return data as Partial<KichiState>;
869
906
  }
870
907
 
908
+ private readSmsStateFile(): Partial<SmsState> | null {
909
+ const smsStatePath = this.getSmsStatePath();
910
+ if (!fs.existsSync(smsStatePath)) {
911
+ return null;
912
+ }
913
+ const data = JSON.parse(fs.readFileSync(smsStatePath, "utf-8")) as unknown;
914
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
915
+ throw new Error(`Invalid SMS state payload in ${smsStatePath}`);
916
+ }
917
+ return data as Partial<SmsState>;
918
+ }
919
+
871
920
  private clearReconnectTimeout(): void {
872
921
  if (!this.reconnectTimeout) return;
873
922
  clearTimeout(this.reconnectTimeout);