@yahaha-studio/kichi-forwarder 0.0.1-alpha.26 → 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 +260 -165
- package/package.json +1 -1
- package/skills/kichi-forwarder/SKILL.md +118 -24
- package/skills/kichi-forwarder/references/heartbeat.md +41 -17
- package/skills/kichi-forwarder/references/install.md +78 -11
- package/src/album-config.ts +511 -0
- package/src/config.ts +1 -1
- package/src/service.ts +111 -85
- package/src/types.ts +46 -71
|
@@ -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,14 +2,13 @@ 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 {
|
|
8
9
|
ActionResult,
|
|
9
10
|
ClockAction,
|
|
10
11
|
ClockConfig,
|
|
11
|
-
CreateNotesBoardNote,
|
|
12
|
-
CreateNotesBoardNoteResultPayload,
|
|
13
12
|
KichiRuntimeConfig,
|
|
14
13
|
KichiForwarderConfig,
|
|
15
14
|
PomodoroPhase,
|
|
@@ -25,6 +24,34 @@ const DEFAULT_ACTIONS: KichiRuntimeConfig["actions"] = {
|
|
|
25
24
|
|
|
26
25
|
const DEFAULT_RUNTIME_CONFIG: KichiRuntimeConfig = {
|
|
27
26
|
actions: DEFAULT_ACTIONS,
|
|
27
|
+
llmRuntimeEnabled: true,
|
|
28
|
+
};
|
|
29
|
+
const FIXED_HOOK_STATUSES: Record<string, ActionResult> = {
|
|
30
|
+
messageReceived: {
|
|
31
|
+
poseType: "sit",
|
|
32
|
+
action: "Study Look At",
|
|
33
|
+
bubble: "Reading request",
|
|
34
|
+
},
|
|
35
|
+
beforePromptBuild: {
|
|
36
|
+
poseType: "sit",
|
|
37
|
+
action: "Thinking",
|
|
38
|
+
bubble: "Planning task",
|
|
39
|
+
},
|
|
40
|
+
beforeToolCall: {
|
|
41
|
+
poseType: "sit",
|
|
42
|
+
action: "Typing with Keyboard",
|
|
43
|
+
bubble: "Working step",
|
|
44
|
+
},
|
|
45
|
+
agentEndSuccess: {
|
|
46
|
+
poseType: "stand",
|
|
47
|
+
action: "Yay",
|
|
48
|
+
bubble: "Task complete",
|
|
49
|
+
},
|
|
50
|
+
agentEndFailure: {
|
|
51
|
+
poseType: "stand",
|
|
52
|
+
action: "Tired",
|
|
53
|
+
bubble: "Task failed",
|
|
54
|
+
},
|
|
28
55
|
};
|
|
29
56
|
|
|
30
57
|
const KICHI_WORLD_DIR = path.join(os.homedir(), ".openclaw", "kichi-world");
|
|
@@ -32,16 +59,15 @@ const RUNTIME_CONFIG_PATH = path.join(KICHI_WORLD_DIR, "kichi-runtime-config.jso
|
|
|
32
59
|
const LEGACY_SKILLS_CONFIG_PATH = path.join(KICHI_WORLD_DIR, "skills-config.json");
|
|
33
60
|
const IDENTITY_PATH = path.join(KICHI_WORLD_DIR, "identity.json");
|
|
34
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);
|
|
35
66
|
let cachedConfig: KichiRuntimeConfig | null = null;
|
|
36
67
|
let cachedConfigMtime = 0;
|
|
37
68
|
let cachedConfigPath = "";
|
|
38
69
|
let service: KichiForwarderService | null = null;
|
|
39
70
|
let pluginApi: OpenClawPluginApi | null = null;
|
|
40
|
-
let lastKnownStatus: ActionResult = {
|
|
41
|
-
poseType: "sit",
|
|
42
|
-
action: DEFAULT_ACTIONS.sit[0],
|
|
43
|
-
bubble: "Working",
|
|
44
|
-
};
|
|
45
71
|
|
|
46
72
|
function sanitizeActions(value: unknown, fallback: string[]): string[] {
|
|
47
73
|
if (!Array.isArray(value)) {
|
|
@@ -57,6 +83,7 @@ function normalizeRuntimeConfig(value: unknown): KichiRuntimeConfig {
|
|
|
57
83
|
const raw = value && typeof value === "object" ? (value as Partial<KichiRuntimeConfig>) : {};
|
|
58
84
|
const actions = raw.actions;
|
|
59
85
|
return {
|
|
86
|
+
llmRuntimeEnabled: typeof raw.llmRuntimeEnabled === "boolean" ? raw.llmRuntimeEnabled : true,
|
|
60
87
|
actions: {
|
|
61
88
|
stand: sanitizeActions(actions?.stand, DEFAULT_ACTIONS.stand),
|
|
62
89
|
sit: sanitizeActions(actions?.sit, DEFAULT_ACTIONS.sit),
|
|
@@ -108,82 +135,35 @@ function loadRuntimeConfig(): KichiRuntimeConfig {
|
|
|
108
135
|
return updateCachedRuntimeConfig(DEFAULT_RUNTIME_CONFIG, null);
|
|
109
136
|
}
|
|
110
137
|
|
|
111
|
-
function
|
|
112
|
-
return text.length > maxLen ? `${text.slice(0, maxLen)}...` : text;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function truncateInline(text: string, maxLen: number): string {
|
|
116
|
-
return text.length > maxLen ? `${text.slice(0, maxLen)}...` : text;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function prefixLogTimestamp(log: string): string {
|
|
120
|
-
const trimmed = log.trim();
|
|
121
|
-
if (!trimmed) {
|
|
122
|
-
return "";
|
|
123
|
-
}
|
|
124
|
-
const timestamp = new Date().toISOString().replace("T", " ");
|
|
125
|
-
return `[${timestamp}] ${trimmed}`;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function stringifyParamsForLog(value: unknown, maxLen = 220): string {
|
|
129
|
-
if (value === undefined) {
|
|
130
|
-
return "{}";
|
|
131
|
-
}
|
|
132
|
-
try {
|
|
133
|
-
return truncateInline(JSON.stringify(value), maxLen);
|
|
134
|
-
} catch {
|
|
135
|
-
return truncateInline(String(value), maxLen);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function rememberStatus(status: ActionResult): void {
|
|
140
|
-
lastKnownStatus = {
|
|
141
|
-
poseType: status.poseType,
|
|
142
|
-
action: status.action,
|
|
143
|
-
bubble: status.bubble.trim() || status.action,
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function sendStatusAndRemember(status: ActionResult, log: string): void {
|
|
148
|
-
rememberStatus(status);
|
|
138
|
+
function sendStatusUpdate(status: ActionResult): void {
|
|
149
139
|
service?.sendStatus(
|
|
150
140
|
status.poseType,
|
|
151
141
|
status.action,
|
|
152
142
|
status.bubble || status.action,
|
|
153
|
-
|
|
143
|
+
typeof status.log === "string" ? status.log.trim() : "",
|
|
154
144
|
);
|
|
155
145
|
}
|
|
156
146
|
|
|
157
|
-
function
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
147
|
+
function isLlmRuntimeEnabled(): boolean {
|
|
148
|
+
return loadRuntimeConfig().llmRuntimeEnabled;
|
|
149
|
+
}
|
|
161
150
|
|
|
162
|
-
|
|
151
|
+
function syncFixedStatus(status: ActionResult): void {
|
|
152
|
+
if (!service?.hasValidIdentity() || !service?.isConnected()) {
|
|
163
153
|
return;
|
|
164
154
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
155
|
+
const bubbleText = status.bubble.trim() || status.action;
|
|
156
|
+
sendStatusUpdate({
|
|
157
|
+
...status,
|
|
158
|
+
bubble: bubbleText,
|
|
159
|
+
log: bubbleText,
|
|
160
|
+
});
|
|
171
161
|
}
|
|
172
162
|
|
|
173
|
-
function
|
|
174
|
-
if (
|
|
175
|
-
|
|
176
|
-
}
|
|
177
|
-
if (typeof ctx?.sessionKey === "string" && ctx.sessionKey.trim()) {
|
|
178
|
-
return ctx.sessionKey.trim();
|
|
163
|
+
async function handleMessageReceivedHook(): Promise<void> {
|
|
164
|
+
if (!isLlmRuntimeEnabled()) {
|
|
165
|
+
syncFixedStatus(FIXED_HOOK_STATUSES.messageReceived);
|
|
179
166
|
}
|
|
180
|
-
return undefined;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
async function handleMessageReceivedHook(
|
|
184
|
-
_event: any,
|
|
185
|
-
_ctx?: { agentId?: string; sessionKey?: string },
|
|
186
|
-
): Promise<void> {
|
|
187
167
|
return;
|
|
188
168
|
}
|
|
189
169
|
|
|
@@ -192,17 +172,30 @@ function registerPluginHooks(api: OpenClawPluginApi): void {
|
|
|
192
172
|
if (!service?.hasValidIdentity() || !service?.isConnected()) {
|
|
193
173
|
return;
|
|
194
174
|
}
|
|
175
|
+
if (!isLlmRuntimeEnabled()) {
|
|
176
|
+
syncFixedStatus(FIXED_HOOK_STATUSES.beforePromptBuild);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
195
179
|
return {
|
|
196
180
|
prependContext: buildKichiPrompt(),
|
|
197
181
|
};
|
|
198
182
|
});
|
|
199
183
|
|
|
200
|
-
api.on("before_tool_call", (
|
|
201
|
-
|
|
184
|
+
api.on("before_tool_call", () => {
|
|
185
|
+
if (!isLlmRuntimeEnabled()) {
|
|
186
|
+
syncFixedStatus(FIXED_HOOK_STATUSES.beforeToolCall);
|
|
187
|
+
}
|
|
202
188
|
});
|
|
203
189
|
|
|
204
|
-
api.on("message_received", async (
|
|
205
|
-
await handleMessageReceivedHook(
|
|
190
|
+
api.on("message_received", async () => {
|
|
191
|
+
await handleMessageReceivedHook();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
api.on("agent_end", (event) => {
|
|
195
|
+
if (isLlmRuntimeEnabled()) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
syncFixedStatus(event.success ? FIXED_HOOK_STATUSES.agentEndSuccess : FIXED_HOOK_STATUSES.agentEndFailure);
|
|
206
199
|
});
|
|
207
200
|
}
|
|
208
201
|
|
|
@@ -218,6 +211,36 @@ function isPositiveInteger(value: unknown): value is number {
|
|
|
218
211
|
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
219
212
|
}
|
|
220
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
|
+
|
|
221
244
|
function isClockAction(value: unknown): value is ClockAction {
|
|
222
245
|
return ["set", "stop"].includes(String(value));
|
|
223
246
|
}
|
|
@@ -345,44 +368,39 @@ function pickRandomAction(actions: string[]): string {
|
|
|
345
368
|
return actions[Math.floor(Math.random() * actions.length)];
|
|
346
369
|
}
|
|
347
370
|
|
|
348
|
-
function
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
): { data: string; dataTruncated: boolean } {
|
|
352
|
-
if (data.length <= maxLen) {
|
|
353
|
-
return { data, dataTruncated: false };
|
|
371
|
+
function normalizeMusicTitles(value: unknown): { titles: string[]; invalidTitles: string[] } {
|
|
372
|
+
if (!Array.isArray(value)) {
|
|
373
|
+
return { titles: [], invalidTitles: [] };
|
|
354
374
|
}
|
|
355
|
-
return { data: `${data.slice(0, maxLen)}...`, dataTruncated: true };
|
|
356
|
-
}
|
|
357
375
|
|
|
358
|
-
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
id: note.id,
|
|
362
|
-
ownerName: note.ownerName,
|
|
363
|
-
createTime: note.createTime,
|
|
364
|
-
data,
|
|
365
|
-
dataTruncated,
|
|
366
|
-
};
|
|
367
|
-
}
|
|
376
|
+
const titles: string[] = [];
|
|
377
|
+
const invalidTitles: string[] = [];
|
|
378
|
+
const seen = new Set<string>();
|
|
368
379
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
parts.push(`error=${result.errorCode}`);
|
|
380
|
+
for (const item of value) {
|
|
381
|
+
if (typeof item !== "string") {
|
|
382
|
+
invalidTitles.push(String(item));
|
|
383
|
+
continue;
|
|
374
384
|
}
|
|
375
|
-
|
|
376
|
-
|
|
385
|
+
const trimmed = item.trim();
|
|
386
|
+
if (!trimmed) {
|
|
387
|
+
continue;
|
|
377
388
|
}
|
|
378
|
-
|
|
379
|
-
|
|
389
|
+
|
|
390
|
+
const key = trimmed.toLowerCase();
|
|
391
|
+
const canonicalTitle = MUSIC_TITLE_LOOKUP.get(key);
|
|
392
|
+
if (!canonicalTitle) {
|
|
393
|
+
invalidTitles.push(trimmed);
|
|
394
|
+
continue;
|
|
380
395
|
}
|
|
381
|
-
|
|
396
|
+
if (seen.has(key)) {
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
seen.add(key);
|
|
400
|
+
titles.push(canonicalTitle);
|
|
382
401
|
}
|
|
383
402
|
|
|
384
|
-
|
|
385
|
-
return `${result.propId} -> ${result.note.id} by ${result.note.ownerName}: ${text}`;
|
|
403
|
+
return { titles, invalidTitles };
|
|
386
404
|
}
|
|
387
405
|
|
|
388
406
|
function buildKichiPrompt(): string {
|
|
@@ -391,23 +409,24 @@ function buildKichiPrompt(): string {
|
|
|
391
409
|
"",
|
|
392
410
|
"When to use `kichi_action`:",
|
|
393
411
|
"- Task start: User gives you a new task to work on",
|
|
412
|
+
"- Step switch: Moving from one meaningful step or subtask to another within the current task",
|
|
394
413
|
"- Task switch: Moving from one distinct task to another",
|
|
395
|
-
"- Major milestone: Completed a significant phase",
|
|
396
414
|
"- Task end (highest priority): Before the final user-visible reply of this turn, MUST call `kichi_action` exactly once",
|
|
397
415
|
"- Required order at task end: 1) call `kichi_action` 2) send final reply",
|
|
398
|
-
"- Trivial-operation skip applies only to Task start /
|
|
399
|
-
"",
|
|
400
|
-
"
|
|
401
|
-
"- Choose poseType, action, and bubble that match your actual current activity",
|
|
402
|
-
"- Use available actions from the configured action list for each poseType",
|
|
403
|
-
"- bubble should be 2-5 words describing what you're doing now",
|
|
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",
|
|
404
419
|
"",
|
|
405
420
|
"When to use `kichi_clock`:",
|
|
406
|
-
"-
|
|
407
|
-
"-
|
|
421
|
+
"- For tasks with 2+ meaningful steps or work likely to take more than a brief moment (~10s), set a `countDown` at task start.",
|
|
422
|
+
"- Skip clock only for truly quick one-shot operations.",
|
|
408
423
|
"- If duration is uncertain, start with a reasonable estimate and adjust as work progresses.",
|
|
409
424
|
"- If user requests a timer style, follow it (`pomodoro`, `countDown`, or `countUp`).",
|
|
410
|
-
"
|
|
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.",
|
|
411
430
|
"",
|
|
412
431
|
"Skip all sync if:",
|
|
413
432
|
"- User says 'don't sync to Kichi' or similar",
|
|
@@ -441,11 +460,11 @@ const plugin = {
|
|
|
441
460
|
|
|
442
461
|
api.registerTool({
|
|
443
462
|
name: "kichi_join",
|
|
444
|
-
description: "Join Kichi world with
|
|
463
|
+
description: "Join Kichi world with avatarId, the current bot name, a short bio, and personality tags",
|
|
445
464
|
parameters: {
|
|
446
465
|
type: "object",
|
|
447
466
|
properties: {
|
|
448
|
-
|
|
467
|
+
avatarId: { type: "string", description: "Avatar ID to join Kichi world" },
|
|
449
468
|
botName: {
|
|
450
469
|
type: "string",
|
|
451
470
|
description: "Current bot name to include in the join message",
|
|
@@ -454,23 +473,31 @@ const plugin = {
|
|
|
454
473
|
type: "string",
|
|
455
474
|
description: "Short bio covering OpenClaw personality and role",
|
|
456
475
|
},
|
|
476
|
+
tags: {
|
|
477
|
+
type: "array",
|
|
478
|
+
description: "Optional list of OpenClaw self-perceived personality tags",
|
|
479
|
+
items: { type: "string" },
|
|
480
|
+
},
|
|
457
481
|
},
|
|
458
482
|
required: ["botName", "bio"],
|
|
459
483
|
},
|
|
460
484
|
execute: async (_toolCallId, params) => {
|
|
461
|
-
let
|
|
485
|
+
let avatarId = (params as { avatarId?: string } | null)?.avatarId;
|
|
462
486
|
const botName = (params as { botName?: string } | null)?.botName?.trim();
|
|
463
487
|
const bio = (params as { bio?: string } | null)?.bio?.trim();
|
|
464
|
-
|
|
488
|
+
const { tags, error: tagsError } = normalizeJoinTags(
|
|
489
|
+
(params as { tags?: unknown } | null)?.tags,
|
|
490
|
+
);
|
|
491
|
+
if (!avatarId) {
|
|
465
492
|
try {
|
|
466
493
|
const identity = JSON.parse(fs.readFileSync(IDENTITY_PATH, "utf-8")) as {
|
|
467
|
-
|
|
494
|
+
avatarId?: string;
|
|
468
495
|
};
|
|
469
|
-
|
|
496
|
+
avatarId = identity.avatarId;
|
|
470
497
|
} catch {}
|
|
471
498
|
}
|
|
472
|
-
if (!
|
|
473
|
-
return { success: false, error: "No
|
|
499
|
+
if (!avatarId) {
|
|
500
|
+
return { success: false, error: "No avatarId" };
|
|
474
501
|
}
|
|
475
502
|
if (!botName) {
|
|
476
503
|
return { success: false, error: "No botName" };
|
|
@@ -478,7 +505,10 @@ const plugin = {
|
|
|
478
505
|
if (!bio) {
|
|
479
506
|
return { success: false, error: "No bio" };
|
|
480
507
|
}
|
|
481
|
-
|
|
508
|
+
if (tagsError) {
|
|
509
|
+
return { success: false, error: tagsError };
|
|
510
|
+
}
|
|
511
|
+
const result = await service?.join(avatarId, botName, bio, tags ?? []);
|
|
482
512
|
return result ? { success: true, authKey: result } : { success: false, error: "Failed" };
|
|
483
513
|
},
|
|
484
514
|
});
|
|
@@ -486,7 +516,7 @@ const plugin = {
|
|
|
486
516
|
api.registerTool({
|
|
487
517
|
name: "kichi_rejoin",
|
|
488
518
|
description:
|
|
489
|
-
"Request an immediate rejoin attempt with saved
|
|
519
|
+
"Request an immediate rejoin attempt with saved avatarId/authKey. Rejoin is also sent automatically after reconnect.",
|
|
490
520
|
parameters: { type: "object", properties: {} },
|
|
491
521
|
execute: async () => {
|
|
492
522
|
if (!service) {
|
|
@@ -540,14 +570,20 @@ const plugin = {
|
|
|
540
570
|
description: "Action name (for example High Five or Typing with Keyboard)",
|
|
541
571
|
},
|
|
542
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
|
+
},
|
|
543
578
|
},
|
|
544
579
|
required: ["poseType", "action"],
|
|
545
580
|
},
|
|
546
581
|
execute: async (_toolCallId, params) => {
|
|
547
|
-
const { poseType, action, bubble } = (params || {}) as {
|
|
582
|
+
const { poseType, action, bubble, log } = (params || {}) as {
|
|
548
583
|
poseType?: string;
|
|
549
584
|
action?: string;
|
|
550
585
|
bubble?: string;
|
|
586
|
+
log?: string;
|
|
551
587
|
};
|
|
552
588
|
if (!poseType || !action) {
|
|
553
589
|
return { success: false, error: "poseType and action parameters are required" };
|
|
@@ -574,20 +610,21 @@ const plugin = {
|
|
|
574
610
|
}
|
|
575
611
|
|
|
576
612
|
const bubbleText = typeof bubble === "string" && bubble.trim() ? bubble.trim() : matched;
|
|
577
|
-
|
|
578
|
-
|
|
613
|
+
const logText = typeof log === "string" ? log.trim() : "";
|
|
614
|
+
sendStatusUpdate(
|
|
579
615
|
{
|
|
580
616
|
poseType: normalizedPoseType,
|
|
581
617
|
action: matched,
|
|
582
618
|
bubble: bubbleText,
|
|
619
|
+
log: logText,
|
|
583
620
|
},
|
|
584
|
-
"",
|
|
585
621
|
);
|
|
586
622
|
return {
|
|
587
623
|
success: true,
|
|
588
624
|
poseType: normalizedPoseType,
|
|
589
625
|
action: matched,
|
|
590
626
|
bubble: bubbleText,
|
|
627
|
+
log: logText,
|
|
591
628
|
};
|
|
592
629
|
},
|
|
593
630
|
});
|
|
@@ -705,9 +742,9 @@ const plugin = {
|
|
|
705
742
|
});
|
|
706
743
|
|
|
707
744
|
api.registerTool({
|
|
708
|
-
name: "
|
|
745
|
+
name: "kichi_query_status",
|
|
709
746
|
description:
|
|
710
|
-
"Query Kichi
|
|
747
|
+
"Query Kichi avatar status (notes, weather/time, timer snapshot, and daily note quota). Use this before creating a new note.",
|
|
711
748
|
parameters: {
|
|
712
749
|
type: "object",
|
|
713
750
|
properties: {
|
|
@@ -727,14 +764,102 @@ const plugin = {
|
|
|
727
764
|
}
|
|
728
765
|
|
|
729
766
|
try {
|
|
730
|
-
const result = await service.
|
|
767
|
+
const result = await service.queryStatus(
|
|
731
768
|
typeof requestId === "string" ? requestId : undefined,
|
|
732
769
|
);
|
|
733
770
|
return result;
|
|
734
771
|
} catch (error) {
|
|
735
772
|
return {
|
|
736
773
|
success: false,
|
|
737
|
-
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}`,
|
|
738
863
|
};
|
|
739
864
|
}
|
|
740
865
|
},
|
|
@@ -755,18 +880,13 @@ const plugin = {
|
|
|
755
880
|
type: "string",
|
|
756
881
|
description: "Note content to create. Maximum 200 characters.",
|
|
757
882
|
},
|
|
758
|
-
requestId: {
|
|
759
|
-
type: "string",
|
|
760
|
-
description: "Optional request ID for tracing or deduplication.",
|
|
761
|
-
},
|
|
762
883
|
},
|
|
763
884
|
required: ["propId", "data"],
|
|
764
885
|
},
|
|
765
886
|
execute: async (_toolCallId, params) => {
|
|
766
|
-
const { propId, data
|
|
887
|
+
const { propId, data } = (params || {}) as {
|
|
767
888
|
propId?: unknown;
|
|
768
889
|
data?: unknown;
|
|
769
|
-
requestId?: unknown;
|
|
770
890
|
};
|
|
771
891
|
if (typeof propId !== "string" || !propId.trim()) {
|
|
772
892
|
return { success: false, error: "propId is required" };
|
|
@@ -780,38 +900,13 @@ const plugin = {
|
|
|
780
900
|
error: `data must be ${MAX_NOTEBOARD_TEXT_LENGTH} characters or fewer`,
|
|
781
901
|
};
|
|
782
902
|
}
|
|
783
|
-
if (requestId !== undefined && typeof requestId !== "string") {
|
|
784
|
-
return { success: false, error: "requestId must be a string when provided" };
|
|
785
|
-
}
|
|
786
903
|
if (!service?.hasValidIdentity() || !service?.isConnected()) {
|
|
787
904
|
return { success: false, error: "Not connected to Kichi world" };
|
|
788
905
|
}
|
|
789
906
|
|
|
790
907
|
try {
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
data.trim(),
|
|
794
|
-
typeof requestId === "string" ? requestId : undefined,
|
|
795
|
-
);
|
|
796
|
-
if (!result.success) {
|
|
797
|
-
return {
|
|
798
|
-
...result,
|
|
799
|
-
summary: buildMutationSummary(result),
|
|
800
|
-
};
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
return {
|
|
804
|
-
success: true,
|
|
805
|
-
requestId: result.requestId,
|
|
806
|
-
mateId: result.mateId,
|
|
807
|
-
spaceId: result.spaceId,
|
|
808
|
-
propId: result.propId,
|
|
809
|
-
dailyLimit: result.dailyLimit,
|
|
810
|
-
remaining: result.remaining,
|
|
811
|
-
resetAtUtc: result.resetAtUtc,
|
|
812
|
-
note: summarizeCreatedNote(result.note),
|
|
813
|
-
summary: buildMutationSummary(result),
|
|
814
|
-
};
|
|
908
|
+
service.createNotesBoardNote(propId.trim(), data.trim());
|
|
909
|
+
return { success: true };
|
|
815
910
|
} catch (error) {
|
|
816
911
|
return {
|
|
817
912
|
success: false,
|
package/package.json
CHANGED