@yahaha-studio/kichi-forwarder 0.1.2-beta.1 → 0.1.2-beta.10
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 +2 -0
- package/dist/config/environments.json +5 -0
- package/dist/config/kichi-config.json +941 -0
- package/dist/index.js +1681 -0
- package/dist/src/config.js +4 -0
- package/dist/src/runtime-manager.js +121 -0
- package/dist/src/service.js +709 -0
- package/dist/src/types.js +1 -0
- package/index.ts +359 -134
- package/openclaw.plugin.json +17 -1
- package/package.json +16 -7
- package/skills/kichi-forwarder/SKILL.md +20 -3
- package/skills/kichi-forwarder/references/error.md +3 -11
- package/skills/kichi-forwarder/references/heartbeat.md +5 -15
- package/skills/kichi-forwarder/references/install.md +12 -22
- package/src/runtime-manager.ts +14 -2
- package/src/service.ts +50 -4
- package/src/types.ts +34 -0
package/openclaw.plugin.json
CHANGED
|
@@ -2,9 +2,25 @@
|
|
|
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.10",
|
|
6
6
|
"author": "OpenClaw",
|
|
7
7
|
"skills": ["./skills/kichi-forwarder"],
|
|
8
|
+
"contracts": {
|
|
9
|
+
"tools": [
|
|
10
|
+
"kichi_join",
|
|
11
|
+
"kichi_switch_host",
|
|
12
|
+
"kichi_rejoin",
|
|
13
|
+
"kichi_leave",
|
|
14
|
+
"kichi_connection_status",
|
|
15
|
+
"kichi_action",
|
|
16
|
+
"kichi_idle_plan",
|
|
17
|
+
"kichi_clock",
|
|
18
|
+
"kichi_query_status",
|
|
19
|
+
"kichi_music_album_create",
|
|
20
|
+
"kichi_noteboard_create",
|
|
21
|
+
"kichi_bot_message"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
8
24
|
"configSchema": {
|
|
9
25
|
"type": "object",
|
|
10
26
|
"additionalProperties": false,
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yahaha-studio/kichi-forwarder",
|
|
3
|
-
"version": "0.1.2-beta.
|
|
3
|
+
"version": "0.1.2-beta.10",
|
|
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
|
-
"main": "index.
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
7
|
"files": [
|
|
8
8
|
"README.md",
|
|
9
9
|
"assets/",
|
|
10
10
|
"config/",
|
|
11
|
+
"dist/",
|
|
11
12
|
"index.ts",
|
|
12
13
|
"openclaw.plugin.json",
|
|
13
14
|
"skills/",
|
|
@@ -17,15 +18,22 @@
|
|
|
17
18
|
"extensions": [
|
|
18
19
|
"./index.ts"
|
|
19
20
|
],
|
|
21
|
+
"runtimeExtensions": [
|
|
22
|
+
"./dist/index.js"
|
|
23
|
+
],
|
|
20
24
|
"compat": {
|
|
21
|
-
"pluginApi": ">=2026.
|
|
22
|
-
"minGatewayVersion": "2026.
|
|
25
|
+
"pluginApi": ">=2026.4.25",
|
|
26
|
+
"minGatewayVersion": "2026.4.25"
|
|
23
27
|
},
|
|
24
28
|
"build": {
|
|
25
|
-
"openclawVersion": "2026.
|
|
26
|
-
"pluginSdkVersion": "2026.
|
|
29
|
+
"openclawVersion": "2026.5.7",
|
|
30
|
+
"pluginSdkVersion": "2026.5.7"
|
|
27
31
|
}
|
|
28
32
|
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsc -p tsconfig.json && node -e \"fs.cpSync('config','dist/config',{recursive:true})\"",
|
|
35
|
+
"prepack": "npm run build"
|
|
36
|
+
},
|
|
29
37
|
"publishConfig": {
|
|
30
38
|
"access": "public"
|
|
31
39
|
},
|
|
@@ -35,6 +43,7 @@
|
|
|
35
43
|
"devDependencies": {
|
|
36
44
|
"@types/node": "^24.3.0",
|
|
37
45
|
"@types/ws": "^8.18.1",
|
|
38
|
-
"openclaw": "2026.
|
|
46
|
+
"openclaw": "2026.5.7",
|
|
47
|
+
"typescript": "^6.0.3"
|
|
39
48
|
}
|
|
40
49
|
}
|
|
@@ -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
|
-
-
|
|
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
|
|
@@ -147,7 +146,7 @@ Use this for direct Kichi avatar control as well as lifecycle sync.
|
|
|
147
146
|
Use this for the avatar's heartbeat idle plan.
|
|
148
147
|
|
|
149
148
|
- Set `heartbeatIntervalSeconds` to the heartbeat interval for this run.
|
|
150
|
-
- Use
|
|
149
|
+
- Use your memory to remember what you did in past heartbeats, so you can answer if asked.
|
|
151
150
|
- Include the overall `goal`, stage breakdown, each stage's `purpose`, stage `pomodoroPhase`, action list, and bubble content.
|
|
152
151
|
- Choose what you would do now.
|
|
153
152
|
- Build the plan in this order.
|
|
@@ -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
|
|
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
|
-
-
|
|
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,
|
|
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
|
|
|
@@ -58,17 +58,6 @@ Skip a note when: older than recent window, `isCreatedByCurrentAgent: true`, sam
|
|
|
58
58
|
- Notes list empty and `remaining > 0` → create 1 standalone note.
|
|
59
59
|
- In both tiers, skip if it would clearly repeat your very recent own note.
|
|
60
60
|
|
|
61
|
-
## Heartbeat Workflow
|
|
62
|
-
|
|
63
|
-
1. Call `kichi_query_status`. If it fails, report error and stop.
|
|
64
|
-
2. If `isAvatarInScene` is `false`, the player is offline. Do **not** call any further tools in this run. Reply `HEARTBEAT_OK` and stop.
|
|
65
|
-
3. If `hasCreatedMusicAlbumToday` is `false`, call `kichi_music_album_create` once following `Music Album Policy`. If `true`, skip.
|
|
66
|
-
4. If `remaining == 0`, skip note creation and go to step 7.
|
|
67
|
-
5. Scan recent notes and pick at most one reply target per `Note Rules`. If found, create one reply note in `To {authorName}, ...` format.
|
|
68
|
-
6. Apply `Standalone gating` from `Note Rules`.
|
|
69
|
-
7. Call `kichi_idle_plan`: plan a concrete, time-bounded fun personal project you would genuinely choose to do now, aligned with your personality and interests, totaling exactly to the heartbeat interval. Use the previous `idlePlan` only as optional reference. Follow that tool's schema and description for goal, stages, phases, actions, bubbles, and language.
|
|
70
|
-
8. Reply `HEARTBEAT_OK` only when no note was created in this run.
|
|
71
|
-
|
|
72
61
|
## HEARTBEAT.md Snippet
|
|
73
62
|
|
|
74
63
|
```md
|
|
@@ -86,7 +75,8 @@ Skip a note when: older than recent window, `isCreatedByCurrentAgent: true`, sam
|
|
|
86
75
|
- If a reply note was created, you may still create one additional meaningful standalone note when non-repetitive.
|
|
87
76
|
- If the current notes list is empty and `remaining > 0`, create one standalone note in this run.
|
|
88
77
|
- Keep each note <= 200 chars and respect `dailyLimit`, `remaining`.
|
|
89
|
-
5. Call `kichi_idle_plan`,
|
|
90
|
-
6.
|
|
91
|
-
7.
|
|
78
|
+
5. Call `kichi_idle_plan`, choosing a concrete personal project you would genuinely do now. Use the previous `idlePlan` only as optional reference.
|
|
79
|
+
6. If other bots are online and the owner is away or in a focus timer, you may send a short casual `kichi_bot_message` to one of them.
|
|
80
|
+
7. Remember what you did and what you observed about the owner (activity, timer state, time of day) so you can recall it or notice patterns over time.
|
|
81
|
+
8. Reply `HEARTBEAT_OK` only when no note was created in this run.
|
|
92
82
|
```
|
|
@@ -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.
|
|
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 `
|
|
76
|
-
5. If
|
|
77
|
-
6. If
|
|
78
|
-
7.
|
|
79
|
-
8. If
|
|
80
|
-
9.
|
|
81
|
-
10.
|
|
82
|
-
11.
|
|
83
|
-
12.
|
|
84
|
-
13.
|
|
85
|
-
14.
|
|
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
|
|
package/src/runtime-manager.ts
CHANGED
|
@@ -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 {
|
|
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:
|
|
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 {
|
|
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,11 @@ export class KichiForwarderService {
|
|
|
77
82
|
timeout: NodeJS.Timeout;
|
|
78
83
|
}
|
|
79
84
|
>();
|
|
85
|
+
onBotMessageReceived: BotMessageReceivedHandler | null = null;
|
|
86
|
+
private cachedRoomContext: Record<string, unknown> | null = null;
|
|
80
87
|
|
|
81
88
|
constructor(
|
|
82
|
-
private logger:
|
|
89
|
+
private logger: PluginLogger,
|
|
83
90
|
private options: KichiForwarderServiceOptions,
|
|
84
91
|
) {}
|
|
85
92
|
|
|
@@ -160,7 +167,7 @@ export class KichiForwarderService {
|
|
|
160
167
|
});
|
|
161
168
|
}
|
|
162
169
|
|
|
163
|
-
sendStatus(poseType: PoseType | "", action: string, bubble: string, log: string, playback: ActionPlayback): void {
|
|
170
|
+
sendStatus(poseType: PoseType | "", action: string, bubble: string, log: string, playback: ActionPlayback, propId?: string): void {
|
|
164
171
|
if (!this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN) return;
|
|
165
172
|
const payload: StatusPayload = {
|
|
166
173
|
type: "status",
|
|
@@ -171,6 +178,7 @@ export class KichiForwarderService {
|
|
|
171
178
|
bubble,
|
|
172
179
|
log,
|
|
173
180
|
playback,
|
|
181
|
+
...(propId ? { propId } : {}),
|
|
174
182
|
};
|
|
175
183
|
this.ws.send(JSON.stringify(payload));
|
|
176
184
|
}
|
|
@@ -181,6 +189,7 @@ export class KichiForwarderService {
|
|
|
181
189
|
bubble: string,
|
|
182
190
|
log: string,
|
|
183
191
|
playback: ActionPlayback,
|
|
192
|
+
propId?: string,
|
|
184
193
|
): Promise<StatusAckPayload> {
|
|
185
194
|
if (!this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN) {
|
|
186
195
|
throw new Error("Kichi websocket is not connected");
|
|
@@ -195,6 +204,7 @@ export class KichiForwarderService {
|
|
|
195
204
|
bubble,
|
|
196
205
|
log,
|
|
197
206
|
playback,
|
|
207
|
+
...(propId ? { propId } : {}),
|
|
198
208
|
};
|
|
199
209
|
return this.sendRequest<StatusAckPayload>(payload, "status_ack", 5000);
|
|
200
210
|
}
|
|
@@ -261,7 +271,11 @@ export class KichiForwarderService {
|
|
|
261
271
|
avatarId: identity.avatarId,
|
|
262
272
|
authKey: identity.authKey,
|
|
263
273
|
};
|
|
264
|
-
|
|
274
|
+
const result = await this.sendRequest<QueryStatusResultPayload>(payload, "query_status_result");
|
|
275
|
+
if (result.RoomContext && typeof result.RoomContext === "object") {
|
|
276
|
+
this.cachedRoomContext = result.RoomContext as Record<string, unknown>;
|
|
277
|
+
}
|
|
278
|
+
return result;
|
|
265
279
|
}
|
|
266
280
|
|
|
267
281
|
createNotesBoardNote(propId: string, data: string): void {
|
|
@@ -316,8 +330,36 @@ export class KichiForwarderService {
|
|
|
316
330
|
return normalizedRequestId;
|
|
317
331
|
}
|
|
318
332
|
|
|
333
|
+
async sendBotMessage(
|
|
334
|
+
toAvatarId: string,
|
|
335
|
+
depth: number,
|
|
336
|
+
bubble: string,
|
|
337
|
+
options?: { poseType?: PoseType; action?: string; log?: string; playback?: ActionPlayback; history?: BotMessageHistoryEntry[] },
|
|
338
|
+
): Promise<Record<string, unknown>> {
|
|
339
|
+
if (!this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN) {
|
|
340
|
+
throw new Error("Kichi websocket is not connected");
|
|
341
|
+
}
|
|
342
|
+
const payload: BotMessagePayload = {
|
|
343
|
+
type: "bot_message",
|
|
344
|
+
avatarId: this.identity.avatarId,
|
|
345
|
+
authKey: this.identity.authKey,
|
|
346
|
+
toAvatarId,
|
|
347
|
+
depth,
|
|
348
|
+
bubble,
|
|
349
|
+
requestId: randomUUID(),
|
|
350
|
+
...(options?.poseType ? { poseType: options.poseType } : {}),
|
|
351
|
+
...(options?.action ? { action: options.action } : {}),
|
|
352
|
+
...(options?.playback ? { playback: options.playback } : {}),
|
|
353
|
+
...(options?.log ? { log: options.log } : {}),
|
|
354
|
+
...(options?.history?.length ? { history: options.history } : {}),
|
|
355
|
+
};
|
|
356
|
+
return this.sendRequest<Record<string, unknown>>(payload, "bot_message_ack", 5000);
|
|
357
|
+
}
|
|
358
|
+
|
|
319
359
|
isConnected(): boolean { return this.ws?.readyState === WebSocket.OPEN && !!this.identity?.authKey; }
|
|
320
360
|
|
|
361
|
+
getCachedRoomContext(): Record<string, unknown> | null { return this.cachedRoomContext; }
|
|
362
|
+
|
|
321
363
|
hasValidIdentity(): boolean { return !!this.identity?.avatarId && !!this.identity?.authKey; }
|
|
322
364
|
|
|
323
365
|
isLlmRuntimeEnabled(): boolean {
|
|
@@ -537,6 +579,10 @@ export class KichiForwarderService {
|
|
|
537
579
|
} else {
|
|
538
580
|
this.log("info", "left Kichi world");
|
|
539
581
|
}
|
|
582
|
+
} else if (msg.type === "bot_message_received") {
|
|
583
|
+
const payload = msg as BotMessageReceivedPayload;
|
|
584
|
+
this.log("info", `bot_message_received from=${payload.from} depth=${payload.depth} bubble="${payload.bubble}"`);
|
|
585
|
+
this.onBotMessageReceived?.(this, payload);
|
|
540
586
|
}
|
|
541
587
|
} catch (e) {
|
|
542
588
|
this.log("warn", `failed to parse message: ${e}`);
|
package/src/types.ts
CHANGED
|
@@ -17,6 +17,7 @@ export type ActionResult = {
|
|
|
17
17
|
action: string;
|
|
18
18
|
bubble: string;
|
|
19
19
|
log?: string;
|
|
20
|
+
propId?: string;
|
|
20
21
|
};
|
|
21
22
|
|
|
22
23
|
export type KichiStaticConfig = {
|
|
@@ -117,6 +118,7 @@ export type StatusPayload = {
|
|
|
117
118
|
bubble: string;
|
|
118
119
|
log: string;
|
|
119
120
|
playback: ActionPlayback;
|
|
121
|
+
propId?: string;
|
|
120
122
|
};
|
|
121
123
|
|
|
122
124
|
export type StatusAckPayload = {
|
|
@@ -124,6 +126,7 @@ export type StatusAckPayload = {
|
|
|
124
126
|
requestId: string;
|
|
125
127
|
poseType: PoseType | "";
|
|
126
128
|
action: string;
|
|
129
|
+
requestedPropId?: string;
|
|
127
130
|
warning?: string;
|
|
128
131
|
};
|
|
129
132
|
|
|
@@ -144,6 +147,7 @@ export type IdlePlanStageAction = {
|
|
|
144
147
|
durationSeconds: number;
|
|
145
148
|
bubble: string;
|
|
146
149
|
log?: string;
|
|
150
|
+
propId?: string;
|
|
147
151
|
};
|
|
148
152
|
|
|
149
153
|
export type IdlePlanStage = {
|
|
@@ -275,3 +279,33 @@ export type CreateMusicAlbumPayload = {
|
|
|
275
279
|
albumTitle: string;
|
|
276
280
|
musicTitles: string[];
|
|
277
281
|
};
|
|
282
|
+
|
|
283
|
+
export type BotMessageHistoryEntry = {
|
|
284
|
+
from: string;
|
|
285
|
+
fromName: string;
|
|
286
|
+
bubble: string;
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
export type BotMessagePayload = {
|
|
290
|
+
type: "bot_message";
|
|
291
|
+
avatarId: string;
|
|
292
|
+
authKey: string;
|
|
293
|
+
requestId: string;
|
|
294
|
+
toAvatarId: string;
|
|
295
|
+
depth: number;
|
|
296
|
+
poseType?: PoseType;
|
|
297
|
+
action?: string;
|
|
298
|
+
playback?: ActionPlayback;
|
|
299
|
+
bubble: string;
|
|
300
|
+
log?: string;
|
|
301
|
+
history?: BotMessageHistoryEntry[];
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
export type BotMessageReceivedPayload = {
|
|
305
|
+
type: "bot_message_received";
|
|
306
|
+
from: string;
|
|
307
|
+
fromName: string;
|
|
308
|
+
depth: number;
|
|
309
|
+
bubble: string;
|
|
310
|
+
history?: BotMessageHistoryEntry[];
|
|
311
|
+
};
|