@yahaha-studio/kichi-forwarder 0.1.2-beta.7 → 0.1.2-beta.9
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 +232 -158
- package/dist/src/service.js +11 -3
- package/index.ts +143 -62
- package/openclaw.plugin.json +17 -1
- package/package.json +6 -6
- package/src/service.ts +12 -2
- package/src/types.ts +4 -0
package/dist/index.js
CHANGED
|
@@ -193,7 +193,7 @@ function resolveEnvironmentHost(environment) {
|
|
|
193
193
|
}
|
|
194
194
|
function sendStatusUpdate(service, status) {
|
|
195
195
|
const actionDefinition = getActionDefinition(status.poseType, status.action);
|
|
196
|
-
service.sendStatus(status.poseType, actionDefinition.name, status.bubble || status.action, typeof status.log === "string" ? status.log.trim() : "", getActionPlayback(actionDefinition));
|
|
196
|
+
service.sendStatus(status.poseType, actionDefinition.name, status.bubble || status.action, typeof status.log === "string" ? status.log.trim() : "", getActionPlayback(actionDefinition), status.propId);
|
|
197
197
|
}
|
|
198
198
|
function syncFixedStatus(service, status) {
|
|
199
199
|
if (!service.hasValidIdentity() || !service.isConnected()) {
|
|
@@ -557,6 +557,7 @@ function normalizeIdlePlan(value) {
|
|
|
557
557
|
const actionDurationSeconds = rawAction.durationSeconds;
|
|
558
558
|
const bubble = rawAction.bubble;
|
|
559
559
|
const log = rawAction.log;
|
|
560
|
+
const propId = rawAction.propId;
|
|
560
561
|
if (!["stand", "sit", "lay", "floor"].includes(String(poseType))) {
|
|
561
562
|
return {
|
|
562
563
|
error: `stages[${stageIndex}].actions[${actionIndex}].poseType must be stand, sit, lay, or floor`,
|
|
@@ -601,6 +602,7 @@ function normalizeIdlePlan(value) {
|
|
|
601
602
|
durationSeconds: actionDurationSeconds,
|
|
602
603
|
bubble: bubble.trim(),
|
|
603
604
|
...(typeof log === "string" && log.trim() ? { log: log.trim() } : {}),
|
|
605
|
+
...(typeof propId === "string" && propId.trim() ? { propId: propId.trim() } : {}),
|
|
604
606
|
});
|
|
605
607
|
}
|
|
606
608
|
if (stageActionDurationSeconds !== durationSeconds) {
|
|
@@ -797,9 +799,9 @@ function formatActionList(actions, playback) {
|
|
|
797
799
|
.map((entry) => entry.name)
|
|
798
800
|
.join(", ");
|
|
799
801
|
}
|
|
800
|
-
function buildKichiActionDescription() {
|
|
802
|
+
function buildKichiActionDescription(service) {
|
|
801
803
|
const actions = loadStaticConfig().actions;
|
|
802
|
-
|
|
804
|
+
const lines = [
|
|
803
805
|
"Directly control the avatar inside Kichi World.",
|
|
804
806
|
"Use this whenever the user explicitly asks you to make the Kichi avatar sit down, stand up, lie down, floor-sit, type, read, meditate, celebrate, or perform another listed animation.",
|
|
805
807
|
"For most work, prefer a sit pose and switch actions as the task moves between stages.",
|
|
@@ -808,7 +810,13 @@ function buildKichiActionDescription() {
|
|
|
808
810
|
`sit actions: ${actions.sit.map((entry) => entry.name).join(", ")}`,
|
|
809
811
|
`lay actions: ${actions.lay.map((entry) => entry.name).join(", ")}`,
|
|
810
812
|
`floor actions: ${actions.floor.map((entry) => entry.name).join(", ")}`,
|
|
811
|
-
]
|
|
813
|
+
];
|
|
814
|
+
const roomContext = service?.getCachedRoomContext();
|
|
815
|
+
const poseableProps = roomContext?.PoseableProps;
|
|
816
|
+
if (Array.isArray(poseableProps) && poseableProps.length > 0) {
|
|
817
|
+
lines.push("", "Cached RoomContext.PoseableProps (from last kichi_query_status):", JSON.stringify(poseableProps), "When using a sit or lay pose, pick the propId whose DisplayName best matches the current task context and whose OccupancyState is not fully_occupied. If no prop fits, omit propId.");
|
|
818
|
+
}
|
|
819
|
+
return lines.join("\n");
|
|
812
820
|
}
|
|
813
821
|
function buildKichiIdlePlanDescription() {
|
|
814
822
|
const actions = loadStaticConfig().actions;
|
|
@@ -850,17 +858,6 @@ function buildKichiPrompt() {
|
|
|
850
858
|
"User opt-out, Kichi config/test work, and explicit pose requests take priority over sync.",
|
|
851
859
|
].join("\n");
|
|
852
860
|
}
|
|
853
|
-
function createAgentScopedTool(runtimeManager, factory) {
|
|
854
|
-
return (ctx) => {
|
|
855
|
-
const locator = resolveToolLocator(ctx);
|
|
856
|
-
const agentId = runtimeManager.resolveRuntimeAgentId(locator);
|
|
857
|
-
if (!agentId) {
|
|
858
|
-
throw new Error("Failed to resolve agent-scoped Kichi runtime");
|
|
859
|
-
}
|
|
860
|
-
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
861
|
-
return factory(service, ctx);
|
|
862
|
-
};
|
|
863
|
-
}
|
|
864
861
|
const GLOBAL_RUNTIME_MANAGER_KEY = "__kichi_forwarder_runtime_manager__";
|
|
865
862
|
function getRuntimeManager(logger) {
|
|
866
863
|
const globalState = globalThis;
|
|
@@ -881,6 +878,11 @@ const plugin = {
|
|
|
881
878
|
configSchema: { parse },
|
|
882
879
|
register(api) {
|
|
883
880
|
const runtimeManager = getRuntimeManager(api.logger);
|
|
881
|
+
runtimeManager.setEnvironmentHostResolver((environment) => {
|
|
882
|
+
const config = loadEnvironmentsConfig();
|
|
883
|
+
const host = config[environment];
|
|
884
|
+
return typeof host === "string" && host.trim() ? host : null;
|
|
885
|
+
});
|
|
884
886
|
registerPluginHooks(api, runtimeManager);
|
|
885
887
|
const musicTitleEnum = getMusicTitleEnum();
|
|
886
888
|
runtimeManager.setBotMessageHandler((service, msg) => {
|
|
@@ -928,11 +930,6 @@ const plugin = {
|
|
|
928
930
|
id: "kichi-forwarder",
|
|
929
931
|
start: (ctx) => {
|
|
930
932
|
parse(ctx.config.plugins?.entries?.["kichi-forwarder"]?.config);
|
|
931
|
-
runtimeManager.setEnvironmentHostResolver((environment) => {
|
|
932
|
-
const config = loadEnvironmentsConfig();
|
|
933
|
-
const host = config[environment];
|
|
934
|
-
return typeof host === "string" && host.trim() ? host : null;
|
|
935
|
-
});
|
|
936
933
|
runtimeManager.initializeStartupRuntimes();
|
|
937
934
|
},
|
|
938
935
|
stop: () => {
|
|
@@ -943,7 +940,7 @@ const plugin = {
|
|
|
943
940
|
}
|
|
944
941
|
},
|
|
945
942
|
});
|
|
946
|
-
api.registerTool(
|
|
943
|
+
api.registerTool((ctx) => ({
|
|
947
944
|
name: "kichi_join",
|
|
948
945
|
label: "kichi_join",
|
|
949
946
|
description: "Join Kichi world with avatarId, the current bot name, a short bio, and personality tags",
|
|
@@ -968,6 +965,12 @@ const plugin = {
|
|
|
968
965
|
required: ["botName", "bio"],
|
|
969
966
|
},
|
|
970
967
|
execute: async (_toolCallId, params) => {
|
|
968
|
+
const locator = resolveToolLocator(ctx);
|
|
969
|
+
const agentId = runtimeManager.resolveRuntimeAgentId(locator);
|
|
970
|
+
if (!agentId) {
|
|
971
|
+
return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
|
|
972
|
+
}
|
|
973
|
+
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
971
974
|
let avatarId = params?.avatarId;
|
|
972
975
|
const botName = params?.botName?.trim();
|
|
973
976
|
const bio = params?.bio?.trim();
|
|
@@ -999,54 +1002,58 @@ const plugin = {
|
|
|
999
1002
|
...(failure.errorMessage ? { errorMessage: failure.errorMessage } : {}),
|
|
1000
1003
|
});
|
|
1001
1004
|
},
|
|
1002
|
-
}))
|
|
1003
|
-
api.registerTool((ctx) => {
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
parameters: {
|
|
1015
|
-
type: "object",
|
|
1016
|
-
properties: {
|
|
1017
|
-
environment: {
|
|
1018
|
-
type: "string",
|
|
1019
|
-
enum: VALID_ENVIRONMENTS,
|
|
1020
|
-
description: "Target environment: steam, steam-playtest, or test",
|
|
1021
|
-
},
|
|
1005
|
+
}));
|
|
1006
|
+
api.registerTool((ctx) => ({
|
|
1007
|
+
name: "kichi_switch_host",
|
|
1008
|
+
label: "kichi_switch_host",
|
|
1009
|
+
description: "Switch Kichi runtime environment and reconnect immediately without restarting the gateway. Host is resolved from config/environments.json.",
|
|
1010
|
+
parameters: {
|
|
1011
|
+
type: "object",
|
|
1012
|
+
properties: {
|
|
1013
|
+
environment: {
|
|
1014
|
+
type: "string",
|
|
1015
|
+
enum: VALID_ENVIRONMENTS,
|
|
1016
|
+
description: "Target environment: steam, steam-playtest, or test",
|
|
1022
1017
|
},
|
|
1023
|
-
required: ["environment"],
|
|
1024
1018
|
},
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
});
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1019
|
+
required: ["environment"],
|
|
1020
|
+
},
|
|
1021
|
+
execute: async (_toolCallId, params) => {
|
|
1022
|
+
const locator = resolveToolLocator(ctx);
|
|
1023
|
+
const agentId = runtimeManager.resolveRuntimeAgentId(locator);
|
|
1024
|
+
if (!agentId) {
|
|
1025
|
+
return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
|
|
1026
|
+
}
|
|
1027
|
+
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
1028
|
+
const environment = params?.environment;
|
|
1029
|
+
if (!isKichiEnvironment(environment)) {
|
|
1030
|
+
return jsonResult({ success: false, error: `environment must be one of: ${VALID_ENVIRONMENTS.join(", ")}` });
|
|
1031
|
+
}
|
|
1032
|
+
const resolved = resolveEnvironmentHost(environment);
|
|
1033
|
+
if (resolved.error) {
|
|
1034
|
+
return jsonResult({ success: false, error: resolved.error });
|
|
1035
|
+
}
|
|
1036
|
+
const status = await service.switchHost(resolved.host, environment);
|
|
1037
|
+
return jsonResult({
|
|
1038
|
+
success: true,
|
|
1039
|
+
environment,
|
|
1040
|
+
host: resolved.host,
|
|
1041
|
+
status,
|
|
1042
|
+
});
|
|
1043
|
+
},
|
|
1044
|
+
}));
|
|
1045
|
+
api.registerTool((ctx) => ({
|
|
1045
1046
|
name: "kichi_rejoin",
|
|
1046
1047
|
label: "kichi_rejoin",
|
|
1047
1048
|
description: "Request an immediate rejoin attempt with saved avatarId/authKey. Rejoin is also sent automatically after reconnect.",
|
|
1048
1049
|
parameters: { type: "object", properties: {} },
|
|
1049
|
-
execute: async () => {
|
|
1050
|
+
execute: async (_toolCallId, _params) => {
|
|
1051
|
+
const locator = resolveToolLocator(ctx);
|
|
1052
|
+
const agentId = runtimeManager.resolveRuntimeAgentId(locator);
|
|
1053
|
+
if (!agentId) {
|
|
1054
|
+
return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
|
|
1055
|
+
}
|
|
1056
|
+
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
1050
1057
|
const result = service.requestRejoin();
|
|
1051
1058
|
return jsonResult({
|
|
1052
1059
|
success: result.accepted,
|
|
@@ -1054,13 +1061,19 @@ const plugin = {
|
|
|
1054
1061
|
status: service.getConnectionStatus(),
|
|
1055
1062
|
});
|
|
1056
1063
|
},
|
|
1057
|
-
}))
|
|
1058
|
-
api.registerTool(
|
|
1064
|
+
}));
|
|
1065
|
+
api.registerTool((ctx) => ({
|
|
1059
1066
|
name: "kichi_leave",
|
|
1060
1067
|
label: "kichi_leave",
|
|
1061
1068
|
description: "Leave Kichi world",
|
|
1062
1069
|
parameters: { type: "object", properties: {} },
|
|
1063
|
-
execute: async () => {
|
|
1070
|
+
execute: async (_toolCallId, _params) => {
|
|
1071
|
+
const locator = resolveToolLocator(ctx);
|
|
1072
|
+
const agentId = runtimeManager.resolveRuntimeAgentId(locator);
|
|
1073
|
+
if (!agentId) {
|
|
1074
|
+
return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
|
|
1075
|
+
}
|
|
1076
|
+
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
1064
1077
|
const result = await service.leave();
|
|
1065
1078
|
if (result.success) {
|
|
1066
1079
|
return jsonResult({ success: true });
|
|
@@ -1073,105 +1086,126 @@ const plugin = {
|
|
|
1073
1086
|
...(failure.errorMessage ? { errorMessage: failure.errorMessage } : {}),
|
|
1074
1087
|
});
|
|
1075
1088
|
},
|
|
1076
|
-
}))
|
|
1077
|
-
api.registerTool(
|
|
1089
|
+
}));
|
|
1090
|
+
api.registerTool((ctx) => ({
|
|
1078
1091
|
name: "kichi_connection_status",
|
|
1079
1092
|
label: "kichi_connection_status",
|
|
1080
1093
|
description: "Check WebSocket connection status and identity readiness only. Does NOT return room info, avatar state, or personnel — use kichi_query_status for that.",
|
|
1081
1094
|
parameters: { type: "object", properties: {} },
|
|
1082
|
-
execute: async () => {
|
|
1095
|
+
execute: async (_toolCallId, _params) => {
|
|
1096
|
+
const locator = resolveToolLocator(ctx);
|
|
1097
|
+
const agentId = runtimeManager.resolveRuntimeAgentId(locator);
|
|
1098
|
+
if (!agentId) {
|
|
1099
|
+
return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
|
|
1100
|
+
}
|
|
1101
|
+
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
1083
1102
|
return jsonResult({
|
|
1084
1103
|
success: true,
|
|
1085
1104
|
status: service.getConnectionStatus(),
|
|
1086
1105
|
});
|
|
1087
1106
|
},
|
|
1088
|
-
}))
|
|
1089
|
-
api.registerTool(
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1107
|
+
}));
|
|
1108
|
+
api.registerTool((ctx) => {
|
|
1109
|
+
const locator = resolveToolLocator(ctx);
|
|
1110
|
+
const existingService = runtimeManager.getRuntime(locator);
|
|
1111
|
+
return ({
|
|
1112
|
+
name: "kichi_action",
|
|
1113
|
+
label: "kichi_action",
|
|
1114
|
+
description: buildKichiActionDescription(existingService ?? undefined),
|
|
1115
|
+
parameters: {
|
|
1116
|
+
type: "object",
|
|
1117
|
+
properties: {
|
|
1118
|
+
poseType: { type: "string", description: "Pose type: stand, sit, lay, or floor" },
|
|
1119
|
+
action: {
|
|
1120
|
+
type: "string",
|
|
1121
|
+
description: "Action name for the selected pose (for example Sit Nicely, Typing with Keyboard, Reading, High Five, or Meditate)",
|
|
1122
|
+
},
|
|
1123
|
+
bubble: { type: "string", description: "Optional bubble text to display (max 5 words)" },
|
|
1124
|
+
log: {
|
|
1125
|
+
type: "string",
|
|
1126
|
+
description: "Short natural first-person sentence under 15 words. Match the language of the bubble and mention the current action and immediate focus.",
|
|
1127
|
+
},
|
|
1128
|
+
verify: {
|
|
1129
|
+
type: "boolean",
|
|
1130
|
+
description: "Set true ONLY when the user explicitly requests a pose or action. Omit during routine sync steps.",
|
|
1131
|
+
},
|
|
1132
|
+
propId: {
|
|
1133
|
+
type: "string",
|
|
1134
|
+
description: "Optional poseable prop ID from RoomContext.PoseableProps (obtained via kichi_query_status or cached). When specified, the avatar is seated at this prop; when omitted, the server picks the nearest available prop.",
|
|
1135
|
+
},
|
|
1109
1136
|
},
|
|
1137
|
+
required: ["poseType", "action"],
|
|
1110
1138
|
},
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
success: false,
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1139
|
+
execute: async (_toolCallId, params) => {
|
|
1140
|
+
const locator = resolveToolLocator(ctx);
|
|
1141
|
+
const agentId = runtimeManager.resolveRuntimeAgentId(locator);
|
|
1142
|
+
if (!agentId) {
|
|
1143
|
+
return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
|
|
1144
|
+
}
|
|
1145
|
+
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
1146
|
+
const { poseType, action, bubble, log, verify, propId } = (params || {});
|
|
1147
|
+
if (!poseType || !action) {
|
|
1148
|
+
return jsonResult({ success: false, error: "poseType and action parameters are required" });
|
|
1149
|
+
}
|
|
1150
|
+
if (!["stand", "sit", "lay", "floor"].includes(poseType)) {
|
|
1151
|
+
return jsonResult({
|
|
1152
|
+
success: false,
|
|
1153
|
+
error: `Invalid poseType: ${poseType}. Must be stand, sit, lay, or floor`,
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
if (!service.hasValidIdentity() || !service.isConnected()) {
|
|
1157
|
+
return jsonResult({ success: false, error: "Not connected to Kichi world" });
|
|
1158
|
+
}
|
|
1159
|
+
const normalizedPoseType = poseType;
|
|
1160
|
+
const poseActions = loadStaticConfig().actions[normalizedPoseType];
|
|
1161
|
+
const matched = poseActions.find((entry) => entry.name.toLowerCase() === action.toLowerCase());
|
|
1162
|
+
if (!matched) {
|
|
1163
|
+
return jsonResult({
|
|
1164
|
+
success: false,
|
|
1165
|
+
error: `Unknown action "${action}" for poseType "${poseType}"`,
|
|
1166
|
+
available: poseActions.map((entry) => entry.name),
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
const bubbleText = typeof bubble === "string" && bubble.trim() ? bubble.trim() : matched.name;
|
|
1170
|
+
const logText = typeof log === "string" ? log.trim() : "";
|
|
1171
|
+
const playback = getActionPlayback(matched);
|
|
1172
|
+
if (verify) {
|
|
1173
|
+
try {
|
|
1174
|
+
const ack = await service.sendStatusVerified(normalizedPoseType, matched.name, bubbleText, logText, playback, propId);
|
|
1175
|
+
if (ack.warning) {
|
|
1176
|
+
return jsonResult({
|
|
1177
|
+
success: true,
|
|
1178
|
+
requested: { poseType: normalizedPoseType, action: matched.name },
|
|
1179
|
+
actual: { poseType: ack.poseType, action: ack.action },
|
|
1180
|
+
warning: ack.warning,
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
catch {
|
|
1185
|
+
// Server not updated or timeout — fall through to normal success
|
|
1150
1186
|
}
|
|
1151
1187
|
}
|
|
1152
|
-
|
|
1153
|
-
|
|
1188
|
+
else {
|
|
1189
|
+
sendStatusUpdate(service, {
|
|
1190
|
+
poseType: normalizedPoseType,
|
|
1191
|
+
action: matched.name,
|
|
1192
|
+
bubble: bubbleText,
|
|
1193
|
+
log: logText,
|
|
1194
|
+
propId,
|
|
1195
|
+
});
|
|
1154
1196
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
sendStatusUpdate(service, {
|
|
1197
|
+
return jsonResult({
|
|
1198
|
+
success: true,
|
|
1158
1199
|
poseType: normalizedPoseType,
|
|
1159
1200
|
action: matched.name,
|
|
1160
1201
|
bubble: bubbleText,
|
|
1161
1202
|
log: logText,
|
|
1203
|
+
playback,
|
|
1162
1204
|
});
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
action: matched.name,
|
|
1168
|
-
bubble: bubbleText,
|
|
1169
|
-
log: logText,
|
|
1170
|
-
playback,
|
|
1171
|
-
});
|
|
1172
|
-
},
|
|
1173
|
-
})));
|
|
1174
|
-
api.registerTool(createAgentScopedTool(runtimeManager, (service) => ({
|
|
1205
|
+
},
|
|
1206
|
+
});
|
|
1207
|
+
});
|
|
1208
|
+
api.registerTool((ctx) => ({
|
|
1175
1209
|
name: "kichi_idle_plan",
|
|
1176
1210
|
label: "kichi_idle_plan",
|
|
1177
1211
|
description: buildKichiIdlePlanDescription(),
|
|
@@ -1239,6 +1273,10 @@ const plugin = {
|
|
|
1239
1273
|
type: "string",
|
|
1240
1274
|
description: "Optional log content for this action. Use the same language as the current conversation.",
|
|
1241
1275
|
},
|
|
1276
|
+
propId: {
|
|
1277
|
+
type: "string",
|
|
1278
|
+
description: "Optional poseable prop ID from RoomContext.PoseableProps. When specified, the avatar is seated at this prop.",
|
|
1279
|
+
},
|
|
1242
1280
|
},
|
|
1243
1281
|
required: ["poseType", "action", "durationSeconds", "bubble"],
|
|
1244
1282
|
},
|
|
@@ -1251,6 +1289,12 @@ const plugin = {
|
|
|
1251
1289
|
required: ["heartbeatIntervalSeconds", "goal", "stages"],
|
|
1252
1290
|
},
|
|
1253
1291
|
execute: async (_toolCallId, params) => {
|
|
1292
|
+
const locator = resolveToolLocator(ctx);
|
|
1293
|
+
const agentId = runtimeManager.resolveRuntimeAgentId(locator);
|
|
1294
|
+
if (!agentId) {
|
|
1295
|
+
return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
|
|
1296
|
+
}
|
|
1297
|
+
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
1254
1298
|
const { idlePlan, error } = normalizeIdlePlan(params);
|
|
1255
1299
|
if (!idlePlan) {
|
|
1256
1300
|
return jsonResult({ success: false, error: error ?? "Invalid idle plan payload" });
|
|
@@ -1276,8 +1320,8 @@ const plugin = {
|
|
|
1276
1320
|
stages: idlePlan.stages,
|
|
1277
1321
|
});
|
|
1278
1322
|
},
|
|
1279
|
-
}))
|
|
1280
|
-
api.registerTool(
|
|
1323
|
+
}));
|
|
1324
|
+
api.registerTool((ctx) => ({
|
|
1281
1325
|
name: "kichi_clock",
|
|
1282
1326
|
label: "kichi_clock",
|
|
1283
1327
|
description: "Send clock commands to Kichi world. Supported actions are set and stop.",
|
|
@@ -1346,6 +1390,12 @@ const plugin = {
|
|
|
1346
1390
|
required: ["action"],
|
|
1347
1391
|
},
|
|
1348
1392
|
execute: async (_toolCallId, params) => {
|
|
1393
|
+
const locator = resolveToolLocator(ctx);
|
|
1394
|
+
const agentId = runtimeManager.resolveRuntimeAgentId(locator);
|
|
1395
|
+
if (!agentId) {
|
|
1396
|
+
return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
|
|
1397
|
+
}
|
|
1398
|
+
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
1349
1399
|
const { action, requestId, clock } = (params || {});
|
|
1350
1400
|
if (!isClockAction(action)) {
|
|
1351
1401
|
return jsonResult({
|
|
@@ -1379,11 +1429,11 @@ const plugin = {
|
|
|
1379
1429
|
...(normalizedClock ? { clock: normalizedClock } : {}),
|
|
1380
1430
|
});
|
|
1381
1431
|
},
|
|
1382
|
-
}))
|
|
1383
|
-
api.registerTool(
|
|
1432
|
+
}));
|
|
1433
|
+
api.registerTool((ctx) => ({
|
|
1384
1434
|
name: "kichi_query_status",
|
|
1385
1435
|
label: "kichi_query_status",
|
|
1386
|
-
description: "Query Kichi room and avatar status — includes room personnel, notes, ownerState, idlePlan, weather/time, timer snapshot, daily note quota,
|
|
1436
|
+
description: "Query Kichi room and avatar status — includes room personnel, notes, ownerState, idlePlan, weather/time, timer snapshot, daily note quota, `hasCreatedMusicAlbumToday`, and RoomContext.PoseableProps (poseable props with PropId, DisplayName, SupportedPoseTypes, OccupancyState). The PoseableProps list is cached internally so that kichi_action can reference a propId during regular work sync without re-querying. Use this when the user asks to check kichi status, room status, or who is in the room. Also use this before creating a new note or daily recommended music album. For heartbeat planning, use the returned idlePlan as reference when shaping the next idle plan.",
|
|
1387
1437
|
parameters: {
|
|
1388
1438
|
type: "object",
|
|
1389
1439
|
properties: {
|
|
@@ -1394,6 +1444,12 @@ const plugin = {
|
|
|
1394
1444
|
},
|
|
1395
1445
|
},
|
|
1396
1446
|
execute: async (_toolCallId, params) => {
|
|
1447
|
+
const locator = resolveToolLocator(ctx);
|
|
1448
|
+
const agentId = runtimeManager.resolveRuntimeAgentId(locator);
|
|
1449
|
+
if (!agentId) {
|
|
1450
|
+
return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
|
|
1451
|
+
}
|
|
1452
|
+
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
1397
1453
|
const requestId = params?.requestId;
|
|
1398
1454
|
if (requestId !== undefined && typeof requestId !== "string") {
|
|
1399
1455
|
return jsonResult({ success: false, error: "requestId must be a string when provided" });
|
|
@@ -1412,8 +1468,8 @@ const plugin = {
|
|
|
1412
1468
|
});
|
|
1413
1469
|
}
|
|
1414
1470
|
},
|
|
1415
|
-
}))
|
|
1416
|
-
api.registerTool(
|
|
1471
|
+
}));
|
|
1472
|
+
api.registerTool((ctx) => ({
|
|
1417
1473
|
name: "kichi_music_album_create",
|
|
1418
1474
|
label: "kichi_music_album_create",
|
|
1419
1475
|
description: buildMusicAlbumToolDescription(),
|
|
@@ -1440,6 +1496,12 @@ const plugin = {
|
|
|
1440
1496
|
required: ["albumTitle", "musicTitles"],
|
|
1441
1497
|
},
|
|
1442
1498
|
execute: async (_toolCallId, params) => {
|
|
1499
|
+
const locator = resolveToolLocator(ctx);
|
|
1500
|
+
const agentId = runtimeManager.resolveRuntimeAgentId(locator);
|
|
1501
|
+
if (!agentId) {
|
|
1502
|
+
return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
|
|
1503
|
+
}
|
|
1504
|
+
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
1443
1505
|
const { requestId, albumTitle, musicTitles, } = (params || {});
|
|
1444
1506
|
if (requestId !== undefined && typeof requestId !== "string") {
|
|
1445
1507
|
return jsonResult({ success: false, error: "requestId must be a string when provided" });
|
|
@@ -1486,8 +1548,8 @@ const plugin = {
|
|
|
1486
1548
|
});
|
|
1487
1549
|
}
|
|
1488
1550
|
},
|
|
1489
|
-
}))
|
|
1490
|
-
api.registerTool(
|
|
1551
|
+
}));
|
|
1552
|
+
api.registerTool((ctx) => ({
|
|
1491
1553
|
name: "kichi_noteboard_create",
|
|
1492
1554
|
label: "kichi_noteboard_create",
|
|
1493
1555
|
description: "Create a new note on a specific Kichi note board. Prefer querying first so you can avoid duplicate posts and respect rate limits.",
|
|
@@ -1506,6 +1568,12 @@ const plugin = {
|
|
|
1506
1568
|
required: ["propId", "data"],
|
|
1507
1569
|
},
|
|
1508
1570
|
execute: async (_toolCallId, params) => {
|
|
1571
|
+
const locator = resolveToolLocator(ctx);
|
|
1572
|
+
const agentId = runtimeManager.resolveRuntimeAgentId(locator);
|
|
1573
|
+
if (!agentId) {
|
|
1574
|
+
return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
|
|
1575
|
+
}
|
|
1576
|
+
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
1509
1577
|
const { propId, data } = (params || {});
|
|
1510
1578
|
if (typeof propId !== "string" || !propId.trim()) {
|
|
1511
1579
|
return jsonResult({ success: false, error: "propId is required" });
|
|
@@ -1533,8 +1601,8 @@ const plugin = {
|
|
|
1533
1601
|
});
|
|
1534
1602
|
}
|
|
1535
1603
|
},
|
|
1536
|
-
}))
|
|
1537
|
-
api.registerTool(
|
|
1604
|
+
}));
|
|
1605
|
+
api.registerTool((ctx) => ({
|
|
1538
1606
|
name: "kichi_bot_message",
|
|
1539
1607
|
label: "kichi_bot_message",
|
|
1540
1608
|
description: "Send a message to another bot in the same Kichi world. The bubble is the visible message content. Do not repeat what has already been said in the conversation history. When targeting a specific bot by name, call kichi_query_status first to resolve their avatarId. Only use \"*\" when broadcasting to all bots without a specific target.",
|
|
@@ -1570,6 +1638,12 @@ const plugin = {
|
|
|
1570
1638
|
required: ["toAvatarId", "depth", "bubble"],
|
|
1571
1639
|
},
|
|
1572
1640
|
execute: async (_toolCallId, params) => {
|
|
1641
|
+
const locator = resolveToolLocator(ctx);
|
|
1642
|
+
const agentId = runtimeManager.resolveRuntimeAgentId(locator);
|
|
1643
|
+
if (!agentId) {
|
|
1644
|
+
return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
|
|
1645
|
+
}
|
|
1646
|
+
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
1573
1647
|
const { toAvatarId, depth, bubble, poseType, action, log } = (params || {});
|
|
1574
1648
|
if (typeof toAvatarId !== "string" || !toAvatarId.trim()) {
|
|
1575
1649
|
return jsonResult({ success: false, error: "toAvatarId is required" });
|
|
@@ -1601,7 +1675,7 @@ const plugin = {
|
|
|
1601
1675
|
return jsonResult({ success: false, error: `Failed to send bot message: ${error}` });
|
|
1602
1676
|
}
|
|
1603
1677
|
},
|
|
1604
|
-
}))
|
|
1678
|
+
}));
|
|
1605
1679
|
},
|
|
1606
1680
|
};
|
|
1607
1681
|
export default plugin;
|
package/dist/src/service.js
CHANGED
|
@@ -17,6 +17,7 @@ export class KichiForwarderService {
|
|
|
17
17
|
joinResolve = null;
|
|
18
18
|
pendingRequests = new Map();
|
|
19
19
|
onBotMessageReceived = null;
|
|
20
|
+
cachedRoomContext = null;
|
|
20
21
|
constructor(logger, options) {
|
|
21
22
|
this.logger = logger;
|
|
22
23
|
this.options = options;
|
|
@@ -91,7 +92,7 @@ export class KichiForwarderService {
|
|
|
91
92
|
}, 10000);
|
|
92
93
|
});
|
|
93
94
|
}
|
|
94
|
-
sendStatus(poseType, action, bubble, log, playback) {
|
|
95
|
+
sendStatus(poseType, action, bubble, log, playback, propId) {
|
|
95
96
|
if (!this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN)
|
|
96
97
|
return;
|
|
97
98
|
const payload = {
|
|
@@ -103,10 +104,11 @@ export class KichiForwarderService {
|
|
|
103
104
|
bubble,
|
|
104
105
|
log,
|
|
105
106
|
playback,
|
|
107
|
+
...(propId ? { propId } : {}),
|
|
106
108
|
};
|
|
107
109
|
this.ws.send(JSON.stringify(payload));
|
|
108
110
|
}
|
|
109
|
-
async sendStatusVerified(poseType, action, bubble, log, playback) {
|
|
111
|
+
async sendStatusVerified(poseType, action, bubble, log, playback, propId) {
|
|
110
112
|
if (!this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN) {
|
|
111
113
|
throw new Error("Kichi websocket is not connected");
|
|
112
114
|
}
|
|
@@ -120,6 +122,7 @@ export class KichiForwarderService {
|
|
|
120
122
|
bubble,
|
|
121
123
|
log,
|
|
122
124
|
playback,
|
|
125
|
+
...(propId ? { propId } : {}),
|
|
123
126
|
};
|
|
124
127
|
return this.sendRequest(payload, "status_ack", 5000);
|
|
125
128
|
}
|
|
@@ -181,7 +184,11 @@ export class KichiForwarderService {
|
|
|
181
184
|
avatarId: identity.avatarId,
|
|
182
185
|
authKey: identity.authKey,
|
|
183
186
|
};
|
|
184
|
-
|
|
187
|
+
const result = await this.sendRequest(payload, "query_status_result");
|
|
188
|
+
if (result.RoomContext && typeof result.RoomContext === "object") {
|
|
189
|
+
this.cachedRoomContext = result.RoomContext;
|
|
190
|
+
}
|
|
191
|
+
return result;
|
|
185
192
|
}
|
|
186
193
|
createNotesBoardNote(propId, data) {
|
|
187
194
|
const identity = this.requireIdentity();
|
|
@@ -250,6 +257,7 @@ export class KichiForwarderService {
|
|
|
250
257
|
return this.sendRequest(payload, "bot_message_ack", 5000);
|
|
251
258
|
}
|
|
252
259
|
isConnected() { return this.ws?.readyState === WebSocket.OPEN && !!this.identity?.authKey; }
|
|
260
|
+
getCachedRoomContext() { return this.cachedRoomContext; }
|
|
253
261
|
hasValidIdentity() { return !!this.identity?.avatarId && !!this.identity?.authKey; }
|
|
254
262
|
isLlmRuntimeEnabled() {
|
|
255
263
|
return this.readStateFile()?.llmRuntimeEnabled ?? DEFAULT_LLM_RUNTIME_ENABLED;
|
package/index.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import type {
|
|
4
|
-
AnyAgentTool,
|
|
5
4
|
OpenClawPluginApi,
|
|
6
5
|
} from "openclaw/plugin-sdk";
|
|
7
6
|
import type { OpenClawPluginToolContext } from "openclaw/plugin-sdk/core";
|
|
@@ -264,6 +263,7 @@ function sendStatusUpdate(service: KichiForwarderService, status: ActionResult):
|
|
|
264
263
|
status.bubble || status.action,
|
|
265
264
|
typeof status.log === "string" ? status.log.trim() : "",
|
|
266
265
|
getActionPlayback(actionDefinition),
|
|
266
|
+
status.propId,
|
|
267
267
|
);
|
|
268
268
|
}
|
|
269
269
|
|
|
@@ -712,6 +712,7 @@ function normalizeIdlePlan(value: unknown): { idlePlan?: IdlePlan; error?: strin
|
|
|
712
712
|
const actionDurationSeconds = rawAction.durationSeconds;
|
|
713
713
|
const bubble = rawAction.bubble;
|
|
714
714
|
const log = rawAction.log;
|
|
715
|
+
const propId = rawAction.propId;
|
|
715
716
|
|
|
716
717
|
if (!["stand", "sit", "lay", "floor"].includes(String(poseType))) {
|
|
717
718
|
return {
|
|
@@ -759,6 +760,7 @@ function normalizeIdlePlan(value: unknown): { idlePlan?: IdlePlan; error?: strin
|
|
|
759
760
|
durationSeconds: actionDurationSeconds,
|
|
760
761
|
bubble: bubble.trim(),
|
|
761
762
|
...(typeof log === "string" && log.trim() ? { log: log.trim() } : {}),
|
|
763
|
+
...(typeof propId === "string" && propId.trim() ? { propId: propId.trim() } : {}),
|
|
762
764
|
});
|
|
763
765
|
}
|
|
764
766
|
|
|
@@ -992,9 +994,9 @@ function formatActionList(actions: ActionDefinition[], playback: ActionPlayback[
|
|
|
992
994
|
.join(", ");
|
|
993
995
|
}
|
|
994
996
|
|
|
995
|
-
function buildKichiActionDescription(): string {
|
|
997
|
+
function buildKichiActionDescription(service?: KichiForwarderService): string {
|
|
996
998
|
const actions = loadStaticConfig().actions;
|
|
997
|
-
|
|
999
|
+
const lines = [
|
|
998
1000
|
"Directly control the avatar inside Kichi World.",
|
|
999
1001
|
"Use this whenever the user explicitly asks you to make the Kichi avatar sit down, stand up, lie down, floor-sit, type, read, meditate, celebrate, or perform another listed animation.",
|
|
1000
1002
|
"For most work, prefer a sit pose and switch actions as the task moves between stages.",
|
|
@@ -1003,7 +1005,20 @@ function buildKichiActionDescription(): string {
|
|
|
1003
1005
|
`sit actions: ${actions.sit.map((entry) => entry.name).join(", ")}`,
|
|
1004
1006
|
`lay actions: ${actions.lay.map((entry) => entry.name).join(", ")}`,
|
|
1005
1007
|
`floor actions: ${actions.floor.map((entry) => entry.name).join(", ")}`,
|
|
1006
|
-
]
|
|
1008
|
+
];
|
|
1009
|
+
|
|
1010
|
+
const roomContext = service?.getCachedRoomContext();
|
|
1011
|
+
const poseableProps = roomContext?.PoseableProps;
|
|
1012
|
+
if (Array.isArray(poseableProps) && poseableProps.length > 0) {
|
|
1013
|
+
lines.push(
|
|
1014
|
+
"",
|
|
1015
|
+
"Cached RoomContext.PoseableProps (from last kichi_query_status):",
|
|
1016
|
+
JSON.stringify(poseableProps),
|
|
1017
|
+
"When using a sit or lay pose, pick the propId whose DisplayName best matches the current task context and whose OccupancyState is not fully_occupied. If no prop fits, omit propId.",
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
return lines.join("\n");
|
|
1007
1022
|
}
|
|
1008
1023
|
|
|
1009
1024
|
function buildKichiIdlePlanDescription(): string {
|
|
@@ -1048,20 +1063,6 @@ function buildKichiPrompt(): string {
|
|
|
1048
1063
|
].join("\n");
|
|
1049
1064
|
}
|
|
1050
1065
|
|
|
1051
|
-
function createAgentScopedTool(
|
|
1052
|
-
runtimeManager: KichiRuntimeManager,
|
|
1053
|
-
factory: (service: KichiForwarderService, ctx: OpenClawPluginToolContext) => AnyAgentTool,
|
|
1054
|
-
) {
|
|
1055
|
-
return (ctx: OpenClawPluginToolContext) => {
|
|
1056
|
-
const locator = resolveToolLocator(ctx);
|
|
1057
|
-
const agentId = runtimeManager.resolveRuntimeAgentId(locator);
|
|
1058
|
-
if (!agentId) {
|
|
1059
|
-
throw new Error("Failed to resolve agent-scoped Kichi runtime");
|
|
1060
|
-
}
|
|
1061
|
-
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
1062
|
-
return factory(service, ctx);
|
|
1063
|
-
};
|
|
1064
|
-
}
|
|
1065
1066
|
|
|
1066
1067
|
const GLOBAL_RUNTIME_MANAGER_KEY = "__kichi_forwarder_runtime_manager__";
|
|
1067
1068
|
|
|
@@ -1091,6 +1092,13 @@ const plugin = {
|
|
|
1091
1092
|
|
|
1092
1093
|
register(api: OpenClawPluginApi) {
|
|
1093
1094
|
const runtimeManager = getRuntimeManager(api.logger);
|
|
1095
|
+
|
|
1096
|
+
runtimeManager.setEnvironmentHostResolver((environment) => {
|
|
1097
|
+
const config = loadEnvironmentsConfig();
|
|
1098
|
+
const host = config[environment];
|
|
1099
|
+
return typeof host === "string" && host.trim() ? host : null;
|
|
1100
|
+
});
|
|
1101
|
+
|
|
1094
1102
|
registerPluginHooks(api, runtimeManager);
|
|
1095
1103
|
const musicTitleEnum = getMusicTitleEnum();
|
|
1096
1104
|
|
|
@@ -1139,11 +1147,6 @@ const plugin = {
|
|
|
1139
1147
|
id: "kichi-forwarder",
|
|
1140
1148
|
start: (ctx) => {
|
|
1141
1149
|
parse(ctx.config.plugins?.entries?.["kichi-forwarder"]?.config);
|
|
1142
|
-
runtimeManager.setEnvironmentHostResolver((environment) => {
|
|
1143
|
-
const config = loadEnvironmentsConfig();
|
|
1144
|
-
const host = config[environment];
|
|
1145
|
-
return typeof host === "string" && host.trim() ? host : null;
|
|
1146
|
-
});
|
|
1147
1150
|
runtimeManager.initializeStartupRuntimes();
|
|
1148
1151
|
},
|
|
1149
1152
|
stop: () => {
|
|
@@ -1155,7 +1158,7 @@ const plugin = {
|
|
|
1155
1158
|
},
|
|
1156
1159
|
});
|
|
1157
1160
|
|
|
1158
|
-
api.registerTool(
|
|
1161
|
+
api.registerTool((ctx) => ({
|
|
1159
1162
|
name: "kichi_join",
|
|
1160
1163
|
label: "kichi_join",
|
|
1161
1164
|
description: "Join Kichi world with avatarId, the current bot name, a short bio, and personality tags",
|
|
@@ -1180,6 +1183,12 @@ const plugin = {
|
|
|
1180
1183
|
required: ["botName", "bio"],
|
|
1181
1184
|
},
|
|
1182
1185
|
execute: async (_toolCallId, params) => {
|
|
1186
|
+
const locator = resolveToolLocator(ctx);
|
|
1187
|
+
const agentId = runtimeManager.resolveRuntimeAgentId(locator);
|
|
1188
|
+
if (!agentId) {
|
|
1189
|
+
return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
|
|
1190
|
+
}
|
|
1191
|
+
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
1183
1192
|
let avatarId = (params as { avatarId?: string } | null)?.avatarId;
|
|
1184
1193
|
const botName = (params as { botName?: string } | null)?.botName?.trim();
|
|
1185
1194
|
const bio = (params as { bio?: string } | null)?.bio?.trim();
|
|
@@ -1213,16 +1222,9 @@ const plugin = {
|
|
|
1213
1222
|
...(failure.errorMessage ? { errorMessage: failure.errorMessage } : {}),
|
|
1214
1223
|
});
|
|
1215
1224
|
},
|
|
1216
|
-
}))
|
|
1225
|
+
}));
|
|
1217
1226
|
|
|
1218
|
-
api.registerTool((ctx
|
|
1219
|
-
const locator = resolveToolLocator(ctx);
|
|
1220
|
-
const agentId = runtimeManager.resolveRuntimeAgentId(locator);
|
|
1221
|
-
if (!agentId) {
|
|
1222
|
-
throw new Error("Failed to resolve agent-scoped Kichi runtime");
|
|
1223
|
-
}
|
|
1224
|
-
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
1225
|
-
return ({
|
|
1227
|
+
api.registerTool((ctx) => ({
|
|
1226
1228
|
name: "kichi_switch_host",
|
|
1227
1229
|
label: "kichi_switch_host",
|
|
1228
1230
|
description:
|
|
@@ -1239,6 +1241,12 @@ const plugin = {
|
|
|
1239
1241
|
required: ["environment"],
|
|
1240
1242
|
},
|
|
1241
1243
|
execute: async (_toolCallId, params) => {
|
|
1244
|
+
const locator = resolveToolLocator(ctx);
|
|
1245
|
+
const agentId = runtimeManager.resolveRuntimeAgentId(locator);
|
|
1246
|
+
if (!agentId) {
|
|
1247
|
+
return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
|
|
1248
|
+
}
|
|
1249
|
+
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
1242
1250
|
const environment = (params as { environment?: unknown } | null)?.environment;
|
|
1243
1251
|
if (!isKichiEnvironment(environment)) {
|
|
1244
1252
|
return jsonResult({ success: false, error: `environment must be one of: ${VALID_ENVIRONMENTS.join(", ")}` });
|
|
@@ -1257,16 +1265,21 @@ const plugin = {
|
|
|
1257
1265
|
status,
|
|
1258
1266
|
});
|
|
1259
1267
|
},
|
|
1260
|
-
|
|
1261
|
-
});
|
|
1268
|
+
}));
|
|
1262
1269
|
|
|
1263
|
-
api.registerTool(
|
|
1270
|
+
api.registerTool((ctx) => ({
|
|
1264
1271
|
name: "kichi_rejoin",
|
|
1265
1272
|
label: "kichi_rejoin",
|
|
1266
1273
|
description:
|
|
1267
1274
|
"Request an immediate rejoin attempt with saved avatarId/authKey. Rejoin is also sent automatically after reconnect.",
|
|
1268
1275
|
parameters: { type: "object", properties: {} },
|
|
1269
|
-
execute: async () => {
|
|
1276
|
+
execute: async (_toolCallId, _params) => {
|
|
1277
|
+
const locator = resolveToolLocator(ctx);
|
|
1278
|
+
const agentId = runtimeManager.resolveRuntimeAgentId(locator);
|
|
1279
|
+
if (!agentId) {
|
|
1280
|
+
return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
|
|
1281
|
+
}
|
|
1282
|
+
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
1270
1283
|
const result = service.requestRejoin();
|
|
1271
1284
|
return jsonResult({
|
|
1272
1285
|
success: result.accepted,
|
|
@@ -1274,14 +1287,20 @@ const plugin = {
|
|
|
1274
1287
|
status: service.getConnectionStatus(),
|
|
1275
1288
|
});
|
|
1276
1289
|
},
|
|
1277
|
-
}))
|
|
1290
|
+
}));
|
|
1278
1291
|
|
|
1279
|
-
api.registerTool(
|
|
1292
|
+
api.registerTool((ctx) => ({
|
|
1280
1293
|
name: "kichi_leave",
|
|
1281
1294
|
label: "kichi_leave",
|
|
1282
1295
|
description: "Leave Kichi world",
|
|
1283
1296
|
parameters: { type: "object", properties: {} },
|
|
1284
|
-
execute: async () => {
|
|
1297
|
+
execute: async (_toolCallId, _params) => {
|
|
1298
|
+
const locator = resolveToolLocator(ctx);
|
|
1299
|
+
const agentId = runtimeManager.resolveRuntimeAgentId(locator);
|
|
1300
|
+
if (!agentId) {
|
|
1301
|
+
return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
|
|
1302
|
+
}
|
|
1303
|
+
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
1285
1304
|
const result = await service.leave();
|
|
1286
1305
|
if (result.success) {
|
|
1287
1306
|
return jsonResult({ success: true });
|
|
@@ -1294,25 +1313,34 @@ const plugin = {
|
|
|
1294
1313
|
...(failure.errorMessage ? { errorMessage: failure.errorMessage } : {}),
|
|
1295
1314
|
});
|
|
1296
1315
|
},
|
|
1297
|
-
}))
|
|
1316
|
+
}));
|
|
1298
1317
|
|
|
1299
|
-
api.registerTool(
|
|
1318
|
+
api.registerTool((ctx) => ({
|
|
1300
1319
|
name: "kichi_connection_status",
|
|
1301
1320
|
label: "kichi_connection_status",
|
|
1302
1321
|
description: "Check WebSocket connection status and identity readiness only. Does NOT return room info, avatar state, or personnel — use kichi_query_status for that.",
|
|
1303
1322
|
parameters: { type: "object", properties: {} },
|
|
1304
|
-
execute: async () => {
|
|
1323
|
+
execute: async (_toolCallId, _params) => {
|
|
1324
|
+
const locator = resolveToolLocator(ctx);
|
|
1325
|
+
const agentId = runtimeManager.resolveRuntimeAgentId(locator);
|
|
1326
|
+
if (!agentId) {
|
|
1327
|
+
return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
|
|
1328
|
+
}
|
|
1329
|
+
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
1305
1330
|
return jsonResult({
|
|
1306
1331
|
success: true,
|
|
1307
1332
|
status: service.getConnectionStatus(),
|
|
1308
1333
|
});
|
|
1309
1334
|
},
|
|
1310
|
-
}))
|
|
1335
|
+
}));
|
|
1311
1336
|
|
|
1312
|
-
api.registerTool(
|
|
1337
|
+
api.registerTool((ctx) => {
|
|
1338
|
+
const locator = resolveToolLocator(ctx);
|
|
1339
|
+
const existingService = runtimeManager.getRuntime(locator);
|
|
1340
|
+
return ({
|
|
1313
1341
|
name: "kichi_action",
|
|
1314
1342
|
label: "kichi_action",
|
|
1315
|
-
description: buildKichiActionDescription(),
|
|
1343
|
+
description: buildKichiActionDescription(existingService ?? undefined),
|
|
1316
1344
|
parameters: {
|
|
1317
1345
|
type: "object",
|
|
1318
1346
|
properties: {
|
|
@@ -1332,16 +1360,28 @@ const plugin = {
|
|
|
1332
1360
|
description:
|
|
1333
1361
|
"Set true ONLY when the user explicitly requests a pose or action. Omit during routine sync steps.",
|
|
1334
1362
|
},
|
|
1363
|
+
propId: {
|
|
1364
|
+
type: "string",
|
|
1365
|
+
description:
|
|
1366
|
+
"Optional poseable prop ID from RoomContext.PoseableProps (obtained via kichi_query_status or cached). When specified, the avatar is seated at this prop; when omitted, the server picks the nearest available prop.",
|
|
1367
|
+
},
|
|
1335
1368
|
},
|
|
1336
1369
|
required: ["poseType", "action"],
|
|
1337
1370
|
},
|
|
1338
1371
|
execute: async (_toolCallId, params) => {
|
|
1339
|
-
const
|
|
1372
|
+
const locator = resolveToolLocator(ctx);
|
|
1373
|
+
const agentId = runtimeManager.resolveRuntimeAgentId(locator);
|
|
1374
|
+
if (!agentId) {
|
|
1375
|
+
return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
|
|
1376
|
+
}
|
|
1377
|
+
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
1378
|
+
const { poseType, action, bubble, log, verify, propId } = (params || {}) as {
|
|
1340
1379
|
poseType?: string;
|
|
1341
1380
|
action?: string;
|
|
1342
1381
|
bubble?: string;
|
|
1343
1382
|
log?: string;
|
|
1344
1383
|
verify?: boolean;
|
|
1384
|
+
propId?: string;
|
|
1345
1385
|
};
|
|
1346
1386
|
if (!poseType || !action) {
|
|
1347
1387
|
return jsonResult({ success: false, error: "poseType and action parameters are required" });
|
|
@@ -1374,7 +1414,7 @@ const plugin = {
|
|
|
1374
1414
|
if (verify) {
|
|
1375
1415
|
try {
|
|
1376
1416
|
const ack = await service.sendStatusVerified(
|
|
1377
|
-
normalizedPoseType, matched.name, bubbleText, logText, playback,
|
|
1417
|
+
normalizedPoseType, matched.name, bubbleText, logText, playback, propId,
|
|
1378
1418
|
);
|
|
1379
1419
|
if (ack.warning) {
|
|
1380
1420
|
return jsonResult({
|
|
@@ -1393,6 +1433,7 @@ const plugin = {
|
|
|
1393
1433
|
action: matched.name,
|
|
1394
1434
|
bubble: bubbleText,
|
|
1395
1435
|
log: logText,
|
|
1436
|
+
propId,
|
|
1396
1437
|
});
|
|
1397
1438
|
}
|
|
1398
1439
|
|
|
@@ -1405,8 +1446,8 @@ const plugin = {
|
|
|
1405
1446
|
playback,
|
|
1406
1447
|
});
|
|
1407
1448
|
},
|
|
1408
|
-
}))
|
|
1409
|
-
api.registerTool(
|
|
1449
|
+
})});
|
|
1450
|
+
api.registerTool((ctx) => ({
|
|
1410
1451
|
name: "kichi_idle_plan",
|
|
1411
1452
|
label: "kichi_idle_plan",
|
|
1412
1453
|
description: buildKichiIdlePlanDescription(),
|
|
@@ -1474,6 +1515,10 @@ const plugin = {
|
|
|
1474
1515
|
type: "string",
|
|
1475
1516
|
description: "Optional log content for this action. Use the same language as the current conversation.",
|
|
1476
1517
|
},
|
|
1518
|
+
propId: {
|
|
1519
|
+
type: "string",
|
|
1520
|
+
description: "Optional poseable prop ID from RoomContext.PoseableProps. When specified, the avatar is seated at this prop.",
|
|
1521
|
+
},
|
|
1477
1522
|
},
|
|
1478
1523
|
required: ["poseType", "action", "durationSeconds", "bubble"],
|
|
1479
1524
|
},
|
|
@@ -1486,6 +1531,12 @@ const plugin = {
|
|
|
1486
1531
|
required: ["heartbeatIntervalSeconds", "goal", "stages"],
|
|
1487
1532
|
},
|
|
1488
1533
|
execute: async (_toolCallId, params) => {
|
|
1534
|
+
const locator = resolveToolLocator(ctx);
|
|
1535
|
+
const agentId = runtimeManager.resolveRuntimeAgentId(locator);
|
|
1536
|
+
if (!agentId) {
|
|
1537
|
+
return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
|
|
1538
|
+
}
|
|
1539
|
+
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
1489
1540
|
const { idlePlan, error } = normalizeIdlePlan(params);
|
|
1490
1541
|
if (!idlePlan) {
|
|
1491
1542
|
return jsonResult({ success: false, error: error ?? "Invalid idle plan payload" });
|
|
@@ -1511,8 +1562,8 @@ const plugin = {
|
|
|
1511
1562
|
stages: idlePlan.stages,
|
|
1512
1563
|
});
|
|
1513
1564
|
},
|
|
1514
|
-
}))
|
|
1515
|
-
api.registerTool(
|
|
1565
|
+
}));
|
|
1566
|
+
api.registerTool((ctx) => ({
|
|
1516
1567
|
name: "kichi_clock",
|
|
1517
1568
|
label: "kichi_clock",
|
|
1518
1569
|
description:
|
|
@@ -1582,6 +1633,12 @@ const plugin = {
|
|
|
1582
1633
|
required: ["action"],
|
|
1583
1634
|
},
|
|
1584
1635
|
execute: async (_toolCallId, params) => {
|
|
1636
|
+
const locator = resolveToolLocator(ctx);
|
|
1637
|
+
const agentId = runtimeManager.resolveRuntimeAgentId(locator);
|
|
1638
|
+
if (!agentId) {
|
|
1639
|
+
return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
|
|
1640
|
+
}
|
|
1641
|
+
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
1585
1642
|
const { action, requestId, clock } = (params || {}) as {
|
|
1586
1643
|
action?: unknown;
|
|
1587
1644
|
requestId?: unknown;
|
|
@@ -1623,13 +1680,13 @@ const plugin = {
|
|
|
1623
1680
|
...(normalizedClock ? { clock: normalizedClock } : {}),
|
|
1624
1681
|
});
|
|
1625
1682
|
},
|
|
1626
|
-
}))
|
|
1683
|
+
}));
|
|
1627
1684
|
|
|
1628
|
-
api.registerTool(
|
|
1685
|
+
api.registerTool((ctx) => ({
|
|
1629
1686
|
name: "kichi_query_status",
|
|
1630
1687
|
label: "kichi_query_status",
|
|
1631
1688
|
description:
|
|
1632
|
-
"Query Kichi room and avatar status — includes room personnel, notes, ownerState, idlePlan, weather/time, timer snapshot, daily note quota,
|
|
1689
|
+
"Query Kichi room and avatar status — includes room personnel, notes, ownerState, idlePlan, weather/time, timer snapshot, daily note quota, `hasCreatedMusicAlbumToday`, and RoomContext.PoseableProps (poseable props with PropId, DisplayName, SupportedPoseTypes, OccupancyState). The PoseableProps list is cached internally so that kichi_action can reference a propId during regular work sync without re-querying. Use this when the user asks to check kichi status, room status, or who is in the room. Also use this before creating a new note or daily recommended music album. For heartbeat planning, use the returned idlePlan as reference when shaping the next idle plan.",
|
|
1633
1690
|
parameters: {
|
|
1634
1691
|
type: "object",
|
|
1635
1692
|
properties: {
|
|
@@ -1640,6 +1697,12 @@ const plugin = {
|
|
|
1640
1697
|
},
|
|
1641
1698
|
},
|
|
1642
1699
|
execute: async (_toolCallId, params) => {
|
|
1700
|
+
const locator = resolveToolLocator(ctx);
|
|
1701
|
+
const agentId = runtimeManager.resolveRuntimeAgentId(locator);
|
|
1702
|
+
if (!agentId) {
|
|
1703
|
+
return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
|
|
1704
|
+
}
|
|
1705
|
+
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
1643
1706
|
const requestId = (params as { requestId?: unknown } | null)?.requestId;
|
|
1644
1707
|
if (requestId !== undefined && typeof requestId !== "string") {
|
|
1645
1708
|
return jsonResult({ success: false, error: "requestId must be a string when provided" });
|
|
@@ -1660,9 +1723,9 @@ const plugin = {
|
|
|
1660
1723
|
});
|
|
1661
1724
|
}
|
|
1662
1725
|
},
|
|
1663
|
-
}))
|
|
1726
|
+
}));
|
|
1664
1727
|
|
|
1665
|
-
api.registerTool(
|
|
1728
|
+
api.registerTool((ctx) => ({
|
|
1666
1729
|
name: "kichi_music_album_create",
|
|
1667
1730
|
label: "kichi_music_album_create",
|
|
1668
1731
|
description: buildMusicAlbumToolDescription(),
|
|
@@ -1689,6 +1752,12 @@ const plugin = {
|
|
|
1689
1752
|
required: ["albumTitle", "musicTitles"],
|
|
1690
1753
|
},
|
|
1691
1754
|
execute: async (_toolCallId, params) => {
|
|
1755
|
+
const locator = resolveToolLocator(ctx);
|
|
1756
|
+
const agentId = runtimeManager.resolveRuntimeAgentId(locator);
|
|
1757
|
+
if (!agentId) {
|
|
1758
|
+
return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
|
|
1759
|
+
}
|
|
1760
|
+
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
1692
1761
|
const {
|
|
1693
1762
|
requestId,
|
|
1694
1763
|
albumTitle,
|
|
@@ -1749,9 +1818,9 @@ const plugin = {
|
|
|
1749
1818
|
});
|
|
1750
1819
|
}
|
|
1751
1820
|
},
|
|
1752
|
-
}))
|
|
1821
|
+
}));
|
|
1753
1822
|
|
|
1754
|
-
api.registerTool(
|
|
1823
|
+
api.registerTool((ctx) => ({
|
|
1755
1824
|
name: "kichi_noteboard_create",
|
|
1756
1825
|
label: "kichi_noteboard_create",
|
|
1757
1826
|
description:
|
|
@@ -1771,6 +1840,12 @@ const plugin = {
|
|
|
1771
1840
|
required: ["propId", "data"],
|
|
1772
1841
|
},
|
|
1773
1842
|
execute: async (_toolCallId, params) => {
|
|
1843
|
+
const locator = resolveToolLocator(ctx);
|
|
1844
|
+
const agentId = runtimeManager.resolveRuntimeAgentId(locator);
|
|
1845
|
+
if (!agentId) {
|
|
1846
|
+
return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
|
|
1847
|
+
}
|
|
1848
|
+
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
1774
1849
|
const { propId, data } = (params || {}) as {
|
|
1775
1850
|
propId?: unknown;
|
|
1776
1851
|
data?: unknown;
|
|
@@ -1801,9 +1876,9 @@ const plugin = {
|
|
|
1801
1876
|
});
|
|
1802
1877
|
}
|
|
1803
1878
|
},
|
|
1804
|
-
}))
|
|
1879
|
+
}));
|
|
1805
1880
|
|
|
1806
|
-
api.registerTool(
|
|
1881
|
+
api.registerTool((ctx) => ({
|
|
1807
1882
|
name: "kichi_bot_message",
|
|
1808
1883
|
label: "kichi_bot_message",
|
|
1809
1884
|
description:
|
|
@@ -1840,6 +1915,12 @@ const plugin = {
|
|
|
1840
1915
|
required: ["toAvatarId", "depth", "bubble"],
|
|
1841
1916
|
},
|
|
1842
1917
|
execute: async (_toolCallId, params) => {
|
|
1918
|
+
const locator = resolveToolLocator(ctx);
|
|
1919
|
+
const agentId = runtimeManager.resolveRuntimeAgentId(locator);
|
|
1920
|
+
if (!agentId) {
|
|
1921
|
+
return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
|
|
1922
|
+
}
|
|
1923
|
+
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
1843
1924
|
const { toAvatarId, depth, bubble, poseType, action, log } = (params || {}) as {
|
|
1844
1925
|
toAvatarId?: string;
|
|
1845
1926
|
depth?: number;
|
|
@@ -1877,7 +1958,7 @@ const plugin = {
|
|
|
1877
1958
|
return jsonResult({ success: false, error: `Failed to send bot message: ${error}` });
|
|
1878
1959
|
}
|
|
1879
1960
|
},
|
|
1880
|
-
}))
|
|
1961
|
+
}));
|
|
1881
1962
|
|
|
1882
1963
|
},
|
|
1883
1964
|
};
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,9 +2,25 @@
|
|
|
2
2
|
"id": "kichi-forwarder",
|
|
3
3
|
"name": "Kichi Forwarder",
|
|
4
4
|
"description": "Native OpenClaw plugin for Kichi World with direct avatar control, status sync, timers, notes, and music tools",
|
|
5
|
-
"version": "0.1.2-beta.
|
|
5
|
+
"version": "0.1.2-beta.9",
|
|
6
6
|
"author": "OpenClaw",
|
|
7
7
|
"skills": ["./skills/kichi-forwarder"],
|
|
8
|
+
"contracts": {
|
|
9
|
+
"tools": [
|
|
10
|
+
"kichi_join",
|
|
11
|
+
"kichi_switch_host",
|
|
12
|
+
"kichi_rejoin",
|
|
13
|
+
"kichi_leave",
|
|
14
|
+
"kichi_connection_status",
|
|
15
|
+
"kichi_action",
|
|
16
|
+
"kichi_idle_plan",
|
|
17
|
+
"kichi_clock",
|
|
18
|
+
"kichi_query_status",
|
|
19
|
+
"kichi_music_album_create",
|
|
20
|
+
"kichi_noteboard_create",
|
|
21
|
+
"kichi_bot_message"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
8
24
|
"configSchema": {
|
|
9
25
|
"type": "object",
|
|
10
26
|
"additionalProperties": false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yahaha-studio/kichi-forwarder",
|
|
3
|
-
"version": "0.1.2-beta.
|
|
3
|
+
"version": "0.1.2-beta.9",
|
|
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",
|
|
@@ -22,12 +22,12 @@
|
|
|
22
22
|
"./dist/index.js"
|
|
23
23
|
],
|
|
24
24
|
"compat": {
|
|
25
|
-
"pluginApi": ">=2026.
|
|
26
|
-
"minGatewayVersion": "2026.
|
|
25
|
+
"pluginApi": ">=2026.4.25",
|
|
26
|
+
"minGatewayVersion": "2026.4.25"
|
|
27
27
|
},
|
|
28
28
|
"build": {
|
|
29
|
-
"openclawVersion": "2026.
|
|
30
|
-
"pluginSdkVersion": "2026.
|
|
29
|
+
"openclawVersion": "2026.5.7",
|
|
30
|
+
"pluginSdkVersion": "2026.5.7"
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/node": "^24.3.0",
|
|
45
45
|
"@types/ws": "^8.18.1",
|
|
46
|
-
"openclaw": "2026.
|
|
46
|
+
"openclaw": "2026.5.7",
|
|
47
47
|
"typescript": "^6.0.3"
|
|
48
48
|
}
|
|
49
49
|
}
|
package/src/service.ts
CHANGED
|
@@ -83,6 +83,7 @@ export class KichiForwarderService {
|
|
|
83
83
|
}
|
|
84
84
|
>();
|
|
85
85
|
onBotMessageReceived: BotMessageReceivedHandler | null = null;
|
|
86
|
+
private cachedRoomContext: Record<string, unknown> | null = null;
|
|
86
87
|
|
|
87
88
|
constructor(
|
|
88
89
|
private logger: PluginLogger,
|
|
@@ -166,7 +167,7 @@ export class KichiForwarderService {
|
|
|
166
167
|
});
|
|
167
168
|
}
|
|
168
169
|
|
|
169
|
-
sendStatus(poseType: PoseType | "", action: string, bubble: string, log: string, playback: ActionPlayback): void {
|
|
170
|
+
sendStatus(poseType: PoseType | "", action: string, bubble: string, log: string, playback: ActionPlayback, propId?: string): void {
|
|
170
171
|
if (!this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN) return;
|
|
171
172
|
const payload: StatusPayload = {
|
|
172
173
|
type: "status",
|
|
@@ -177,6 +178,7 @@ export class KichiForwarderService {
|
|
|
177
178
|
bubble,
|
|
178
179
|
log,
|
|
179
180
|
playback,
|
|
181
|
+
...(propId ? { propId } : {}),
|
|
180
182
|
};
|
|
181
183
|
this.ws.send(JSON.stringify(payload));
|
|
182
184
|
}
|
|
@@ -187,6 +189,7 @@ export class KichiForwarderService {
|
|
|
187
189
|
bubble: string,
|
|
188
190
|
log: string,
|
|
189
191
|
playback: ActionPlayback,
|
|
192
|
+
propId?: string,
|
|
190
193
|
): Promise<StatusAckPayload> {
|
|
191
194
|
if (!this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN) {
|
|
192
195
|
throw new Error("Kichi websocket is not connected");
|
|
@@ -201,6 +204,7 @@ export class KichiForwarderService {
|
|
|
201
204
|
bubble,
|
|
202
205
|
log,
|
|
203
206
|
playback,
|
|
207
|
+
...(propId ? { propId } : {}),
|
|
204
208
|
};
|
|
205
209
|
return this.sendRequest<StatusAckPayload>(payload, "status_ack", 5000);
|
|
206
210
|
}
|
|
@@ -267,7 +271,11 @@ export class KichiForwarderService {
|
|
|
267
271
|
avatarId: identity.avatarId,
|
|
268
272
|
authKey: identity.authKey,
|
|
269
273
|
};
|
|
270
|
-
|
|
274
|
+
const result = await this.sendRequest<QueryStatusResultPayload>(payload, "query_status_result");
|
|
275
|
+
if (result.RoomContext && typeof result.RoomContext === "object") {
|
|
276
|
+
this.cachedRoomContext = result.RoomContext as Record<string, unknown>;
|
|
277
|
+
}
|
|
278
|
+
return result;
|
|
271
279
|
}
|
|
272
280
|
|
|
273
281
|
createNotesBoardNote(propId: string, data: string): void {
|
|
@@ -350,6 +358,8 @@ export class KichiForwarderService {
|
|
|
350
358
|
|
|
351
359
|
isConnected(): boolean { return this.ws?.readyState === WebSocket.OPEN && !!this.identity?.authKey; }
|
|
352
360
|
|
|
361
|
+
getCachedRoomContext(): Record<string, unknown> | null { return this.cachedRoomContext; }
|
|
362
|
+
|
|
353
363
|
hasValidIdentity(): boolean { return !!this.identity?.avatarId && !!this.identity?.authKey; }
|
|
354
364
|
|
|
355
365
|
isLlmRuntimeEnabled(): boolean {
|
package/src/types.ts
CHANGED
|
@@ -17,6 +17,7 @@ export type ActionResult = {
|
|
|
17
17
|
action: string;
|
|
18
18
|
bubble: string;
|
|
19
19
|
log?: string;
|
|
20
|
+
propId?: string;
|
|
20
21
|
};
|
|
21
22
|
|
|
22
23
|
export type KichiStaticConfig = {
|
|
@@ -117,6 +118,7 @@ export type StatusPayload = {
|
|
|
117
118
|
bubble: string;
|
|
118
119
|
log: string;
|
|
119
120
|
playback: ActionPlayback;
|
|
121
|
+
propId?: string;
|
|
120
122
|
};
|
|
121
123
|
|
|
122
124
|
export type StatusAckPayload = {
|
|
@@ -124,6 +126,7 @@ export type StatusAckPayload = {
|
|
|
124
126
|
requestId: string;
|
|
125
127
|
poseType: PoseType | "";
|
|
126
128
|
action: string;
|
|
129
|
+
requestedPropId?: string;
|
|
127
130
|
warning?: string;
|
|
128
131
|
};
|
|
129
132
|
|
|
@@ -144,6 +147,7 @@ export type IdlePlanStageAction = {
|
|
|
144
147
|
durationSeconds: number;
|
|
145
148
|
bubble: string;
|
|
146
149
|
log?: string;
|
|
150
|
+
propId?: string;
|
|
147
151
|
};
|
|
148
152
|
|
|
149
153
|
export type IdlePlanStage = {
|