@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 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: "kichi_status",
1188
- description: "Read current Kichi connection status and identity readiness",
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
- sendStatusUpdate(
1252
- service,
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: getActionPlayback(matched),
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 (notes, ownerState, idlePlan, weather/time, timer snapshot, daily note quota, and `hasCreatedMusicAlbumToday`). 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.",
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
+
@@ -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.3",
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",
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 `kichi_status` or `kichi_join`, you MUST update workspace `HEARTBEAT.md` by following `heartbeat.md`.
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 `kichi_status` first.
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
- ### kichi_status
97
+ ### kichi_connection_status
98
98
 
99
99
  ```text
100
- kichi_status()
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 `kichi_status` or `kichi_join` in an install/onboarding flow:
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 `kichi_status` or `kichi_join` until `HEARTBEAT.md` is updated.
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 `kichi_status` or `kichi_join`.
81
- 14. Call `kichi_status`.
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 `kichi_status` again and confirm connection and auth state.
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
- - [ ] `kichi_status` verified the final connected/auth state
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 = {