@yahaha-studio/kichi-forwarder 0.1.2-beta.1 → 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/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 +359 -134
- package/openclaw.plugin.json +17 -1
- package/package.json +16 -7
- package/skills/kichi-forwarder/SKILL.md +20 -3
- package/skills/kichi-forwarder/references/error.md +3 -11
- package/skills/kichi-forwarder/references/heartbeat.md +5 -15
- package/skills/kichi-forwarder/references/install.md +12 -22
- package/src/runtime-manager.ts +14 -2
- package/src/service.ts +50 -4
- package/src/types.ts +34 -0
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,6 +13,8 @@ import type {
|
|
|
13
13
|
ActionPlayback,
|
|
14
14
|
ActionResult,
|
|
15
15
|
Album,
|
|
16
|
+
BotMessageHistoryEntry,
|
|
17
|
+
BotMessageReceivedPayload,
|
|
16
18
|
ClockAction,
|
|
17
19
|
ClockConfig,
|
|
18
20
|
KichiEnvironment,
|
|
@@ -22,6 +24,10 @@ import type {
|
|
|
22
24
|
PoseType,
|
|
23
25
|
} from "./src/types.js";
|
|
24
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
|
+
}
|
|
25
31
|
const BUNDLED_ENVIRONMENTS_CONFIG_PATH = new URL("./config/environments.json", import.meta.url);
|
|
26
32
|
const FIXED_HOOK_STATUSES: Record<string, ActionResult> = {
|
|
27
33
|
beforePromptBuild: {
|
|
@@ -257,6 +263,7 @@ function sendStatusUpdate(service: KichiForwarderService, status: ActionResult):
|
|
|
257
263
|
status.bubble || status.action,
|
|
258
264
|
typeof status.log === "string" ? status.log.trim() : "",
|
|
259
265
|
getActionPlayback(actionDefinition),
|
|
266
|
+
status.propId,
|
|
260
267
|
);
|
|
261
268
|
}
|
|
262
269
|
|
|
@@ -705,6 +712,7 @@ function normalizeIdlePlan(value: unknown): { idlePlan?: IdlePlan; error?: strin
|
|
|
705
712
|
const actionDurationSeconds = rawAction.durationSeconds;
|
|
706
713
|
const bubble = rawAction.bubble;
|
|
707
714
|
const log = rawAction.log;
|
|
715
|
+
const propId = rawAction.propId;
|
|
708
716
|
|
|
709
717
|
if (!["stand", "sit", "lay", "floor"].includes(String(poseType))) {
|
|
710
718
|
return {
|
|
@@ -752,6 +760,7 @@ function normalizeIdlePlan(value: unknown): { idlePlan?: IdlePlan; error?: strin
|
|
|
752
760
|
durationSeconds: actionDurationSeconds,
|
|
753
761
|
bubble: bubble.trim(),
|
|
754
762
|
...(typeof log === "string" && log.trim() ? { log: log.trim() } : {}),
|
|
763
|
+
...(typeof propId === "string" && propId.trim() ? { propId: propId.trim() } : {}),
|
|
755
764
|
});
|
|
756
765
|
}
|
|
757
766
|
|
|
@@ -985,9 +994,9 @@ function formatActionList(actions: ActionDefinition[], playback: ActionPlayback[
|
|
|
985
994
|
.join(", ");
|
|
986
995
|
}
|
|
987
996
|
|
|
988
|
-
function buildKichiActionDescription(): string {
|
|
997
|
+
function buildKichiActionDescription(service?: KichiForwarderService): string {
|
|
989
998
|
const actions = loadStaticConfig().actions;
|
|
990
|
-
|
|
999
|
+
const lines = [
|
|
991
1000
|
"Directly control the avatar inside Kichi World.",
|
|
992
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.",
|
|
993
1002
|
"For most work, prefer a sit pose and switch actions as the task moves between stages.",
|
|
@@ -996,7 +1005,20 @@ function buildKichiActionDescription(): string {
|
|
|
996
1005
|
`sit actions: ${actions.sit.map((entry) => entry.name).join(", ")}`,
|
|
997
1006
|
`lay actions: ${actions.lay.map((entry) => entry.name).join(", ")}`,
|
|
998
1007
|
`floor actions: ${actions.floor.map((entry) => entry.name).join(", ")}`,
|
|
999
|
-
]
|
|
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");
|
|
1000
1022
|
}
|
|
1001
1023
|
|
|
1002
1024
|
function buildKichiIdlePlanDescription(): string {
|
|
@@ -1006,12 +1028,13 @@ function buildKichiIdlePlanDescription(): string {
|
|
|
1006
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.",
|
|
1007
1029
|
"Build the plan in this order.",
|
|
1008
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.",
|
|
1009
|
-
"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.",
|
|
1010
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.",
|
|
1011
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.",
|
|
1012
1034
|
"5. Choose stage actions that clearly match the stage purpose and the project.",
|
|
1013
1035
|
"6. Write each action bubble as the current presented state, not a next step, plan, or instruction.",
|
|
1014
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.",
|
|
1015
1038
|
"Use the same language as the current conversation for goal, purpose, bubble, and log.",
|
|
1016
1039
|
`stand actions: ${actions.stand.map((entry) => entry.name).join(", ")}`,
|
|
1017
1040
|
`sit actions: ${actions.sit.map((entry) => entry.name).join(", ")}`,
|
|
@@ -1034,24 +1057,12 @@ function buildKichiPrompt(): string {
|
|
|
1034
1057
|
"",
|
|
1035
1058
|
"kichi_clock: set countDown for tasks with 2+ steps or >10s work. Skip for quick one-shots.",
|
|
1036
1059
|
"",
|
|
1060
|
+
"When sending a bot message, do NOT call kichi_action separately.",
|
|
1061
|
+
"",
|
|
1037
1062
|
"User opt-out, Kichi config/test work, and explicit pose requests take priority over sync.",
|
|
1038
1063
|
].join("\n");
|
|
1039
1064
|
}
|
|
1040
1065
|
|
|
1041
|
-
function createAgentScopedTool(
|
|
1042
|
-
runtimeManager: KichiRuntimeManager,
|
|
1043
|
-
factory: (service: KichiForwarderService, ctx: OpenClawPluginToolContext) => AnyAgentTool,
|
|
1044
|
-
) {
|
|
1045
|
-
return (ctx: OpenClawPluginToolContext) => {
|
|
1046
|
-
const locator = resolveToolLocator(ctx);
|
|
1047
|
-
const agentId = runtimeManager.resolveRuntimeAgentId(locator);
|
|
1048
|
-
if (!agentId) {
|
|
1049
|
-
throw new Error("Failed to resolve agent-scoped Kichi runtime");
|
|
1050
|
-
}
|
|
1051
|
-
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
1052
|
-
return factory(service, ctx);
|
|
1053
|
-
};
|
|
1054
|
-
}
|
|
1055
1066
|
|
|
1056
1067
|
const GLOBAL_RUNTIME_MANAGER_KEY = "__kichi_forwarder_runtime_manager__";
|
|
1057
1068
|
|
|
@@ -1070,6 +1081,10 @@ function getRuntimeManager(logger: OpenClawPluginApi["logger"]): KichiRuntimeMan
|
|
|
1070
1081
|
return runtimeManager;
|
|
1071
1082
|
}
|
|
1072
1083
|
|
|
1084
|
+
const BOT_MESSAGE_MAX_DEPTH = 5;
|
|
1085
|
+
const BOT_MESSAGE_COOLDOWN_MS = 5_000;
|
|
1086
|
+
const botMessageCooldowns = new Map<string, number>();
|
|
1087
|
+
|
|
1073
1088
|
const plugin = {
|
|
1074
1089
|
id: "kichi-forwarder",
|
|
1075
1090
|
name: "Kichi Forwarder",
|
|
@@ -1077,18 +1092,61 @@ const plugin = {
|
|
|
1077
1092
|
|
|
1078
1093
|
register(api: OpenClawPluginApi) {
|
|
1079
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
|
+
|
|
1080
1102
|
registerPluginHooks(api, runtimeManager);
|
|
1081
1103
|
const musicTitleEnum = getMusicTitleEnum();
|
|
1082
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
|
+
|
|
1083
1146
|
api.registerService({
|
|
1084
1147
|
id: "kichi-forwarder",
|
|
1085
1148
|
start: (ctx) => {
|
|
1086
1149
|
parse(ctx.config.plugins?.entries?.["kichi-forwarder"]?.config);
|
|
1087
|
-
runtimeManager.setEnvironmentHostResolver((environment) => {
|
|
1088
|
-
const config = loadEnvironmentsConfig();
|
|
1089
|
-
const host = config[environment];
|
|
1090
|
-
return typeof host === "string" && host.trim() ? host : null;
|
|
1091
|
-
});
|
|
1092
1150
|
runtimeManager.initializeStartupRuntimes();
|
|
1093
1151
|
},
|
|
1094
1152
|
stop: () => {
|
|
@@ -1100,8 +1158,9 @@ const plugin = {
|
|
|
1100
1158
|
},
|
|
1101
1159
|
});
|
|
1102
1160
|
|
|
1103
|
-
api.registerTool(
|
|
1161
|
+
api.registerTool((ctx) => ({
|
|
1104
1162
|
name: "kichi_join",
|
|
1163
|
+
label: "kichi_join",
|
|
1105
1164
|
description: "Join Kichi world with avatarId, the current bot name, a short bio, and personality tags",
|
|
1106
1165
|
parameters: {
|
|
1107
1166
|
type: "object",
|
|
@@ -1124,6 +1183,12 @@ const plugin = {
|
|
|
1124
1183
|
required: ["botName", "bio"],
|
|
1125
1184
|
},
|
|
1126
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);
|
|
1127
1192
|
let avatarId = (params as { avatarId?: string } | null)?.avatarId;
|
|
1128
1193
|
const botName = (params as { botName?: string } | null)?.botName?.trim();
|
|
1129
1194
|
const bio = (params as { bio?: string } | null)?.bio?.trim();
|
|
@@ -1134,39 +1199,34 @@ const plugin = {
|
|
|
1134
1199
|
avatarId = service.readSavedAvatarId() ?? undefined;
|
|
1135
1200
|
}
|
|
1136
1201
|
if (!avatarId) {
|
|
1137
|
-
return { success: false, error: "No avatarId" };
|
|
1202
|
+
return jsonResult({ success: false, error: "No avatarId" });
|
|
1138
1203
|
}
|
|
1139
1204
|
if (!botName) {
|
|
1140
|
-
return { success: false, error: "No botName" };
|
|
1205
|
+
return jsonResult({ success: false, error: "No botName" });
|
|
1141
1206
|
}
|
|
1142
1207
|
if (!bio) {
|
|
1143
|
-
return { success: false, error: "No bio" };
|
|
1208
|
+
return jsonResult({ success: false, error: "No bio" });
|
|
1144
1209
|
}
|
|
1145
1210
|
if (tagsError) {
|
|
1146
|
-
return { success: false, error: tagsError };
|
|
1211
|
+
return jsonResult({ success: false, error: tagsError });
|
|
1147
1212
|
}
|
|
1148
1213
|
const result = await service.join(avatarId, botName, bio, tags ?? []);
|
|
1149
1214
|
if (result.success) {
|
|
1150
|
-
return { success: true, authKey: result.authKey };
|
|
1215
|
+
return jsonResult({ success: true, authKey: result.authKey });
|
|
1151
1216
|
}
|
|
1152
|
-
|
|
1217
|
+
const failure = result as { success: false; error: string; errorCode?: string; errorMessage?: string };
|
|
1218
|
+
return jsonResult({
|
|
1153
1219
|
success: false,
|
|
1154
|
-
error:
|
|
1155
|
-
...(
|
|
1156
|
-
...(
|
|
1157
|
-
};
|
|
1220
|
+
error: failure.error,
|
|
1221
|
+
...(failure.errorCode ? { errorCode: failure.errorCode } : {}),
|
|
1222
|
+
...(failure.errorMessage ? { errorMessage: failure.errorMessage } : {}),
|
|
1223
|
+
});
|
|
1158
1224
|
},
|
|
1159
|
-
}))
|
|
1225
|
+
}), { name: "kichi_join" });
|
|
1160
1226
|
|
|
1161
|
-
api.registerTool((ctx
|
|
1162
|
-
const locator = resolveToolLocator(ctx);
|
|
1163
|
-
const agentId = runtimeManager.resolveRuntimeAgentId(locator);
|
|
1164
|
-
if (!agentId) {
|
|
1165
|
-
throw new Error("Failed to resolve agent-scoped Kichi runtime");
|
|
1166
|
-
}
|
|
1167
|
-
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
1168
|
-
return ({
|
|
1227
|
+
api.registerTool((ctx) => ({
|
|
1169
1228
|
name: "kichi_switch_host",
|
|
1229
|
+
label: "kichi_switch_host",
|
|
1170
1230
|
description:
|
|
1171
1231
|
"Switch Kichi runtime environment and reconnect immediately without restarting the gateway. Host is resolved from config/environments.json.",
|
|
1172
1232
|
parameters: {
|
|
@@ -1181,75 +1241,106 @@ const plugin = {
|
|
|
1181
1241
|
required: ["environment"],
|
|
1182
1242
|
},
|
|
1183
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);
|
|
1184
1250
|
const environment = (params as { environment?: unknown } | null)?.environment;
|
|
1185
1251
|
if (!isKichiEnvironment(environment)) {
|
|
1186
|
-
return { success: false, error: `environment must be one of: ${VALID_ENVIRONMENTS.join(", ")}` };
|
|
1252
|
+
return jsonResult({ success: false, error: `environment must be one of: ${VALID_ENVIRONMENTS.join(", ")}` });
|
|
1187
1253
|
}
|
|
1188
1254
|
|
|
1189
1255
|
const resolved = resolveEnvironmentHost(environment);
|
|
1190
1256
|
if (resolved.error) {
|
|
1191
|
-
return { success: false, error: resolved.error };
|
|
1257
|
+
return jsonResult({ success: false, error: resolved.error });
|
|
1192
1258
|
}
|
|
1193
1259
|
|
|
1194
1260
|
const status = await service.switchHost(resolved.host!, environment);
|
|
1195
|
-
return {
|
|
1261
|
+
return jsonResult({
|
|
1196
1262
|
success: true,
|
|
1197
1263
|
environment,
|
|
1198
1264
|
host: resolved.host,
|
|
1199
1265
|
status,
|
|
1200
|
-
};
|
|
1266
|
+
});
|
|
1201
1267
|
},
|
|
1202
|
-
|
|
1203
|
-
});
|
|
1268
|
+
}), { name: "kichi_switch_host" });
|
|
1204
1269
|
|
|
1205
|
-
api.registerTool(
|
|
1270
|
+
api.registerTool((ctx) => ({
|
|
1206
1271
|
name: "kichi_rejoin",
|
|
1272
|
+
label: "kichi_rejoin",
|
|
1207
1273
|
description:
|
|
1208
1274
|
"Request an immediate rejoin attempt with saved avatarId/authKey. Rejoin is also sent automatically after reconnect.",
|
|
1209
1275
|
parameters: { type: "object", properties: {} },
|
|
1210
|
-
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);
|
|
1211
1283
|
const result = service.requestRejoin();
|
|
1212
|
-
return {
|
|
1284
|
+
return jsonResult({
|
|
1213
1285
|
success: result.accepted,
|
|
1214
1286
|
...result,
|
|
1215
1287
|
status: service.getConnectionStatus(),
|
|
1216
|
-
};
|
|
1288
|
+
});
|
|
1217
1289
|
},
|
|
1218
|
-
}))
|
|
1290
|
+
}), { name: "kichi_rejoin" });
|
|
1219
1291
|
|
|
1220
|
-
api.registerTool(
|
|
1292
|
+
api.registerTool((ctx) => ({
|
|
1221
1293
|
name: "kichi_leave",
|
|
1294
|
+
label: "kichi_leave",
|
|
1222
1295
|
description: "Leave Kichi world",
|
|
1223
1296
|
parameters: { type: "object", properties: {} },
|
|
1224
|
-
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);
|
|
1225
1304
|
const result = await service.leave();
|
|
1226
1305
|
if (result.success) {
|
|
1227
|
-
return { success: true };
|
|
1306
|
+
return jsonResult({ success: true });
|
|
1228
1307
|
}
|
|
1229
|
-
|
|
1308
|
+
const failure = result as { success: false; error: string; errorCode?: string; errorMessage?: string };
|
|
1309
|
+
return jsonResult({
|
|
1230
1310
|
success: false,
|
|
1231
|
-
error:
|
|
1232
|
-
...(
|
|
1233
|
-
...(
|
|
1234
|
-
};
|
|
1311
|
+
error: failure.error,
|
|
1312
|
+
...(failure.errorCode ? { errorCode: failure.errorCode } : {}),
|
|
1313
|
+
...(failure.errorMessage ? { errorMessage: failure.errorMessage } : {}),
|
|
1314
|
+
});
|
|
1235
1315
|
},
|
|
1236
|
-
}))
|
|
1316
|
+
}), { name: "kichi_leave" });
|
|
1237
1317
|
|
|
1238
|
-
api.registerTool(
|
|
1318
|
+
api.registerTool((ctx) => ({
|
|
1239
1319
|
name: "kichi_connection_status",
|
|
1320
|
+
label: "kichi_connection_status",
|
|
1240
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.",
|
|
1241
1322
|
parameters: { type: "object", properties: {} },
|
|
1242
|
-
execute: async () => {
|
|
1243
|
-
|
|
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({
|
|
1244
1331
|
success: true,
|
|
1245
1332
|
status: service.getConnectionStatus(),
|
|
1246
|
-
};
|
|
1333
|
+
});
|
|
1247
1334
|
},
|
|
1248
|
-
}))
|
|
1335
|
+
}), { name: "kichi_connection_status" });
|
|
1249
1336
|
|
|
1250
|
-
api.registerTool(
|
|
1337
|
+
api.registerTool((ctx) => {
|
|
1338
|
+
const locator = resolveToolLocator(ctx);
|
|
1339
|
+
const existingService = runtimeManager.getRuntime(locator);
|
|
1340
|
+
return ({
|
|
1251
1341
|
name: "kichi_action",
|
|
1252
|
-
|
|
1342
|
+
label: "kichi_action",
|
|
1343
|
+
description: buildKichiActionDescription(existingService ?? undefined),
|
|
1253
1344
|
parameters: {
|
|
1254
1345
|
type: "object",
|
|
1255
1346
|
properties: {
|
|
@@ -1269,39 +1360,51 @@ const plugin = {
|
|
|
1269
1360
|
description:
|
|
1270
1361
|
"Set true ONLY when the user explicitly requests a pose or action. Omit during routine sync steps.",
|
|
1271
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
|
+
},
|
|
1272
1368
|
},
|
|
1273
1369
|
required: ["poseType", "action"],
|
|
1274
1370
|
},
|
|
1275
1371
|
execute: async (_toolCallId, params) => {
|
|
1276
|
-
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 {
|
|
1277
1379
|
poseType?: string;
|
|
1278
1380
|
action?: string;
|
|
1279
1381
|
bubble?: string;
|
|
1280
1382
|
log?: string;
|
|
1281
1383
|
verify?: boolean;
|
|
1384
|
+
propId?: string;
|
|
1282
1385
|
};
|
|
1283
1386
|
if (!poseType || !action) {
|
|
1284
|
-
return { success: false, error: "poseType and action parameters are required" };
|
|
1387
|
+
return jsonResult({ success: false, error: "poseType and action parameters are required" });
|
|
1285
1388
|
}
|
|
1286
1389
|
if (!["stand", "sit", "lay", "floor"].includes(poseType)) {
|
|
1287
|
-
return {
|
|
1390
|
+
return jsonResult({
|
|
1288
1391
|
success: false,
|
|
1289
1392
|
error: `Invalid poseType: ${poseType}. Must be stand, sit, lay, or floor`,
|
|
1290
|
-
};
|
|
1393
|
+
});
|
|
1291
1394
|
}
|
|
1292
1395
|
if (!service.hasValidIdentity() || !service.isConnected()) {
|
|
1293
|
-
return { success: false, error: "Not connected to Kichi world" };
|
|
1396
|
+
return jsonResult({ success: false, error: "Not connected to Kichi world" });
|
|
1294
1397
|
}
|
|
1295
1398
|
|
|
1296
1399
|
const normalizedPoseType = poseType as PoseType;
|
|
1297
1400
|
const poseActions = loadStaticConfig().actions[normalizedPoseType];
|
|
1298
1401
|
const matched = poseActions.find((entry) => entry.name.toLowerCase() === action.toLowerCase());
|
|
1299
1402
|
if (!matched) {
|
|
1300
|
-
return {
|
|
1403
|
+
return jsonResult({
|
|
1301
1404
|
success: false,
|
|
1302
1405
|
error: `Unknown action "${action}" for poseType "${poseType}"`,
|
|
1303
1406
|
available: poseActions.map((entry) => entry.name),
|
|
1304
|
-
};
|
|
1407
|
+
});
|
|
1305
1408
|
}
|
|
1306
1409
|
|
|
1307
1410
|
const bubbleText = typeof bubble === "string" && bubble.trim() ? bubble.trim() : matched.name;
|
|
@@ -1311,15 +1414,15 @@ const plugin = {
|
|
|
1311
1414
|
if (verify) {
|
|
1312
1415
|
try {
|
|
1313
1416
|
const ack = await service.sendStatusVerified(
|
|
1314
|
-
normalizedPoseType, matched.name, bubbleText, logText, playback,
|
|
1417
|
+
normalizedPoseType, matched.name, bubbleText, logText, playback, propId,
|
|
1315
1418
|
);
|
|
1316
1419
|
if (ack.warning) {
|
|
1317
|
-
return {
|
|
1420
|
+
return jsonResult({
|
|
1318
1421
|
success: true,
|
|
1319
1422
|
requested: { poseType: normalizedPoseType, action: matched.name },
|
|
1320
1423
|
actual: { poseType: ack.poseType, action: ack.action },
|
|
1321
1424
|
warning: ack.warning,
|
|
1322
|
-
};
|
|
1425
|
+
});
|
|
1323
1426
|
}
|
|
1324
1427
|
} catch {
|
|
1325
1428
|
// Server not updated or timeout — fall through to normal success
|
|
@@ -1330,21 +1433,23 @@ const plugin = {
|
|
|
1330
1433
|
action: matched.name,
|
|
1331
1434
|
bubble: bubbleText,
|
|
1332
1435
|
log: logText,
|
|
1436
|
+
propId,
|
|
1333
1437
|
});
|
|
1334
1438
|
}
|
|
1335
1439
|
|
|
1336
|
-
return {
|
|
1440
|
+
return jsonResult({
|
|
1337
1441
|
success: true,
|
|
1338
1442
|
poseType: normalizedPoseType,
|
|
1339
1443
|
action: matched.name,
|
|
1340
1444
|
bubble: bubbleText,
|
|
1341
1445
|
log: logText,
|
|
1342
1446
|
playback,
|
|
1343
|
-
};
|
|
1447
|
+
});
|
|
1344
1448
|
},
|
|
1345
|
-
}))
|
|
1346
|
-
api.registerTool(
|
|
1449
|
+
})}, { name: "kichi_action" });
|
|
1450
|
+
api.registerTool((ctx) => ({
|
|
1347
1451
|
name: "kichi_idle_plan",
|
|
1452
|
+
label: "kichi_idle_plan",
|
|
1348
1453
|
description: buildKichiIdlePlanDescription(),
|
|
1349
1454
|
parameters: {
|
|
1350
1455
|
type: "object",
|
|
@@ -1410,6 +1515,10 @@ const plugin = {
|
|
|
1410
1515
|
type: "string",
|
|
1411
1516
|
description: "Optional log content for this action. Use the same language as the current conversation.",
|
|
1412
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
|
+
},
|
|
1413
1522
|
},
|
|
1414
1523
|
required: ["poseType", "action", "durationSeconds", "bubble"],
|
|
1415
1524
|
},
|
|
@@ -1422,12 +1531,18 @@ const plugin = {
|
|
|
1422
1531
|
required: ["heartbeatIntervalSeconds", "goal", "stages"],
|
|
1423
1532
|
},
|
|
1424
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);
|
|
1425
1540
|
const { idlePlan, error } = normalizeIdlePlan(params);
|
|
1426
1541
|
if (!idlePlan) {
|
|
1427
|
-
return { success: false, error: error ?? "Invalid idle plan payload" };
|
|
1542
|
+
return jsonResult({ success: false, error: error ?? "Invalid idle plan payload" });
|
|
1428
1543
|
}
|
|
1429
1544
|
if (!service.hasValidIdentity() || !service.isConnected()) {
|
|
1430
|
-
return { success: false, error: "Not connected to Kichi world" };
|
|
1545
|
+
return jsonResult({ success: false, error: "Not connected to Kichi world" });
|
|
1431
1546
|
}
|
|
1432
1547
|
const sent = service.sendIdlePlan({
|
|
1433
1548
|
...(idlePlan.requestId ? { requestId: idlePlan.requestId } : {}),
|
|
@@ -1436,20 +1551,21 @@ const plugin = {
|
|
|
1436
1551
|
stages: idlePlan.stages,
|
|
1437
1552
|
});
|
|
1438
1553
|
if (!sent) {
|
|
1439
|
-
return { success: false, error: "Failed to send idle plan payload" };
|
|
1554
|
+
return jsonResult({ success: false, error: "Failed to send idle plan payload" });
|
|
1440
1555
|
}
|
|
1441
|
-
return {
|
|
1556
|
+
return jsonResult({
|
|
1442
1557
|
success: true,
|
|
1443
1558
|
...(idlePlan.requestId ? { requestId: idlePlan.requestId } : {}),
|
|
1444
1559
|
heartbeatIntervalSeconds: idlePlan.heartbeatIntervalSeconds,
|
|
1445
1560
|
totalDurationSeconds: idlePlan.totalDurationSeconds,
|
|
1446
1561
|
goal: idlePlan.goal,
|
|
1447
1562
|
stages: idlePlan.stages,
|
|
1448
|
-
};
|
|
1563
|
+
});
|
|
1449
1564
|
},
|
|
1450
|
-
}))
|
|
1451
|
-
api.registerTool(
|
|
1565
|
+
}), { name: "kichi_idle_plan" });
|
|
1566
|
+
api.registerTool((ctx) => ({
|
|
1452
1567
|
name: "kichi_clock",
|
|
1568
|
+
label: "kichi_clock",
|
|
1453
1569
|
description:
|
|
1454
1570
|
"Send clock commands to Kichi world. Supported actions are set and stop.",
|
|
1455
1571
|
parameters: {
|
|
@@ -1517,6 +1633,12 @@ const plugin = {
|
|
|
1517
1633
|
required: ["action"],
|
|
1518
1634
|
},
|
|
1519
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);
|
|
1520
1642
|
const { action, requestId, clock } = (params || {}) as {
|
|
1521
1643
|
action?: unknown;
|
|
1522
1644
|
requestId?: unknown;
|
|
@@ -1524,46 +1646,47 @@ const plugin = {
|
|
|
1524
1646
|
};
|
|
1525
1647
|
|
|
1526
1648
|
if (!isClockAction(action)) {
|
|
1527
|
-
return {
|
|
1649
|
+
return jsonResult({
|
|
1528
1650
|
success: false,
|
|
1529
1651
|
error: "action must be one of: set, stop",
|
|
1530
|
-
};
|
|
1652
|
+
});
|
|
1531
1653
|
}
|
|
1532
1654
|
if (requestId !== undefined && typeof requestId !== "string") {
|
|
1533
|
-
return { success: false, error: "requestId must be a string when provided" };
|
|
1655
|
+
return jsonResult({ success: false, error: "requestId must be a string when provided" });
|
|
1534
1656
|
}
|
|
1535
1657
|
const normalizedRequestId = typeof requestId === "string" ? requestId : undefined;
|
|
1536
1658
|
if (!service.hasValidIdentity() || !service.isConnected()) {
|
|
1537
|
-
return { success: false, error: "Not connected to Kichi world" };
|
|
1659
|
+
return jsonResult({ success: false, error: "Not connected to Kichi world" });
|
|
1538
1660
|
}
|
|
1539
1661
|
|
|
1540
1662
|
let normalizedClock: ClockConfig | undefined;
|
|
1541
1663
|
if (action === "set") {
|
|
1542
1664
|
const { clock: nextClock, error } = normalizeClockConfig(clock);
|
|
1543
1665
|
if (!nextClock) {
|
|
1544
|
-
return { success: false, error: error ?? "Invalid clock payload" };
|
|
1666
|
+
return jsonResult({ success: false, error: error ?? "Invalid clock payload" });
|
|
1545
1667
|
}
|
|
1546
1668
|
normalizedClock = nextClock;
|
|
1547
1669
|
}
|
|
1548
1670
|
|
|
1549
1671
|
const sent = service.sendClock(action, normalizedClock, normalizedRequestId);
|
|
1550
1672
|
if (!sent) {
|
|
1551
|
-
return { success: false, error: "Failed to send clock payload" };
|
|
1673
|
+
return jsonResult({ success: false, error: "Failed to send clock payload" });
|
|
1552
1674
|
}
|
|
1553
1675
|
|
|
1554
|
-
return {
|
|
1676
|
+
return jsonResult({
|
|
1555
1677
|
success: true,
|
|
1556
1678
|
action,
|
|
1557
1679
|
requestId: normalizedRequestId,
|
|
1558
1680
|
...(normalizedClock ? { clock: normalizedClock } : {}),
|
|
1559
|
-
};
|
|
1681
|
+
});
|
|
1560
1682
|
},
|
|
1561
|
-
}))
|
|
1683
|
+
}), { name: "kichi_clock" });
|
|
1562
1684
|
|
|
1563
|
-
api.registerTool(
|
|
1685
|
+
api.registerTool((ctx) => ({
|
|
1564
1686
|
name: "kichi_query_status",
|
|
1687
|
+
label: "kichi_query_status",
|
|
1565
1688
|
description:
|
|
1566
|
-
"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.",
|
|
1567
1690
|
parameters: {
|
|
1568
1691
|
type: "object",
|
|
1569
1692
|
properties: {
|
|
@@ -1574,30 +1697,37 @@ const plugin = {
|
|
|
1574
1697
|
},
|
|
1575
1698
|
},
|
|
1576
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);
|
|
1577
1706
|
const requestId = (params as { requestId?: unknown } | null)?.requestId;
|
|
1578
1707
|
if (requestId !== undefined && typeof requestId !== "string") {
|
|
1579
|
-
return { success: false, error: "requestId must be a string when provided" };
|
|
1708
|
+
return jsonResult({ success: false, error: "requestId must be a string when provided" });
|
|
1580
1709
|
}
|
|
1581
1710
|
if (!service.hasValidIdentity() || !service.isConnected()) {
|
|
1582
|
-
return { success: false, error: "Not connected to Kichi world" };
|
|
1711
|
+
return jsonResult({ success: false, error: "Not connected to Kichi world" });
|
|
1583
1712
|
}
|
|
1584
1713
|
|
|
1585
1714
|
try {
|
|
1586
1715
|
const result = await service.queryStatus(
|
|
1587
1716
|
typeof requestId === "string" ? requestId : undefined,
|
|
1588
1717
|
);
|
|
1589
|
-
return result;
|
|
1718
|
+
return jsonResult(result);
|
|
1590
1719
|
} catch (error) {
|
|
1591
|
-
return {
|
|
1720
|
+
return jsonResult({
|
|
1592
1721
|
success: false,
|
|
1593
1722
|
error: `Failed to query status: ${error}`,
|
|
1594
|
-
};
|
|
1723
|
+
});
|
|
1595
1724
|
}
|
|
1596
1725
|
},
|
|
1597
|
-
}))
|
|
1726
|
+
}), { name: "kichi_query_status" });
|
|
1598
1727
|
|
|
1599
|
-
api.registerTool(
|
|
1728
|
+
api.registerTool((ctx) => ({
|
|
1600
1729
|
name: "kichi_music_album_create",
|
|
1730
|
+
label: "kichi_music_album_create",
|
|
1601
1731
|
description: buildMusicAlbumToolDescription(),
|
|
1602
1732
|
parameters: {
|
|
1603
1733
|
type: "object",
|
|
@@ -1622,6 +1752,12 @@ const plugin = {
|
|
|
1622
1752
|
required: ["albumTitle", "musicTitles"],
|
|
1623
1753
|
},
|
|
1624
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);
|
|
1625
1761
|
const {
|
|
1626
1762
|
requestId,
|
|
1627
1763
|
albumTitle,
|
|
@@ -1633,33 +1769,33 @@ const plugin = {
|
|
|
1633
1769
|
};
|
|
1634
1770
|
|
|
1635
1771
|
if (requestId !== undefined && typeof requestId !== "string") {
|
|
1636
|
-
return { success: false, error: "requestId must be a string when provided" };
|
|
1772
|
+
return jsonResult({ success: false, error: "requestId must be a string when provided" });
|
|
1637
1773
|
}
|
|
1638
1774
|
if (typeof albumTitle !== "string" || !albumTitle.trim()) {
|
|
1639
|
-
return { success: false, error: "albumTitle is required" };
|
|
1775
|
+
return jsonResult({ success: false, error: "albumTitle is required" });
|
|
1640
1776
|
}
|
|
1641
1777
|
if (!Array.isArray(musicTitles)) {
|
|
1642
|
-
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" });
|
|
1643
1779
|
}
|
|
1644
1780
|
|
|
1645
1781
|
const { titles: normalizedTitles, invalidTitles } = normalizeMusicTitles(musicTitles);
|
|
1646
1782
|
if (normalizedTitles.length === 0) {
|
|
1647
|
-
return {
|
|
1783
|
+
return jsonResult({
|
|
1648
1784
|
success: false,
|
|
1649
1785
|
error: "musicTitles must contain at least one valid track name from the static config bundled with the plugin package",
|
|
1650
1786
|
examples: getMusicTitleExamples(),
|
|
1651
|
-
};
|
|
1787
|
+
});
|
|
1652
1788
|
}
|
|
1653
1789
|
if (invalidTitles.length > 0) {
|
|
1654
|
-
return {
|
|
1790
|
+
return jsonResult({
|
|
1655
1791
|
success: false,
|
|
1656
1792
|
error: `Unknown musicTitles: ${invalidTitles.join(", ")}`,
|
|
1657
1793
|
hint: "Use exact track names from the static config bundled with the plugin package",
|
|
1658
1794
|
examples: getMusicTitleExamples(),
|
|
1659
|
-
};
|
|
1795
|
+
});
|
|
1660
1796
|
}
|
|
1661
1797
|
if (!service.hasValidIdentity() || !service.isConnected()) {
|
|
1662
|
-
return { success: false, error: "Not connected to Kichi world" };
|
|
1798
|
+
return jsonResult({ success: false, error: "Not connected to Kichi world" });
|
|
1663
1799
|
}
|
|
1664
1800
|
|
|
1665
1801
|
try {
|
|
@@ -1668,24 +1804,25 @@ const plugin = {
|
|
|
1668
1804
|
normalizedTitles,
|
|
1669
1805
|
typeof requestId === "string" ? requestId : undefined,
|
|
1670
1806
|
);
|
|
1671
|
-
return {
|
|
1807
|
+
return jsonResult({
|
|
1672
1808
|
success: true,
|
|
1673
1809
|
requestId: normalizedRequestId,
|
|
1674
1810
|
albumTitle: albumTitle.trim(),
|
|
1675
1811
|
musicTitles: normalizedTitles,
|
|
1676
1812
|
trackCount: normalizedTitles.length,
|
|
1677
|
-
};
|
|
1813
|
+
});
|
|
1678
1814
|
} catch (error) {
|
|
1679
|
-
return {
|
|
1815
|
+
return jsonResult({
|
|
1680
1816
|
success: false,
|
|
1681
1817
|
error: `Failed to create music album: ${error}`,
|
|
1682
|
-
};
|
|
1818
|
+
});
|
|
1683
1819
|
}
|
|
1684
1820
|
},
|
|
1685
|
-
}))
|
|
1821
|
+
}), { name: "kichi_music_album_create" });
|
|
1686
1822
|
|
|
1687
|
-
api.registerTool(
|
|
1823
|
+
api.registerTool((ctx) => ({
|
|
1688
1824
|
name: "kichi_noteboard_create",
|
|
1825
|
+
label: "kichi_noteboard_create",
|
|
1689
1826
|
description:
|
|
1690
1827
|
"Create a new note on a specific Kichi note board. Prefer querying first so you can avoid duplicate posts and respect rate limits.",
|
|
1691
1828
|
parameters: {
|
|
@@ -1703,37 +1840,125 @@ const plugin = {
|
|
|
1703
1840
|
required: ["propId", "data"],
|
|
1704
1841
|
},
|
|
1705
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);
|
|
1706
1849
|
const { propId, data } = (params || {}) as {
|
|
1707
1850
|
propId?: unknown;
|
|
1708
1851
|
data?: unknown;
|
|
1709
1852
|
};
|
|
1710
1853
|
if (typeof propId !== "string" || !propId.trim()) {
|
|
1711
|
-
return { success: false, error: "propId is required" };
|
|
1854
|
+
return jsonResult({ success: false, error: "propId is required" });
|
|
1712
1855
|
}
|
|
1713
1856
|
if (typeof data !== "string" || !data.trim()) {
|
|
1714
|
-
return { success: false, error: "data is required" };
|
|
1857
|
+
return jsonResult({ success: false, error: "data is required" });
|
|
1715
1858
|
}
|
|
1716
1859
|
if (data.trim().length > MAX_NOTEBOARD_TEXT_LENGTH) {
|
|
1717
|
-
return {
|
|
1860
|
+
return jsonResult({
|
|
1718
1861
|
success: false,
|
|
1719
1862
|
error: `data must be ${MAX_NOTEBOARD_TEXT_LENGTH} characters or fewer`,
|
|
1720
|
-
};
|
|
1863
|
+
});
|
|
1721
1864
|
}
|
|
1722
1865
|
if (!service.hasValidIdentity() || !service.isConnected()) {
|
|
1723
|
-
return { success: false, error: "Not connected to Kichi world" };
|
|
1866
|
+
return jsonResult({ success: false, error: "Not connected to Kichi world" });
|
|
1724
1867
|
}
|
|
1725
1868
|
|
|
1726
1869
|
try {
|
|
1727
1870
|
service.createNotesBoardNote(propId.trim(), data.trim());
|
|
1728
|
-
return { success: true };
|
|
1871
|
+
return jsonResult({ success: true });
|
|
1729
1872
|
} catch (error) {
|
|
1730
|
-
return {
|
|
1873
|
+
return jsonResult({
|
|
1731
1874
|
success: false,
|
|
1732
1875
|
error: `Failed to create note: ${error}`,
|
|
1733
|
-
};
|
|
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}` });
|
|
1734
1959
|
}
|
|
1735
1960
|
},
|
|
1736
|
-
}))
|
|
1961
|
+
}), { name: "kichi_bot_message" });
|
|
1737
1962
|
|
|
1738
1963
|
},
|
|
1739
1964
|
};
|