@yahaha-studio/kichi-forwarder 0.1.2-beta.16 → 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);
@@ -843,9 +860,12 @@ function buildKichiIdlePlanDescription() {
843
860
  function buildKichiPrompt() {
844
861
  return [
845
862
  "Kichi avatar control and status sync are available via `kichi_action` and `kichi_clock`.",
863
+ "",
864
+ "IMAGE GENERATION RULES FOR SELFIES AND AVATAR APPEARANCE:",
865
+ "- If the user asks for a selfie, portrait, photo, avatar image, or any generated image of your appearance, you MUST read the workspace `IDENTITY.md` first and use it as the source of truth for your actual avatar description. If it references an avatar image URL, analyze that image with the available image analysis capability before calling image generation. Never guess or invent your appearance from personality, SOUL.md traits, or conversation tone alone. If the identity source is missing or cannot be analyzed, say so instead of fabricating your appearance.",
866
+ "",
846
867
  "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.",
847
868
  "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.",
848
- "If the user asks OpenClaw to send a selfie, base the visual appearance on the avatar details in IDENTITY.",
849
869
  "",
850
870
  "kichi_action timing (all required when sync is active):",
851
871
  "1. Task start: call BEFORE your first tool call OR before composing a multi-paragraph reply. For most work, start from a sit pose unless the user asked for a different pose or the task clearly fits another pose better.",
@@ -945,18 +965,27 @@ const plugin = {
945
965
  api.registerTool((ctx) => ({
946
966
  name: "kichi_join",
947
967
  label: "kichi_join",
948
- 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.",
949
969
  parameters: {
950
970
  type: "object",
951
971
  properties: {
952
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
+ },
953
982
  botName: {
954
983
  type: "string",
955
984
  description: "Current bot name to include in the join message",
956
985
  },
957
986
  bio: {
958
987
  type: "string",
959
- description: "Short bio covering OpenClaw personality and role",
988
+ description: "Short bio extracted from SOUL.md, covering persona and idle plan goals if present",
960
989
  },
961
990
  tags: {
962
991
  type: "array",
@@ -968,7 +997,7 @@ const plugin = {
968
997
  description: "Optional join source identifier. Defaults to Kichi World join-source.json, then openclaw.",
969
998
  },
970
999
  },
971
- required: ["botName", "bio"],
1000
+ required: ["environment", "avatarId", "botName", "bio"],
972
1001
  },
973
1002
  execute: async (_toolCallId, params) => {
974
1003
  const locator = resolveToolLocator(ctx);
@@ -977,17 +1006,23 @@ const plugin = {
977
1006
  return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
978
1007
  }
979
1008
  const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
980
- let avatarId = params?.avatarId;
981
- const botName = params?.botName?.trim();
982
- const bio = params?.bio?.trim();
983
- const rawSource = params?.source;
984
- const { tags, error: tagsError } = normalizeJoinTags(params?.tags);
985
- if (!avatarId) {
986
- 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 });
987
1016
  }
988
- if (!avatarId) {
989
- 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;
990
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);
991
1026
  if (!botName) {
992
1027
  return jsonResult({ success: false, error: "No botName" });
993
1028
  }
@@ -1009,14 +1044,49 @@ const plugin = {
1009
1044
  if (tagsError) {
1010
1045
  return jsonResult({ success: false, error: tagsError });
1011
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
+ }
1012
1071
  const result = await service.join(avatarId, botName, bio, tags ?? [], source);
1013
1072
  if (result.success) {
1014
- 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
+ });
1015
1081
  }
1016
1082
  const failure = result;
1017
1083
  return jsonResult({
1018
1084
  success: false,
1019
1085
  error: failure.error,
1086
+ ...(target.environment ? { environment: target.environment } : {}),
1087
+ ...(target.host ? { host: target.host } : {}),
1088
+ ...(switchStatus ? { switchStatus } : {}),
1089
+ ...(leaveStatus ? { leaveStatus } : {}),
1020
1090
  ...(failure.errorCode ? { errorCode: failure.errorCode } : {}),
1021
1091
  ...(failure.errorMessage ? { errorMessage: failure.errorMessage } : {}),
1022
1092
  });
@@ -1036,7 +1106,7 @@ const plugin = {
1036
1106
  },
1037
1107
  host: {
1038
1108
  type: "string",
1039
- description: "Test node host (required for test environment, ignored otherwise)",
1109
+ description: "Test host (required for test environment, ignored otherwise)",
1040
1110
  },
1041
1111
  },
1042
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(
@@ -1047,9 +1068,12 @@ function buildKichiIdlePlanDescription(): string {
1047
1068
  function buildKichiPrompt(): string {
1048
1069
  return [
1049
1070
  "Kichi avatar control and status sync are available via `kichi_action` and `kichi_clock`.",
1071
+ "",
1072
+ "IMAGE GENERATION RULES FOR SELFIES AND AVATAR APPEARANCE:",
1073
+ "- If the user asks for a selfie, portrait, photo, avatar image, or any generated image of your appearance, you MUST read the workspace `IDENTITY.md` first and use it as the source of truth for your actual avatar description. If it references an avatar image URL, analyze that image with the available image analysis capability before calling image generation. Never guess or invent your appearance from personality, SOUL.md traits, or conversation tone alone. If the identity source is missing or cannot be analyzed, say so instead of fabricating your appearance.",
1074
+ "",
1050
1075
  "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.",
1051
1076
  "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.",
1052
- "If the user asks OpenClaw to send a selfie, base the visual appearance on the avatar details in IDENTITY.",
1053
1077
  "",
1054
1078
  "kichi_action timing (all required when sync is active):",
1055
1079
  "1. Task start: call BEFORE your first tool call OR before composing a multi-paragraph reply. For most work, start from a sit pose unless the user asked for a different pose or the task clearly fits another pose better.",
@@ -1163,18 +1187,29 @@ const plugin = {
1163
1187
  api.registerTool((ctx) => ({
1164
1188
  name: "kichi_join",
1165
1189
  label: "kichi_join",
1166
- 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.",
1167
1192
  parameters: {
1168
1193
  type: "object",
1169
1194
  properties: {
1170
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
+ },
1171
1206
  botName: {
1172
1207
  type: "string",
1173
1208
  description: "Current bot name to include in the join message",
1174
1209
  },
1175
1210
  bio: {
1176
1211
  type: "string",
1177
- description: "Short bio covering OpenClaw personality and role",
1212
+ description: "Short bio extracted from SOUL.md, covering persona and idle plan goals if present",
1178
1213
  },
1179
1214
  tags: {
1180
1215
  type: "array",
@@ -1186,7 +1221,7 @@ const plugin = {
1186
1221
  description: "Optional join source identifier. Defaults to Kichi World join-source.json, then openclaw.",
1187
1222
  },
1188
1223
  },
1189
- required: ["botName", "bio"],
1224
+ required: ["environment", "avatarId", "botName", "bio"],
1190
1225
  },
1191
1226
  execute: async (_toolCallId, params) => {
1192
1227
  const locator = resolveToolLocator(ctx);
@@ -1195,19 +1230,33 @@ const plugin = {
1195
1230
  return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
1196
1231
  }
1197
1232
  const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
1198
- let avatarId = (params as { avatarId?: string } | null)?.avatarId;
1199
- const botName = (params as { botName?: string } | null)?.botName?.trim();
1200
- const bio = (params as { bio?: string } | null)?.bio?.trim();
1201
- const rawSource = (params as { source?: unknown } | null)?.source;
1202
- const { tags, error: tagsError } = normalizeJoinTags(
1203
- (params as { tags?: unknown } | null)?.tags,
1204
- );
1205
- if (!avatarId) {
1206
- 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 });
1207
1248
  }
1208
- if (!avatarId) {
1209
- 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;
1210
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
+ );
1211
1260
  if (!botName) {
1212
1261
  return jsonResult({ success: false, error: "No botName" });
1213
1262
  }
@@ -1228,14 +1277,50 @@ const plugin = {
1228
1277
  if (tagsError) {
1229
1278
  return jsonResult({ success: false, error: tagsError });
1230
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
+ }
1231
1305
  const result = await service.join(avatarId, botName, bio, tags ?? [], source);
1232
1306
  if (result.success) {
1233
- 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
+ });
1234
1315
  }
1235
1316
  const failure = result as { success: false; error: string; errorCode?: string; errorMessage?: string };
1236
1317
  return jsonResult({
1237
1318
  success: false,
1238
1319
  error: failure.error,
1320
+ ...(target.environment ? { environment: target.environment } : {}),
1321
+ ...(target.host ? { host: target.host } : {}),
1322
+ ...(switchStatus ? { switchStatus } : {}),
1323
+ ...(leaveStatus ? { leaveStatus } : {}),
1239
1324
  ...(failure.errorCode ? { errorCode: failure.errorCode } : {}),
1240
1325
  ...(failure.errorMessage ? { errorMessage: failure.errorMessage } : {}),
1241
1326
  });
@@ -1257,7 +1342,7 @@ const plugin = {
1257
1342
  },
1258
1343
  host: {
1259
1344
  type: "string",
1260
- description: "Test node host (required for test environment, ignored otherwise)",
1345
+ description: "Test host (required for test environment, ignored otherwise)",
1261
1346
  },
1262
1347
  },
1263
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.16",
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.16",
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