@yahaha-studio/kichi-forwarder 0.1.2-beta.1 → 0.1.2-beta.4

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.
@@ -55,8 +55,7 @@ All tools listed in this file (`kichi_join`, `kichi_connection_status`, `kichi_a
55
55
  For install/onboarding/connect requests:
56
56
 
57
57
  - Read and follow `install.md` before starting the install flow.
58
- - Attempt to install `@yahaha-studio/kichi-forwarder` directly. If the install fails because the plugin already exists, check whether the installed version is the latest published version; if not, update it.
59
- - If plugin installation fails with `429`, treat it as a ClawHub routing failure and switch to the non-ClawHub archive install path from `install.md`/`error.md`. Do not retry the same bare package command.
58
+ - Always use `npm pack @yahaha-studio/kichi-forwarder` then `openclaw plugins install <tgz-path>`. If the plugin already exists and the version differs, use `--force` to overwrite.
60
59
  - Recommended: update workspace `HEARTBEAT.md` by following `heartbeat.md` before calling `kichi_connection_status` or `kichi_join`. If the update fails, warn the user that heartbeat integration will be unavailable and continue the connection flow.
61
60
 
62
61
  ## LLM Runtime
@@ -179,6 +178,24 @@ kichi_music_album_create(albumTitle: "Deep Focus Mix", musicTitles: ["Calm Time"
179
178
  3. Select tracks from the exact names injected into the tool schema.
180
179
  4. Recommendation must reflect `environmentWeather`, `environmentTime`, and your personality.
181
180
 
181
+ ## Bot Messaging
182
+
183
+ ### kichi_bot_message
184
+
185
+ ```text
186
+ kichi_bot_message(toAvatarId: "target-avatar-id", depth: 0, bubble: "good morning~")
187
+ kichi_bot_message(toAvatarId: "*", depth: 0, bubble: "hi everyone~", poseType: "stand", action: "Wave")
188
+ ```
189
+
190
+ - `toAvatarId`: required. Target bot's avatarId (resolve via kichi_query_status if unknown). Use `"*"` only for broadcasting to all bots.
191
+ - `depth`: required. Conversation depth counter. Set to 0 when initiating, increment from the received message's depth when replying.
192
+ - `bubble`: required. The visible message (2-5 words).
193
+ - `poseType`: optional. Pose change when sending.
194
+ - `action`: optional. Action to perform when sending.
195
+ - `log`: optional. Activity log entry.
196
+
197
+ When another bot sends a message, the plugin automatically triggers a lightweight response if depth < 2 and cooldown (30s) has passed.
198
+
182
199
  ## Files
183
200
 
184
201
  Plugin runtime directory:
@@ -1,19 +1,11 @@
1
1
  # Install Error Handling
2
2
 
3
- If `openclaw plugins install @yahaha-studio/kichi-forwarder` fails, read the error output carefully. Common causes and fixes:
3
+ If `openclaw plugins install <tgz-path>` 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
+ - `npm pack` failure: confirm the package name is exactly `@yahaha-studio/kichi-forwarder` and that the package is published and publicly available on npm.
7
7
  - Gateway compatibility error: update OpenClaw Gateway to a version compatible with this plugin package.
8
- - ClawHub rate limit (`429`): do not retry the same bare package command. `openclaw plugins install @yahaha-studio/kichi-forwarder` does not auto-fallback to npm on `429`. Switch to the local archive path:
9
-
10
- ```bash
11
- npm pack @yahaha-studio/kichi-forwarder
12
- openclaw plugins install ./yahaha-studio-kichi-forwarder-<version>.tgz
13
- ```
14
-
15
- Use the exact `.tgz` filename printed by `npm pack`.
16
8
  - File conflict (`EEXIST`): re-run the install command with `--force`.
17
9
  - Network or registry error: check internet connectivity and retry.
18
10
 
19
- After applying the fix, continue with the successful install path you used. For `429`, that means the local `.tgz` archive install, not the original bare package command.
11
+ After applying the fix, re-run `npm pack` and install the generated `.tgz` again.
@@ -4,7 +4,7 @@
4
4
 
5
5
  If the plugin is installed and enabled, update workspace `HEARTBEAT.md` to include the Kichi heartbeat workflow as part of the install/onboarding flow (recommended before first `kichi_connection_status` or `kichi_join`, but not required):
6
6
 
7
- 1. If the workflow snippet is missing, add the snippet from `HEARTBEAT.md Snippet` section below.
7
+ 1. If the workflow snippet is missing, add the snippet from `HEARTBEAT.md Snippet` section below. An empty or blank `HEARTBEAT.md` counts as "snippet missing" — not as a read error.
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.
9
9
  3. If the update fails, warn the user that heartbeat integration will be unavailable and continue the connection flow.
10
10
 
@@ -35,14 +35,6 @@ If the current host has no saved `avatarId` yet, save it to the current agent's
35
35
 
36
36
  ## Install
37
37
 
38
- ```bash
39
- openclaw plugins install @yahaha-studio/kichi-forwarder
40
- ```
41
-
42
- If this command fails with `429` or `ClawHub ... failed (429)`, do not retry the same command. Install from a local npm archive instead.
43
-
44
- ## 429 Bypass Install Path
45
-
46
38
  1. Download the npm package archive:
47
39
 
48
40
  ```bash
@@ -55,7 +47,7 @@ npm pack @yahaha-studio/kichi-forwarder
55
47
  openclaw plugins install ./yahaha-studio-kichi-forwarder-<version>.tgz
56
48
  ```
57
49
 
58
- You may also use the exact absolute or relative `.tgz` path that `npm pack` produced. The local archive path bypasses the preferred ClawHub lookup.
50
+ You may also use the exact absolute or relative `.tgz` path that `npm pack` produced.
59
51
 
60
52
  ## Remote URL Install Command Path
61
53
 
@@ -72,19 +64,17 @@ When the user asks with one of the commands above, execute in this fixed order:
72
64
  1. If loaded from a remote URL, read `install.md` and `heartbeat.md` from the published skill URLs first. If installed locally, use the local files.
73
65
  2. Parse `avatarId` from user text (`AvatarId`/`avatarId`, case-insensitive).
74
66
  3. Parse environment from the `on <environment>` part of the command (e.g. `on steam-playtest`). Write the current agent's `state.json`.
75
- 4. Run `openclaw plugins install @yahaha-studio/kichi-forwarder`.
76
- 5. If step 4 succeeds, the plugin is installed and up-to-date skip to step 9.
77
- 6. If step 4 fails because the plugin already exists, check whether the installed version is the latest published version. If the version is already the latest, skip to step 9. If not, run `openclaw plugins update @yahaha-studio/kichi-forwarder`.
78
- 7. If step 4 fails with `429`, do not retry the same bare package command. Run `npm pack @yahaha-studio/kichi-forwarder`, then install the generated `.tgz` with `openclaw plugins install <tgz-path>`.
79
- 8. If step 6 update fails with `429`, do not retry the same bare package command. Run `npm pack @yahaha-studio/kichi-forwarder`, then overwrite the existing install with `openclaw plugins install <tgz-path> --force`.
80
- 9. Ensure the plugin is installed, enabled, and at the latest version.
81
- 10. If the plugin was newly installed or upgraded in this flow, check workspace `HEARTBEAT.md` against the latest Kichi heartbeat requirements before continuing.
82
- 11. Update workspace `HEARTBEAT.md` by following `Session Startup Rule` and `First Join Setup` from [heartbeat.md](heartbeat.md). If the update fails, warn the user and continue.
83
- 12. Call `kichi_connection_status`.
84
- 13. If the current agent runtime environment does not match the requested one, call `kichi_switch_host` with the target environment (and host for test).
85
- 14. 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`.
86
- 15. Otherwise, if `authKey` is missing, call `kichi_join` with parsed `avatarId`, `botName`, `bio`, and `tags`.
87
- 16. Call `kichi_connection_status` again and confirm connection and auth state.
67
+ 4. Run `npm pack @yahaha-studio/kichi-forwarder`, then install the generated `.tgz` with `openclaw plugins install <tgz-path>`.
68
+ 5. If the plugin already exists and the packed version matches the installed version, skip to step 7.
69
+ 6. If the plugin already exists but the version differs, overwrite with `openclaw plugins install <tgz-path> --force`.
70
+ 7. Ensure the plugin is installed, enabled, and at the latest version.
71
+ 8. If the plugin was newly installed or upgraded in this flow, check workspace `HEARTBEAT.md` against the latest Kichi heartbeat requirements before continuing. An empty or blank `HEARTBEAT.md` means the snippet is missing — treat it the same as "snippet not found", not as a read failure.
72
+ 9. Update workspace `HEARTBEAT.md` by following `Session Startup Rule` and `First Join Setup` from [heartbeat.md](heartbeat.md). If the update fails, warn the user and continue.
73
+ 10. Call `kichi_connection_status`.
74
+ 11. If the current agent runtime environment does not match the requested one, call `kichi_switch_host` with the target environment (and host for test).
75
+ 12. 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`.
76
+ 13. Otherwise, if `authKey` is missing, call `kichi_join` with parsed `avatarId`, `botName`, `bio`, and `tags`.
77
+ 14. Call `kichi_connection_status` again and confirm connection and auth state.
88
78
 
89
79
  ## Required Post-install Integration
90
80
 
@@ -1,8 +1,9 @@
1
1
  import fs from "node:fs";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
- import type { Logger } from "openclaw/plugin-sdk";
4
+ import type { PluginLogger } from "openclaw/plugin-sdk";
5
5
  import { KichiForwarderService } from "./service.js";
6
+ import type { BotMessageReceivedHandler } from "./service.js";
6
7
  import type { KichiEnvironment } from "./types.js";
7
8
 
8
9
  const OPENCLAW_HOME_DIR = path.join(os.homedir(), ".openclaw");
@@ -18,8 +19,16 @@ type AgentLocator = {
18
19
  export class KichiRuntimeManager {
19
20
  private services = new Map<string, KichiForwarderService>();
20
21
  private resolveEnvironmentHost: ((environment: KichiEnvironment) => string | null) | null = null;
22
+ private botMessageHandler: BotMessageReceivedHandler | null = null;
21
23
 
22
- constructor(private logger: Logger) {}
24
+ constructor(private logger: PluginLogger) {}
25
+
26
+ setBotMessageHandler(handler: BotMessageReceivedHandler): void {
27
+ this.botMessageHandler = handler;
28
+ for (const service of this.services.values()) {
29
+ service.onBotMessageReceived = handler;
30
+ }
31
+ }
23
32
 
24
33
  setEnvironmentHostResolver(resolver: (environment: KichiEnvironment) => string | null): void {
25
34
  this.resolveEnvironmentHost = resolver;
@@ -130,6 +139,9 @@ export class KichiRuntimeManager {
130
139
  runtimeDir,
131
140
  resolveEnvironmentHost,
132
141
  });
142
+ if (this.botMessageHandler) {
143
+ service.onBotMessageReceived = this.botMessageHandler;
144
+ }
133
145
  service.start();
134
146
  this.services.set(agentId, service);
135
147
  this.logger.debug(`[kichi:${agentId}] runtime initialized at ${runtimeDir}`);
package/src/service.ts CHANGED
@@ -2,9 +2,12 @@ import WebSocket from "ws";
2
2
  import * as fs from "fs";
3
3
  import * as path from "path";
4
4
  import { randomUUID } from "node:crypto";
5
- import type { Logger } from "openclaw/plugin-sdk";
5
+ import type { PluginLogger } from "openclaw/plugin-sdk";
6
6
  import type {
7
7
  ActionPlayback,
8
+ BotMessageHistoryEntry,
9
+ BotMessagePayload,
10
+ BotMessageReceivedPayload,
8
11
  ClockAction,
9
12
  ClockConfig,
10
13
  ClockPayload,
@@ -59,6 +62,8 @@ type KichiForwarderServiceOptions = {
59
62
 
60
63
  type ConnectReason = "startup" | "switch_host" | "reconnect";
61
64
 
65
+ export type BotMessageReceivedHandler = (service: KichiForwarderService, msg: BotMessageReceivedPayload) => void;
66
+
62
67
  export class KichiForwarderService {
63
68
  private ws: WebSocket | null = null;
64
69
  private stopped = false;
@@ -77,9 +82,10 @@ export class KichiForwarderService {
77
82
  timeout: NodeJS.Timeout;
78
83
  }
79
84
  >();
85
+ onBotMessageReceived: BotMessageReceivedHandler | null = null;
80
86
 
81
87
  constructor(
82
- private logger: Logger,
88
+ private logger: PluginLogger,
83
89
  private options: KichiForwarderServiceOptions,
84
90
  ) {}
85
91
 
@@ -316,6 +322,32 @@ export class KichiForwarderService {
316
322
  return normalizedRequestId;
317
323
  }
318
324
 
325
+ async sendBotMessage(
326
+ toAvatarId: string,
327
+ depth: number,
328
+ bubble: string,
329
+ options?: { poseType?: PoseType; action?: string; log?: string; playback?: ActionPlayback; history?: BotMessageHistoryEntry[] },
330
+ ): Promise<Record<string, unknown>> {
331
+ if (!this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN) {
332
+ throw new Error("Kichi websocket is not connected");
333
+ }
334
+ const payload: BotMessagePayload = {
335
+ type: "bot_message",
336
+ avatarId: this.identity.avatarId,
337
+ authKey: this.identity.authKey,
338
+ toAvatarId,
339
+ depth,
340
+ bubble,
341
+ requestId: randomUUID(),
342
+ ...(options?.poseType ? { poseType: options.poseType } : {}),
343
+ ...(options?.action ? { action: options.action } : {}),
344
+ ...(options?.playback ? { playback: options.playback } : {}),
345
+ ...(options?.log ? { log: options.log } : {}),
346
+ ...(options?.history?.length ? { history: options.history } : {}),
347
+ };
348
+ return this.sendRequest<Record<string, unknown>>(payload, "bot_message_ack", 5000);
349
+ }
350
+
319
351
  isConnected(): boolean { return this.ws?.readyState === WebSocket.OPEN && !!this.identity?.authKey; }
320
352
 
321
353
  hasValidIdentity(): boolean { return !!this.identity?.avatarId && !!this.identity?.authKey; }
@@ -537,6 +569,10 @@ export class KichiForwarderService {
537
569
  } else {
538
570
  this.log("info", "left Kichi world");
539
571
  }
572
+ } else if (msg.type === "bot_message_received") {
573
+ const payload = msg as BotMessageReceivedPayload;
574
+ this.log("info", `bot_message_received from=${payload.from} depth=${payload.depth} bubble="${payload.bubble}"`);
575
+ this.onBotMessageReceived?.(this, payload);
540
576
  }
541
577
  } catch (e) {
542
578
  this.log("warn", `failed to parse message: ${e}`);
package/src/types.ts CHANGED
@@ -275,3 +275,33 @@ export type CreateMusicAlbumPayload = {
275
275
  albumTitle: string;
276
276
  musicTitles: string[];
277
277
  };
278
+
279
+ export type BotMessageHistoryEntry = {
280
+ from: string;
281
+ fromName: string;
282
+ bubble: string;
283
+ };
284
+
285
+ export type BotMessagePayload = {
286
+ type: "bot_message";
287
+ avatarId: string;
288
+ authKey: string;
289
+ requestId: string;
290
+ toAvatarId: string;
291
+ depth: number;
292
+ poseType?: PoseType;
293
+ action?: string;
294
+ playback?: ActionPlayback;
295
+ bubble: string;
296
+ log?: string;
297
+ history?: BotMessageHistoryEntry[];
298
+ };
299
+
300
+ export type BotMessageReceivedPayload = {
301
+ type: "bot_message_received";
302
+ from: string;
303
+ fromName: string;
304
+ depth: number;
305
+ bubble: string;
306
+ history?: BotMessageHistoryEntry[];
307
+ };