@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 +41 -8
- package/dist/src/service.js +29 -2
- package/index.ts +39 -9
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/skills/kichi-forwarder/SKILL.md +4 -4
- package/skills/kichi-forwarder/references/install.md +12 -9
- package/src/service.ts +35 -1
- package/src/types.ts +2 -2
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.
|
|
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
|
|
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
|
-
|
|
1033
|
-
if (
|
|
1034
|
-
|
|
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(
|
|
1069
|
+
const status = await service.switchHost(targetHost, environment);
|
|
1037
1070
|
return jsonResult({
|
|
1038
1071
|
success: true,
|
|
1039
1072
|
environment,
|
|
1040
|
-
host:
|
|
1073
|
+
host: targetHost,
|
|
1041
1074
|
status,
|
|
1042
1075
|
});
|
|
1043
1076
|
},
|
package/dist/src/service.js
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
-
|
|
1256
|
-
if (
|
|
1257
|
-
|
|
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(
|
|
1291
|
+
const status = await service.switchHost(targetHost, environment);
|
|
1261
1292
|
return jsonResult({
|
|
1262
1293
|
success: true,
|
|
1263
1294
|
environment,
|
|
1264
|
-
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
|
-
|
package/openclaw.plugin.json
CHANGED
|
@@ -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.
|
|
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.
|
|
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,
|
|
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
|
-
-
|
|
102
|
-
- For `
|
|
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,
|
|
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
|
|
72
|
-
9.
|
|
73
|
-
10.
|
|
74
|
-
11.
|
|
75
|
-
12. If the current
|
|
76
|
-
13.
|
|
77
|
-
14.
|
|
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 = {
|