@yahaha-studio/kichi-forwarder 0.1.2-beta.17 → 0.1.2-beta.18

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 CHANGED
@@ -31,7 +31,7 @@ Use the bare package name for installation. OpenClaw tries ClawHub first and fal
31
31
 
32
32
  Kichi provides the install command and the connection details you need to connect a companion.
33
33
 
34
- Get the `host` and `avatarId` from Kichi, then use them with `kichi_switch_host` and `kichi_join`.
34
+ Get the environment, `avatarId`, and test `host` when using test, then use them with `kichi_join`.
35
35
 
36
36
  ## What Your Companion Can Do
37
37
 
@@ -48,7 +48,7 @@ Get the `host` and `avatarId` from Kichi, then use them with `kichi_switch_host`
48
48
 
49
49
  1. Install the plugin.
50
50
  2. Start OpenClaw with the plugin enabled.
51
- 3. Use `kichi_switch_host` and `kichi_join` to connect your companion to Kichi.
51
+ 3. Use `kichi_join` to connect your companion to Kichi.
52
52
  4. Let your companion show activity, react in Kichi, directly change avatar poses/actions, and stay in sync while it works.
53
53
  5. Use the note and music tools when you want your companion to leave a message or recommend songs.
54
54
 
package/dist/index.js CHANGED
@@ -192,6 +192,23 @@ function resolveEnvironmentHost(environment) {
192
192
  }
193
193
  return { error: `environment "${environment}" has no configured host — update config/environments.json first` };
194
194
  }
