@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 +108 -0
- package/dist/src/service.js +16 -0
- package/index.ts +126 -0
- package/openclaw.plugin.json +2 -1
- package/package.json +1 -1
- package/src/service.ts +20 -0
- package/src/types.ts +27 -0
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",
|
package/dist/src/service.js
CHANGED
|
@@ -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",
|
package/openclaw.plugin.json
CHANGED
|
@@ -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.
|
|
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.
|
|
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";
|