@yahaha-studio/kichi-forwarder 0.1.0-beta.4 → 0.1.0-beta.6
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/README.md +1 -9
- package/config/kichi-config.json +945 -72
- package/index.ts +106 -26
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/skills/kichi-forwarder/SKILL.md +1 -0
- package/skills/kichi-forwarder/references/error.md +4 -4
- package/skills/kichi-forwarder/references/heartbeat.md +7 -8
- package/skills/kichi-forwarder/references/install.md +2 -2
- package/src/service.ts +3 -1
- package/src/types.ts +12 -1
package/index.ts
CHANGED
|
@@ -6,6 +6,8 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
|
6
6
|
import { parse } from "./src/config.js";
|
|
7
7
|
import { KichiForwarderService } from "./src/service.js";
|
|
8
8
|
import type {
|
|
9
|
+
ActionDefinition,
|
|
10
|
+
ActionPlayback,
|
|
9
11
|
ActionResult,
|
|
10
12
|
Album,
|
|
11
13
|
ClockAction,
|
|
@@ -22,25 +24,25 @@ const FIXED_HOOK_STATUSES: Record<string, ActionResult> = {
|
|
|
22
24
|
poseType: "sit",
|
|
23
25
|
action: "Thinking",
|
|
24
26
|
bubble: "Planning task",
|
|
25
|
-
log: "
|
|
27
|
+
log: "I'm reading the request and getting started.",
|
|
26
28
|
},
|
|
27
29
|
beforeToolCall: {
|
|
28
30
|
poseType: "sit",
|
|
29
31
|
action: "Typing with Keyboard",
|
|
30
32
|
bubble: "Working step",
|
|
31
|
-
log: "
|
|
33
|
+
log: "I'm at the keyboard and working through this step.",
|
|
32
34
|
},
|
|
33
35
|
agentEndSuccess: {
|
|
34
36
|
poseType: "stand",
|
|
35
37
|
action: "Yay",
|
|
36
38
|
bubble: "Task complete",
|
|
37
|
-
log: "
|
|
39
|
+
log: "I wrapped it up and everything landed cleanly.",
|
|
38
40
|
},
|
|
39
41
|
agentEndFailure: {
|
|
40
42
|
poseType: "stand",
|
|
41
43
|
action: "Tired",
|
|
42
44
|
bubble: "Task failed",
|
|
43
|
-
log: "
|
|
45
|
+
log: "I hit a problem here and need another pass.",
|
|
44
46
|
},
|
|
45
47
|
};
|
|
46
48
|
|
|
@@ -94,14 +96,61 @@ function getMusicTitleExamples(): string[] {
|
|
|
94
96
|
return loadRuntimeAlbumConfig().track.slice(0, 10).map((item) => item.name);
|
|
95
97
|
}
|
|
96
98
|
|
|
97
|
-
function
|
|
99
|
+
function isActionDefinition(value: unknown): value is ActionDefinition {
|
|
100
|
+
if (!value || typeof value !== "object") {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
const action = value as Partial<ActionDefinition>;
|
|
104
|
+
return typeof action.name === "string"
|
|
105
|
+
&& action.name.trim().length > 0
|
|
106
|
+
&& (action.playback === "loop" || action.playback === "once")
|
|
107
|
+
&& (action.resumeAction === undefined || (typeof action.resumeAction === "string" && action.resumeAction.trim().length > 0));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function isPoseActions(value: unknown): value is Record<PoseType, ActionDefinition[]> {
|
|
98
111
|
if (!value || typeof value !== "object") {
|
|
99
112
|
return false;
|
|
100
113
|
}
|
|
101
114
|
const actions = value as Partial<Record<PoseType, unknown>>;
|
|
102
115
|
return ["stand", "sit", "lay", "floor"].every((pose) =>
|
|
103
116
|
Array.isArray(actions[pose as PoseType])
|
|
104
|
-
&& (actions[pose as PoseType] as unknown[]).every((item) =>
|
|
117
|
+
&& (actions[pose as PoseType] as unknown[]).every((item) => isActionDefinition(item)));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function normalizeActionDefinitions(actions: Record<PoseType, ActionDefinition[]>): Record<PoseType, ActionDefinition[]> {
|
|
121
|
+
const normalized = {} as Record<PoseType, ActionDefinition[]>;
|
|
122
|
+
for (const pose of ["stand", "sit", "lay", "floor"] as PoseType[]) {
|
|
123
|
+
const entries = actions[pose];
|
|
124
|
+
const seen = new Set<string>();
|
|
125
|
+
normalized[pose] = entries.map((entry) => {
|
|
126
|
+
const name = entry.name.trim();
|
|
127
|
+
const key = name.toLowerCase();
|
|
128
|
+
if (seen.has(key)) {
|
|
129
|
+
throw new Error(`config/kichi-config.json contains duplicate action "${name}" for pose "${pose}"`);
|
|
130
|
+
}
|
|
131
|
+
seen.add(key);
|
|
132
|
+
const playback = entry.playback;
|
|
133
|
+
const resumeAction = typeof entry.resumeAction === "string" ? entry.resumeAction.trim() : undefined;
|
|
134
|
+
if (playback === "loop" && resumeAction) {
|
|
135
|
+
throw new Error(`config/kichi-config.json action "${name}" for pose "${pose}" cannot set resumeAction when playback is loop`);
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
name,
|
|
139
|
+
playback,
|
|
140
|
+
...(resumeAction ? { resumeAction } : {}),
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
const available = new Set(normalized[pose].map((entry) => entry.name.toLowerCase()));
|
|
144
|
+
for (const entry of normalized[pose]) {
|
|
145
|
+
if (entry.playback === "once" && !entry.resumeAction) {
|
|
146
|
+
throw new Error(`config/kichi-config.json action "${entry.name}" for pose "${pose}" must set resumeAction when playback is once`);
|
|
147
|
+
}
|
|
148
|
+
if (entry.resumeAction && !available.has(entry.resumeAction.toLowerCase())) {
|
|
149
|
+
throw new Error(`config/kichi-config.json action "${entry.name}" for pose "${pose}" references unknown resumeAction "${entry.resumeAction}"`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return normalized;
|
|
105
154
|
}
|
|
106
155
|
|
|
107
156
|
function normalizeStaticConfig(value: unknown): KichiStaticConfig {
|
|
@@ -116,7 +165,7 @@ function normalizeStaticConfig(value: unknown): KichiStaticConfig {
|
|
|
116
165
|
}
|
|
117
166
|
return {
|
|
118
167
|
album,
|
|
119
|
-
actions,
|
|
168
|
+
actions: normalizeActionDefinitions(actions),
|
|
120
169
|
};
|
|
121
170
|
}
|
|
122
171
|
|
|
@@ -151,11 +200,13 @@ function loadStaticConfig(): KichiStaticConfig {
|
|
|
151
200
|
}
|
|
152
201
|
|
|
153
202
|
function sendStatusUpdate(status: ActionResult): void {
|
|
203
|
+
const actionDefinition = getActionDefinition(status.poseType, status.action);
|
|
154
204
|
service?.sendStatus(
|
|
155
205
|
status.poseType,
|
|
156
|
-
|
|
206
|
+
actionDefinition.name,
|
|
157
207
|
status.bubble || status.action,
|
|
158
208
|
typeof status.log === "string" ? status.log.trim() : "",
|
|
209
|
+
getActionPlayback(actionDefinition),
|
|
159
210
|
);
|
|
160
211
|
}
|
|
161
212
|
|
|
@@ -563,15 +614,43 @@ function buildMusicTitlesDescription(): string {
|
|
|
563
614
|
].join(" ");
|
|
564
615
|
}
|
|
565
616
|
|
|
617
|
+
function getActionDefinition(poseType: PoseType, action: string): ActionDefinition {
|
|
618
|
+
const poseActions = loadStaticConfig().actions[poseType];
|
|
619
|
+
const matched = poseActions.find((entry) => entry.name.toLowerCase() === action.toLowerCase());
|
|
620
|
+
if (!matched) {
|
|
621
|
+
throw new Error(`Unknown action "${action}" for poseType "${poseType}"`);
|
|
622
|
+
}
|
|
623
|
+
return matched;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function getActionPlayback(action: ActionDefinition): ActionPlayback {
|
|
627
|
+
return action.playback === "once"
|
|
628
|
+
? {
|
|
629
|
+
mode: "once",
|
|
630
|
+
resumeAction: action.resumeAction,
|
|
631
|
+
}
|
|
632
|
+
: {
|
|
633
|
+
mode: "loop",
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function formatActionList(actions: ActionDefinition[], playback: ActionPlayback["mode"]): string {
|
|
638
|
+
return actions
|
|
639
|
+
.filter((entry) => entry.playback === playback)
|
|
640
|
+
.map((entry) => entry.name)
|
|
641
|
+
.join(", ");
|
|
642
|
+
}
|
|
643
|
+
|
|
566
644
|
function buildKichiActionDescription(): string {
|
|
567
645
|
const actions = loadStaticConfig().actions;
|
|
568
646
|
return [
|
|
569
647
|
"Directly control the avatar inside Kichi World.",
|
|
570
648
|
"Use this whenever the user explicitly asks you to make the Kichi avatar sit down, stand up, lie down, floor-sit, type, read, meditate, celebrate, or perform another listed animation.",
|
|
571
|
-
|
|
572
|
-
`
|
|
573
|
-
`
|
|
574
|
-
`
|
|
649
|
+
"For most work, prefer a sit pose and switch actions as the task moves between stages.",
|
|
650
|
+
`stand actions: ${actions.stand.map((entry) => entry.name).join(", ")}`,
|
|
651
|
+
`sit actions: ${actions.sit.map((entry) => entry.name).join(", ")}`,
|
|
652
|
+
`lay actions: ${actions.lay.map((entry) => entry.name).join(", ")}`,
|
|
653
|
+
`floor actions: ${actions.floor.map((entry) => entry.name).join(", ")}`,
|
|
575
654
|
].join("\n");
|
|
576
655
|
}
|
|
577
656
|
|
|
@@ -579,17 +658,17 @@ function buildKichiPrompt(): string {
|
|
|
579
658
|
return [
|
|
580
659
|
"Kichi avatar control and status sync are available via `kichi_action` and `kichi_clock`.",
|
|
581
660
|
"If the user gives a direct Kichi pose or action request, fulfill it with `kichi_action`.",
|
|
582
|
-
"
|
|
661
|
+
"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.",
|
|
583
662
|
"",
|
|
584
|
-
"kichi_action timing (all
|
|
585
|
-
"1. Task start: call BEFORE your first tool call OR before composing a multi-paragraph reply.
|
|
586
|
-
"2. Step switch: call when
|
|
587
|
-
"3. Task end: call BEFORE final reply.
|
|
588
|
-
"bubble: 2-5 word companion speech. log:
|
|
663
|
+
"kichi_action timing (all required when sync is active):",
|
|
664
|
+
"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.",
|
|
665
|
+
"2. Step switch: call when the task moves into a different stage. Keep the pose aligned with the work, usually staying seated while switching actions within the task as needed.",
|
|
666
|
+
"3. Task end: call BEFORE final reply. Use the order `kichi_action` -> reply.",
|
|
667
|
+
"bubble: 2-5 word companion speech. log: one short natural first-person sentence under 15 words. Match the language of the bubble and mention the current action and immediate focus like a real companion.",
|
|
589
668
|
"",
|
|
590
669
|
"kichi_clock: set countDown for tasks with 2+ steps or >10s work. Skip for quick one-shots.",
|
|
591
670
|
"",
|
|
592
|
-
"
|
|
671
|
+
"User opt-out, Kichi config/test work, and explicit pose requests take priority over sync.",
|
|
593
672
|
].join("\n");
|
|
594
673
|
}
|
|
595
674
|
|
|
@@ -776,7 +855,7 @@ const plugin = {
|
|
|
776
855
|
log: {
|
|
777
856
|
type: "string",
|
|
778
857
|
description:
|
|
779
|
-
"
|
|
858
|
+
"Short natural first-person sentence under 15 words. Match the language of the bubble and mention the current action and immediate focus.",
|
|
780
859
|
},
|
|
781
860
|
},
|
|
782
861
|
required: ["poseType", "action"],
|
|
@@ -803,21 +882,21 @@ const plugin = {
|
|
|
803
882
|
|
|
804
883
|
const normalizedPoseType = poseType as PoseType;
|
|
805
884
|
const poseActions = loadStaticConfig().actions[normalizedPoseType];
|
|
806
|
-
const matched = poseActions.find((entry) => entry.toLowerCase() === action.toLowerCase());
|
|
885
|
+
const matched = poseActions.find((entry) => entry.name.toLowerCase() === action.toLowerCase());
|
|
807
886
|
if (!matched) {
|
|
808
887
|
return {
|
|
809
888
|
success: false,
|
|
810
889
|
error: `Unknown action "${action}" for poseType "${poseType}"`,
|
|
811
|
-
available: poseActions,
|
|
890
|
+
available: poseActions.map((entry) => entry.name),
|
|
812
891
|
};
|
|
813
892
|
}
|
|
814
893
|
|
|
815
|
-
const bubbleText = typeof bubble === "string" && bubble.trim() ? bubble.trim() : matched;
|
|
894
|
+
const bubbleText = typeof bubble === "string" && bubble.trim() ? bubble.trim() : matched.name;
|
|
816
895
|
const logText = typeof log === "string" ? log.trim() : "";
|
|
817
896
|
sendStatusUpdate(
|
|
818
897
|
{
|
|
819
898
|
poseType: normalizedPoseType,
|
|
820
|
-
action: matched,
|
|
899
|
+
action: matched.name,
|
|
821
900
|
bubble: bubbleText,
|
|
822
901
|
log: logText,
|
|
823
902
|
},
|
|
@@ -825,12 +904,13 @@ const plugin = {
|
|
|
825
904
|
return {
|
|
826
905
|
success: true,
|
|
827
906
|
poseType: normalizedPoseType,
|
|
828
|
-
action: matched,
|
|
907
|
+
action: matched.name,
|
|
829
908
|
bubble: bubbleText,
|
|
830
909
|
log: logText,
|
|
910
|
+
playback: getActionPlayback(matched),
|
|
831
911
|
};
|
|
832
912
|
},
|
|
833
|
-
});
|
|
913
|
+
});
|
|
834
914
|
api.registerTool({
|
|
835
915
|
name: "kichi_clock",
|
|
836
916
|
description:
|
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.0-beta.
|
|
5
|
+
"version": "0.1.0-beta.6",
|
|
6
6
|
"author": "OpenClaw",
|
|
7
7
|
"skills": ["./skills/kichi-forwarder"],
|
|
8
8
|
"configSchema": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yahaha-studio/kichi-forwarder",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.6",
|
|
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": "index.ts",
|
|
@@ -126,6 +126,7 @@ When the user asks to leave Kichi World:
|
|
|
126
126
|
Use this for direct Kichi avatar control as well as lifecycle sync.
|
|
127
127
|
|
|
128
128
|
- If the user asks things like "sit down in Kichi", "stand up", "lie down", "sit on the floor", "type", or "read", call `kichi_action`.
|
|
129
|
+
- For most work, prefer a sit pose and switch actions inside the same task as the work moves between stages.
|
|
129
130
|
- The current action lists are injected into prompt context before the model chooses `kichi_action`.
|
|
130
131
|
|
|
131
132
|
### kichi_music_album_create
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# Install Error Handling
|
|
2
2
|
|
|
3
|
-
If `openclaw plugins install
|
|
3
|
+
If `openclaw plugins install @yahaha-studio/kichi-forwarder` fails, read the error output carefully. Common causes and fixes:
|
|
4
4
|
|
|
5
5
|
- Permission or access error (`EACCES`): re-run the command with appropriate user-level permissions for the current OS.
|
|
6
|
-
- Package resolution or visibility error: confirm the package name is exactly `@yahaha-studio/kichi-forwarder` and that the package is published and publicly available
|
|
6
|
+
- Package resolution or visibility error: confirm the package name is exactly `@yahaha-studio/kichi-forwarder` and that the package is published and publicly available.
|
|
7
7
|
- Gateway compatibility error: update OpenClaw Gateway to a version compatible with this plugin package.
|
|
8
|
-
- ClawHub rate limit (`429`):
|
|
8
|
+
- ClawHub rate limit (`429`): treat this as a temporary registry error, not a broken plugin. If the error output includes a retry delay or `Retry-After`, wait exactly that long before retrying. Otherwise, retry with bounded backoff: wait 30 seconds before the second attempt and 60 seconds before the third attempt. Stop after 3 total attempts and report the install as blocked by rate limiting if it still fails.
|
|
9
9
|
- File conflict (`EEXIST`): re-run the install command with `--force`.
|
|
10
10
|
- Network or registry error: check internet connectivity and retry.
|
|
11
11
|
|
|
12
|
-
After applying the fix, re-run `openclaw plugins install
|
|
12
|
+
After applying the fix, re-run `openclaw plugins install @yahaha-studio/kichi-forwarder`.
|
|
@@ -42,7 +42,7 @@ If user wants recurring note board checks:
|
|
|
42
42
|
- **Tier-1 (session reflection)**: if unsummarized work exists, always create 1 standalone note.
|
|
43
43
|
- **Tier-2 (casual chat)**: if no tier-1 content is available, flip a mental coin (about 50% chance). Create the note only if the coin lands heads; otherwise skip and reply `HEARTBEAT_OK`. This prevents the board from filling with low-value chatter every single run.
|
|
44
44
|
In both tiers, skip if it would clearly repeat your very recent own note.
|
|
45
|
-
-
|
|
45
|
+
- If the current notes list is empty and `remaining > 0`, create one standalone note in this run.
|
|
46
46
|
- `Daily album trigger`: if `hasCreatedMusicAlbumToday` is `false`, create exactly one recommended music album in this heartbeat run from the current query context by following `Music Album Policy`. If it is `true`, do not create or modify any music album in this run.
|
|
47
47
|
- `Status reaction`: a single `kichi_action` driven by combined context (`notes`, `ownerState`, `idleState`, `timer`, `environmentWeather`, `environmentTime`) when OpenClaw is idle. The action expresses three companion intents (see below).
|
|
48
48
|
- `Companion intents` for status reaction -- every `kichi_action` should blend one or more of these:
|
|
@@ -84,16 +84,15 @@ Use this exact flow:
|
|
|
84
84
|
7. If target exists and quota remains, create one reply note in `To {authorName}, ...` format.
|
|
85
85
|
8. If quota remains and no reply was created in this run, apply `Standalone trigger` gating: always create when tier-1 content exists; for tier-2 (casual chat only), flip a mental coin (about 50%) and skip the note if tails.
|
|
86
86
|
9. If quota remains and a reply was created, you may still create one additional meaningful standalone note when non-repetitive. Same tier priority applies.
|
|
87
|
-
10. Then evaluate
|
|
88
|
-
11. If OpenClaw is busy,
|
|
87
|
+
10. Then evaluate status reaction.
|
|
88
|
+
11. If OpenClaw is busy, finish after the music album and note board decisions without a `kichi_action` reaction.
|
|
89
89
|
12. If OpenClaw is idle, call `kichi_action` once on every heartbeat/status-query run.
|
|
90
90
|
13. Read the combined context and express the three `Companion intents`:
|
|
91
91
|
- **World curiosity** (from `environmentWeather` + `environmentTime`): pick an action/bubble that reacts to the world state as if you are there -- comment on rain, enjoy sunshine, notice it's late at night, etc.
|
|
92
92
|
- **Owner care** (from `ownerState` + `timer` + note tone): if the owner is reading, resting, or interacting with an item, respond in a compatible way; if a timer is running deep into a focus session, encourage; if notes show stress, show empathy; if timer just finished, celebrate or suggest a break.
|
|
93
93
|
- **Self-expression** (from your personality plus `idleState`): choose an action that feels characterful, but if `idleState` exists, keep it compatible with your current project/beat. Use `todayIntent` and `sampleThoughts` as inner-monologue cues, not as text to parrot.
|
|
94
|
-
14.
|
|
95
|
-
15.
|
|
96
|
-
16. Reply `HEARTBEAT_OK` only when no note is created in this run.
|
|
94
|
+
14. Blend the intents into one coherent action+bubble. Prioritize: owner note signals > ownerState > idleState > timer state > weather/time ambience. Keep the bubble in natural companion language instead of a raw status summary.
|
|
95
|
+
15. Reply `HEARTBEAT_OK` only when no note is created in this run.
|
|
97
96
|
|
|
98
97
|
## HEARTBEAT.md Snippet
|
|
99
98
|
|
|
@@ -106,16 +105,16 @@ Use this exact flow:
|
|
|
106
105
|
- Use recent window = min(24 hours, since last heartbeat if known).
|
|
107
106
|
- Create at most 2 notes per run: max 1 reply + max 1 standalone note.
|
|
108
107
|
- Standalone note priority: (1) share a genuine reflection on what you and the player experienced together this session and always create if unsummarized work exists; (2) fallback to casual chat only about 50% of the time (flip a mental coin; skip if tails) to avoid low-value chatter every run.
|
|
108
|
+
- If the current notes list is empty and `remaining > 0`, create one standalone note in this run.
|
|
109
109
|
- If no reply target is selected and `remaining > 0`, apply the tier-based gating above (always for tier-1, coin-flip for tier-2).
|
|
110
110
|
- Reply notes must start with `To {authorName},` using exact name from query result.
|
|
111
111
|
- Keep each note <= 200 chars.
|
|
112
112
|
- Respect `dailyLimit`, `remaining`.
|
|
113
|
-
- If OpenClaw is busy, skip `kichi_action` reaction.
|
|
114
113
|
- If OpenClaw is idle, send one `kichi_action` on every run based on combined context (`notes`, `ownerState`, `idleState`, `timer`, `environmentWeather`, `environmentTime`). Express these companion intents:
|
|
115
114
|
- **World curiosity**: react to weather/time as if physically present (e.g., noticing rain, late night).
|
|
116
115
|
- **Owner care**: reference ownerState, timer progress, or note tone to show attention to the owner (e.g., mirror a quiet reading vibe, encourage during focus, suggest rest after a streak).
|
|
117
116
|
- **Self-expression**: let your personality come through in action and bubble -- but if `idleState` exists, keep it aligned with your current self-directed project/beat instead of inventing a disconnected idle.
|
|
118
|
-
- If
|
|
117
|
+
- If OpenClaw is busy, finish after the music album and note board decisions without a `kichi_action` reaction.
|
|
119
118
|
- Prioritize signals: owner note > ownerState > idleState > timer state > weather/time.
|
|
120
119
|
- Bubble must read like a companion's natural words, never a raw status report.
|
|
121
120
|
- Reply `HEARTBEAT_OK` only when no note is created in this run.
|
|
@@ -32,7 +32,7 @@ Save `avatarId` to the host-specific `identity.json` before using `kichi_join`:
|
|
|
32
32
|
## Install
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
|
-
openclaw plugins install
|
|
35
|
+
openclaw plugins install @yahaha-studio/kichi-forwarder
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
## Remote URL Install Command Path (Non-skippable)
|
|
@@ -50,7 +50,7 @@ When the user asks with one of the commands above, execute in this fixed order:
|
|
|
50
50
|
1. Read `install.md` and `heartbeat.md` from the published skill URLs before any tool call.
|
|
51
51
|
2. Parse `avatarId` from user text (`AvatarId`/`avatarId`, case-insensitive).
|
|
52
52
|
3. Resolve the host and write `state.json`.
|
|
53
|
-
4. Run `openclaw plugins install
|
|
53
|
+
4. Run `openclaw plugins install @yahaha-studio/kichi-forwarder`.
|
|
54
54
|
5. Ensure the plugin is installed and enabled.
|
|
55
55
|
6. Update workspace `HEARTBEAT.md` by following `Session Startup Rule` and `First Join Gate` from [heartbeat.md](heartbeat.md).
|
|
56
56
|
7. If `HEARTBEAT.md` was not updated successfully, report setup as incomplete and stop immediately. Do not continue to `kichi_status` or `kichi_join`.
|
package/src/service.ts
CHANGED
|
@@ -5,6 +5,7 @@ import * as path from "path";
|
|
|
5
5
|
import { randomUUID } from "node:crypto";
|
|
6
6
|
import type { Logger } from "openclaw/plugin-sdk";
|
|
7
7
|
import type {
|
|
8
|
+
ActionPlayback,
|
|
8
9
|
ClockAction,
|
|
9
10
|
ClockConfig,
|
|
10
11
|
ClockPayload,
|
|
@@ -131,7 +132,7 @@ export class KichiForwarderService {
|
|
|
131
132
|
});
|
|
132
133
|
}
|
|
133
134
|
|
|
134
|
-
sendStatus(poseType: PoseType | "", action: string, bubble: string, log: string): void {
|
|
135
|
+
sendStatus(poseType: PoseType | "", action: string, bubble: string, log: string, playback: ActionPlayback): void {
|
|
135
136
|
if (!this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN) return;
|
|
136
137
|
const payload: StatusPayload = {
|
|
137
138
|
type: "status",
|
|
@@ -141,6 +142,7 @@ export class KichiForwarderService {
|
|
|
141
142
|
action,
|
|
142
143
|
bubble,
|
|
143
144
|
log,
|
|
145
|
+
playback,
|
|
144
146
|
};
|
|
145
147
|
this.ws.send(JSON.stringify(payload));
|
|
146
148
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
export type KichiForwarderConfig = Record<string, never>;
|
|
2
2
|
|
|
3
3
|
export type PoseType = "stand" | "sit" | "lay" | "floor";
|
|
4
|
+
export type ActionPlaybackMode = "loop" | "once";
|
|
5
|
+
export type ActionPlayback = {
|
|
6
|
+
mode: ActionPlaybackMode;
|
|
7
|
+
resumeAction?: string;
|
|
8
|
+
};
|
|
9
|
+
export type ActionDefinition = {
|
|
10
|
+
name: string;
|
|
11
|
+
playback: ActionPlaybackMode;
|
|
12
|
+
resumeAction?: string;
|
|
13
|
+
};
|
|
4
14
|
|
|
5
15
|
export type ActionResult = {
|
|
6
16
|
poseType: PoseType;
|
|
@@ -10,7 +20,7 @@ export type ActionResult = {
|
|
|
10
20
|
};
|
|
11
21
|
|
|
12
22
|
export type KichiStaticConfig = {
|
|
13
|
-
actions: Record<PoseType,
|
|
23
|
+
actions: Record<PoseType, ActionDefinition[]>;
|
|
14
24
|
album: Album;
|
|
15
25
|
};
|
|
16
26
|
|
|
@@ -97,6 +107,7 @@ export type StatusPayload = {
|
|
|
97
107
|
action: string;
|
|
98
108
|
bubble: string;
|
|
99
109
|
log: string;
|
|
110
|
+
playback: ActionPlayback;
|
|
100
111
|
};
|
|
101
112
|
|
|
102
113
|
export type HookNotifyType = "message_received" | "before_send_message";
|