195
+ function resolveJoinEnvironmentHost(params) {
196
+ if (!isKichiEnvironment(params.environment)) {
197
+ return { error: `environment must be one of: ${VALID_ENVIRONMENTS.join(", ")}` };
198
+ }
199
+ if (params.environment === "test") {
200
+ const testHost = typeof params.host === "string" ? params.host.trim() : "";
201
+ if (!testHost) {
202
+ return { error: "host is required for the test environment" };
203
+ }
204
+ return { environment: params.environment, host: testHost };
205
+ }
206
+ const resolved = resolveEnvironmentHost(params.environment);
207
+ if (resolved.error) {
208
+ return { environment: params.environment, error: resolved.error };
209
+ }
210
+ return { environment: params.environment, host: resolved.host };
211
+ }
195
212
  function sendStatusUpdate(service, status) {
196
213
  const actionDefinition = getActionDefinition(status.poseType, status.action);
197
214
  service.sendStatus(status.poseType, actionDefinition.name, status.bubble || status.action, typeof status.log === "string" ? status.log.trim() : "", getActionPlayback(actionDefinition), status.propId);
@@ -948,18 +965,27 @@ const plugin = {
948
965
  api.registerTool((ctx) => ({
949
966
  name: "kichi_join",
950
967
  label: "kichi_join",
951
- description: "Join Kichi world with avatarId, the current bot name, a short bio, and personality tags",
968
+ description: "Join Kichi world in the target environment with avatarId, the current bot name, a short bio, and personality tags. For test, pass host.",
952
969
  parameters: {
953
970
  type: "object",
954
971
  properties: {
955
972
  avatarId: { type: "string", description: "Avatar ID to join Kichi world" },
973
+ environment: {
974
+ type: "string",
975
+ enum: VALID_ENVIRONMENTS,
976
+ description: "Target environment. kichi_join switches to this environment before joining.",
977
+ },
978
+ host: {
979
+ type: "string",
980
+ description: "Test host, required when environment is test and ignored otherwise",
981
+ },
956
982
  botName: {
957
983
  type: "string",
958
984
  description: "Current bot name to include in the join message",
959
985
  },
960
986
  bio: {
961
987
  type: "string",
962
- description: "Short bio covering OpenClaw personality and role",
988
+ description: "Short bio extracted from SOUL.md, covering persona and idle plan goals if present",
963
989
  },
964
990
  tags: {
965
991
  type: "array",
@@ -971,7 +997,7 @@ const plugin = {
971
997
  description: "Optional join source identifier. Defaults to Kichi World join-source.json, then openclaw.",
972
998
  },
973
999
  },
974
- required: ["botName", "bio"],
1000
+ required: ["environment", "avatarId", "botName", "bio"],
975
1001
  },
976
1002
  execute: async (_toolCallId, params) => {
977
1003
  const locator = resolveToolLocator(ctx);
@@ -980,17 +1006,23 @@ const plugin = {
980
1006
  return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
981
1007
  }
982
1008
  const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
983
- let avatarId = params?.avatarId;
984
- const botName = params?.botName?.trim();
985
- const bio = params?.bio?.trim();
986
- const rawSource = params?.source;
987
- const { tags, error: tagsError } = normalizeJoinTags(params?.tags);
988
- if (!avatarId) {
989
- avatarId = service.readSavedAvatarId() ?? undefined;
1009
+ const p = params;
1010
+ const target = resolveJoinEnvironmentHost({
1011
+ environment: p?.environment,
1012
+ host: p?.host,
1013
+ });
1014
+ if (target.error) {
1015
+ return jsonResult({ success: false, error: target.error });
990
1016
  }
991
- if (!avatarId) {
992
- return jsonResult({ success: false, error: "No avatarId" });
1017
+ const currentStatus = service.getConnectionStatus();
1018
+ let avatarId = p?.avatarId;
1019
+ if (!avatarId && currentStatus.host === target.host) {
1020
+ avatarId = service.readSavedAvatarId() ?? undefined;
993
1021
  }
1022
+ const botName = p?.botName?.trim();
1023
+ const bio = p?.bio?.trim();
1024
+ const rawSource = p?.source;
1025
+ const { tags, error: tagsError } = normalizeJoinTags(p?.tags);
994
1026
  if (!botName) {
995
1027
  return jsonResult({ success: false, error: "No botName" });
996
1028
  }
@@ -1012,14 +1044,49 @@ const plugin = {
1012
1044
  if (tagsError) {
1013
1045
  return jsonResult({ success: false, error: tagsError });
1014
1046
  }
1047
+ let leaveStatus;
1048
+ const shouldLeaveCurrentConnection = currentStatus.connected && currentStatus.hasAuthKey && ((!!currentStatus.host && currentStatus.host !== target.host) ||
1049
+ (currentStatus.host === target.host && !!currentStatus.avatarId && !!avatarId && currentStatus.avatarId !== avatarId));
1050
+ if (shouldLeaveCurrentConnection) {
1051
+ try {
1052
+ leaveStatus = await service.leave();
1053
+ }
1054
+ catch (err) {
1055
+ leaveStatus = {
1056
+ success: false,
1057
+ error: err instanceof Error ? err.message : String(err),
1058
+ };
1059
+ }
1060
+ }
1061
+ let switchStatus;
1062
+ if (target.environment && target.host && service.getCurrentHost() !== target.host) {
1063
+ switchStatus = await service.switchHost(target.host, target.environment);
1064
+ }
1065
+ if (!avatarId) {
1066
+ avatarId = service.readSavedAvatarId() ?? undefined;
1067
+ }
1068
+ if (!avatarId) {
1069
+ return jsonResult({ success: false, error: "No avatarId" });
1070
+ }
1015
1071
  const result = await service.join(avatarId, botName, bio, tags ?? [], source);
1016
1072
  if (result.success) {
1017
- return jsonResult({ success: true, authKey: result.authKey });
1073
+ return jsonResult({
1074
+ success: true,
1075
+ authKey: result.authKey,
1076
+ ...(target.environment ? { environment: target.environment } : {}),
1077
+ ...(target.host ? { host: target.host } : {}),
1078
+ ...(switchStatus ? { switchStatus } : {}),
1079
+ ...(leaveStatus ? { leaveStatus } : {}),
1080
+ });
1018
1081
  }
1019
1082
  const failure = result;
1020
1083
  return jsonResult({
1021
1084
  success: false,
1022
1085
  error: failure.error,
1086
+ ...(target.environment ? { environment: target.environment } : {}),
1087
+ ...(target.host ? { host: target.host } : {}),
1088
+ ...(switchStatus ? { switchStatus } : {}),
1089
+ ...(leaveStatus ? { leaveStatus } : {}),
1023
1090
  ...(failure.errorCode ? { errorCode: failure.errorCode } : {}),
1024
1091
  ...(failure.errorMessage ? { errorMessage: failure.errorMessage } : {}),
1025
1092
  });
@@ -1039,7 +1106,7 @@ const plugin = {
1039
1106
  },
1040
1107
  host: {
1041
1108
  type: "string",
1042
- description: "Test node host (required for test environment, ignored otherwise)",
1109
+ description: "Test host (required for test environment, ignored otherwise)",
1043
1110
  },
1044
1111
  },
1045
1112
  required: ["environment"],
package/index.ts CHANGED
@@ -256,6 +256,27 @@ function resolveEnvironmentHost(environment: KichiEnvironment): { host?: string;
256
256
  return { error: `environment "${environment}" has no configured host — update config/environments.json first` };
257
257
  }
258
258
 
259
+ function resolveJoinEnvironmentHost(params: {
260
+ environment?: unknown;
261
+ host?: unknown;
262
+ }): { environment?: KichiEnvironment; host?: string; error?: string } {
263
+ if (!isKichiEnvironment(params.environment)) {
264
+ return { error: `environment must be one of: ${VALID_ENVIRONMENTS.join(", ")}` };
265
+ }
266
+ if (params.environment === "test") {
267
+ const testHost = typeof params.host === "string" ? params.host.trim() : "";
268
+ if (!testHost) {
269
+ return { error: "host is required for the test environment" };
270
+ }
271
+ return { environment: params.environment, host: testHost };
272
+ }
273
+ const resolved = resolveEnvironmentHost(params.environment);
274
+ if (resolved.error) {
275
+ return { environment: params.environment, error: resolved.error };
276
+ }
277
+ return { environment: params.environment, host: resolved.host };
278
+ }
279
+
259
280
  function sendStatusUpdate(service: KichiForwarderService, status: ActionResult): void {
260
281
  const actionDefinition = getActionDefinition(status.poseType, status.action);
261
282
  service.sendStatus(
@@ -1166,18 +1187,29 @@ const plugin = {
1166
1187
  api.registerTool((ctx) => ({
1167
1188
  name: "kichi_join",
1168
1189
  label: "kichi_join",
1169
- description: "Join Kichi world with avatarId, the current bot name, a short bio, and personality tags",
1190
+ description:
1191
+ "Join Kichi world in the target environment with avatarId, the current bot name, a short bio, and personality tags. For test, pass host.",
1170
1192
  parameters: {
1171
1193
  type: "object",
1172
1194
  properties: {
1173
1195
  avatarId: { type: "string", description: "Avatar ID to join Kichi world" },
1196
+ environment: {
1197
+ type: "string",
1198
+ enum: VALID_ENVIRONMENTS,
1199
+ description:
1200
+ "Target environment. kichi_join switches to this environment before joining.",
1201
+ },
1202
+ host: {
1203
+ type: "string",
1204
+ description: "Test host, required when environment is test and ignored otherwise",
1205
+ },
1174
1206
  botName: {
1175
1207
  type: "string",
1176
1208
  description: "Current bot name to include in the join message",
1177
1209
  },
1178
1210
  bio: {
1179
1211
  type: "string",
1180
- description: "Short bio covering OpenClaw personality and role",
1212
+ description: "Short bio extracted from SOUL.md, covering persona and idle plan goals if present",
1181
1213
  },
1182
1214
  tags: {
1183
1215
  type: "array",
@@ -1189,7 +1221,7 @@ const plugin = {
1189
1221
  description: "Optional join source identifier. Defaults to Kichi World join-source.json, then openclaw.",
1190
1222
  },
1191
1223
  },
1192
- required: ["botName", "bio"],
1224
+ required: ["environment", "avatarId", "botName", "bio"],
1193
1225
  },
1194
1226
  execute: async (_toolCallId, params) => {
1195
1227
  const locator = resolveToolLocator(ctx);
@@ -1198,19 +1230,33 @@ const plugin = {
1198
1230
  return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
1199
1231
  }
1200
1232
  const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
1201
- let avatarId = (params as { avatarId?: string } | null)?.avatarId;
1202
- const botName = (params as { botName?: string } | null)?.botName?.trim();
1203
- const bio = (params as { bio?: string } | null)?.bio?.trim();
1204
- const rawSource = (params as { source?: unknown } | null)?.source;
1205
- const { tags, error: tagsError } = normalizeJoinTags(
1206
- (params as { tags?: unknown } | null)?.tags,
1207
- );
1208
- if (!avatarId) {
1209
- avatarId = service.readSavedAvatarId() ?? undefined;
1233
+ const p = params as {
1234
+ avatarId?: string;
1235
+ environment?: unknown;
1236
+ host?: unknown;
1237
+ botName?: string;
1238
+ bio?: string;
1239
+ source?: unknown;
1240
+ tags?: unknown;
1241
+ } | null;
1242
+ const target = resolveJoinEnvironmentHost({
1243
+ environment: p?.environment,
1244
+ host: p?.host,
1245
+ });
1246
+ if (target.error) {
1247
+ return jsonResult({ success: false, error: target.error });
1210
1248
  }
1211
- if (!avatarId) {
1212
- return jsonResult({ success: false, error: "No avatarId" });
1249
+ const currentStatus = service.getConnectionStatus();
1250
+ let avatarId = p?.avatarId;
1251
+ if (!avatarId && currentStatus.host === target.host) {
1252
+ avatarId = service.readSavedAvatarId() ?? undefined;
1213
1253
  }
1254
+ const botName = p?.botName?.trim();
1255
+ const bio = p?.bio?.trim();
1256
+ const rawSource = p?.source;
1257
+ const { tags, error: tagsError } = normalizeJoinTags(
1258
+ p?.tags,
1259
+ );
1214
1260
  if (!botName) {
1215
1261
  return jsonResult({ success: false, error: "No botName" });
1216
1262
  }
@@ -1231,14 +1277,50 @@ const plugin = {
1231
1277
  if (tagsError) {
1232
1278
  return jsonResult({ success: false, error: tagsError });
1233
1279
  }
1280
+ let leaveStatus;
1281
+ const shouldLeaveCurrentConnection = currentStatus.connected && currentStatus.hasAuthKey && (
1282
+ (!!currentStatus.host && currentStatus.host !== target.host) ||
1283
+ (currentStatus.host === target.host && !!currentStatus.avatarId && !!avatarId && currentStatus.avatarId !== avatarId)
1284
+ );
1285
+ if (shouldLeaveCurrentConnection) {
1286
+ try {
1287
+ leaveStatus = await service.leave();
1288
+ } catch (err) {
1289
+ leaveStatus = {
1290
+ success: false,
1291
+ error: err instanceof Error ? err.message : String(err),
1292
+ };
1293
+ }
1294
+ }
1295
+ let switchStatus;
1296
+ if (target.environment && target.host && service.getCurrentHost() !== target.host) {
1297
+ switchStatus = await service.switchHost(target.host, target.environment);
1298
+ }
1299
+ if (!avatarId) {
1300
+ avatarId = service.readSavedAvatarId() ?? undefined;
1301
+ }
1302
+ if (!avatarId) {
1303
+ return jsonResult({ success: false, error: "No avatarId" });
1304
+ }
1234
1305
  const result = await service.join(avatarId, botName, bio, tags ?? [], source);
1235
1306
  if (result.success) {
1236
- return jsonResult({ success: true, authKey: result.authKey });
1307
+ return jsonResult({
1308
+ success: true,
1309
+ authKey: result.authKey,
1310
+ ...(target.environment ? { environment: target.environment } : {}),
1311
+ ...(target.host ? { host: target.host } : {}),
1312
+ ...(switchStatus ? { switchStatus } : {}),
1313
+ ...(leaveStatus ? { leaveStatus } : {}),
1314
+ });
1237
1315
  }
1238
1316
  const failure = result as { success: false; error: string; errorCode?: string; errorMessage?: string };
1239
1317
  return jsonResult({
1240
1318
  success: false,
1241
1319
  error: failure.error,
1320
+ ...(target.environment ? { environment: target.environment } : {}),
1321
+ ...(target.host ? { host: target.host } : {}),
1322
+ ...(switchStatus ? { switchStatus } : {}),
1323
+ ...(leaveStatus ? { leaveStatus } : {}),
1242
1324
  ...(failure.errorCode ? { errorCode: failure.errorCode } : {}),
1243
1325
  ...(failure.errorMessage ? { errorMessage: failure.errorMessage } : {}),
1244
1326
  });
@@ -1260,7 +1342,7 @@ const plugin = {
1260
1342
  },
1261
1343
  host: {
1262
1344
  type: "string",
1263
- description: "Test node host (required for test environment, ignored otherwise)",
1345
+ description: "Test host (required for test environment, ignored otherwise)",
1264
1346
  },
1265
1347
  },
1266
1348
  required: ["environment"],
@@ -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.2-beta.17",
5
+ "version": "0.1.2-beta.18",
6
6
  "author": "OpenClaw",
7
7
  "skills": ["./skills/kichi-forwarder"],
8
8
  "contracts": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yahaha-studio/kichi-forwarder",
3
- "version": "0.1.2-beta.17",
3
+ "version": "0.1.2-beta.18",
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": "./dist/index.js",
@@ -23,7 +23,7 @@ Install and connect requests use `on <environment>` syntax. Supported environmen
23
23
 
24
24
  - `steam`: connects to `focus-wss.yahaha.com`
25
25
  - `steam-playtest`: connects to `focus-steam-playtest-wss-int.yahaha.com`
26
- - `test`: no fixed host — ask the user for the current test node host, then call `kichi_switch_host` with both the environment and host. The host is persisted in `state.json` and reused on restart
26
+ - `test`: no fixed host — use the test host from the user request when provided; otherwise ask the user for the test host before calling `kichi_join`
27
27
 
28
28
  ## Runtime State
29
29
 
@@ -69,26 +69,23 @@ For install/onboarding/connect requests:
69
69
 
70
70
  Use this order unless the user asks for a different explicit action. For install/onboarding requests, follow `install.md` first.
71
71
 
72
- 1. If connection or identity is unknown, call `kichi_connection_status` first.
73
- 2. If the requested environment differs from the current environment, call `kichi_switch_host` with the target environment.
74
- 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`.
75
- 4. If no `authKey` is available, call `kichi_join`.
76
- 5. If `authKey` exists but websocket is not open, call `kichi_rejoin` or wait for automatic reconnect and rejoin.
77
- 6. Use `kichi_action`, `kichi_glance`, `kichi_clock`, note board tools, and music album tools after status is ready.
72
+ 1. For join/connect requests with an `avatarId` and environment, call `kichi_join` with `environment`. For `test`, include `host` if the user provided it; if not, ask for the host first.
78
73
 
79
74
  ## Tools
80
75
 
81
76
  ### kichi_join
82
77
 
83
78
  ```text
84
- kichi_join(avatarId: "your-avatar-id", botName: "<from IDENTITY.md>", bio: "<from SOUL.md>", tags: ["calm", "focused", "curious"])
79
+ kichi_join(environment: "steam-playtest", avatarId: "your-avatar-id", botName: "<from IDENTITY.md>", bio: "<from SOUL.md>", tags: ["calm", "focused", "curious"])
80
+ kichi_join(environment: "test", host: "192.168.1.100", avatarId: "your-avatar-id", botName: "<from IDENTITY.md>", bio: "<from SOUL.md>", tags: ["calm", "focused", "curious"])
85
81
  ```
86
82
 
83
+ - `environment`: required. One of `steam`, `steam-playtest`, `test`. `kichi_join` switches to the target environment before joining.
84
+ - `host`: required for `test` environment, ignored otherwise. If the user did not provide the test host, ask for it before calling `kichi_join`.
85
+ - `avatarId`: required
87
86
  - `botName`: required
88
- - `bio`: required
89
- - `avatarId`: optional. If omitted, the tool reads `avatarId` from the current host's `identity.json`. If missing, the call fails.
87
+ - `bio`: required. Extract from `SOUL.md`, covering persona and idle plan goals if present.
90
88
  - `tags`: optional string list. Empty strings are ignored and duplicates are removed. If omitted, the join payload sends `[]`.
91
- - If the current host is still joined with a different `avatarId`, call `kichi_leave` first, then call `kichi_join` with the new `avatarId`.
92
89
 
93
90
  ### kichi_switch_host
94
91
 
@@ -98,7 +95,7 @@ kichi_switch_host(environment: "test", host: "192.168.1.100")
98
95
  ```
99
96
 
100
97
  - `environment`: required. One of `steam`, `steam-playtest`, `test`.
101
- - `host`: required for `test` environment, ignored otherwise. The test host is persisted in `state.json` and reused on restart.
98
+ - `host`: required for `test` environment, ignored otherwise.
102
99
  - For `steam` and `steam-playtest`, the host is resolved automatically from the bundled config.
103
100
  - This reloads the host-specific `identity.json` and reconnects the websocket immediately.
104
101
 
@@ -6,7 +6,7 @@ Install and connect requests use `on <environment>` syntax. Supported environmen
6
6
 
7
7
  - `steam`: connects to `focus-wss.yahaha.com`
8
8
  - `steam-playtest`: connects to `focus-steam-playtest-wss-int.yahaha.com`
9
- - `test`: no fixed host — ask the user for the current test node host, then call `kichi_switch_host` with both the environment and host
9
+ - `test`: no fixed host — use the test host from the user request when provided; otherwise ask the user for the test host before calling `kichi_join`
10
10
 
11
11
  ## Runtime Files
12
12
 
@@ -23,17 +23,6 @@ Persist runtime state to the current agent's `state.json`:
23
23
  }
24
24
  ```
25
25
 
26
- If the current host has no saved `avatarId` yet, save it to the current agent's host-specific `identity.json` (this helps `kichi_join` resolve the avatar automatically when `avatarId` is omitted):
27
-
28
- - Linux/macOS: `~/.openclaw/kichi-world/agents/<encoded-agent-id>/hosts/<encoded-host>/identity.json`
29
- - Windows: `%USERPROFILE%\.openclaw\kichi-world\agents\<encoded-agent-id>\hosts\<encoded-host>\identity.json`
30
-
31
- ```json
32
- {
33
- "avatarId": "your-avatar-id"
34
- }
35
- ```
36
-
37
26
  ## Install
38
27
 
39
28
  1. Download the npm package archive:
@@ -64,7 +53,7 @@ When the user asks with one of the commands above, execute in this fixed order:
64
53
 
65
54
  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.
66
55
  2. Parse `avatarId` from user text (`AvatarId`/`avatarId`, case-insensitive).
67
- 3. Parse environment from the `on <environment>` part of the command (e.g. `on steam-playtest`). Write the current agent's `state.json`.
56
+ 3. Parse environment from the `on <environment>` part of the command (e.g. `on steam-playtest`). For `test`, parse the host from the request or ask the user for it before continuing.
68
57
  4. Run `npm pack @yahaha-studio/kichi-forwarder`, then install the generated `.tgz` with `openclaw plugins install <tgz-path>`.
69
58
  5. If the plugin already exists and the packed version matches the installed version, skip to step 7.
70
59
  6. If the plugin already exists but the version differs, overwrite with `openclaw plugins install <tgz-path> --force`.
@@ -72,11 +61,7 @@ When the user asks with one of the commands above, execute in this fixed order:
72
61
  8. Run `openclaw --version`. If the version is **5.7 or later**, ensure `openclaw.json` has `plugins.entries.kichi-forwarder.hooks.allowConversationAccess` set to `true`. If missing, add it. On older versions, skip this step.
73
62
  9. 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.
74
63
  10. 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.
75
- 11. Call `kichi_connection_status`.
76
- 12. If the current agent runtime environment does not match the requested one, call `kichi_switch_host` with the target environment (and host for test).
77
- 13. 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`.
78
- 14. Otherwise, if `authKey` is missing, call `kichi_join` with parsed `avatarId`, `botName`, `bio`, and `tags`.
79
- 15. Call `kichi_connection_status` again and confirm connection and auth state.
64
+ 11. Call `kichi_join` with parsed `environment`, `host` for test, `avatarId`, `botName`, `bio`, and `tags`.
80
65
 
81
66
  ## Required Post-install Integration
82
67
 
@@ -85,7 +70,7 @@ Use this completion checklist:
85
70
  - [ ] plugin installed, enabled, and at latest version
86
71
  - [ ] `openclaw.json` has `plugins.entries.kichi-forwarder.hooks.allowConversationAccess: true` (OpenClaw >= 5.7 only)
87
72
  - [ ] `HEARTBEAT.md` updated with the Kichi heartbeat workflow snippet from [heartbeat.md](heartbeat.md)
88
- - [ ] `kichi_connection_status` verified the final connected/auth state
73
+ - [ ] `kichi_join` completed successfully
89
74
 
90
75
  If any box is unchecked, the onboarding remains incomplete.
91
76