@yahaha-studio/kichi-forwarder 0.0.1-alpha.27 → 0.0.1-alpha.28
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/.claude/settings.local.json +9 -1
- package/index.ts +215 -118
- package/package.json +1 -1
- package/skills/kichi-forwarder/SKILL.md +109 -18
- package/skills/kichi-forwarder/references/heartbeat.md +40 -9
- package/skills/kichi-forwarder/references/install.md +75 -7
- package/src/album-config.ts +511 -0
- package/src/config.ts +1 -1
- package/src/service.ts +61 -30
- package/src/types.ts +46 -39
|
@@ -12,7 +12,15 @@
|
|
|
12
12
|
"Bash(gh api:*)",
|
|
13
13
|
"WebFetch(domain:docs.openclaw.ai)",
|
|
14
14
|
"WebFetch(domain:openclawskill.cc)",
|
|
15
|
-
"WebFetch(domain:yingtu.ai)"
|
|
15
|
+
"WebFetch(domain:yingtu.ai)",
|
|
16
|
+
"WebFetch(domain:lumadock.com)",
|
|
17
|
+
"WebFetch(domain:dev.to)",
|
|
18
|
+
"WebFetch(domain:www.learnclawdbot.org)",
|
|
19
|
+
"Bash(gh auth status:*)",
|
|
20
|
+
"WebFetch(domain:playbooks.com)",
|
|
21
|
+
"WebFetch(domain:smithery.ai)",
|
|
22
|
+
"WebFetch(domain:lobehub.com)",
|
|
23
|
+
"WebFetch(domain:snyk.io)"
|
|
16
24
|
]
|
|
17
25
|
}
|
|
18
26
|
}
|
package/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
5
|
+
import { DEFAULT_ALBUM_CONFIG } from "./src/album-config.js";
|
|
5
6
|
import { parse } from "./src/config.js";
|
|
6
7
|
import { KichiForwarderService } from "./src/service.js";
|
|
7
8
|
import type {
|
|
@@ -58,16 +59,15 @@ const RUNTIME_CONFIG_PATH = path.join(KICHI_WORLD_DIR, "kichi-runtime-config.jso
|
|
|
58
59
|
const LEGACY_SKILLS_CONFIG_PATH = path.join(KICHI_WORLD_DIR, "skills-config.json");
|
|
59
60
|
const IDENTITY_PATH = path.join(KICHI_WORLD_DIR, "identity.json");
|
|
60
61
|
const MAX_NOTEBOARD_TEXT_LENGTH = 200;
|
|
62
|
+
const MUSIC_TITLE_LOOKUP = new Map(
|
|
63
|
+
DEFAULT_ALBUM_CONFIG.track.map((item) => [item.name.toLowerCase(), item.name] as const),
|
|
64
|
+
);
|
|
65
|
+
const MUSIC_TITLE_EXAMPLES = DEFAULT_ALBUM_CONFIG.track.slice(0, 10).map((item) => item.name);
|
|
61
66
|
let cachedConfig: KichiRuntimeConfig | null = null;
|
|
62
67
|
let cachedConfigMtime = 0;
|
|
63
68
|
let cachedConfigPath = "";
|
|
64
69
|
let service: KichiForwarderService | null = null;
|
|
65
70
|
let pluginApi: OpenClawPluginApi | null = null;
|
|
66
|
-
let lastKnownStatus: ActionResult = {
|
|
67
|
-
poseType: "sit",
|
|
68
|
-
action: DEFAULT_ACTIONS.sit[0],
|
|
69
|
-
bubble: "Working",
|
|
70
|
-
};
|
|
71
71
|
|
|
72
72
|
function sanitizeActions(value: unknown, fallback: string[]): string[] {
|
|
73
73
|
if (!Array.isArray(value)) {
|
|
@@ -135,104 +135,34 @@ function loadRuntimeConfig(): KichiRuntimeConfig {
|
|
|
135
135
|
return updateCachedRuntimeConfig(DEFAULT_RUNTIME_CONFIG, null);
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
function
|
|
139
|
-
return text.length > maxLen ? `${text.slice(0, maxLen)}...` : text;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function truncateInline(text: string, maxLen: number): string {
|
|
143
|
-
return text.length > maxLen ? `${text.slice(0, maxLen)}...` : text;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function prefixLogTimestamp(log: string): string {
|
|
147
|
-
const trimmed = log.trim();
|
|
148
|
-
if (!trimmed) {
|
|
149
|
-
return "";
|
|
150
|
-
}
|
|
151
|
-
const timestamp = new Date().toISOString().replace("T", " ");
|
|
152
|
-
return `[${timestamp}] ${trimmed}`;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function stringifyParamsForLog(value: unknown, maxLen = 220): string {
|
|
156
|
-
if (value === undefined) {
|
|
157
|
-
return "{}";
|
|
158
|
-
}
|
|
159
|
-
try {
|
|
160
|
-
return truncateInline(JSON.stringify(value), maxLen);
|
|
161
|
-
} catch {
|
|
162
|
-
return truncateInline(String(value), maxLen);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function rememberStatus(status: ActionResult): void {
|
|
167
|
-
lastKnownStatus = {
|
|
168
|
-
poseType: status.poseType,
|
|
169
|
-
action: status.action,
|
|
170
|
-
bubble: status.bubble.trim() || status.action,
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function sendStatusAndRemember(status: ActionResult, log: string): void {
|
|
175
|
-
rememberStatus(status);
|
|
138
|
+
function sendStatusUpdate(status: ActionResult): void {
|
|
176
139
|
service?.sendStatus(
|
|
177
140
|
status.poseType,
|
|
178
141
|
status.action,
|
|
179
142
|
status.bubble || status.action,
|
|
180
|
-
|
|
143
|
+
typeof status.log === "string" ? status.log.trim() : "",
|
|
181
144
|
);
|
|
182
145
|
}
|
|
183
146
|
|
|
184
|
-
function forwardToolCallLog(toolName: string, params: unknown, agentId?: string): void {
|
|
185
|
-
if (!service?.hasValidIdentity() || !service?.isConnected()) {
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if (!toolName || toolName === "kichi_action") {
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const paramsText = stringifyParamsForLog(params);
|
|
194
|
-
const bubble = lastKnownStatus.bubble.trim() || lastKnownStatus.action;
|
|
195
|
-
const prefix = typeof agentId === "string" && agentId.trim() ? `[${agentId.trim()}] ` : "";
|
|
196
|
-
const log = truncateLog(`${prefix}exec tool: ${toolName}, params: ${paramsText}`, 300);
|
|
197
|
-
service.sendStatus(lastKnownStatus.poseType, lastKnownStatus.action, bubble, prefixLogTimestamp(log));
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function resolveStatusSourceId(ctx?: { agentId?: string; sessionKey?: string }): string | undefined {
|
|
201
|
-
if (typeof ctx?.agentId === "string" && ctx.agentId.trim()) {
|
|
202
|
-
return ctx.agentId.trim();
|
|
203
|
-
}
|
|
204
|
-
if (typeof ctx?.sessionKey === "string" && ctx.sessionKey.trim()) {
|
|
205
|
-
return ctx.sessionKey.trim();
|
|
206
|
-
}
|
|
207
|
-
return undefined;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
147
|
function isLlmRuntimeEnabled(): boolean {
|
|
211
148
|
return loadRuntimeConfig().llmRuntimeEnabled;
|
|
212
149
|
}
|
|
213
150
|
|
|
214
|
-
function syncFixedStatus(status: ActionResult
|
|
151
|
+
function syncFixedStatus(status: ActionResult): void {
|
|
215
152
|
if (!service?.hasValidIdentity() || !service?.isConnected()) {
|
|
216
153
|
return;
|
|
217
154
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
return truncateLog(`${prefix}exec tool: ${toolName}, params: ${paramsText}`, 300);
|
|
155
|
+
const bubbleText = status.bubble.trim() || status.action;
|
|
156
|
+
sendStatusUpdate({
|
|
157
|
+
...status,
|
|
158
|
+
bubble: bubbleText,
|
|
159
|
+
log: bubbleText,
|
|
160
|
+
});
|
|
225
161
|
}
|
|
226
162
|
|
|
227
|
-
async function handleMessageReceivedHook(
|
|
228
|
-
event: { content?: string },
|
|
229
|
-
_ctx?: { agentId?: string; sessionKey?: string },
|
|
230
|
-
): Promise<void> {
|
|
163
|
+
async function handleMessageReceivedHook(): Promise<void> {
|
|
231
164
|
if (!isLlmRuntimeEnabled()) {
|
|
232
|
-
|
|
233
|
-
? truncateLog(`message received: ${event.content.trim()}`, 220)
|
|
234
|
-
: "message received";
|
|
235
|
-
syncFixedStatus(FIXED_HOOK_STATUSES.messageReceived, preview);
|
|
165
|
+
syncFixedStatus(FIXED_HOOK_STATUSES.messageReceived);
|
|
236
166
|
}
|
|
237
167
|
return;
|
|
238
168
|
}
|
|
@@ -251,31 +181,21 @@ function registerPluginHooks(api: OpenClawPluginApi): void {
|
|
|
251
181
|
};
|
|
252
182
|
});
|
|
253
183
|
|
|
254
|
-
api.on("before_tool_call", (
|
|
184
|
+
api.on("before_tool_call", () => {
|
|
255
185
|
if (!isLlmRuntimeEnabled()) {
|
|
256
|
-
syncFixedStatus(
|
|
257
|
-
FIXED_HOOK_STATUSES.beforeToolCall,
|
|
258
|
-
buildToolExecutionLog(event.toolName, event.params, ctx?.agentId),
|
|
259
|
-
);
|
|
260
|
-
return;
|
|
186
|
+
syncFixedStatus(FIXED_HOOK_STATUSES.beforeToolCall);
|
|
261
187
|
}
|
|
262
|
-
forwardToolCallLog(event.toolName, event.params, ctx?.agentId);
|
|
263
188
|
});
|
|
264
189
|
|
|
265
|
-
api.on("message_received", async (
|
|
266
|
-
await handleMessageReceivedHook(
|
|
190
|
+
api.on("message_received", async () => {
|
|
191
|
+
await handleMessageReceivedHook();
|
|
267
192
|
});
|
|
268
193
|
|
|
269
194
|
api.on("agent_end", (event) => {
|
|
270
195
|
if (isLlmRuntimeEnabled()) {
|
|
271
196
|
return;
|
|
272
197
|
}
|
|
273
|
-
syncFixedStatus(
|
|
274
|
-
event.success ? FIXED_HOOK_STATUSES.agentEndSuccess : FIXED_HOOK_STATUSES.agentEndFailure,
|
|
275
|
-
event.success
|
|
276
|
-
? "task finished"
|
|
277
|
-
: truncateLog(`task failed: ${event.error ?? "unknown error"}`, 220),
|
|
278
|
-
);
|
|
198
|
+
syncFixedStatus(event.success ? FIXED_HOOK_STATUSES.agentEndSuccess : FIXED_HOOK_STATUSES.agentEndFailure);
|
|
279
199
|
});
|
|
280
200
|
}
|
|
281
201
|
|
|
@@ -291,6 +211,36 @@ function isPositiveInteger(value: unknown): value is number {
|
|
|
291
211
|
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
292
212
|
}
|
|
293
213
|
|
|
214
|
+
function normalizeJoinTags(value: unknown): { tags?: string[]; error?: string } {
|
|
215
|
+
if (value === undefined) {
|
|
216
|
+
return { tags: [] };
|
|
217
|
+
}
|
|
218
|
+
if (!Array.isArray(value)) {
|
|
219
|
+
return { error: "tags must be an array of strings" };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const tags: string[] = [];
|
|
223
|
+
const seen = new Set<string>();
|
|
224
|
+
|
|
225
|
+
for (const item of value) {
|
|
226
|
+
if (typeof item !== "string") {
|
|
227
|
+
return { error: "tags must be an array of strings" };
|
|
228
|
+
}
|
|
229
|
+
const trimmed = item.trim();
|
|
230
|
+
if (!trimmed) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
const key = trimmed.toLowerCase();
|
|
234
|
+
if (seen.has(key)) {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
seen.add(key);
|
|
238
|
+
tags.push(trimmed);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return { tags };
|
|
242
|
+
}
|
|
243
|
+
|
|
294
244
|
function isClockAction(value: unknown): value is ClockAction {
|
|
295
245
|
return ["set", "stop"].includes(String(value));
|
|
296
246
|
}
|
|
@@ -418,6 +368,40 @@ function pickRandomAction(actions: string[]): string {
|
|
|
418
368
|
return actions[Math.floor(Math.random() * actions.length)];
|
|
419
369
|
}
|
|
420
370
|
|
|
371
|
+
function normalizeMusicTitles(value: unknown): { titles: string[]; invalidTitles: string[] } {
|
|
372
|
+
if (!Array.isArray(value)) {
|
|
373
|
+
return { titles: [], invalidTitles: [] };
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const titles: string[] = [];
|
|
377
|
+
const invalidTitles: string[] = [];
|
|
378
|
+
const seen = new Set<string>();
|
|
379
|
+
|
|
380
|
+
for (const item of value) {
|
|
381
|
+
if (typeof item !== "string") {
|
|
382
|
+
invalidTitles.push(String(item));
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
const trimmed = item.trim();
|
|
386
|
+
if (!trimmed) {
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const key = trimmed.toLowerCase();
|
|
391
|
+
const canonicalTitle = MUSIC_TITLE_LOOKUP.get(key);
|
|
392
|
+
if (!canonicalTitle) {
|
|
393
|
+
invalidTitles.push(trimmed);
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
if (seen.has(key)) {
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
seen.add(key);
|
|
400
|
+
titles.push(canonicalTitle);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return { titles, invalidTitles };
|
|
404
|
+
}
|
|
421
405
|
|
|
422
406
|
function buildKichiPrompt(): string {
|
|
423
407
|
return [
|
|
@@ -430,6 +414,8 @@ function buildKichiPrompt(): string {
|
|
|
430
414
|
"- Task end (highest priority): Before the final user-visible reply of this turn, MUST call `kichi_action` exactly once",
|
|
431
415
|
"- Required order at task end: 1) call `kichi_action` 2) send final reply",
|
|
432
416
|
"- Trivial-operation skip applies only to Task start / Step switch / Task switch, NOT Task end",
|
|
417
|
+
"- `bubble`: short natural companion speech, not a raw status report",
|
|
418
|
+
"- `log`: optional first-person diary-style note about the current operation, action, status, mood, feeling, or feedback; keep it within 20 words",
|
|
433
419
|
"",
|
|
434
420
|
"When to use `kichi_clock`:",
|
|
435
421
|
"- For tasks with 2+ meaningful steps or work likely to take more than a brief moment (~10s), set a `countDown` at task start.",
|
|
@@ -437,6 +423,11 @@ function buildKichiPrompt(): string {
|
|
|
437
423
|
"- If duration is uncertain, start with a reasonable estimate and adjust as work progresses.",
|
|
438
424
|
"- If user requests a timer style, follow it (`pomodoro`, `countDown`, or `countUp`).",
|
|
439
425
|
"",
|
|
426
|
+
"When to use `kichi_music_album_create`:",
|
|
427
|
+
"- Call `kichi_query_status` first.",
|
|
428
|
+
"- Recommend a variable-length playlist based on weather, time, and your own personality.",
|
|
429
|
+
"- `albumTitle` is user-defined and `musicTitles` must be exact track names from album-config.",
|
|
430
|
+
"",
|
|
440
431
|
"Skip all sync if:",
|
|
441
432
|
"- User says 'don't sync to Kichi' or similar",
|
|
442
433
|
"- Task is only about configuring/testing kichi_* tools",
|
|
@@ -469,11 +460,11 @@ const plugin = {
|
|
|
469
460
|
|
|
470
461
|
api.registerTool({
|
|
471
462
|
name: "kichi_join",
|
|
472
|
-
description: "Join Kichi world with
|
|
463
|
+
description: "Join Kichi world with avatarId, the current bot name, a short bio, and personality tags",
|
|
473
464
|
parameters: {
|
|
474
465
|
type: "object",
|
|
475
466
|
properties: {
|
|
476
|
-
|
|
467
|
+
avatarId: { type: "string", description: "Avatar ID to join Kichi world" },
|
|
477
468
|
botName: {
|
|
478
469
|
type: "string",
|
|
479
470
|
description: "Current bot name to include in the join message",
|
|
@@ -482,23 +473,31 @@ const plugin = {
|
|
|
482
473
|
type: "string",
|
|
483
474
|
description: "Short bio covering OpenClaw personality and role",
|
|
484
475
|
},
|
|
476
|
+
tags: {
|
|
477
|
+
type: "array",
|
|
478
|
+
description: "Optional list of OpenClaw self-perceived personality tags",
|
|
479
|
+
items: { type: "string" },
|
|
480
|
+
},
|
|
485
481
|
},
|
|
486
482
|
required: ["botName", "bio"],
|
|
487
483
|
},
|
|
488
484
|
execute: async (_toolCallId, params) => {
|
|
489
|
-
let
|
|
485
|
+
let avatarId = (params as { avatarId?: string } | null)?.avatarId;
|
|
490
486
|
const botName = (params as { botName?: string } | null)?.botName?.trim();
|
|
491
487
|
const bio = (params as { bio?: string } | null)?.bio?.trim();
|
|
492
|
-
|
|
488
|
+
const { tags, error: tagsError } = normalizeJoinTags(
|
|
489
|
+
(params as { tags?: unknown } | null)?.tags,
|
|
490
|
+
);
|
|
491
|
+
if (!avatarId) {
|
|
493
492
|
try {
|
|
494
493
|
const identity = JSON.parse(fs.readFileSync(IDENTITY_PATH, "utf-8")) as {
|
|
495
|
-
|
|
494
|
+
avatarId?: string;
|
|
496
495
|
};
|
|
497
|
-
|
|
496
|
+
avatarId = identity.avatarId;
|
|
498
497
|
} catch {}
|
|
499
498
|
}
|
|
500
|
-
if (!
|
|
501
|
-
return { success: false, error: "No
|
|
499
|
+
if (!avatarId) {
|
|
500
|
+
return { success: false, error: "No avatarId" };
|
|
502
501
|
}
|
|
503
502
|
if (!botName) {
|
|
504
503
|
return { success: false, error: "No botName" };
|
|
@@ -506,7 +505,10 @@ const plugin = {
|
|
|
506
505
|
if (!bio) {
|
|
507
506
|
return { success: false, error: "No bio" };
|
|
508
507
|
}
|
|
509
|
-
|
|
508
|
+
if (tagsError) {
|
|
509
|
+
return { success: false, error: tagsError };
|
|
510
|
+
}
|
|
511
|
+
const result = await service?.join(avatarId, botName, bio, tags ?? []);
|
|
510
512
|
return result ? { success: true, authKey: result } : { success: false, error: "Failed" };
|
|
511
513
|
},
|
|
512
514
|
});
|
|
@@ -514,7 +516,7 @@ const plugin = {
|
|
|
514
516
|
api.registerTool({
|
|
515
517
|
name: "kichi_rejoin",
|
|
516
518
|
description:
|
|
517
|
-
"Request an immediate rejoin attempt with saved
|
|
519
|
+
"Request an immediate rejoin attempt with saved avatarId/authKey. Rejoin is also sent automatically after reconnect.",
|
|
518
520
|
parameters: { type: "object", properties: {} },
|
|
519
521
|
execute: async () => {
|
|
520
522
|
if (!service) {
|
|
@@ -568,14 +570,20 @@ const plugin = {
|
|
|
568
570
|
description: "Action name (for example High Five or Typing with Keyboard)",
|
|
569
571
|
},
|
|
570
572
|
bubble: { type: "string", description: "Optional bubble text to display (max 5 words)" },
|
|
573
|
+
log: {
|
|
574
|
+
type: "string",
|
|
575
|
+
description:
|
|
576
|
+
"Optional first-person log about the current operation, action, status, mood, or feedback (max 20 words)",
|
|
577
|
+
},
|
|
571
578
|
},
|
|
572
579
|
required: ["poseType", "action"],
|
|
573
580
|
},
|
|
574
581
|
execute: async (_toolCallId, params) => {
|
|
575
|
-
const { poseType, action, bubble } = (params || {}) as {
|
|
582
|
+
const { poseType, action, bubble, log } = (params || {}) as {
|
|
576
583
|
poseType?: string;
|
|
577
584
|
action?: string;
|
|
578
585
|
bubble?: string;
|
|
586
|
+
log?: string;
|
|
579
587
|
};
|
|
580
588
|
if (!poseType || !action) {
|
|
581
589
|
return { success: false, error: "poseType and action parameters are required" };
|
|
@@ -602,20 +610,21 @@ const plugin = {
|
|
|
602
610
|
}
|
|
603
611
|
|
|
604
612
|
const bubbleText = typeof bubble === "string" && bubble.trim() ? bubble.trim() : matched;
|
|
605
|
-
|
|
606
|
-
|
|
613
|
+
const logText = typeof log === "string" ? log.trim() : "";
|
|
614
|
+
sendStatusUpdate(
|
|
607
615
|
{
|
|
608
616
|
poseType: normalizedPoseType,
|
|
609
617
|
action: matched,
|
|
610
618
|
bubble: bubbleText,
|
|
619
|
+
log: logText,
|
|
611
620
|
},
|
|
612
|
-
"",
|
|
613
621
|
);
|
|
614
622
|
return {
|
|
615
623
|
success: true,
|
|
616
624
|
poseType: normalizedPoseType,
|
|
617
625
|
action: matched,
|
|
618
626
|
bubble: bubbleText,
|
|
627
|
+
log: logText,
|
|
619
628
|
};
|
|
620
629
|
},
|
|
621
630
|
});
|
|
@@ -735,7 +744,7 @@ const plugin = {
|
|
|
735
744
|
api.registerTool({
|
|
736
745
|
name: "kichi_query_status",
|
|
737
746
|
description:
|
|
738
|
-
"Query Kichi
|
|
747
|
+
"Query Kichi avatar status (notes, weather/time, timer snapshot, and daily note quota). Use this before creating a new note.",
|
|
739
748
|
parameters: {
|
|
740
749
|
type: "object",
|
|
741
750
|
properties: {
|
|
@@ -755,14 +764,102 @@ const plugin = {
|
|
|
755
764
|
}
|
|
756
765
|
|
|
757
766
|
try {
|
|
758
|
-
const result = await service.
|
|
767
|
+
const result = await service.queryStatus(
|
|
759
768
|
typeof requestId === "string" ? requestId : undefined,
|
|
760
769
|
);
|
|
761
770
|
return result;
|
|
762
771
|
} catch (error) {
|
|
763
772
|
return {
|
|
764
773
|
success: false,
|
|
765
|
-
error: `Failed to query
|
|
774
|
+
error: `Failed to query status: ${error}`,
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
},
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
api.registerTool({
|
|
781
|
+
name: "kichi_music_album_create",
|
|
782
|
+
description:
|
|
783
|
+
"Create a custom Kichi music album. Query status first, then choose track names from album-config that match weather/time and personality.",
|
|
784
|
+
parameters: {
|
|
785
|
+
type: "object",
|
|
786
|
+
properties: {
|
|
787
|
+
requestId: {
|
|
788
|
+
type: "string",
|
|
789
|
+
description: "Optional request ID for tracing or deduplication.",
|
|
790
|
+
},
|
|
791
|
+
albumTitle: {
|
|
792
|
+
type: "string",
|
|
793
|
+
description: "Custom album title.",
|
|
794
|
+
},
|
|
795
|
+
musicTitles: {
|
|
796
|
+
type: "array",
|
|
797
|
+
description: "Track names chosen from album-config.",
|
|
798
|
+
items: {
|
|
799
|
+
type: "string",
|
|
800
|
+
},
|
|
801
|
+
},
|
|
802
|
+
},
|
|
803
|
+
required: ["albumTitle", "musicTitles"],
|
|
804
|
+
},
|
|
805
|
+
execute: async (_toolCallId, params) => {
|
|
806
|
+
const {
|
|
807
|
+
requestId,
|
|
808
|
+
albumTitle,
|
|
809
|
+
musicTitles,
|
|
810
|
+
} = (params || {}) as {
|
|
811
|
+
requestId?: unknown;
|
|
812
|
+
albumTitle?: unknown;
|
|
813
|
+
musicTitles?: unknown;
|
|
814
|
+
};
|
|
815
|
+
|
|
816
|
+
if (requestId !== undefined && typeof requestId !== "string") {
|
|
817
|
+
return { success: false, error: "requestId must be a string when provided" };
|
|
818
|
+
}
|
|
819
|
+
if (typeof albumTitle !== "string" || !albumTitle.trim()) {
|
|
820
|
+
return { success: false, error: "albumTitle is required" };
|
|
821
|
+
}
|
|
822
|
+
if (!Array.isArray(musicTitles)) {
|
|
823
|
+
return { success: false, error: "musicTitles must be an array of track names" };
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
const { titles: normalizedTitles, invalidTitles } = normalizeMusicTitles(musicTitles);
|
|
827
|
+
if (normalizedTitles.length === 0) {
|
|
828
|
+
return {
|
|
829
|
+
success: false,
|
|
830
|
+
error: "musicTitles must contain at least one valid track name from album-config",
|
|
831
|
+
examples: MUSIC_TITLE_EXAMPLES,
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
if (invalidTitles.length > 0) {
|
|
835
|
+
return {
|
|
836
|
+
success: false,
|
|
837
|
+
error: `Unknown musicTitles: ${invalidTitles.join(", ")}`,
|
|
838
|
+
hint: "Use exact track names from src/album-config.ts",
|
|
839
|
+
examples: MUSIC_TITLE_EXAMPLES,
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
if (!service?.hasValidIdentity() || !service?.isConnected()) {
|
|
843
|
+
return { success: false, error: "Not connected to Kichi world" };
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
try {
|
|
847
|
+
const normalizedRequestId = service.createMusicAlbum(
|
|
848
|
+
albumTitle.trim(),
|
|
849
|
+
normalizedTitles,
|
|
850
|
+
typeof requestId === "string" ? requestId : undefined,
|
|
851
|
+
);
|
|
852
|
+
return {
|
|
853
|
+
success: true,
|
|
854
|
+
requestId: normalizedRequestId,
|
|
855
|
+
albumTitle: albumTitle.trim(),
|
|
856
|
+
musicTitles: normalizedTitles,
|
|
857
|
+
trackCount: normalizedTitles.length,
|
|
858
|
+
};
|
|
859
|
+
} catch (error) {
|
|
860
|
+
return {
|
|
861
|
+
success: false,
|
|
862
|
+
error: `Failed to create music album: ${error}`,
|
|
766
863
|
};
|
|
767
864
|
}
|
|
768
865
|
},
|
package/package.json
CHANGED