@yahaha-studio/kichi-forwarder 0.1.1-beta.3 → 0.1.1-beta.5
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/index.ts +41 -16
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/skills/kichi-forwarder/SKILL.md +4 -4
- package/skills/kichi-forwarder/references/heartbeat.md +2 -2
- package/skills/kichi-forwarder/references/install.md +4 -4
- package/src/service.ts +25 -0
- package/src/types.ts +9 -0
package/index.ts
CHANGED
|
@@ -76,7 +76,7 @@ type IdlePlan = {
|
|
|
76
76
|
actions: IdlePlanAction[];
|
|
77
77
|
}>;
|
|
78
78
|
};
|
|
79
|
-
|
|
79
|
+
|
|
80
80
|
function isAlbumConfig(value: unknown): value is Album {
|
|
81
81
|
if (!value || typeof value !== "object") {
|
|
82
82
|
return false;
|
|
@@ -954,6 +954,7 @@ function buildKichiActionDescription(): string {
|
|
|
954
954
|
"Directly control the avatar inside Kichi World.",
|
|
955
955
|
"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.",
|
|
956
956
|
"For most work, prefer a sit pose and switch actions as the task moves between stages.",
|
|
957
|
+
"Set verify to true ONLY when the user explicitly requests a pose or action change. The server will confirm whether the avatar actually applied the requested pose. If it could not (e.g. no available seats), the result will contain the actual fallback pose so you can inform the user accurately. During routine sync steps, omit verify.",
|
|
957
958
|
`stand actions: ${actions.stand.map((entry) => entry.name).join(", ")}`,
|
|
958
959
|
`sit actions: ${actions.sit.map((entry) => entry.name).join(", ")}`,
|
|
959
960
|
`lay actions: ${actions.lay.map((entry) => entry.name).join(", ")}`,
|
|
@@ -985,7 +986,7 @@ function buildKichiIdlePlanDescription(): string {
|
|
|
985
986
|
function buildKichiPrompt(): string {
|
|
986
987
|
return [
|
|
987
988
|
"Kichi avatar control and status sync are available via `kichi_action` and `kichi_clock`.",
|
|
988
|
-
"If the user gives a direct Kichi pose or action request, fulfill it with `kichi_action
|
|
989
|
+
"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.",
|
|
989
990
|
"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.",
|
|
990
991
|
"",
|
|
991
992
|
"kichi_action timing (all required when sync is active):",
|
|
@@ -1101,8 +1102,8 @@ const plugin = {
|
|
|
1101
1102
|
return { success: false, error: tagsError };
|
|
1102
1103
|
}
|
|
1103
1104
|
const result = await service.join(avatarId, botName, bio, tags ?? []);
|
|
1104
|
-
if (result.success) {
|
|
1105
|
-
return { success: true, authKey: result.authKey };
|
|
1105
|
+
if (result.success) {
|
|
1106
|
+
return { success: true, authKey: result.authKey };
|
|
1106
1107
|
}
|
|
1107
1108
|
return {
|
|
1108
1109
|
success: false,
|
|
@@ -1184,8 +1185,8 @@ const plugin = {
|
|
|
1184
1185
|
})));
|
|
1185
1186
|
|
|
1186
1187
|
api.registerTool(createAgentScopedTool(runtimeManager, (service) => ({
|
|
1187
|
-
name: "
|
|
1188
|
-
description: "
|
|
1188
|
+
name: "kichi_connection_status",
|
|
1189
|
+
description: "Check WebSocket connection status and identity readiness only. Does NOT return room info, avatar state, or personnel — use kichi_query_status for that.",
|
|
1189
1190
|
parameters: { type: "object", properties: {} },
|
|
1190
1191
|
execute: async () => {
|
|
1191
1192
|
return {
|
|
@@ -1212,15 +1213,21 @@ const plugin = {
|
|
|
1212
1213
|
description:
|
|
1213
1214
|
"Short natural first-person sentence under 15 words. Match the language of the bubble and mention the current action and immediate focus.",
|
|
1214
1215
|
},
|
|
1216
|
+
verify: {
|
|
1217
|
+
type: "boolean",
|
|
1218
|
+
description:
|
|
1219
|
+
"Set true ONLY when the user explicitly requests a pose or action. Omit during routine sync steps.",
|
|
1220
|
+
},
|
|
1215
1221
|
},
|
|
1216
1222
|
required: ["poseType", "action"],
|
|
1217
1223
|
},
|
|
1218
1224
|
execute: async (_toolCallId, params) => {
|
|
1219
|
-
const { poseType, action, bubble, log } = (params || {}) as {
|
|
1225
|
+
const { poseType, action, bubble, log, verify } = (params || {}) as {
|
|
1220
1226
|
poseType?: string;
|
|
1221
1227
|
action?: string;
|
|
1222
1228
|
bubble?: string;
|
|
1223
1229
|
log?: string;
|
|
1230
|
+
verify?: boolean;
|
|
1224
1231
|
};
|
|
1225
1232
|
if (!poseType || !action) {
|
|
1226
1233
|
return { success: false, error: "poseType and action parameters are required" };
|
|
@@ -1248,22 +1255,40 @@ const plugin = {
|
|
|
1248
1255
|
|
|
1249
1256
|
const bubbleText = typeof bubble === "string" && bubble.trim() ? bubble.trim() : matched.name;
|
|
1250
1257
|
const logText = typeof log === "string" ? log.trim() : "";
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1258
|
+
const playback = getActionPlayback(matched);
|
|
1259
|
+
|
|
1260
|
+
if (verify) {
|
|
1261
|
+
try {
|
|
1262
|
+
const ack = await service.sendStatusVerified(
|
|
1263
|
+
normalizedPoseType, matched.name, bubbleText, logText, playback,
|
|
1264
|
+
);
|
|
1265
|
+
if (ack.warning) {
|
|
1266
|
+
return {
|
|
1267
|
+
success: true,
|
|
1268
|
+
requested: { poseType: normalizedPoseType, action: matched.name },
|
|
1269
|
+
actual: { poseType: ack.poseType, action: ack.action },
|
|
1270
|
+
warning: ack.warning,
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
} catch {
|
|
1274
|
+
// Server not updated or timeout — fall through to normal success
|
|
1275
|
+
}
|
|
1276
|
+
} else {
|
|
1277
|
+
sendStatusUpdate(service, {
|
|
1254
1278
|
poseType: normalizedPoseType,
|
|
1255
1279
|
action: matched.name,
|
|
1256
1280
|
bubble: bubbleText,
|
|
1257
1281
|
log: logText,
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1260
1285
|
return {
|
|
1261
1286
|
success: true,
|
|
1262
1287
|
poseType: normalizedPoseType,
|
|
1263
1288
|
action: matched.name,
|
|
1264
1289
|
bubble: bubbleText,
|
|
1265
1290
|
log: logText,
|
|
1266
|
-
playback
|
|
1291
|
+
playback,
|
|
1267
1292
|
};
|
|
1268
1293
|
},
|
|
1269
1294
|
})));
|
|
@@ -1487,7 +1512,7 @@ const plugin = {
|
|
|
1487
1512
|
api.registerTool(createAgentScopedTool(runtimeManager, (service) => ({
|
|
1488
1513
|
name: "kichi_query_status",
|
|
1489
1514
|
description:
|
|
1490
|
-
"Query Kichi avatar status
|
|
1515
|
+
"Query Kichi room and avatar status — includes room personnel, notes, ownerState, idlePlan, weather/time, timer snapshot, daily note quota, and `hasCreatedMusicAlbumToday`. Use this when the user asks to check kichi status, room status, or who is in the room. Also use this before creating a new note or daily recommended music album. For heartbeat planning, use the returned idlePlan as reference when shaping the next idle plan.",
|
|
1491
1516
|
parameters: {
|
|
1492
1517
|
type: "object",
|
|
1493
1518
|
properties: {
|
|
@@ -1662,5 +1687,5 @@ const plugin = {
|
|
|
1662
1687
|
},
|
|
1663
1688
|
};
|
|
1664
1689
|
|
|
1665
|
-
export default plugin;
|
|
1666
|
-
|
|
1690
|
+
export default plugin;
|
|
1691
|
+
|
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.1-beta.
|
|
5
|
+
"version": "0.1.1-beta.5",
|
|
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.1-beta.
|
|
3
|
+
"version": "0.1.1-beta.5",
|
|
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",
|
|
@@ -49,7 +49,7 @@ For install/onboarding/connect requests:
|
|
|
49
49
|
- Read and follow `install.md` before any tool call.
|
|
50
50
|
- Before continuing any install/onboarding/connect flow, check whether `kichi-forwarder` is already installed. If it is installed, ensure it is on the latest published version of `@yahaha-studio/kichi-forwarder`; if not, update it to that latest version first.
|
|
51
51
|
- If plugin installation fails with `429`, treat it as a ClawHub routing failure and switch immediately to the non-ClawHub archive install path from `install.md`/`error.md`. Do not wait and do not retry the same bare package command.
|
|
52
|
-
- Before calling `
|
|
52
|
+
- Before calling `kichi_connection_status` or `kichi_join`, you MUST update workspace `HEARTBEAT.md` by following `heartbeat.md`.
|
|
53
53
|
|
|
54
54
|
## LLM Runtime
|
|
55
55
|
|
|
@@ -64,7 +64,7 @@ Use this order unless the user asks for a different explicit action:
|
|
|
64
64
|
|
|
65
65
|
Install/onboarding requests are the exception: follow `install.md` first.
|
|
66
66
|
|
|
67
|
-
1. If connection or identity is unknown, call `
|
|
67
|
+
1. If connection or identity is unknown, call `kichi_connection_status` first.
|
|
68
68
|
2. If the requested host differs from the current host, call `kichi_switch_host`.
|
|
69
69
|
3. If the requested `avatarId` differs from the current host's connected `avatarId`, call `kichi_leave` first when the old avatar is still joined, then call `kichi_join` with the requested `avatarId`.
|
|
70
70
|
4. Otherwise, if no `authKey` is available, call `kichi_join`.
|
|
@@ -94,10 +94,10 @@ kichi_switch_host(host: "your.kichi.host")
|
|
|
94
94
|
- `host`: required
|
|
95
95
|
- This reloads the host-specific `identity.json` and reconnects the websocket immediately.
|
|
96
96
|
|
|
97
|
-
###
|
|
97
|
+
### kichi_connection_status
|
|
98
98
|
|
|
99
99
|
```text
|
|
100
|
-
|
|
100
|
+
kichi_connection_status()
|
|
101
101
|
```
|
|
102
102
|
|
|
103
103
|
Use this to confirm:
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Session Startup Rule
|
|
4
4
|
|
|
5
|
-
If the plugin is installed and enabled, ensure workspace `HEARTBEAT.md` includes the Kichi heartbeat workflow before first `
|
|
5
|
+
If the plugin is installed and enabled, ensure workspace `HEARTBEAT.md` includes the Kichi heartbeat workflow before first `kichi_connection_status` or `kichi_join` in an install/onboarding flow:
|
|
6
6
|
|
|
7
7
|
1. If the workflow snippet is missing, add the snippet from `HEARTBEAT.md Snippet` section below.
|
|
8
8
|
2. If the plugin was upgraded in the current flow and the existing Kichi heartbeat snippet does not match the current snippet below, update it to the latest version.
|
|
@@ -16,7 +16,7 @@ For "join Kichi World" onboarding requests:
|
|
|
16
16
|
|
|
17
17
|
1. Complete `Session Startup Rule` first.
|
|
18
18
|
2. If `HEARTBEAT.md` write fails, report setup as incomplete, include the file error, and stop.
|
|
19
|
-
3. Do not call `
|
|
19
|
+
3. Do not call `kichi_connection_status` or `kichi_join` until `HEARTBEAT.md` is updated.
|
|
20
20
|
4. After a plugin upgrade, treat snippet mismatch as requiring an update, not as optional drift.
|
|
21
21
|
5. Final setup completion is defined in `install.md` `Completion Check`.
|
|
22
22
|
|
|
@@ -77,12 +77,12 @@ When the user asks with one of the commands above, execute in this fixed order:
|
|
|
77
77
|
10. Ensure the plugin is installed, enabled, and at the latest version.
|
|
78
78
|
11. If the plugin was newly installed or upgraded in this flow, re-check workspace `HEARTBEAT.md` against the latest Kichi heartbeat requirements before continuing.
|
|
79
79
|
12. Update workspace `HEARTBEAT.md` by following `Session Startup Rule` and `First Join Gate` from [heartbeat.md](heartbeat.md).
|
|
80
|
-
13. If `HEARTBEAT.md` was not updated successfully, report setup as incomplete and stop immediately. Do not continue to `
|
|
81
|
-
14. Call `
|
|
80
|
+
13. If `HEARTBEAT.md` was not updated successfully, report setup as incomplete and stop immediately. Do not continue to `kichi_connection_status` or `kichi_join`.
|
|
81
|
+
14. Call `kichi_connection_status`.
|
|
82
82
|
15. If the current agent runtime host does not match the requested one, call `kichi_switch_host`.
|
|
83
83
|
16. If the current host is still connected with a different `avatarId`, call `kichi_leave` first, then call `kichi_join` with parsed `avatarId`, `botName`, `bio`, and `tags`.
|
|
84
84
|
17. Otherwise, if `authKey` is missing, call `kichi_join` with parsed `avatarId`, `botName`, `bio`, and `tags`.
|
|
85
|
-
18. Call `
|
|
85
|
+
18. Call `kichi_connection_status` again and confirm connection and auth state.
|
|
86
86
|
|
|
87
87
|
## Required Post-install Integration
|
|
88
88
|
|
|
@@ -90,7 +90,7 @@ Use this completion checklist:
|
|
|
90
90
|
|
|
91
91
|
- [ ] plugin installed, enabled, and at latest version
|
|
92
92
|
- [ ] `HEARTBEAT.md` updated with the Kichi heartbeat workflow snippet from [heartbeat.md](heartbeat.md)
|
|
93
|
-
- [ ] `
|
|
93
|
+
- [ ] `kichi_connection_status` verified the final connected/auth state
|
|
94
94
|
|
|
95
95
|
If any box is unchecked, the onboarding remains incomplete.
|
|
96
96
|
|
package/src/service.ts
CHANGED
|
@@ -23,6 +23,7 @@ import type {
|
|
|
23
23
|
PoseType,
|
|
24
24
|
QueryStatusPayload,
|
|
25
25
|
QueryStatusResultPayload,
|
|
26
|
+
StatusAckPayload,
|
|
26
27
|
StatusPayload,
|
|
27
28
|
} from "./types.js";
|
|
28
29
|
|
|
@@ -164,6 +165,30 @@ export class KichiForwarderService {
|
|
|
164
165
|
this.ws.send(JSON.stringify(payload));
|
|
165
166
|
}
|
|
166
167
|
|
|
168
|
+
async sendStatusVerified(
|
|
169
|
+
poseType: PoseType | "",
|
|
170
|
+
action: string,
|
|
171
|
+
bubble: string,
|
|
172
|
+
log: string,
|
|
173
|
+
playback: ActionPlayback,
|
|
174
|
+
): Promise<StatusAckPayload> {
|
|
175
|
+
if (!this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN) {
|
|
176
|
+
throw new Error("Kichi websocket is not connected");
|
|
177
|
+
}
|
|
178
|
+
const payload: StatusPayload = {
|
|
179
|
+
type: "status",
|
|
180
|
+
requestId: randomUUID(),
|
|
181
|
+
avatarId: this.identity.avatarId,
|
|
182
|
+
authKey: this.identity.authKey,
|
|
183
|
+
poseType,
|
|
184
|
+
action,
|
|
185
|
+
bubble,
|
|
186
|
+
log,
|
|
187
|
+
playback,
|
|
188
|
+
};
|
|
189
|
+
return this.sendRequest<StatusAckPayload>(payload, "status_ack", 5000);
|
|
190
|
+
}
|
|
191
|
+
|
|
167
192
|
sendHookNotify(hookType: HookNotifyType, bubble: string): void {
|
|
168
193
|
if (!this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN) return;
|
|
169
194
|
const payload: HookNotifyPayload = {
|
package/src/types.ts
CHANGED
|
@@ -104,6 +104,7 @@ export type LeavePayload = {
|
|
|
104
104
|
|
|
105
105
|
export type StatusPayload = {
|
|
106
106
|
type: "status";
|
|
107
|
+
requestId?: string;
|
|
107
108
|
avatarId: string;
|
|
108
109
|
authKey: string;
|
|
109
110
|
poseType: PoseType | "";
|
|
@@ -113,6 +114,14 @@ export type StatusPayload = {
|
|
|
113
114
|
playback: ActionPlayback;
|
|
114
115
|
};
|
|
115
116
|
|
|
117
|
+
export type StatusAckPayload = {
|
|
118
|
+
type: "status_ack";
|
|
119
|
+
requestId: string;
|
|
120
|
+
poseType: PoseType | "";
|
|
121
|
+
action: string;
|
|
122
|
+
warning?: string;
|
|
123
|
+
};
|
|
124
|
+
|
|
116
125
|
export type HookNotifyType = "message_received" | "before_send_message";
|
|
117
126
|
|
|
118
127
|
export type HookNotifyPayload = {
|