@yahaha-studio/kichi-forwarder 0.1.2-beta.29 → 0.1.2-beta.30

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
@@ -1,9 +1,12 @@
1
1
  import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
2
4
  import { fileURLToPath } from "node:url";
3
5
  import { agentCommandFromIngress } from "openclaw/plugin-sdk/agent-runtime";
4
6
  import { parse } from "./src/config.js";
5
7
  import { KichiRuntimeManager } from "./src/runtime-manager.js";
6
8
  const BUNDLED_STATIC_CONFIG_PATH = new URL("./config/kichi-config.json", import.meta.url);
9
+ const MATE_DAILY_SCHEDULE_PATH = path.join(os.homedir(), ".openclaw", "kichi-world", "agents", "main", "daily-schedule.json");
7
10
  function jsonResult(payload) {
8
11
  return { content: [{ type: "text", text: JSON.stringify(payload) }], details: payload };
9
12
  }
@@ -490,6 +493,99 @@ function registerPluginHooks(api, runtimeManager) {
490
493
  function isPlainObject(value) {
491
494
  return !!value && typeof value === "object" && !Array.isArray(value);
492
495
  }
496
+ function parseMateDailySchedulePlace(value, slotIndex) {
497
+ if (!isPlainObject(value)) {
498
+ throw new Error(`slots[${slotIndex}].place must be an object`);
499
+ }
500
+ const type = value.type;
501
+ if (type === "home" || type === "kichi_room") {
502
+ return { type };
503
+ }
504
+ if (type === "real_world") {
505
+ if (typeof value.name !== "string" || !value.name.trim()) {
506
+ throw new Error(`slots[${slotIndex}].place.name must be a non-empty string for real_world`);
507
+ }
508
+ return { type, name: value.name };
509
+ }
510
+ throw new Error(`slots[${slotIndex}].place.type must be one of: home, kichi_room, real_world`);
511
+ }
512
+ function parseMateDailyScheduleSlot(value, index) {
513
+ if (!isPlainObject(value)) {
514
+ throw new Error(`slots[${index}] must be an object`);
515
+ }
516
+ const hour = value.h;
517
+ if (typeof hour !== "number" || !Number.isInteger(hour) || hour < 0 || hour > 23) {
518
+ throw new Error(`slots[${index}].h must be an integer between 0 and 23`);
519
+ }
520
+ if (typeof value.act !== "string") {
521
+ throw new Error(`slots[${index}].act must be a string`);
522
+ }
523
+ if (typeof value.mood !== "string") {
524
+ throw new Error(`slots[${index}].mood must be a string`);
525
+ }
526
+ if (typeof value.sleep !== "boolean") {
527
+ throw new Error(`slots[${index}].sleep must be a boolean`);
528
+ }
529
+ return {
530
+ h: hour,
531
+ place: parseMateDailySchedulePlace(value.place, index),
532
+ act: value.act,
533
+ mood: value.mood,
534
+ sleep: value.sleep,
535
+ };
536
+ }
537
+ function parseMateDailySchedule(value) {
538
+ if (!isPlainObject(value)) {
539
+ throw new Error("daily-schedule.json must contain a JSON object");
540
+ }
541
+ if (typeof value.date !== "string" || !value.date.trim()) {
542
+ throw new Error("date must be a non-empty string");
543
+ }
544
+ if (typeof value.timezone !== "string" || !value.timezone.trim()) {
545
+ throw new Error("timezone must be a non-empty string");
546
+ }
547
+ if (!Array.isArray(value.activeArcs) || !value.activeArcs.every((item) => typeof item === "string")) {
548
+ throw new Error("activeArcs must be an array of strings");
549
+ }
550
+ if (!Array.isArray(value.slots)) {
551
+ throw new Error("slots must be an array");
552
+ }
553
+ return {
554
+ date: value.date,
555
+ timezone: value.timezone,
556
+ activeArcs: value.activeArcs,
557
+ slots: value.slots.map(parseMateDailyScheduleSlot),
558
+ };
559
+ }
560
+ function readMateDailySchedule() {
561
+ const raw = fs.readFileSync(MATE_DAILY_SCHEDULE_PATH, "utf8");
562
+ return parseMateDailySchedule(JSON.parse(raw));
563
+ }
564
+ function createMateDailyScheduleSyncTool(service) {
565
+ return {
566
+ name: "kichi_sync_mate_daily_schedule",
567
+ label: "kichi_sync_mate_daily_schedule",
568
+ description: "Read today's mate daily schedule from the canonical Kichi World file and sync it to Kichi server.",
569
+ parameters: { type: "object", properties: {} },
570
+ execute: async (_toolCallId, _params) => {
571
+ try {
572
+ const schedule = readMateDailySchedule();
573
+ service.syncMateDailySchedule(schedule);
574
+ return jsonResult({
575
+ success: true,
576
+ date: schedule.date,
577
+ slotCount: schedule.slots.length,
578
+ });
579
+ }
580
+ catch (error) {
581
+ return jsonResult({
582
+ success: false,
583
+ error: `Failed to sync mate daily schedule from ${MATE_DAILY_SCHEDULE_PATH}: ${error instanceof Error ? error.message : String(error)}`,
584
+ });
585
+ }
586
+ },
587
+ };
588
+ }
493
589
  function isNonNegativeInteger(value) {
494
590
  return typeof value === "number" && Number.isInteger(value) && value >= 0;
495
591
  }
@@ -1019,6 +1115,18 @@ const plugin = {
1019
1115
  }
1020
1116
  },
