@yahaha-studio/kichi-forwarder 0.1.1-beta.9 → 0.1.2-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/config/environments.json +5 -0
- package/dist/config/environments.json +5 -0
- package/dist/config/kichi-config.json +941 -0
- package/dist/index.js +1681 -0
- package/dist/src/config.js +4 -0
- package/dist/src/runtime-manager.js +121 -0
- package/dist/src/service.js +709 -0
- package/dist/src/types.js +1 -0
- package/index.ts +422 -146
- package/openclaw.plugin.json +17 -1
- package/package.json +16 -7
- package/skills/kichi-forwarder/SKILL.md +46 -20
- package/skills/kichi-forwarder/references/error.md +3 -11
- package/skills/kichi-forwarder/references/heartbeat.md +7 -17
- package/skills/kichi-forwarder/references/install.md +22 -29
- package/src/runtime-manager.ts +25 -2
- package/src/service.ts +70 -27
- package/src/types.ts +40 -1
package/index.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import type {
|
|
4
|
-
AnyAgentTool,
|
|
5
4
|
OpenClawPluginApi,
|
|
6
|
-
OpenClawPluginToolContext,
|
|
7
5
|
} from "openclaw/plugin-sdk";
|
|
6
|
+
import type { OpenClawPluginToolContext } from "openclaw/plugin-sdk/core";
|
|
7
|
+
import { agentCommandFromIngress } from "openclaw/plugin-sdk/agent-runtime";
|
|
8
8
|
import { parse } from "./src/config.js";
|
|
9
9
|
import { KichiRuntimeManager } from "./src/runtime-manager.js";
|
|
10
10
|
import { KichiForwarderService } from "./src/service.js";
|
|
@@ -13,13 +13,22 @@ import type {
|
|
|
13
13
|
ActionPlayback,
|
|
14
14
|
ActionResult,
|
|
15
15
|
Album,
|
|
16
|
+
BotMessageHistoryEntry,
|
|
17
|
+
BotMessageReceivedPayload,
|
|
16
18
|
ClockAction,
|
|
17
19
|
ClockConfig,
|
|
20
|
+
KichiEnvironment,
|
|
21
|
+
KichiEnvironmentsConfig,
|
|
18
22
|
KichiStaticConfig,
|
|
19
23
|
PomodoroPhase,
|
|
20
24
|
PoseType,
|
|
21
25
|
} from "./src/types.js";
|
|
22
26
|
const BUNDLED_STATIC_CONFIG_PATH = new URL("./config/kichi-config.json", import.meta.url);
|
|
27
|
+
|
|
28
|
+
function jsonResult(payload: unknown): { content: { type: "text"; text: string }[]; details: unknown } {
|
|
29
|
+
return { content: [{ type: "text", text: JSON.stringify(payload) }], details: payload };
|
|
30
|
+
}
|
|
31
|
+
const BUNDLED_ENVIRONMENTS_CONFIG_PATH = new URL("./config/environments.json", import.meta.url);
|
|
23
32
|
const FIXED_HOOK_STATUSES: Record<string, ActionResult> = {
|
|
24
33
|
beforePromptBuild: {
|
|
25
34
|
poseType: "sit",
|
|
@@ -200,6 +209,52 @@ function loadStaticConfig(): KichiStaticConfig {
|
|
|
200
209
|
return cachedStaticConfig;
|
|
201
210
|
}
|
|
202
211
|
|
|
212
|
+
const VALID_ENVIRONMENTS: KichiEnvironment[] = ["steam", "steam-playtest", "test"];
|
|
213
|
+
let cachedEnvironmentsConfig: KichiEnvironmentsConfig | null = null;
|
|
214
|
+
let cachedEnvironmentsConfigMtime = 0;
|
|
215
|
+
|
|
216
|
+
function getEnvironmentsConfigPath(): string {
|
|
217
|
+
return fileURLToPath(BUNDLED_ENVIRONMENTS_CONFIG_PATH);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function loadEnvironmentsConfig(): KichiEnvironmentsConfig {
|
|
221
|
+
const configPath = getEnvironmentsConfigPath();
|
|
222
|
+
const stat = fs.statSync(configPath);
|
|
223
|
+
if (cachedEnvironmentsConfig && stat.mtimeMs === cachedEnvironmentsConfigMtime) {
|
|
224
|
+
return cachedEnvironmentsConfig;
|
|
225
|
+
}
|
|
226
|
+
const raw = JSON.parse(fs.readFileSync(configPath, "utf-8")) as unknown;
|
|
227
|
+
if (!raw || typeof raw !== "object") {
|
|
228
|
+
throw new Error("config/environments.json must be a valid object");
|
|
229
|
+
}
|
|
230
|
+
const config = raw as Record<string, unknown>;
|
|
231
|
+
for (const env of VALID_ENVIRONMENTS) {
|
|
232
|
+
if (!(env in config)) {
|
|
233
|
+
throw new Error(`config/environments.json missing environment "${env}"`);
|
|
234
|
+
}
|
|
235
|
+
const value = config[env];
|
|
236
|
+
if (value !== null && typeof value !== "string") {
|
|
237
|
+
throw new Error(`config/environments.json environment "${env}" must be a string or null`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
cachedEnvironmentsConfig = config as KichiEnvironmentsConfig;
|
|
241
|
+
cachedEnvironmentsConfigMtime = stat.mtimeMs;
|
|
242
|
+
return cachedEnvironmentsConfig;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function isKichiEnvironment(value: unknown): value is KichiEnvironment {
|
|
246
|
+
return typeof value === "string" && VALID_ENVIRONMENTS.includes(value as KichiEnvironment);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function resolveEnvironmentHost(environment: KichiEnvironment): { host?: string; error?: string } {
|
|
250
|
+
const config = loadEnvironmentsConfig();
|
|
251
|
+
const configuredHost = config[environment];
|
|
252
|
+
if (typeof configuredHost === "string" && configuredHost.trim()) {
|
|
253
|
+
return { host: configuredHost };
|
|
254
|
+
}
|
|
255
|
+
return { error: `environment "${environment}" has no configured host — update config/environments.json first` };
|
|
256
|
+
}
|
|
257
|
+
|
|
203
258
|
function sendStatusUpdate(service: KichiForwarderService, status: ActionResult): void {
|
|
204
259
|
const actionDefinition = getActionDefinition(status.poseType, status.action);
|
|
205
260
|
service.sendStatus(
|
|
@@ -208,6 +263,7 @@ function sendStatusUpdate(service: KichiForwarderService, status: ActionResult):
|
|
|
208
263
|
status.bubble || status.action,
|
|
209
264
|
typeof status.log === "string" ? status.log.trim() : "",
|
|
210
265
|
getActionPlayback(actionDefinition),
|
|
266
|
+
status.propId,
|
|
211
267
|
);
|
|
212
268
|
}
|
|
213
269
|
|
|
@@ -656,6 +712,7 @@ function normalizeIdlePlan(value: unknown): { idlePlan?: IdlePlan; error?: strin
|
|
|
656
712
|
const actionDurationSeconds = rawAction.durationSeconds;
|
|
657
713
|
const bubble = rawAction.bubble;
|
|
658
714
|
const log = rawAction.log;
|
|
715
|
+
const propId = rawAction.propId;
|
|
659
716
|
|
|
660
717
|
if (!["stand", "sit", "lay", "floor"].includes(String(poseType))) {
|
|
661
718
|
return {
|
|
@@ -703,6 +760,7 @@ function normalizeIdlePlan(value: unknown): { idlePlan?: IdlePlan; error?: strin
|
|
|
703
760
|
durationSeconds: actionDurationSeconds,
|
|
704
761
|
bubble: bubble.trim(),
|
|
705
762
|
...(typeof log === "string" && log.trim() ? { log: log.trim() } : {}),
|
|
763
|
+
...(typeof propId === "string" && propId.trim() ? { propId: propId.trim() } : {}),
|
|
706
764
|
});
|
|
707
765
|
}
|
|
708
766
|
|
|
@@ -902,18 +960,6 @@ function buildMusicAlbumToolDescription(): string {
|
|
|
902
960
|
].join("\n");
|
|
903
961
|
}
|
|
904
962
|
|
|
905
|
-
function isKichiHost(value: unknown): value is string {
|
|
906
|
-
if (typeof value !== "string") {
|
|
907
|
-
return false;
|
|
908
|
-
}
|
|
909
|
-
const trimmed = value.trim();
|
|
910
|
-
return trimmed.length > 0
|
|
911
|
-
&& !trimmed.includes("://")
|
|
912
|
-
&& !trimmed.includes("/")
|
|
913
|
-
&& !trimmed.includes("?")
|
|
914
|
-
&& !trimmed.includes("#");
|
|
915
|
-
}
|
|
916
|
-
|
|
917
963
|
function buildMusicTitlesDescription(): string {
|
|
918
964
|
return [
|
|
919
965
|
"Track names are injected into this tool schema from the static config bundled with the plugin package.",
|
|
@@ -948,9 +994,9 @@ function formatActionList(actions: ActionDefinition[], playback: ActionPlayback[
|
|
|
948
994
|
.join(", ");
|
|
949
995
|
}
|
|
950
996
|
|
|
951
|
-
function buildKichiActionDescription(): string {
|
|
997
|
+
function buildKichiActionDescription(service?: KichiForwarderService): string {
|
|
952
998
|
const actions = loadStaticConfig().actions;
|
|
953
|
-
|
|
999
|
+
const lines = [
|
|
954
1000
|
"Directly control the avatar inside Kichi World.",
|
|
955
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.",
|
|
956
1002
|
"For most work, prefer a sit pose and switch actions as the task moves between stages.",
|
|
@@ -959,7 +1005,20 @@ function buildKichiActionDescription(): string {
|
|
|
959
1005
|
`sit actions: ${actions.sit.map((entry) => entry.name).join(", ")}`,
|
|
960
1006
|
`lay actions: ${actions.lay.map((entry) => entry.name).join(", ")}`,
|
|
961
1007
|
`floor actions: ${actions.floor.map((entry) => entry.name).join(", ")}`,
|
|
962
|
-
]
|
|
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");
|
|
963
1022
|
}
|
|
964
1023
|
|
|
965
1024
|
function buildKichiIdlePlanDescription(): string {
|
|
@@ -969,12 +1028,13 @@ function buildKichiIdlePlanDescription(): string {
|
|
|
969
1028
|
"The payload must include the overall goal, heartbeat interval, stage breakdown, each stage's purpose, each stage's pomodoroPhase, action list, and bubble content.",
|
|
970
1029
|
"Build the plan in this order.",
|
|
971
1030
|
"1. Pick one concrete, time-bounded fun personal project you would genuinely choose to do on your own when nobody needs you. It must fit your personality, tastes, and established character, stay rooted in your personal interests or hobbies, and be something the available Kichi actions can express clearly.",
|
|
972
|
-
"2.
|
|
1031
|
+
"2. Set the overall goal to that project. Do not use a vague atmosphere, a generic productivity task, or a catch-all routine summary as the goal.",
|
|
973
1032
|
"3. Break the full heartbeat interval into ordered stages. Each stage purpose must explain what you are actually doing in that stage as part of the same project, not just how you want to feel. Do not switch to unrelated tasks just to use more actions.",
|
|
974
1033
|
"4. Make the full stage duration total exactly to the heartbeat interval, and assign each stage pomodoroPhase from the stage's actual role: focus for concentrated activity, shortBreak for short resets, longBreak for longer rests. Do not default the whole idle plan to none. Use none only for a stage that truly has no pomodoro role.",
|
|
975
1034
|
"5. Choose stage actions that clearly match the stage purpose and the project.",
|
|
976
1035
|
"6. Write each action bubble as the current presented state, not a next step, plan, or instruction.",
|
|
977
1036
|
"7. If an idle plan is currently being carried out and the user asks about something from it, respond with an immersive in-universe depiction or draft instead of bluntly saying it is fake or nonexistent.",
|
|
1037
|
+
"Use your memory to recall what you did in past heartbeats and to stay consistent with your established personality and interests.",
|
|
978
1038
|
"Use the same language as the current conversation for goal, purpose, bubble, and log.",
|
|
979
1039
|
`stand actions: ${actions.stand.map((entry) => entry.name).join(", ")}`,
|
|
980
1040
|
`sit actions: ${actions.sit.map((entry) => entry.name).join(", ")}`,
|
|
@@ -997,22 +1057,12 @@ function buildKichiPrompt(): string {
|
|
|
997
1057
|
"",
|
|
998
1058
|
"kichi_clock: set countDown for tasks with 2+ steps or >10s work. Skip for quick one-shots.",
|
|
999
1059
|
"",
|
|
1060
|
+
"When sending a bot message, do NOT call kichi_action separately.",
|
|
1061
|
+
"",
|
|
1000
1062
|
"User opt-out, Kichi config/test work, and explicit pose requests take priority over sync.",
|
|
1001
1063
|
].join("\n");
|
|
1002
1064
|
}
|
|
1003
1065
|
|
|
1004
|
-
function createAgentScopedTool(
|
|
1005
|
-
runtimeManager: KichiRuntimeManager,
|
|
1006
|
-
factory: (service: KichiForwarderService, ctx: OpenClawPluginToolContext) => AnyAgentTool,
|
|
1007
|
-
) {
|
|
1008
|
-
return (ctx: OpenClawPluginToolContext) => {
|
|
1009
|
-
const service = runtimeManager.getRuntime(resolveToolLocator(ctx));
|
|
1010
|
-
if (!service) {
|
|
1011
|
-
throw new Error("Failed to resolve agent-scoped Kichi runtime");
|
|
1012
|
-
}
|
|
1013
|
-
return factory(service, ctx);
|
|
1014
|
-
};
|
|
1015
|
-
}
|
|
1016
1066
|
|
|
1017
1067
|
const GLOBAL_RUNTIME_MANAGER_KEY = "__kichi_forwarder_runtime_manager__";
|
|
1018
1068
|
|
|
@@ -1031,6 +1081,10 @@ function getRuntimeManager(logger: OpenClawPluginApi["logger"]): KichiRuntimeMan
|
|
|
1031
1081
|
return runtimeManager;
|
|
1032
1082
|
}
|
|
1033
1083
|
|
|
1084
|
+
const BOT_MESSAGE_MAX_DEPTH = 5;
|
|
1085
|
+
const BOT_MESSAGE_COOLDOWN_MS = 5_000;
|
|
1086
|
+
const botMessageCooldowns = new Map<string, number>();
|
|
1087
|
+
|
|
1034
1088
|
const plugin = {
|
|
1035
1089
|
id: "kichi-forwarder",
|
|
1036
1090
|
name: "Kichi Forwarder",
|
|
@@ -1038,9 +1092,57 @@ const plugin = {
|
|
|
1038
1092
|
|
|
1039
1093
|
register(api: OpenClawPluginApi) {
|
|
1040
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
|
+
|
|
1041
1102
|
registerPluginHooks(api, runtimeManager);
|
|
1042
1103
|
const musicTitleEnum = getMusicTitleEnum();
|
|
1043
1104
|
|
|
1105
|
+
runtimeManager.setBotMessageHandler((service, msg) => {
|
|
1106
|
+
if (msg.depth >= BOT_MESSAGE_MAX_DEPTH) {
|
|
1107
|
+
api.logger.info(`[kichi:${service.getAgentId()}] bot_message depth=${msg.depth} >= max=${BOT_MESSAGE_MAX_DEPTH}, ignoring`);
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
const now = Date.now();
|
|
1111
|
+
const cooldownKey = `${service.getAgentId()}:${msg.from}`;
|
|
1112
|
+
const lastReply = botMessageCooldowns.get(cooldownKey) ?? 0;
|
|
1113
|
+
if (now - lastReply < BOT_MESSAGE_COOLDOWN_MS) return;
|
|
1114
|
+
botMessageCooldowns.set(cooldownKey, now);
|
|
1115
|
+
const sessionKey = `agent:${service.getAgentId()}:default`;
|
|
1116
|
+
const history: BotMessageHistoryEntry[] = [
|
|
1117
|
+
...(msg.history ?? []),
|
|
1118
|
+
{ from: msg.from, fromName: msg.fromName, bubble: msg.bubble },
|
|
1119
|
+
];
|
|
1120
|
+
const historyLines = history.map((h) => `${h.fromName}: "${h.bubble}"`);
|
|
1121
|
+
const message = `[Bot conversation]\n${historyLines.join("\n")}\n\nReply with a short bubble (2-5 words). Do not repeat what has already been said. Just output the bubble text, nothing else.`;
|
|
1122
|
+
agentCommandFromIngress({
|
|
1123
|
+
message,
|
|
1124
|
+
sessionKey,
|
|
1125
|
+
agentId: service.getAgentId(),
|
|
1126
|
+
senderIsOwner: false,
|
|
1127
|
+
allowModelOverride: false,
|
|
1128
|
+
deliver: false,
|
|
1129
|
+
}).then((result) => {
|
|
1130
|
+
const replyText = (result.payloads ?? [])
|
|
1131
|
+
.map((p: { text?: string }) => p.text)
|
|
1132
|
+
.filter((t): t is string => typeof t === "string" && t.trim().length > 0)
|
|
1133
|
+
.join(" ")
|
|
1134
|
+
.trim();
|
|
1135
|
+
if (!replyText) {
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1138
|
+
service.sendBotMessage(msg.from, msg.depth + 1, replyText, { history }).catch((sendErr) => {
|
|
1139
|
+
api.logger.warn(`[kichi:${service.getAgentId()}] bot_message send failed: ${sendErr}`);
|
|
1140
|
+
});
|
|
1141
|
+
}).catch((err) => {
|
|
1142
|
+
api.logger.warn(`[kichi:${service.getAgentId()}] bot_message agent run failed: ${err}`);
|
|
1143
|
+
});
|
|
1144
|
+
});
|
|
1145
|
+
|
|
1044
1146
|
api.registerService({
|
|
1045
1147
|
id: "kichi-forwarder",
|
|
1046
1148
|
start: (ctx) => {
|
|
@@ -1056,8 +1158,9 @@ const plugin = {
|
|
|
1056
1158
|
},
|
|
1057
1159
|
});
|
|
1058
1160
|
|
|
1059
|
-
api.registerTool(
|
|
1161
|
+
api.registerTool((ctx) => ({
|
|
1060
1162
|
name: "kichi_join",
|
|
1163
|
+
label: "kichi_join",
|
|
1061
1164
|
description: "Join Kichi world with avatarId, the current bot name, a short bio, and personality tags",
|
|
1062
1165
|
parameters: {
|
|
1063
1166
|
type: "object",
|
|
@@ -1080,6 +1183,12 @@ const plugin = {
|
|
|
1080
1183
|
required: ["botName", "bio"],
|
|
1081
1184
|
},
|
|
1082
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);
|
|
1083
1192
|
let avatarId = (params as { avatarId?: string } | null)?.avatarId;
|
|
1084
1193
|
const botName = (params as { botName?: string } | null)?.botName?.trim();
|
|
1085
1194
|
const bio = (params as { bio?: string } | null)?.bio?.trim();
|
|
@@ -1090,115 +1199,148 @@ const plugin = {
|
|
|
1090
1199
|
avatarId = service.readSavedAvatarId() ?? undefined;
|
|
1091
1200
|
}
|
|
1092
1201
|
if (!avatarId) {
|
|
1093
|
-
return { success: false, error: "No avatarId" };
|
|
1202
|
+
return jsonResult({ success: false, error: "No avatarId" });
|
|
1094
1203
|
}
|
|
1095
1204
|
if (!botName) {
|
|
1096
|
-
return { success: false, error: "No botName" };
|
|
1205
|
+
return jsonResult({ success: false, error: "No botName" });
|
|
1097
1206
|
}
|
|
1098
1207
|
if (!bio) {
|
|
1099
|
-
return { success: false, error: "No bio" };
|
|
1208
|
+
return jsonResult({ success: false, error: "No bio" });
|
|
1100
1209
|
}
|
|
1101
1210
|
if (tagsError) {
|
|
1102
|
-
return { success: false, error: tagsError };
|
|
1211
|
+
return jsonResult({ success: false, error: tagsError });
|
|
1103
1212
|
}
|
|
1104
1213
|
const result = await service.join(avatarId, botName, bio, tags ?? []);
|
|
1105
1214
|
if (result.success) {
|
|
1106
|
-
return { success: true, authKey: result.authKey };
|
|
1215
|
+
return jsonResult({ success: true, authKey: result.authKey });
|
|
1107
1216
|
}
|
|
1108
|
-
|
|
1217
|
+
const failure = result as { success: false; error: string; errorCode?: string; errorMessage?: string };
|
|
1218
|
+
return jsonResult({
|
|
1109
1219
|
success: false,
|
|
1110
|
-
error:
|
|
1111
|
-
...(
|
|
1112
|
-
...(
|
|
1113
|
-
};
|
|
1220
|
+
error: failure.error,
|
|
1221
|
+
...(failure.errorCode ? { errorCode: failure.errorCode } : {}),
|
|
1222
|
+
...(failure.errorMessage ? { errorMessage: failure.errorMessage } : {}),
|
|
1223
|
+
});
|
|
1114
1224
|
},
|
|
1115
|
-
}))
|
|
1225
|
+
}), { name: "kichi_join" });
|
|
1116
1226
|
|
|
1117
|
-
api.registerTool((ctx
|
|
1118
|
-
const locator = resolveToolLocator(ctx);
|
|
1119
|
-
const agentId = runtimeManager.resolveRuntimeAgentId(locator);
|
|
1120
|
-
if (!agentId) {
|
|
1121
|
-
throw new Error("Failed to resolve agent-scoped Kichi runtime");
|
|
1122
|
-
}
|
|
1123
|
-
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
1124
|
-
return ({
|
|
1227
|
+
api.registerTool((ctx) => ({
|
|
1125
1228
|
name: "kichi_switch_host",
|
|
1229
|
+
label: "kichi_switch_host",
|
|
1126
1230
|
description:
|
|
1127
|
-
"Switch Kichi runtime
|
|
1231
|
+
"Switch Kichi runtime environment and reconnect immediately without restarting the gateway. Host is resolved from config/environments.json.",
|
|
1128
1232
|
parameters: {
|
|
1129
1233
|
type: "object",
|
|
1130
1234
|
properties: {
|
|
1131
|
-
|
|
1235
|
+
environment: {
|
|
1132
1236
|
type: "string",
|
|
1133
|
-
|
|
1237
|
+
enum: VALID_ENVIRONMENTS,
|
|
1238
|
+
description: "Target environment: steam, steam-playtest, or test",
|
|
1134
1239
|
},
|
|
1135
1240
|
},
|
|
1136
|
-
required: ["
|
|
1241
|
+
required: ["environment"],
|
|
1137
1242
|
},
|
|
1138
1243
|
execute: async (_toolCallId, params) => {
|
|
1139
|
-
const
|
|
1140
|
-
|
|
1141
|
-
|
|
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);
|
|
1250
|
+
const environment = (params as { environment?: unknown } | null)?.environment;
|
|
1251
|
+
if (!isKichiEnvironment(environment)) {
|
|
1252
|
+
return jsonResult({ success: false, error: `environment must be one of: ${VALID_ENVIRONMENTS.join(", ")}` });
|
|
1142
1253
|
}
|
|
1143
1254
|
|
|
1144
|
-
const
|
|
1145
|
-
|
|
1255
|
+
const resolved = resolveEnvironmentHost(environment);
|
|
1256
|
+
if (resolved.error) {
|
|
1257
|
+
return jsonResult({ success: false, error: resolved.error });
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
const status = await service.switchHost(resolved.host!, environment);
|
|
1261
|
+
return jsonResult({
|
|
1146
1262
|
success: true,
|
|
1147
|
-
|
|
1263
|
+
environment,
|
|
1264
|
+
host: resolved.host,
|
|
1148
1265
|
status,
|
|
1149
|
-
};
|
|
1266
|
+
});
|
|
1150
1267
|
},
|
|
1151
|
-
|
|
1152
|
-
});
|
|
1268
|
+
}), { name: "kichi_switch_host" });
|
|
1153
1269
|
|
|
1154
|
-
api.registerTool(
|
|
1270
|
+
api.registerTool((ctx) => ({
|
|
1155
1271
|
name: "kichi_rejoin",
|
|
1272
|
+
label: "kichi_rejoin",
|
|
1156
1273
|
description:
|
|
1157
1274
|
"Request an immediate rejoin attempt with saved avatarId/authKey. Rejoin is also sent automatically after reconnect.",
|
|
1158
1275
|
parameters: { type: "object", properties: {} },
|
|
1159
|
-
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);
|
|
1160
1283
|
const result = service.requestRejoin();
|
|
1161
|
-
return {
|
|
1284
|
+
return jsonResult({
|
|
1162
1285
|
success: result.accepted,
|
|
1163
1286
|
...result,
|
|
1164
1287
|
status: service.getConnectionStatus(),
|
|
1165
|
-
};
|
|
1288
|
+
});
|
|
1166
1289
|
},
|
|
1167
|
-
}))
|
|
1290
|
+
}), { name: "kichi_rejoin" });
|
|
1168
1291
|
|
|
1169
|
-
api.registerTool(
|
|
1292
|
+
api.registerTool((ctx) => ({
|
|
1170
1293
|
name: "kichi_leave",
|
|
1294
|
+
label: "kichi_leave",
|
|
1171
1295
|
description: "Leave Kichi world",
|
|
1172
1296
|
parameters: { type: "object", properties: {} },
|
|
1173
|
-
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);
|
|
1174
1304
|
const result = await service.leave();
|
|
1175
1305
|
if (result.success) {
|
|
1176
|
-
return { success: true };
|
|
1306
|
+
return jsonResult({ success: true });
|
|
1177
1307
|
}
|
|
1178
|
-
|
|
1308
|
+
const failure = result as { success: false; error: string; errorCode?: string; errorMessage?: string };
|
|
1309
|
+
return jsonResult({
|
|
1179
1310
|
success: false,
|
|
1180
|
-
error:
|
|
1181
|
-
...(
|
|
1182
|
-
...(
|
|
1183
|
-
};
|
|
1311
|
+
error: failure.error,
|
|
1312
|
+
...(failure.errorCode ? { errorCode: failure.errorCode } : {}),
|
|
1313
|
+
...(failure.errorMessage ? { errorMessage: failure.errorMessage } : {}),
|
|
1314
|
+
});
|
|
1184
1315
|
},
|
|
1185
|
-
}))
|
|
1316
|
+
}), { name: "kichi_leave" });
|
|
1186
1317
|
|
|
1187
|
-
api.registerTool(
|
|
1318
|
+
api.registerTool((ctx) => ({
|
|
1188
1319
|
name: "kichi_connection_status",
|
|
1320
|
+
label: "kichi_connection_status",
|
|
1189
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.",
|
|
1190
1322
|
parameters: { type: "object", properties: {} },
|
|
1191
|
-
execute: async () => {
|
|
1192
|
-
|
|
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);
|
|
1330
|
+
return jsonResult({
|
|
1193
1331
|
success: true,
|
|
1194
1332
|
status: service.getConnectionStatus(),
|
|
1195
|
-
};
|
|
1333
|
+
});
|
|
1196
1334
|
},
|
|
1197
|
-
}))
|
|
1335
|
+
}), { name: "kichi_connection_status" });
|
|
1198
1336
|
|
|
1199
|
-
api.registerTool(
|
|
1337
|
+
api.registerTool((ctx) => {
|
|
1338
|
+
const locator = resolveToolLocator(ctx);
|
|
1339
|
+
const existingService = runtimeManager.getRuntime(locator);
|
|
1340
|
+
return ({
|
|
1200
1341
|
name: "kichi_action",
|
|
1201
|
-
|
|
1342
|
+
label: "kichi_action",
|
|
1343
|
+
description: buildKichiActionDescription(existingService ?? undefined),
|
|
1202
1344
|
parameters: {
|
|
1203
1345
|
type: "object",
|
|
1204
1346
|
properties: {
|
|
@@ -1218,39 +1360,51 @@ const plugin = {
|
|
|
1218
1360
|
description:
|
|
1219
1361
|
"Set true ONLY when the user explicitly requests a pose or action. Omit during routine sync steps.",
|
|
1220
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
|
+
},
|
|
1221
1368
|
},
|
|
1222
1369
|
required: ["poseType", "action"],
|
|
1223
1370
|
},
|
|
1224
1371
|
execute: async (_toolCallId, params) => {
|
|
1225
|
-
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 {
|
|
1226
1379
|
poseType?: string;
|
|
1227
1380
|
action?: string;
|
|
1228
1381
|
bubble?: string;
|
|
1229
1382
|
log?: string;
|
|
1230
1383
|
verify?: boolean;
|
|
1384
|
+
propId?: string;
|
|
1231
1385
|
};
|
|
1232
1386
|
if (!poseType || !action) {
|
|
1233
|
-
return { success: false, error: "poseType and action parameters are required" };
|
|
1387
|
+
return jsonResult({ success: false, error: "poseType and action parameters are required" });
|
|
1234
1388
|
}
|
|
1235
1389
|
if (!["stand", "sit", "lay", "floor"].includes(poseType)) {
|
|
1236
|
-
return {
|
|
1390
|
+
return jsonResult({
|
|
1237
1391
|
success: false,
|
|
1238
1392
|
error: `Invalid poseType: ${poseType}. Must be stand, sit, lay, or floor`,
|
|
1239
|
-
};
|
|
1393
|
+
});
|
|
1240
1394
|
}
|
|
1241
1395
|
if (!service.hasValidIdentity() || !service.isConnected()) {
|
|
1242
|
-
return { success: false, error: "Not connected to Kichi world" };
|
|
1396
|
+
return jsonResult({ success: false, error: "Not connected to Kichi world" });
|
|
1243
1397
|
}
|
|
1244
1398
|
|
|
1245
1399
|
const normalizedPoseType = poseType as PoseType;
|
|
1246
1400
|
const poseActions = loadStaticConfig().actions[normalizedPoseType];
|
|
1247
1401
|
const matched = poseActions.find((entry) => entry.name.toLowerCase() === action.toLowerCase());
|
|
1248
1402
|
if (!matched) {
|
|
1249
|
-
return {
|
|
1403
|
+
return jsonResult({
|
|
1250
1404
|
success: false,
|
|
1251
1405
|
error: `Unknown action "${action}" for poseType "${poseType}"`,
|
|
1252
1406
|
available: poseActions.map((entry) => entry.name),
|
|
1253
|
-
};
|
|
1407
|
+
});
|
|
1254
1408
|
}
|
|
1255
1409
|
|
|
1256
1410
|
const bubbleText = typeof bubble === "string" && bubble.trim() ? bubble.trim() : matched.name;
|
|
@@ -1260,15 +1414,15 @@ const plugin = {
|
|
|
1260
1414
|
if (verify) {
|
|
1261
1415
|
try {
|
|
1262
1416
|
const ack = await service.sendStatusVerified(
|
|
1263
|
-
normalizedPoseType, matched.name, bubbleText, logText, playback,
|
|
1417
|
+
normalizedPoseType, matched.name, bubbleText, logText, playback, propId,
|
|
1264
1418
|
);
|
|
1265
1419
|
if (ack.warning) {
|
|
1266
|
-
return {
|
|
1420
|
+
return jsonResult({
|
|
1267
1421
|
success: true,
|
|
1268
1422
|
requested: { poseType: normalizedPoseType, action: matched.name },
|
|
1269
1423
|
actual: { poseType: ack.poseType, action: ack.action },
|
|
1270
1424
|
warning: ack.warning,
|
|
1271
|
-
};
|
|
1425
|
+
});
|
|
1272
1426
|
}
|
|
1273
1427
|
} catch {
|
|
1274
1428
|
// Server not updated or timeout — fall through to normal success
|
|
@@ -1279,21 +1433,23 @@ const plugin = {
|
|
|
1279
1433
|
action: matched.name,
|
|
1280
1434
|
bubble: bubbleText,
|
|
1281
1435
|
log: logText,
|
|
1436
|
+
propId,
|
|
1282
1437
|
});
|
|
1283
1438
|
}
|
|
1284
1439
|
|
|
1285
|
-
return {
|
|
1440
|
+
return jsonResult({
|
|
1286
1441
|
success: true,
|
|
1287
1442
|
poseType: normalizedPoseType,
|
|
1288
1443
|
action: matched.name,
|
|
1289
1444
|
bubble: bubbleText,
|
|
1290
1445
|
log: logText,
|
|
1291
1446
|
playback,
|
|
1292
|
-
};
|
|
1447
|
+
});
|
|
1293
1448
|
},
|
|
1294
|
-
}))
|
|
1295
|
-
api.registerTool(
|
|
1449
|
+
})}, { name: "kichi_action" });
|
|
1450
|
+
api.registerTool((ctx) => ({
|
|
1296
1451
|
name: "kichi_idle_plan",
|
|
1452
|
+
label: "kichi_idle_plan",
|
|
1297
1453
|
description: buildKichiIdlePlanDescription(),
|
|
1298
1454
|
parameters: {
|
|
1299
1455
|
type: "object",
|
|
@@ -1359,6 +1515,10 @@ const plugin = {
|
|
|
1359
1515
|
type: "string",
|
|
1360
1516
|
description: "Optional log content for this action. Use the same language as the current conversation.",
|
|
1361
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
|
+
},
|
|
1362
1522
|
},
|
|
1363
1523
|
required: ["poseType", "action", "durationSeconds", "bubble"],
|
|
1364
1524
|
},
|
|
@@ -1371,12 +1531,18 @@ const plugin = {
|
|
|
1371
1531
|
required: ["heartbeatIntervalSeconds", "goal", "stages"],
|
|
1372
1532
|
},
|
|
1373
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);
|
|
1374
1540
|
const { idlePlan, error } = normalizeIdlePlan(params);
|
|
1375
1541
|
if (!idlePlan) {
|
|
1376
|
-
return { success: false, error: error ?? "Invalid idle plan payload" };
|
|
1542
|
+
return jsonResult({ success: false, error: error ?? "Invalid idle plan payload" });
|
|
1377
1543
|
}
|
|
1378
1544
|
if (!service.hasValidIdentity() || !service.isConnected()) {
|
|
1379
|
-
return { success: false, error: "Not connected to Kichi world" };
|
|
1545
|
+
return jsonResult({ success: false, error: "Not connected to Kichi world" });
|
|
1380
1546
|
}
|
|
1381
1547
|
const sent = service.sendIdlePlan({
|
|
1382
1548
|
...(idlePlan.requestId ? { requestId: idlePlan.requestId } : {}),
|
|
@@ -1385,20 +1551,21 @@ const plugin = {
|
|
|
1385
1551
|
stages: idlePlan.stages,
|
|
1386
1552
|
});
|
|
1387
1553
|
if (!sent) {
|
|
1388
|
-
return { success: false, error: "Failed to send idle plan payload" };
|
|
1554
|
+
return jsonResult({ success: false, error: "Failed to send idle plan payload" });
|
|
1389
1555
|
}
|
|
1390
|
-
return {
|
|
1556
|
+
return jsonResult({
|
|
1391
1557
|
success: true,
|
|
1392
1558
|
...(idlePlan.requestId ? { requestId: idlePlan.requestId } : {}),
|
|
1393
1559
|
heartbeatIntervalSeconds: idlePlan.heartbeatIntervalSeconds,
|
|
1394
1560
|
totalDurationSeconds: idlePlan.totalDurationSeconds,
|
|
1395
1561
|
goal: idlePlan.goal,
|
|
1396
1562
|
stages: idlePlan.stages,
|
|
1397
|
-
};
|
|
1563
|
+
});
|
|
1398
1564
|
},
|
|
1399
|
-
}))
|
|
1400
|
-
api.registerTool(
|
|
1565
|
+
}), { name: "kichi_idle_plan" });
|
|
1566
|
+
api.registerTool((ctx) => ({
|
|
1401
1567
|
name: "kichi_clock",
|
|
1568
|
+
label: "kichi_clock",
|
|
1402
1569
|
description:
|
|
1403
1570
|
"Send clock commands to Kichi world. Supported actions are set and stop.",
|
|
1404
1571
|
parameters: {
|
|
@@ -1466,6 +1633,12 @@ const plugin = {
|
|
|
1466
1633
|
required: ["action"],
|
|
1467
1634
|
},
|
|
1468
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);
|
|
1469
1642
|
const { action, requestId, clock } = (params || {}) as {
|
|
1470
1643
|
action?: unknown;
|
|
1471
1644
|
requestId?: unknown;
|
|
@@ -1473,46 +1646,47 @@ const plugin = {
|
|
|
1473
1646
|
};
|
|
1474
1647
|
|
|
1475
1648
|
if (!isClockAction(action)) {
|
|
1476
|
-
return {
|
|
1649
|
+
return jsonResult({
|
|
1477
1650
|
success: false,
|
|
1478
1651
|
error: "action must be one of: set, stop",
|
|
1479
|
-
};
|
|
1652
|
+
});
|
|
1480
1653
|
}
|
|
1481
1654
|
if (requestId !== undefined && typeof requestId !== "string") {
|
|
1482
|
-
return { success: false, error: "requestId must be a string when provided" };
|
|
1655
|
+
return jsonResult({ success: false, error: "requestId must be a string when provided" });
|
|
1483
1656
|
}
|
|
1484
1657
|
const normalizedRequestId = typeof requestId === "string" ? requestId : undefined;
|
|
1485
1658
|
if (!service.hasValidIdentity() || !service.isConnected()) {
|
|
1486
|
-
return { success: false, error: "Not connected to Kichi world" };
|
|
1659
|
+
return jsonResult({ success: false, error: "Not connected to Kichi world" });
|
|
1487
1660
|
}
|
|
1488
1661
|
|
|
1489
1662
|
let normalizedClock: ClockConfig | undefined;
|
|
1490
1663
|
if (action === "set") {
|
|
1491
1664
|
const { clock: nextClock, error } = normalizeClockConfig(clock);
|
|
1492
1665
|
if (!nextClock) {
|
|
1493
|
-
return { success: false, error: error ?? "Invalid clock payload" };
|
|
1666
|
+
return jsonResult({ success: false, error: error ?? "Invalid clock payload" });
|
|
1494
1667
|
}
|
|
1495
1668
|
normalizedClock = nextClock;
|
|
1496
1669
|
}
|
|
1497
1670
|
|
|
1498
1671
|
const sent = service.sendClock(action, normalizedClock, normalizedRequestId);
|
|
1499
1672
|
if (!sent) {
|
|
1500
|
-
return { success: false, error: "Failed to send clock payload" };
|
|
1673
|
+
return jsonResult({ success: false, error: "Failed to send clock payload" });
|
|
1501
1674
|
}
|
|
1502
1675
|
|
|
1503
|
-
return {
|
|
1676
|
+
return jsonResult({
|
|
1504
1677
|
success: true,
|
|
1505
1678
|
action,
|
|
1506
1679
|
requestId: normalizedRequestId,
|
|
1507
1680
|
...(normalizedClock ? { clock: normalizedClock } : {}),
|
|
1508
|
-
};
|
|
1681
|
+
});
|
|
1509
1682
|
},
|
|
1510
|
-
}))
|
|
1683
|
+
}), { name: "kichi_clock" });
|
|
1511
1684
|
|
|
1512
|
-
api.registerTool(
|
|
1685
|
+
api.registerTool((ctx) => ({
|
|
1513
1686
|
name: "kichi_query_status",
|
|
1687
|
+
label: "kichi_query_status",
|
|
1514
1688
|
description:
|
|
1515
|
-
"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.",
|
|
1516
1690
|
parameters: {
|
|
1517
1691
|
type: "object",
|
|
1518
1692
|
properties: {
|
|
@@ -1523,30 +1697,37 @@ const plugin = {
|
|
|
1523
1697
|
},
|
|
1524
1698
|
},
|
|
1525
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);
|
|
1526
1706
|
const requestId = (params as { requestId?: unknown } | null)?.requestId;
|
|
1527
1707
|
if (requestId !== undefined && typeof requestId !== "string") {
|
|
1528
|
-
return { success: false, error: "requestId must be a string when provided" };
|
|
1708
|
+
return jsonResult({ success: false, error: "requestId must be a string when provided" });
|
|
1529
1709
|
}
|
|
1530
1710
|
if (!service.hasValidIdentity() || !service.isConnected()) {
|
|
1531
|
-
return { success: false, error: "Not connected to Kichi world" };
|
|
1711
|
+
return jsonResult({ success: false, error: "Not connected to Kichi world" });
|
|
1532
1712
|
}
|
|
1533
1713
|
|
|
1534
1714
|
try {
|
|
1535
1715
|
const result = await service.queryStatus(
|
|
1536
1716
|
typeof requestId === "string" ? requestId : undefined,
|
|
1537
1717
|
);
|
|
1538
|
-
return result;
|
|
1718
|
+
return jsonResult(result);
|
|
1539
1719
|
} catch (error) {
|
|
1540
|
-
return {
|
|
1720
|
+
return jsonResult({
|
|
1541
1721
|
success: false,
|
|
1542
1722
|
error: `Failed to query status: ${error}`,
|
|
1543
|
-
};
|
|
1723
|
+
});
|
|
1544
1724
|
}
|
|
1545
1725
|
},
|
|
1546
|
-
}))
|
|
1726
|
+
}), { name: "kichi_query_status" });
|
|
1547
1727
|
|
|
1548
|
-
api.registerTool(
|
|
1728
|
+
api.registerTool((ctx) => ({
|
|
1549
1729
|
name: "kichi_music_album_create",
|
|
1730
|
+
label: "kichi_music_album_create",
|
|
1550
1731
|
description: buildMusicAlbumToolDescription(),
|
|
1551
1732
|
parameters: {
|
|
1552
1733
|
type: "object",
|
|
@@ -1571,6 +1752,12 @@ const plugin = {
|
|
|
1571
1752
|
required: ["albumTitle", "musicTitles"],
|
|
1572
1753
|
},
|
|
1573
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);
|
|
1574
1761
|
const {
|
|
1575
1762
|
requestId,
|
|
1576
1763
|
albumTitle,
|
|
@@ -1582,33 +1769,33 @@ const plugin = {
|
|
|
1582
1769
|
};
|
|
1583
1770
|
|
|
1584
1771
|
if (requestId !== undefined && typeof requestId !== "string") {
|
|
1585
|
-
return { success: false, error: "requestId must be a string when provided" };
|
|
1772
|
+
return jsonResult({ success: false, error: "requestId must be a string when provided" });
|
|
1586
1773
|
}
|
|
1587
1774
|
if (typeof albumTitle !== "string" || !albumTitle.trim()) {
|
|
1588
|
-
return { success: false, error: "albumTitle is required" };
|
|
1775
|
+
return jsonResult({ success: false, error: "albumTitle is required" });
|
|
1589
1776
|
}
|
|
1590
1777
|
if (!Array.isArray(musicTitles)) {
|
|
1591
|
-
return { success: false, error: "musicTitles must be an array of track names" };
|
|
1778
|
+
return jsonResult({ success: false, error: "musicTitles must be an array of track names" });
|
|
1592
1779
|
}
|
|
1593
1780
|
|
|
1594
1781
|
const { titles: normalizedTitles, invalidTitles } = normalizeMusicTitles(musicTitles);
|
|
1595
1782
|
if (normalizedTitles.length === 0) {
|
|
1596
|
-
return {
|
|
1783
|
+
return jsonResult({
|
|
1597
1784
|
success: false,
|
|
1598
1785
|
error: "musicTitles must contain at least one valid track name from the static config bundled with the plugin package",
|
|
1599
1786
|
examples: getMusicTitleExamples(),
|
|
1600
|
-
};
|
|
1787
|
+
});
|
|
1601
1788
|
}
|
|
1602
1789
|
if (invalidTitles.length > 0) {
|
|
1603
|
-
return {
|
|
1790
|
+
return jsonResult({
|
|
1604
1791
|
success: false,
|
|
1605
1792
|
error: `Unknown musicTitles: ${invalidTitles.join(", ")}`,
|
|
1606
1793
|
hint: "Use exact track names from the static config bundled with the plugin package",
|
|
1607
1794
|
examples: getMusicTitleExamples(),
|
|
1608
|
-
};
|
|
1795
|
+
});
|
|
1609
1796
|
}
|
|
1610
1797
|
if (!service.hasValidIdentity() || !service.isConnected()) {
|
|
1611
|
-
return { success: false, error: "Not connected to Kichi world" };
|
|
1798
|
+
return jsonResult({ success: false, error: "Not connected to Kichi world" });
|
|
1612
1799
|
}
|
|
1613
1800
|
|
|
1614
1801
|
try {
|
|
@@ -1617,24 +1804,25 @@ const plugin = {
|
|
|
1617
1804
|
normalizedTitles,
|
|
1618
1805
|
typeof requestId === "string" ? requestId : undefined,
|
|
1619
1806
|
);
|
|
1620
|
-
return {
|
|
1807
|
+
return jsonResult({
|
|
1621
1808
|
success: true,
|
|
1622
1809
|
requestId: normalizedRequestId,
|
|
1623
1810
|
albumTitle: albumTitle.trim(),
|
|
1624
1811
|
musicTitles: normalizedTitles,
|
|
1625
1812
|
trackCount: normalizedTitles.length,
|
|
1626
|
-
};
|
|
1813
|
+
});
|
|
1627
1814
|
} catch (error) {
|
|
1628
|
-
return {
|
|
1815
|
+
return jsonResult({
|
|
1629
1816
|
success: false,
|
|
1630
1817
|
error: `Failed to create music album: ${error}`,
|
|
1631
|
-
};
|
|
1818
|
+
});
|
|
1632
1819
|
}
|
|
1633
1820
|
},
|
|
1634
|
-
}))
|
|
1821
|
+
}), { name: "kichi_music_album_create" });
|
|
1635
1822
|
|
|
1636
|
-
api.registerTool(
|
|
1823
|
+
api.registerTool((ctx) => ({
|
|
1637
1824
|
name: "kichi_noteboard_create",
|
|
1825
|
+
label: "kichi_noteboard_create",
|
|
1638
1826
|
description:
|
|
1639
1827
|
"Create a new note on a specific Kichi note board. Prefer querying first so you can avoid duplicate posts and respect rate limits.",
|
|
1640
1828
|
parameters: {
|
|
@@ -1652,37 +1840,125 @@ const plugin = {
|
|
|
1652
1840
|
required: ["propId", "data"],
|
|
1653
1841
|
},
|
|
1654
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);
|
|
1655
1849
|
const { propId, data } = (params || {}) as {
|
|
1656
1850
|
propId?: unknown;
|
|
1657
1851
|
data?: unknown;
|
|
1658
1852
|
};
|
|
1659
1853
|
if (typeof propId !== "string" || !propId.trim()) {
|
|
1660
|
-
return { success: false, error: "propId is required" };
|
|
1854
|
+
return jsonResult({ success: false, error: "propId is required" });
|
|
1661
1855
|
}
|
|
1662
1856
|
if (typeof data !== "string" || !data.trim()) {
|
|
1663
|
-
return { success: false, error: "data is required" };
|
|
1857
|
+
return jsonResult({ success: false, error: "data is required" });
|
|
1664
1858
|
}
|
|
1665
1859
|
if (data.trim().length > MAX_NOTEBOARD_TEXT_LENGTH) {
|
|
1666
|
-
return {
|
|
1860
|
+
return jsonResult({
|
|
1667
1861
|
success: false,
|
|
1668
1862
|
error: `data must be ${MAX_NOTEBOARD_TEXT_LENGTH} characters or fewer`,
|
|
1669
|
-
};
|
|
1863
|
+
});
|
|
1670
1864
|
}
|
|
1671
1865
|
if (!service.hasValidIdentity() || !service.isConnected()) {
|
|
1672
|
-
return { success: false, error: "Not connected to Kichi world" };
|
|
1866
|
+
return jsonResult({ success: false, error: "Not connected to Kichi world" });
|
|
1673
1867
|
}
|
|
1674
1868
|
|
|
1675
1869
|
try {
|
|
1676
1870
|
service.createNotesBoardNote(propId.trim(), data.trim());
|
|
1677
|
-
return { success: true };
|
|
1871
|
+
return jsonResult({ success: true });
|
|
1678
1872
|
} catch (error) {
|
|
1679
|
-
return {
|
|
1873
|
+
return jsonResult({
|
|
1680
1874
|
success: false,
|
|
1681
1875
|
error: `Failed to create note: ${error}`,
|
|
1682
|
-
};
|
|
1876
|
+
});
|
|
1877
|
+
}
|
|
1878
|
+
},
|
|
1879
|
+
}), { name: "kichi_noteboard_create" });
|
|
1880
|
+
|
|
1881
|
+
api.registerTool((ctx) => ({
|
|
1882
|
+
name: "kichi_bot_message",
|
|
1883
|
+
label: "kichi_bot_message",
|
|
1884
|
+
description:
|
|
1885
|
+
"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.",
|
|
1886
|
+
parameters: {
|
|
1887
|
+
type: "object",
|
|
1888
|
+
properties: {
|
|
1889
|
+
toAvatarId: {
|
|
1890
|
+
type: "string",
|
|
1891
|
+
description: "Target bot's avatarId (resolve via kichi_query_status if unknown). Use \"*\" only for broadcasting to all bots.",
|
|
1892
|
+
},
|
|
1893
|
+
depth: {
|
|
1894
|
+
type: "number",
|
|
1895
|
+
description: "Conversation depth counter. Increment from the received message's depth.",
|
|
1896
|
+
},
|
|
1897
|
+
bubble: {
|
|
1898
|
+
type: "string",
|
|
1899
|
+
description: "The message to send (2-5 words, visible to everyone). Must not repeat previous messages.",
|
|
1900
|
+
},
|
|
1901
|
+
poseType: {
|
|
1902
|
+
type: "string",
|
|
1903
|
+
enum: ["stand", "sit", "lay", "floor"],
|
|
1904
|
+
description: "Optional pose change when sending.",
|
|
1905
|
+
},
|
|
1906
|
+
action: {
|
|
1907
|
+
type: "string",
|
|
1908
|
+
description: "Optional action to perform when sending.",
|
|
1909
|
+
},
|
|
1910
|
+
log: {
|
|
1911
|
+
type: "string",
|
|
1912
|
+
description: "Optional activity log entry.",
|
|
1913
|
+
},
|
|
1914
|
+
},
|
|
1915
|
+
required: ["toAvatarId", "depth", "bubble"],
|
|
1916
|
+
},
|
|
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);
|
|
1924
|
+
const { toAvatarId, depth, bubble, poseType, action, log } = (params || {}) as {
|
|
1925
|
+
toAvatarId?: string;
|
|
1926
|
+
depth?: number;
|
|
1927
|
+
bubble?: string;
|
|
1928
|
+
poseType?: PoseType;
|
|
1929
|
+
action?: string;
|
|
1930
|
+
log?: string;
|
|
1931
|
+
};
|
|
1932
|
+
if (typeof toAvatarId !== "string" || !toAvatarId.trim()) {
|
|
1933
|
+
return jsonResult({ success: false, error: "toAvatarId is required" });
|
|
1934
|
+
}
|
|
1935
|
+
if (typeof depth !== "number" || depth < 0) {
|
|
1936
|
+
return jsonResult({ success: false, error: "depth must be a non-negative number" });
|
|
1937
|
+
}
|
|
1938
|
+
if (typeof bubble !== "string" || !bubble.trim()) {
|
|
1939
|
+
return jsonResult({ success: false, error: "bubble is required" });
|
|
1940
|
+
}
|
|
1941
|
+
if (!service.hasValidIdentity() || !service.isConnected()) {
|
|
1942
|
+
return jsonResult({ success: false, error: "Not connected to Kichi world" });
|
|
1943
|
+
}
|
|
1944
|
+
try {
|
|
1945
|
+
let playback: ActionPlayback | undefined;
|
|
1946
|
+
if (poseType && action) {
|
|
1947
|
+
const actionDef = getActionDefinition(poseType, action);
|
|
1948
|
+
playback = getActionPlayback(actionDef);
|
|
1949
|
+
}
|
|
1950
|
+
const ack = await service.sendBotMessage(toAvatarId.trim(), depth, bubble.trim(), {
|
|
1951
|
+
poseType,
|
|
1952
|
+
action: action?.trim(),
|
|
1953
|
+
log: log?.trim(),
|
|
1954
|
+
playback,
|
|
1955
|
+
});
|
|
1956
|
+
return jsonResult({ success: true, ...ack });
|
|
1957
|
+
} catch (error) {
|
|
1958
|
+
return jsonResult({ success: false, error: `Failed to send bot message: ${error}` });
|
|
1683
1959
|
}
|
|
1684
1960
|
},
|
|
1685
|
-
}))
|
|
1961
|
+
}), { name: "kichi_bot_message" });
|
|
1686
1962
|
|
|
1687
1963
|
},
|
|
1688
1964
|
};
|