@yahaha-studio/kichi-forwarder 0.1.2-beta.11 → 0.1.2-beta.13

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/dist/index.js CHANGED
@@ -961,6 +961,10 @@ const plugin = {
961
961
  description: "Optional list of OpenClaw self-perceived personality tags",
962
962
  items: { type: "string" },
963
963
  },
964
+ source: {
965
+ type: "string",
966
+ description: "Optional join source identifier. Defaults to Kichi World join-source.json, then openclaw.",
967
+ },
964
968
  },
965
969
  required: ["botName", "bio"],
966
970
  },
@@ -974,6 +978,7 @@ const plugin = {
974
978
  let avatarId = params?.avatarId;
975
979
  const botName = params?.botName?.trim();
976
980
  const bio = params?.bio?.trim();
981
+ const rawSource = params?.source;
977
982
  const { tags, error: tagsError } = normalizeJoinTags(params?.tags);
978
983
  if (!avatarId) {
979
984
  avatarId = service.readSavedAvatarId() ?? undefined;
@@ -987,10 +992,22 @@ const plugin = {
987
992
  if (!bio) {
988
993
  return jsonResult({ success: false, error: "No bio" });
989
994
  }
995
+ let source;
996
+ try {
997
+ source = rawSource === undefined
998
+ ? service.readConfiguredJoinSource() ?? "openclaw"
999
+ : trimOptionalString(rawSource);
1000
+ }
1001
+ catch (err) {
1002
+ return jsonResult({ success: false, error: err instanceof Error ? err.message : String(err) });
1003
+ }
1004
+ if (!source) {
1005
+ return jsonResult({ success: false, error: "source must be a non-empty string" });
1006
+ }
990
1007
  if (tagsError) {
991
1008
  return jsonResult({ success: false, error: tagsError });
992
1009
  }
993
- const result = await service.join(avatarId, botName, bio, tags ?? []);
1010
+ const result = await service.join(avatarId, botName, bio, tags ?? [], source);
994
1011
  if (result.success) {
995
1012
  return jsonResult({ success: true, authKey: result.authKey });
996
1013
  }
@@ -1006,7 +1023,7 @@ const plugin = {
1006
1023
  api.registerTool((ctx) => ({
1007
1024
  name: "kichi_switch_host",
1008
1025
  label: "kichi_switch_host",
1009
- description: "Switch Kichi runtime environment and reconnect immediately without restarting the gateway. Host is resolved from config/environments.json.",
1026
+ description: "Switch Kichi runtime environment and reconnect immediately without restarting the gateway. For steam/steam-playtest the host is resolved automatically. For test, pass the host explicitly.",
1010
1027
  parameters: {
1011
1028
  type: "object",
1012
1029
  properties: {
@@ -1015,6 +1032,10 @@ const plugin = {
1015
1032
  enum: VALID_ENVIRONMENTS,
1016
1033
  description: "Target environment: steam, steam-playtest, or test",
1017
1034
  },
1035
+ host: {
1036
+ type: "string",
1037
+ description: "Test node host (required for test environment, ignored otherwise)",
1038
+ },
1018
1039
  },
1019
1040
  required: ["environment"],
1020
1041
  },
@@ -1025,19 +1046,31 @@ const plugin = {
1025
1046
  return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
1026
1047
  }
1027
1048
  const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
1028
- const environment = params?.environment;
1049
+ const p = params;
1050
+ const environment = p?.environment;
1029
1051
  if (!isKichiEnvironment(environment)) {
1030
1052
  return jsonResult({ success: false, error: `environment must be one of: ${VALID_ENVIRONMENTS.join(", ")}` });
1031
1053
  }
1032
- const resolved = resolveEnvironmentHost(environment);
1033
- if (resolved.error) {
1034
- return jsonResult({ success: false, error: resolved.error });
1054
+ let targetHost;
1055
+ if (environment === "test") {
1056
+ const testHost = typeof p?.host === "string" ? p.host.trim() : "";
1057
+ if (!testHost) {
1058
+ return jsonResult({ success: false, error: "host is required for the test environment" });
1059
+ }
1060
+ targetHost = testHost;
1061
+ }
1062
+ else {
1063
+ const resolved = resolveEnvironmentHost(environment);
1064
+ if (resolved.error) {
1065
+ return jsonResult({ success: false, error: resolved.error });
1066
+ }
1067
+ targetHost = resolved.host;
1035
1068
  }
1036
- const status = await service.switchHost(resolved.host, environment);
1069
+ const status = await service.switchHost(targetHost, environment);
1037
1070
  return jsonResult({
1038
1071
  success: true,
1039
1072
  environment,
1040
- host: resolved.host,
1073
+ host: targetHost,
1041
1074
  status,
1042
1075
  });
1043
1076
  },
@@ -4,6 +4,7 @@ import * as path from "path";
4
4
  import { randomUUID } from "node:crypto";
5
5
  const MAX_NOTEBOARD_TEXT_LENGTH = 200;
6
6
  const DEFAULT_LLM_RUNTIME_ENABLED = true;
7
+ const JOIN_SOURCE_FILE_NAME = "join-source.json";
7
8
  export class KichiForwarderService {
8
9
  logger;
9
10
  options;
@@ -27,6 +28,9 @@ export class KichiForwarderService {
27
28
  this.environment = state?.currentEnvironment ?? null;
28
29
  if (this.environment) {
29
30
  this.host = this.options.resolveEnvironmentHost(this.environment);
31
+ if (!this.host && this.environment === "test" && state?.testHost) {
32
+ this.host = state.testHost;
33
+ }
30
34
  }
31
35
  else {
32
36
  this.host = null;
@@ -60,7 +64,7 @@ export class KichiForwarderService {
60
64
  }
61
65
  return this.getConnectionStatus();
62
66
  }
63
- async join(avatarId, botName, bio, tags) {
67
+ async join(avatarId, botName, bio, tags, source) {
64
68
  if (!this.host) {
65
69
  return { success: false, error: "No Kichi host configured. Run kichi_switch_host first." };
66
70
  }
@@ -75,7 +79,7 @@ export class KichiForwarderService {
75
79
  this.identity = { avatarId };
76
80
  this.saveIdentity();
77
81
  this.joinResolve = resolve;
78
- const payload = { type: "join", avatarId, botName, bio, tags };
82
+ const payload = { type: "join", avatarId, botName, bio, tags, source };
79
83
  const sendJoin = () => this.ws?.send(JSON.stringify(payload));
80
84
  if (this.ws?.readyState === WebSocket.OPEN) {
81
85
  sendJoin();
@@ -271,6 +275,24 @@ export class KichiForwarderService {
271
275
  getRuntimeDir() {
272
276
  return this.options.runtimeDir;
273
277
  }
278
+ getJoinSourcePath() {
279
+ return path.join(this.getKichiWorldRootDir(), JOIN_SOURCE_FILE_NAME);
280
+ }
281
+ readConfiguredJoinSource() {
282
+ const sourcePath = this.getJoinSourcePath();
283
+ if (!fs.existsSync(sourcePath)) {
284
+ return null;
285
+ }
286
+ const data = JSON.parse(fs.readFileSync(sourcePath, "utf-8"));
287
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
288
+ throw new Error(`${JOIN_SOURCE_FILE_NAME} must contain a JSON object`);
289
+ }
290
+ const source = data.source;
291
+ if (typeof source !== "string" || !source.trim()) {
292
+ throw new Error(`${JOIN_SOURCE_FILE_NAME} must contain a non-empty string source`);
293
+ }
294
+ return source.trim();
295
+ }
274
296
  getStatePath() {
275
297
  return path.join(this.options.runtimeDir, "state.json");
276
298
  }
@@ -618,6 +640,9 @@ export class KichiForwarderService {
618
640
  }
619
641
  return path.join(this.options.runtimeDir, "hosts", encodeURIComponent(this.host));
620
642
  }
643
+ getKichiWorldRootDir() {
644
+ return path.dirname(path.dirname(this.options.runtimeDir));
645
+ }
621
646
  getWsUrl() {
622
647
  if (!this.host) {
623
648
  throw new Error("No Kichi host configured");
@@ -634,9 +659,11 @@ export class KichiForwarderService {
634
659
  }
635
660
  persistCurrentHost(host, environment) {
636
661
  const previousState = this.readStateFile();
662
+ const testHost = environment === "test" ? host : (previousState?.testHost ?? undefined);
637
663
  const nextState = {
638
664
  ...(environment ? { currentEnvironment: environment } : {}),
639
665
  llmRuntimeEnabled: previousState?.llmRuntimeEnabled ?? DEFAULT_LLM_RUNTIME_ENABLED,
666
+ ...(testHost ? { testHost } : {}),
640
667
  };
641
668
  fs.mkdirSync(this.options.runtimeDir, { recursive: true, mode: 0o700 });
642
669
  fs.writeFileSync(this.getStatePath(), JSON.stringify(nextState, null, 2), { mode: 0o600 });
package/index.ts CHANGED
@@ -1179,6 +1179,10 @@ const plugin = {
1179
1179
  description: "Optional list of OpenClaw self-perceived personality tags",
1180
1180
  items: { type: "string" },
1181
1181
  },
1182
+ source: {
1183
+ type: "string",
1184
+ description: "Optional join source identifier. Defaults to Kichi World join-source.json, then openclaw.",
1185
+ },
1182
1186
  },
1183
1187
  required: ["botName", "bio"],
1184
1188
  },
@@ -1192,6 +1196,7 @@ const plugin = {
1192
1196
  let avatarId = (params as { avatarId?: string } | null)?.avatarId;
1193
1197
  const botName = (params as { botName?: string } | null)?.botName?.trim();
1194
1198
  const bio = (params as { bio?: string } | null)?.bio?.trim();
1199
+ const rawSource = (params as { source?: unknown } | null)?.source;
1195
1200
  const { tags, error: tagsError } = normalizeJoinTags(
1196
1201
  (params as { tags?: unknown } | null)?.tags,
1197
1202
  );
@@ -1207,10 +1212,21 @@ const plugin = {
1207
1212
  if (!bio) {
1208
1213
  return jsonResult({ success: false, error: "No bio" });
1209
1214
  }
1215
+ let source: string | null | undefined;
1216
+ try {
1217
+ source = rawSource === undefined
1218
+ ? service.readConfiguredJoinSource() ?? "openclaw"
1219
+ : trimOptionalString(rawSource);
1220
+ } catch (err) {
1221
+ return jsonResult({ success: false, error: err instanceof Error ? err.message : String(err) });
1222
+ }
1223
+ if (!source) {
1224
+ return jsonResult({ success: false, error: "source must be a non-empty string" });
1225
+ }
1210
1226
  if (tagsError) {
1211
1227
  return jsonResult({ success: false, error: tagsError });
1212
1228
  }
1213
- const result = await service.join(avatarId, botName, bio, tags ?? []);
1229
+ const result = await service.join(avatarId, botName, bio, tags ?? [], source);
1214
1230
  if (result.success) {
1215
1231
  return jsonResult({ success: true, authKey: result.authKey });
1216
1232
  }
@@ -1228,7 +1244,7 @@ const plugin = {
1228
1244
  name: "kichi_switch_host",
1229
1245
  label: "kichi_switch_host",
1230
1246
  description:
1231
- "Switch Kichi runtime environment and reconnect immediately without restarting the gateway. Host is resolved from config/environments.json.",
1247
+ "Switch Kichi runtime environment and reconnect immediately without restarting the gateway. For steam/steam-playtest the host is resolved automatically. For test, pass the host explicitly.",
1232
1248
  parameters: {
1233
1249
  type: "object",
1234
1250
  properties: {
@@ -1237,6 +1253,10 @@ const plugin = {
1237
1253
  enum: VALID_ENVIRONMENTS,
1238
1254
  description: "Target environment: steam, steam-playtest, or test",
1239
1255
  },
1256
+ host: {
1257
+ type: "string",
1258
+ description: "Test node host (required for test environment, ignored otherwise)",
1259
+ },
1240
1260
  },
1241
1261
  required: ["environment"],
1242
1262
  },
@@ -1247,21 +1267,32 @@ const plugin = {
1247
1267
  return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
1248
1268
  }
1249
1269
  const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
1250
- const environment = (params as { environment?: unknown } | null)?.environment;
1270
+ const p = params as { environment?: unknown; host?: unknown } | null;
1271
+ const environment = p?.environment;
1251
1272
  if (!isKichiEnvironment(environment)) {
1252
1273
  return jsonResult({ success: false, error: `environment must be one of: ${VALID_ENVIRONMENTS.join(", ")}` });
1253
1274
  }
1254
1275
 
1255
- const resolved = resolveEnvironmentHost(environment);
1256
- if (resolved.error) {
1257
- return jsonResult({ success: false, error: resolved.error });
1276
+ let targetHost: string;
1277
+ if (environment === "test") {
1278
+ const testHost = typeof p?.host === "string" ? p.host.trim() : "";
1279
+ if (!testHost) {
1280
+ return jsonResult({ success: false, error: "host is required for the test environment" });
1281
+ }
1282
+ targetHost = testHost;
1283
+ } else {
1284
+ const resolved = resolveEnvironmentHost(environment);
1285
+ if (resolved.error) {
1286
+ return jsonResult({ success: false, error: resolved.error });
1287
+ }
1288
+ targetHost = resolved.host!;
1258
1289
  }
1259
1290
 
1260
- const status = await service.switchHost(resolved.host!, environment);
1291
+ const status = await service.switchHost(targetHost, environment);
1261
1292
  return jsonResult({
1262
1293
  success: true,
1263
1294
  environment,
1264
- host: resolved.host,
1295
+ host: targetHost,
1265
1296
  status,
1266
1297
  });
1267
1298
  },
@@ -1964,4 +1995,3 @@ const plugin = {
1964
1995
  };
1965
1996
 
1966
1997
  export default plugin;
1967
-
@@ -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.11",
5
+ "version": "0.1.2-beta.13",
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.11",
3
+ "version": "0.1.2-beta.13",
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, write it to the plugin's `config/environments.json`, then call `kichi_switch_host`
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
27
27
 
28
28
  ## Runtime State
29
29
 
@@ -94,12 +94,12 @@ kichi_join(avatarId: "your-avatar-id", botName: "<from IDENTITY.md>", bio: "<fro
94
94
 
95
95
  ```text
96
96
  kichi_switch_host(environment: "steam")
97
- kichi_switch_host(environment: "test")
97
+ kichi_switch_host(environment: "test", host: "192.168.1.100")
98
98
  ```
99
99
 
100
100
  - `environment`: required. One of `steam`, `steam-playtest`, `test`.
101
- - Host is resolved from `config/environments.json`. If the environment has no configured host (null), the call fails.
102
- - For `test` environment: ask the user for the test node host, write it to the plugin's `config/environments.json`, then call this tool.
101
+ - `host`: required for `test` environment, ignored otherwise. The test host is persisted in `state.json` and reused on restart.
102
+ - For `steam` and `steam-playtest`, the host is resolved automatically from the bundled config.
103
103
  - This reloads the host-specific `identity.json` and reconnects the websocket immediately.
104
104
 
105
105
  ### kichi_connection_status
@@ -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, write it to the plugin's `config/environments.json`, then connect
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
10
10
 
11
11
  ## Runtime Files
12
12
 
@@ -18,7 +18,8 @@ Persist runtime state to the current agent's `state.json`:
18
18
  ```json
19
19
  {
20
20
  "currentEnvironment": "steam",
21
- "llmRuntimeEnabled": true
21
+ "llmRuntimeEnabled": true,
22
+ "testHost": "192.168.1.100"
22
23
  }
23
24
  ```
24
25
 
@@ -68,19 +69,21 @@ When the user asks with one of the commands above, execute in this fixed order:
68
69
  5. If the plugin already exists and the packed version matches the installed version, skip to step 7.
69
70
  6. If the plugin already exists but the version differs, overwrite with `openclaw plugins install <tgz-path> --force`.
70
71
  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.
72
+ 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
+ 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
+ 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.
78
80
 
79
81
  ## Required Post-install Integration
80
82
 
81
83
  Use this completion checklist:
82
84
 
83
85
  - [ ] plugin installed, enabled, and at latest version
86
+ - [ ] `openclaw.json` has `plugins.entries.kichi-forwarder.hooks.allowConversationAccess: true` (OpenClaw >= 5.7 only)
84
87
  - [ ] `HEARTBEAT.md` updated with the Kichi heartbeat workflow snippet from [heartbeat.md](heartbeat.md)
85
88
  - [ ] `kichi_connection_status` verified the final connected/auth state
86
89
 
package/src/service.ts CHANGED
@@ -33,6 +33,7 @@ import type {
33
33
 
34
34
  const MAX_NOTEBOARD_TEXT_LENGTH = 200;
35
35
  const DEFAULT_LLM_RUNTIME_ENABLED = true;
36
+ const JOIN_SOURCE_FILE_NAME = "join-source.json";
36
37
 
37
38
  type AckFailureResult = {
38
39
  success: false;
@@ -95,6 +96,9 @@ export class KichiForwarderService {
95
96
  this.environment = (state?.currentEnvironment as KichiEnvironment) ?? null;
96
97
  if (this.environment) {
97
98
  this.host = this.options.resolveEnvironmentHost(this.environment);
99
+ if (!this.host && this.environment === "test" && state?.testHost) {
100
+ this.host = state.testHost as string;
101
+ }
98
102
  } else {
99
103
  this.host = null;
100
104
  }
@@ -135,6 +139,7 @@ export class KichiForwarderService {
135
139
  botName: string,
136
140
  bio: string,
137
141
  tags: string[],
142
+ source: string,
138
143
  ): Promise<JoinResult> {
139
144
  if (!this.host) {
140
145
  return { success: false, error: "No Kichi host configured. Run kichi_switch_host first." };
@@ -150,7 +155,7 @@ export class KichiForwarderService {
150
155
  this.identity = { avatarId };
151
156
  this.saveIdentity();
152
157
  this.joinResolve = resolve;
153
- const payload: JoinPayload = { type: "join", avatarId, botName, bio, tags };
158
+ const payload: JoinPayload = { type: "join", avatarId, botName, bio, tags, source };
154
159
  const sendJoin = () => this.ws?.send(JSON.stringify(payload));
155
160
  if (this.ws?.readyState === WebSocket.OPEN) {
156
161
  sendJoin();
@@ -378,6 +383,29 @@ export class KichiForwarderService {
378
383
  return this.options.runtimeDir;
379
384
  }
380
385
 
386
+ getJoinSourcePath(): string {
387
+ return path.join(this.getKichiWorldRootDir(), JOIN_SOURCE_FILE_NAME);
388
+ }
389
+
390
+ readConfiguredJoinSource(): string | null {
391
+ const sourcePath = this.getJoinSourcePath();
392
+ if (!fs.existsSync(sourcePath)) {
393
+ return null;
394
+ }
395
+
396
+ const data = JSON.parse(fs.readFileSync(sourcePath, "utf-8")) as unknown;
397
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
398
+ throw new Error(`${JOIN_SOURCE_FILE_NAME} must contain a JSON object`);
399
+ }
400
+
401
+ const source = (data as { source?: unknown }).source;
402
+ if (typeof source !== "string" || !source.trim()) {
403
+ throw new Error(`${JOIN_SOURCE_FILE_NAME} must contain a non-empty string source`);
404
+ }
405
+
406
+ return source.trim();
407
+ }
408
+
381
409
  getStatePath(): string {
382
410
  return path.join(this.options.runtimeDir, "state.json");
383
411
  }
@@ -762,6 +790,10 @@ export class KichiForwarderService {
762
790
  return path.join(this.options.runtimeDir, "hosts", encodeURIComponent(this.host));
763
791
  }
764
792
 
793
+ private getKichiWorldRootDir(): string {
794
+ return path.dirname(path.dirname(this.options.runtimeDir));
795
+ }
796
+
765
797
  private getWsUrl(): string {
766
798
  if (!this.host) {
767
799
  throw new Error("No Kichi host configured");
@@ -780,9 +812,11 @@ export class KichiForwarderService {
780
812
 
781
813
  private persistCurrentHost(host: string, environment?: KichiEnvironment): void {
782
814
  const previousState = this.readStateFile();
815
+ const testHost = environment === "test" ? host : (previousState?.testHost ?? undefined);
783
816
  const nextState: KichiState = {
784
817
  ...(environment ? { currentEnvironment: environment } : {}),
785
818
  llmRuntimeEnabled: previousState?.llmRuntimeEnabled ?? DEFAULT_LLM_RUNTIME_ENABLED,
819
+ ...(testHost ? { testHost } : {}),
786
820
  };
787
821
  fs.mkdirSync(this.options.runtimeDir, { recursive: true, mode: 0o700 });
788
822
  fs.writeFileSync(this.getStatePath(), JSON.stringify(nextState, null, 2), { mode: 0o600 });
package/src/types.ts CHANGED
@@ -44,6 +44,7 @@ export type KichiEnvironmentsConfig = Record<KichiEnvironment, string | null>;
44
44
  export type KichiState = {
45
45
  currentEnvironment?: KichiEnvironment;
46
46
  llmRuntimeEnabled: boolean;
47
+ testHost?: string;
47
48
  };
48
49
 
49
50
  export type KichiIdentity = {
@@ -85,6 +86,7 @@ export type JoinPayload = {
85
86
  botName: string;
86
87
  bio: string;
87
88
  tags: string[];
89
+ source: string;
88
90
  };
89
91
 
90
92
  export type JoinAckPayload = {
@@ -249,9 +251,7 @@ export type QueryStatusOwnerState = {
249
251
  poseType?: string;
250
252
  action?: string;
251
253
  interactingItemName?: string;
252
- desktopActivityCategory?: string;
253
254
  desktopAppName?: string;
254
- desktopSummary?: string;
255
255
  };
256
256
 
257
257
  export type QueryStatusNote = {