1021
1117
  });
1118
+ api.registerTool((ctx) => {
1119
+ const locator = resolveToolLocator(ctx);
1120
+ const agentId = runtimeManager.resolveRuntimeAgentId(locator);
1121
+ if (!agentId) {
1122
+ return null;
1123
+ }
1124
+ const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
1125
+ if (!service.isOfficialOpenClawSource()) {
1126
+ return null;
1127
+ }
1128
+ return createMateDailyScheduleSyncTool(service);
1129
+ }, { name: "kichi_sync_mate_daily_schedule" });
1022
1130
  api.registerTool((ctx) => ({
1023
1131
  name: "kichi_join",
1024
1132
  label: "kichi_join",
@@ -169,6 +169,22 @@ export class KichiForwarderService {
169
169
  this.ws.send(JSON.stringify(outboundPayload));
170
170
  return true;
171
171
  }
172
+ syncMateDailySchedule(schedule) {
173
+ const identity = this.requireIdentity();
174
+ if (!identity) {
175
+ throw new Error("Missing Kichi identity");
176
+ }
177
+ if (this.ws?.readyState !== WebSocket.OPEN) {
178
+ throw new Error("Kichi websocket is not connected");
179
+ }
180
+ const payload = {
181
+ type: "kichi_sync_mate_daily_schedule",
182
+ avatarId: identity.avatarId,
183
+ authKey: identity.authKey,
184
+ schedule,
185
+ };
186
+ this.ws.send(JSON.stringify(payload));
187
+ }
172
188
  sendClock(action, clock, requestId) {
173
189
  if (!this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN)
174
190
  return false;
package/index.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
2
4
  import { fileURLToPath } from "node:url";
3
5
  import type {
4
6
  OpenClawPluginApi,
@@ -21,10 +23,21 @@ import type {
21
23
  KichiEnvironment,
22
24
  KichiEnvironmentsConfig,
23
25
  KichiStaticConfig,
26
+ MateDailySchedule,
27
+ MateDailySchedulePlace,
28
+ MateDailyScheduleSlot,
24
29
  PomodoroPhase,
25
30
  PoseType,
26
31
  } from "./src/types.js";
27
32
  const BUNDLED_STATIC_CONFIG_PATH = new URL("./config/kichi-config.json", import.meta.url);
33
+ const MATE_DAILY_SCHEDULE_PATH = path.join(
34
+ os.homedir(),
35
+ ".openclaw",
36
+ "kichi-world",
37
+ "agents",
38
+ "main",
39
+ "daily-schedule.json",
40
+ );
28
41
 
29
42
  function jsonResult(payload: unknown): { content: { type: "text"; text: string }[]; details: unknown } {
30
43
  return { content: [{ type: "text", text: JSON.stringify(payload) }], details: payload };
@@ -636,6 +649,106 @@ function isPlainObject(value: unknown): value is Record<string, unknown> {
636
649
  return !!value && typeof value === "object" && !Array.isArray(value);
637
650
  }
638
651
 
652
+ function parseMateDailySchedulePlace(value: unknown, slotIndex: number): MateDailySchedulePlace {
653
+ if (!isPlainObject(value)) {
654
+ throw new Error(`slots[${slotIndex}].place must be an object`);
655
+ }
656
+
657
+ const type = value.type;
658
+ if (type === "home" || type === "kichi_room") {
659
+ return { type };
660
+ }
661
+ if (type === "real_world") {
662
+ if (typeof value.name !== "string" || !value.name.trim()) {
663
+ throw new Error(`slots[${slotIndex}].place.name must be a non-empty string for real_world`);
664
+ }
665
+ return { type, name: value.name };
666
+ }
667
+ throw new Error(`slots[${slotIndex}].place.type must be one of: home, kichi_room, real_world`);
668
+ }
669
+
670
+ function parseMateDailyScheduleSlot(value: unknown, index: number): MateDailyScheduleSlot {
671
+ if (!isPlainObject(value)) {
672
+ throw new Error(`slots[${index}] must be an object`);
673
+ }
674
+ const hour = value.h;
675
+ if (typeof hour !== "number" || !Number.isInteger(hour) || hour < 0 || hour > 23) {
676
+ throw new Error(`slots[${index}].h must be an integer between 0 and 23`);
677
+ }
678
+ if (typeof value.act !== "string") {
679
+ throw new Error(`slots[${index}].act must be a string`);
680
+ }
681
+ if (typeof value.mood !== "string") {
682
+ throw new Error(`slots[${index}].mood must be a string`);
683
+ }
684
+ if (typeof value.sleep !== "boolean") {
685
+ throw new Error(`slots[${index}].sleep must be a boolean`);
686
+ }
687
+
688
+ return {
689
+ h: hour,
690
+ place: parseMateDailySchedulePlace(value.place, index),
691
+ act: value.act,
692
+ mood: value.mood,
693
+ sleep: value.sleep,
694
+ };
695
+ }
696
+
697
+ function parseMateDailySchedule(value: unknown): MateDailySchedule {
698
+ if (!isPlainObject(value)) {
699
+ throw new Error("daily-schedule.json must contain a JSON object");
700
+ }
701
+ if (typeof value.date !== "string" || !value.date.trim()) {
702
+ throw new Error("date must be a non-empty string");
703
+ }
704
+ if (typeof value.timezone !== "string" || !value.timezone.trim()) {
705
+ throw new Error("timezone must be a non-empty string");
706
+ }
707
+ if (!Array.isArray(value.activeArcs) || !value.activeArcs.every((item) => typeof item === "string")) {
708
+ throw new Error("activeArcs must be an array of strings");
709
+ }
710
+ if (!Array.isArray(value.slots)) {
711
+ throw new Error("slots must be an array");
712
+ }
713
+
714
+ return {
715
+ date: value.date,
716
+ timezone: value.timezone,
717
+ activeArcs: value.activeArcs,
718
+ slots: value.slots.map(parseMateDailyScheduleSlot),
719
+ };
720
+ }
721
+
722
+ function readMateDailySchedule(): MateDailySchedule {
723
+ const raw = fs.readFileSync(MATE_DAILY_SCHEDULE_PATH, "utf8");
724
+ return parseMateDailySchedule(JSON.parse(raw) as unknown);
725
+ }
726
+
727
+ function createMateDailyScheduleSyncTool(service: KichiForwarderService) {
728
+ return {
729
+ name: "kichi_sync_mate_daily_schedule" as const,
730
+ label: "kichi_sync_mate_daily_schedule",
731
+ description: "Read today's mate daily schedule from the canonical Kichi World file and sync it to Kichi server.",
732
+ parameters: { type: "object" as const, properties: {} },
733
+ execute: async (_toolCallId: string, _params: unknown) => {
734
+ try {
735
+ const schedule = readMateDailySchedule();
736
+ service.syncMateDailySchedule(schedule);
737
+ return jsonResult({
738
+ success: true,
739
+ date: schedule.date,
740
+ slotCount: schedule.slots.length,
741
+ });
742
+ } catch (error) {
743
+ return jsonResult({
744
+ success: false,
745
+ error: `Failed to sync mate daily schedule from ${MATE_DAILY_SCHEDULE_PATH}: ${error instanceof Error ? error.message : String(error)}`,
746
+ });
747
+ }
748
+ },
749
+ };
750
+ }
751
+
639
752
  function isNonNegativeInteger(value: unknown): value is number {
640
753
  return typeof value === "number" && Number.isInteger(value) && value >= 0;
641
754
  }
@@ -1247,6 +1360,19 @@ const plugin = {
1247
1360
  },
1248
1361
  });
1249
1362
 
1363
+ api.registerTool((ctx) => {
1364
+ const locator = resolveToolLocator(ctx);
1365
+ const agentId = runtimeManager.resolveRuntimeAgentId(locator);
1366
+ if (!agentId) {
1367
+ return null;
1368
+ }
1369
+ const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
1370
+ if (!service.isOfficialOpenClawSource()) {
1371
+ return null;
1372
+ }
1373
+ return createMateDailyScheduleSyncTool(service);
1374
+ }, { name: "kichi_sync_mate_daily_schedule" });
1375
+
1250
1376
  api.registerTool((ctx) => ({
1251
1377
  name: "kichi_join",
1252
1378
  label: "kichi_join",
@@ -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.29",
5
+ "version": "0.1.2-beta.30",
6
6
  "author": "OpenClaw",
7
7
  "skills": ["./skills/kichi-forwarder"],
8
8
  "contracts": {
@@ -15,6 +15,7 @@
15
15
  "kichi_action",
16
16
  "kichi_glance",
17
17
  "kichi_idle_plan",
18
+ "kichi_sync_mate_daily_schedule",
18
19
  "kichi_clock",
19
20
  "kichi_query_status",
20
21
  "kichi_music_album_create",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yahaha-studio/kichi-forwarder",
3
- "version": "0.1.2-beta.29",
3
+ "version": "0.1.2-beta.30",
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
@@ -30,11 +30,13 @@ import type {
30
30
  KichiIdentity,
31
31
  KichiState,
32
32
  LeaveAckPayload,
33
+ MateDailySchedule,
33
34
  PoseType,
34
35
  QueryStatusPayload,
35
36
  QueryStatusResultPayload,
36
37
  StatusAckPayload,
37
38
  StatusPayload,
39
+ SyncMateDailySchedulePayload,
38
40
  } from "./types.js";
39
41
 
40
42
  const MAX_NOTEBOARD_TEXT_LENGTH = 200;
@@ -281,6 +283,24 @@ export class KichiForwarderService {
281
283
  return true;
282
284
  }
283
285
 
286
+ syncMateDailySchedule(schedule: MateDailySchedule): void {
287
+ const identity = this.requireIdentity();
288
+ if (!identity) {
289
+ throw new Error("Missing Kichi identity");
290
+ }
291
+ if (this.ws?.readyState !== WebSocket.OPEN) {
292
+ throw new Error("Kichi websocket is not connected");
293
+ }
294
+
295
+ const payload: SyncMateDailySchedulePayload = {
296
+ type: "kichi_sync_mate_daily_schedule",
297
+ avatarId: identity.avatarId,
298
+ authKey: identity.authKey,
299
+ schedule,
300
+ };
301
+ this.ws.send(JSON.stringify(payload));
302
+ }
303
+
284
304
  sendClock(action: ClockAction, clock?: ClockConfig, requestId?: string): boolean {
285
305
  if (!this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN) return false;
286
306
  if (action === "set" && !clock) return false;
package/src/types.ts CHANGED
@@ -194,6 +194,33 @@ export type IdlePlanPayload = IdlePlanContent & {
194
194
  authKey: string;
195
195
  };
196
196
 
197
+ export type MateDailySchedulePlace =
198
+ | { type: "home" }
199
+ | { type: "kichi_room" }
200
+ | { type: "real_world"; name: string };
201
+
202
+ export type MateDailyScheduleSlot = {
203
+ h: number;
204
+ place: MateDailySchedulePlace;
205
+ act: string;
206
+ mood: string;
207
+ sleep: boolean;
208
+ };
209
+
210
+ export type MateDailySchedule = {
211
+ date: string;
212
+ timezone: string;
213
+ activeArcs: string[];
214
+ slots: MateDailyScheduleSlot[];
215
+ };
216
+
217
+ export type SyncMateDailySchedulePayload = {
218
+ type: "kichi_sync_mate_daily_schedule";
219
+ avatarId: string;
220
+ authKey: string;
221
+ schedule: MateDailySchedule;
222
+ };
223
+
197
224
  export type ClockAction = "set" | "stop";
198
225
 
199
226
  export type ClockMode = "pomodoro" | "countDown" | "countUp";