@yahaha-studio/kichi-forwarder 0.0.1-alpha.49 → 0.0.1-alpha.50
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/config/kichi-config.json +72 -0
- package/index.ts +108 -133
- package/package.json +1 -1
- package/skills/kichi-forwarder/SKILL.md +60 -279
- package/skills/kichi-forwarder/references/install.md +42 -61
- package/src/config.ts +15 -4
- package/src/service.ts +345 -233
- package/src/types.ts +12 -5
- package/config/album-config.json +0 -509
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"actions": {
|
|
3
|
+
"stand": ["High Five", "Listen Music", "Arm Stretch", "Backbend Stretch", "Making Selfie", "Arms Crossed", "Epiphany", "Angry", "Yay", "Dance", "Sing", "Tired", "Wait", "Stand Phone Talk", "Stand Phone Play", "Curtsy", "Stand Writing", "Stand Drawing", "Stand Play Guitar", "Stand Typing with Keyboard", "Cry", "Dance with Joy", "Float", "Hand on Chest", "Horse Stance", "Idle Backup Hands", "No", "Panic", "Playful Point Up", "Rub Hands", "Run Jump", "Star Showing", "Walk", "Goofy Moves", "Reading"],
|
|
4
|
+
"sit": ["Typing with Keyboard", "Thinking", "Writing", "Crazy", "Hand Cramp", "Dozing", "Phone Talk", "Situp with Arms Crossed", "Situp with Cross Legs", "Eating", "Laze with Cross Legs", "Sit with Arm Stretch", "Drink", "Sit with Making Selfie", "Play Game", "Situp Sleep", "Sit Phone Play", "Painting", "Daze", "Trace Circles", "Reading", "Contemplate", "Chin Rest", "Sleep with Table", "Cute Chin Rest", "Sit Nicely", "Sit Play Guitar", "Meditate"],
|
|
5
|
+
"lay": ["Bend One Knee", "Sleep Curl up Side way", "Rest Chin", "Lie Flat", "Lie Face Down", "Lie Side", "Lay Writing", "Lay Painting", "Sleep Getup", "Starfish", "Lie Side Play Phone", "Prone Play Phone", "Play Laptop"],
|
|
6
|
+
"floor": ["Seiza", "Cross Legged", "Knee Hug", "Writing", "Painting", "Floor Phone Play", "Typing with Keyboard", "Reading", "Phone Talk", "Phone Talk with Point", "Thinking", "Yawn", "Chin Rest", "Finger Tap Chin", "Arm Stretch", "Crazy", "Remorse", "Tantrum", "Squat", "Cross Legs", "Lean Sit", "Playful Point up", "Swing Legs", "Drained", "Meditate"]
|
|
7
|
+
},
|
|
8
|
+
"album": {
|
|
9
|
+
"albumCount": 10,
|
|
10
|
+
"trackCount": 58,
|
|
11
|
+
"track": [
|
|
12
|
+
{ "album": "Dusty Bookshelves", "name": "A moment of calm", "tags": ["Gentle", "Relaxing", "Easy listening"] },
|
|
13
|
+
{ "album": "Dusty Bookshelves", "name": "Closed Love", "tags": ["Dark ", "Magnificent"] },
|
|
14
|
+
{ "album": "Dusty Bookshelves", "name": "In That Mood", "tags": ["Lounge Bar Cafe", "Relaxing"] },
|
|
15
|
+
{ "album": "Dusty Bookshelves", "name": "Let It Happen", "tags": ["Lounge Restaurant Cafe", "Light"] },
|
|
16
|
+
{ "album": "Dusty Bookshelves", "name": "Memories forever", "tags": ["Moving", "Gentle", "Warm", "Sad ballad"] },
|
|
17
|
+
{ "album": "Dusty Bookshelves", "name": "Outer Space", "tags": ["Space ", "Magnificent"] },
|
|
18
|
+
{ "album": "Dusty Bookshelves", "name": "Spiritual world", "tags": ["Inorganic", "Unrealistic", "Simple"] },
|
|
19
|
+
{ "album": "Late Night Coder", "name": "Calm Time", "tags": ["Calm ", "Gentle"] },
|
|
20
|
+
{ "album": "Late Night Coder", "name": "Counting the Stars", "tags": ["Gentle", "Calm", "Sleep", "Night", "Easy listening"] },
|
|
21
|
+
{ "album": "Late Night Coder", "name": "Deserted landscape", "tags": ["Inorganic", "5 beats Polyrhythm"] },
|
|
22
|
+
{ "album": "Late Night Coder", "name": "Dog Days", "tags": ["Slightly dark", "Drowsy"] },
|
|
23
|
+
{ "album": "Late Night Coder", "name": "It Was an Unpleasant Incident", "tags": ["Gentle", "Slightly sad"] },
|
|
24
|
+
{ "album": "Late Night Coder", "name": "The Dark Eternal Night", "tags": ["Slightly dark", "Eerie"] },
|
|
25
|
+
{ "album": "Midnight Cyber-Transit", "name": "Afterimage Metaphor", "tags": ["Sad ", "Lonely", "Transparent"] },
|
|
26
|
+
{ "album": "Midnight Cyber-Transit", "name": "I keep looking at the floor", "tags": ["Somewhat dark", "Empty"] },
|
|
27
|
+
{ "album": "Midnight Cyber-Transit", "name": "Interstellar matter", "tags": ["Space ", "Floating feeling"] },
|
|
28
|
+
{ "album": "Midnight Cyber-Transit", "name": "Special To Me", "tags": ["Bebop jazz", "Light", "Radio background music"] },
|
|
29
|
+
{ "album": "Midnight Cyber-Transit", "name": "Specification", "tags": ["Slightly happy", "General"] },
|
|
30
|
+
{ "album": "Morning Dew & Drip Coffee", "name": "Daytime activities", "tags": ["General", "Languid", "Daytime"] },
|
|
31
|
+
{ "album": "Morning Dew & Drip Coffee", "name": "Enjoy the rest of your day", "tags": ["Comical", "Cheerful", "Fun", "Simple"] },
|
|
32
|
+
{ "album": "Morning Dew & Drip Coffee", "name": "Entertainment Committee", "tags": ["Slightly bright", "Relaxing"] },
|
|
33
|
+
{ "album": "Morning Dew & Drip Coffee", "name": "Heavy Rain", "tags": ["Inorganic", "Mysterious"] },
|
|
34
|
+
{ "album": "Morning Dew & Drip Coffee", "name": "Lovely Day", "tags": ["Bright", "Gentle", "Calm"] },
|
|
35
|
+
{ "album": "Morning Dew & Drip Coffee", "name": "Relax in the shade", "tags": ["Bright", "Kind", "Warm", "Fun", "Relaxing"] },
|
|
36
|
+
{ "album": "Morning Dew & Drip Coffee", "name": "Spring is a nap", "tags": ["Relaxing", "Gentle", "Cheerful", "Easy listening"] },
|
|
37
|
+
{ "album": "Morning Dew & Drip Coffee", "name": "Warm tea time", "tags": ["Slightly bright", "Gentle", "Ennui"] },
|
|
38
|
+
{ "album": "Nostalgic Frequencies", "name": "Full of Energy", "tags": ["Slightly bright", "Uplifting", "Medium tempo"] },
|
|
39
|
+
{ "album": "Nostalgic Frequencies", "name": "Someday in the Rain", "tags": ["Slightly bright", "Moist"] },
|
|
40
|
+
{ "album": "Nostalgic Frequencies", "name": "Still in Love", "tags": ["Slightly sad"] },
|
|
41
|
+
{ "album": "Nostalgic Frequencies", "name": "Trace", "tags": ["Melancholic", "Sentimental", "Nostalgic"] },
|
|
42
|
+
{ "album": "Nostalgic Frequencies", "name": "Water Town", "tags": ["Slightly bright", "Gentle", "Simple"] },
|
|
43
|
+
{ "album": "Overgrown Ruins", "name": "Blank and Silence", "tags": ["Light", "General purpose"] },
|
|
44
|
+
{ "album": "Overgrown Ruins", "name": "Darkness of the New Moon", "tags": ["Dark", "Horror"] },
|
|
45
|
+
{ "album": "Overgrown Ruins", "name": "Flowers that Bloom in Sadness", "tags": ["Slightly dark ", "Sad"] },
|
|
46
|
+
{ "album": "Overgrown Ruins", "name": "The Price of Mistakes", "tags": ["Sad", "Painful", "Serious", "Flashback"] },
|
|
47
|
+
{ "album": "Raindrops on the Windowpane", "name": "A little love there", "tags": ["Gentle ", "Slumber"] },
|
|
48
|
+
{ "album": "Raindrops on the Windowpane", "name": "Beside You", "tags": ["Moving", "Gentle", "Warm", "Sad ballad"] },
|
|
49
|
+
{ "album": "Raindrops on the Windowpane", "name": "Blue Star", "tags": ["Calm", "Night"] },
|
|
50
|
+
{ "album": "Raindrops on the Windowpane", "name": "I want to get to know you", "tags": ["Touching", "Sad", "Kind"] },
|
|
51
|
+
{ "album": "Raindrops on the Windowpane", "name": "Passing each other", "tags": ["Slightly dark", "Calm", "Weak"] },
|
|
52
|
+
{ "album": "Raindrops on the Windowpane", "name": "Pieces of a Dream", "tags": ["Lounge Bar Relaxation"] },
|
|
53
|
+
{ "album": "Raindrops on the Windowpane", "name": "Puzzle", "tags": ["Slightly dark", "Calm"] },
|
|
54
|
+
{ "album": "Raindrops on the Windowpane", "name": "The meaning of tears", "tags": ["Touching ", "Mellow"] },
|
|
55
|
+
{ "album": "Sunday Laundry", "name": "Abandoned cat in the Rain", "tags": ["Little dark", "Lonely"] },
|
|
56
|
+
{ "album": "Sunday Laundry", "name": "Fairy Forest", "tags": ["Bright ", "Fantasy ", "Lively"] },
|
|
57
|
+
{ "album": "Sunday Laundry", "name": "In the sunlight filtering through the trees", "tags": ["Bright", "Relaxed", "Peaceful"] },
|
|
58
|
+
{ "album": "Sunday Laundry", "name": "Perfect Weather for a Walk", "tags": ["Cute", "Bright", "Simple", "Fun", "Relaxing"] },
|
|
59
|
+
{ "album": "Sunday Laundry", "name": "Rural Prairie", "tags": ["Bright", "Pastoral", "Gentle"] },
|
|
60
|
+
{ "album": "Sunday Laundry", "name": "Spring Classroom", "tags": ["Bright", "Gentle"] },
|
|
61
|
+
{ "album": "Sunset at the Pier", "name": "Deep Sea Passage", "tags": ["Slightly bright", "Inorganic"] },
|
|
62
|
+
{ "album": "Sunset at the Pier", "name": "Memory", "tags": ["Slightly dark", "Magnificent"] },
|
|
63
|
+
{ "album": "Sunset at the Pier", "name": "Smiles and Tears", "tags": ["Slightly bright", "Sad"] },
|
|
64
|
+
{ "album": "Sunset at the Pier", "name": "Surrounded by Silence", "tags": ["Gentle", "Calm", "Stylish", "Easy listening"] },
|
|
65
|
+
{ "album": "Zenith Garden", "name": "Cold Sweat", "tags": ["Comical", "Failure", "Simple"] },
|
|
66
|
+
{ "album": "Zenith Garden", "name": "Hometown Sunset", "tags": ["Slightly dark", "Gentle", "Simple"] },
|
|
67
|
+
{ "album": "Zenith Garden", "name": "Nostalgia", "tags": ["Moving", "Sad", "Melancholy", "Lonely"] },
|
|
68
|
+
{ "album": "Zenith Garden", "name": "Reverie", "tags": ["Slightly dark", "Transparent"] },
|
|
69
|
+
{ "album": "Zenith Garden", "name": "Water Cave", "tags": ["Slightly dark", "Inorganic", "Transparent"] }
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
}
|
package/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
4
5
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
5
6
|
import { parse } from "./src/config.js";
|
|
6
7
|
import { KichiForwarderService } from "./src/service.js";
|
|
@@ -9,23 +10,15 @@ import type {
|
|
|
9
10
|
Album,
|
|
10
11
|
ClockAction,
|
|
11
12
|
ClockConfig,
|
|
12
|
-
|
|
13
|
+
KichiEnv,
|
|
13
14
|
KichiForwarderConfig,
|
|
15
|
+
KichiState,
|
|
16
|
+
KichiStaticConfig,
|
|
14
17
|
PomodoroPhase,
|
|
15
18
|
PoseType,
|
|
16
19
|
} from "./src/types.js";
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
stand: ["High Five", "Listen Music", "Arm Stretch", "Backbend Stretch", "Making Selfie", "Arms Crossed", "Epiphany", "Angry", "Yay", "Dance", "Sing", "Tired", "Wait", "Stand Phone Talk", "Stand Phone Play", "Curtsy", "Stand Writing", "Stand Drawing", "Stand Play Guitar", "Stand Typing with Keyboard", "Cry", "Dance with Joy", "Float", "Hand on Chest", "Horse Stance", "Idle Backup Hands", "No", "Panic", "Playful Point Up", "Rub Hands", "Run Jump", "Star Showing", "Walk", "Goofy Moves", "Reading"],
|
|
20
|
-
sit: ["Typing with Keyboard", "Thinking", "Writing", "Crazy", "Hand Cramp", "Dozing", "Phone Talk", "Situp with Arms Crossed", "Situp with Cross Legs", "Eating", "Laze with Cross Legs", "Sit with Arm Stretch", "Drink", "Sit with Making Selfie", "Play Game", "Situp Sleep", "Sit Phone Play", "Painting", "Daze", "Trace Circles", "Reading", "Contemplate", "Chin Rest", "Sleep with Table", "Cute Chin Rest", "Sit Nicely", "Sit Play Guitar", "Meditate"],
|
|
21
|
-
lay: ["Bend One Knee", "Sleep Curl up Side way", "Rest Chin", "Lie Flat", "Lie Face Down", "Lie Side", "Lay Writing", "Lay Painting", "Sleep Getup", "Starfish", "Lie Side Play Phone", "Prone Play Phone", "Play Laptop"],
|
|
22
|
-
floor: ["Seiza", "Cross Legged", "Knee Hug", "Writing", "Painting", "Floor Phone Play", "Typing with Keyboard", "Reading", "Phone Talk", "Phone Talk with Point", "Thinking", "Yawn", "Chin Rest", "Finger Tap Chin", "Arm Stretch", "Crazy", "Remorse", "Tantrum", "Squat", "Cross Legs", "Lean Sit", "Playful Point up", "Swing Legs", "Drained", "Meditate"],
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const DEFAULT_RUNTIME_CONFIG: KichiRuntimeConfig = {
|
|
26
|
-
actions: DEFAULT_ACTIONS,
|
|
27
|
-
llmRuntimeEnabled: true,
|
|
28
|
-
};
|
|
20
|
+
const BUNDLED_STATIC_CONFIG_PATH = new URL("./config/kichi-config.json", import.meta.url);
|
|
21
|
+
const DEFAULT_LLM_RUNTIME_ENABLED = true;
|
|
29
22
|
const FIXED_HOOK_STATUSES: Record<string, ActionResult> = {
|
|
30
23
|
beforePromptBuild: {
|
|
31
24
|
poseType: "sit",
|
|
@@ -53,25 +46,12 @@ const FIXED_HOOK_STATUSES: Record<string, ActionResult> = {
|
|
|
53
46
|
},
|
|
54
47
|
};
|
|
55
48
|
|
|
56
|
-
const MESSAGE_SENT_BUBBLES = [
|
|
57
|
-
"All set!",
|
|
58
|
-
"Sent.",
|
|
59
|
-
"Delivered.",
|
|
60
|
-
"Done and sent.",
|
|
61
|
-
"It's out.",
|
|
62
|
-
"All yours.",
|
|
63
|
-
];
|
|
64
|
-
|
|
65
49
|
const KICHI_WORLD_DIR = path.join(os.homedir(), ".openclaw", "kichi-world");
|
|
66
|
-
const
|
|
67
|
-
const LEGACY_SKILLS_CONFIG_PATH = path.join(KICHI_WORLD_DIR, "skills-config.json");
|
|
68
|
-
const IDENTITY_PATH = path.join(KICHI_WORLD_DIR, "identity.json");
|
|
69
|
-
const RUNTIME_ALBUM_CONFIG_PATH = path.join(KICHI_WORLD_DIR, "album-config.json");
|
|
50
|
+
const STATE_PATH = path.join(KICHI_WORLD_DIR, "state.json");
|
|
70
51
|
const MAX_NOTEBOARD_TEXT_LENGTH = 200;
|
|
71
52
|
const MAX_MESSAGE_RECEIVED_PREVIEW_WIDTH = 20;
|
|
72
53
|
const MAX_AGENT_END_PREVIEW_WIDTH = 10;
|
|
73
54
|
const MESSAGE_RECEIVED_ELLIPSIS = "...";
|
|
74
|
-
const BUNDLED_ALBUM_CONFIG_PATH = new URL("./config/album-config.json", import.meta.url);
|
|
75
55
|
const ANSI = {
|
|
76
56
|
reset: "\u001b[0m",
|
|
77
57
|
bold: "\u001b[1m",
|
|
@@ -87,11 +67,8 @@ const ANSI = {
|
|
|
87
67
|
const WORKSPACE_SCREEN_WIDTH = 109;
|
|
88
68
|
const WORKSPACE_ACTIVITY_LIMIT = 8;
|
|
89
69
|
const WORKSPACE_SCREEN_PUSH_DEBOUNCE_MS = 150;
|
|
90
|
-
let
|
|
91
|
-
let
|
|
92
|
-
let cachedConfigPath = "";
|
|
93
|
-
let cachedAlbumConfig: Album | null = null;
|
|
94
|
-
let cachedAlbumConfigMtime = 0;
|
|
70
|
+
let cachedStaticConfig: KichiStaticConfig | null = null;
|
|
71
|
+
let cachedStaticConfigMtime = 0;
|
|
95
72
|
let service: KichiForwarderService | null = null;
|
|
96
73
|
let pluginApi: OpenClawPluginApi | null = null;
|
|
97
74
|
let workspaceState: WorkspaceScreenState = createWorkspaceScreenState();
|
|
@@ -283,39 +260,8 @@ function isAlbumConfig(value: unknown): value is Album {
|
|
|
283
260
|
});
|
|
284
261
|
}
|
|
285
262
|
|
|
286
|
-
function loadAlbumConfigFromPath(configPath: string | URL): Album {
|
|
287
|
-
const raw = fs.readFileSync(configPath, "utf-8");
|
|
288
|
-
const parsed = JSON.parse(raw) as unknown;
|
|
289
|
-
if (!isAlbumConfig(parsed)) {
|
|
290
|
-
throw new Error(`Invalid album config at ${String(configPath)}`);
|
|
291
|
-
}
|
|
292
|
-
return parsed;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
function ensureRuntimeAlbumConfig(): void {
|
|
296
|
-
fs.mkdirSync(KICHI_WORLD_DIR, { recursive: true });
|
|
297
|
-
if (!fs.existsSync(RUNTIME_ALBUM_CONFIG_PATH)) {
|
|
298
|
-
fs.copyFileSync(BUNDLED_ALBUM_CONFIG_PATH, RUNTIME_ALBUM_CONFIG_PATH);
|
|
299
|
-
pluginApi?.logger.debug("[kichi] seeded runtime album config from bundled config");
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
try {
|
|
304
|
-
loadAlbumConfigFromPath(RUNTIME_ALBUM_CONFIG_PATH);
|
|
305
|
-
} catch (error) {
|
|
306
|
-
pluginApi?.logger.warn(`[kichi] invalid runtime album config, resetting from bundled config: ${error}`);
|
|
307
|
-
fs.copyFileSync(BUNDLED_ALBUM_CONFIG_PATH, RUNTIME_ALBUM_CONFIG_PATH);
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
263
|
function loadRuntimeAlbumConfig(): Album {
|
|
312
|
-
|
|
313
|
-
const stat = fs.statSync(RUNTIME_ALBUM_CONFIG_PATH);
|
|
314
|
-
if (!cachedAlbumConfig || stat.mtimeMs !== cachedAlbumConfigMtime) {
|
|
315
|
-
cachedAlbumConfig = loadAlbumConfigFromPath(RUNTIME_ALBUM_CONFIG_PATH);
|
|
316
|
-
cachedAlbumConfigMtime = stat.mtimeMs;
|
|
317
|
-
}
|
|
318
|
-
return cachedAlbumConfig;
|
|
264
|
+
return loadStaticConfig().album;
|
|
319
265
|
}
|
|
320
266
|
|
|
321
267
|
function getMusicTitleLookup(): Map<string, string> {
|
|
@@ -332,70 +278,61 @@ function getMusicTitleExamples(): string[] {
|
|
|
332
278
|
return loadRuntimeAlbumConfig().track.slice(0, 10).map((item) => item.name);
|
|
333
279
|
}
|
|
334
280
|
|
|
335
|
-
function
|
|
336
|
-
if (!
|
|
337
|
-
return
|
|
281
|
+
function isPoseActions(value: unknown): value is Record<PoseType, string[]> {
|
|
282
|
+
if (!value || typeof value !== "object") {
|
|
283
|
+
return false;
|
|
338
284
|
}
|
|
339
|
-
const actions = value
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
285
|
+
const actions = value as Partial<Record<PoseType, unknown>>;
|
|
286
|
+
return ["stand", "sit", "lay", "floor"].every((pose) =>
|
|
287
|
+
Array.isArray(actions[pose as PoseType])
|
|
288
|
+
&& (actions[pose as PoseType] as unknown[]).every((item) => typeof item === "string" && item.trim().length > 0));
|
|
343
289
|
}
|
|
344
290
|
|
|
345
|
-
function
|
|
346
|
-
const raw = value && typeof value === "object" ? (value as Partial<
|
|
291
|
+
function normalizeStaticConfig(value: unknown): KichiStaticConfig {
|
|
292
|
+
const raw = value && typeof value === "object" ? (value as Partial<KichiStaticConfig>) : {};
|
|
347
293
|
const actions = raw.actions;
|
|
294
|
+
const album = raw.album;
|
|
295
|
+
if (!isPoseActions(actions)) {
|
|
296
|
+
throw new Error("config/kichi-config.json must include valid actions");
|
|
297
|
+
}
|
|
298
|
+
if (!isAlbumConfig(album)) {
|
|
299
|
+
throw new Error("config/kichi-config.json must include a valid album object");
|
|
300
|
+
}
|
|
348
301
|
return {
|
|
349
|
-
|
|
350
|
-
actions
|
|
351
|
-
stand: sanitizeActions(actions?.stand, DEFAULT_ACTIONS.stand),
|
|
352
|
-
sit: sanitizeActions(actions?.sit, DEFAULT_ACTIONS.sit),
|
|
353
|
-
lay: sanitizeActions(actions?.lay, DEFAULT_ACTIONS.lay),
|
|
354
|
-
floor: sanitizeActions(actions?.floor, DEFAULT_ACTIONS.floor),
|
|
355
|
-
},
|
|
302
|
+
album,
|
|
303
|
+
actions,
|
|
356
304
|
};
|
|
357
305
|
}
|
|
358
306
|
|
|
359
|
-
function
|
|
360
|
-
if (fs.existsSync(
|
|
361
|
-
return
|
|
307
|
+
function readState(): KichiState {
|
|
308
|
+
if (!fs.existsSync(STATE_PATH)) {
|
|
309
|
+
return {
|
|
310
|
+
currentEnv: "prod",
|
|
311
|
+
llmRuntimeEnabled: DEFAULT_LLM_RUNTIME_ENABLED,
|
|
312
|
+
};
|
|
362
313
|
}
|
|
363
|
-
|
|
364
|
-
|
|
314
|
+
const data = JSON.parse(fs.readFileSync(STATE_PATH, "utf-8")) as Partial<KichiState>;
|
|
315
|
+
if (data.currentEnv !== "local" && data.currentEnv !== "dev" && data.currentEnv !== "prod") {
|
|
316
|
+
throw new Error(`Invalid currentEnv in ${STATE_PATH}`);
|
|
365
317
|
}
|
|
366
|
-
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
function updateCachedRuntimeConfig(config: KichiRuntimeConfig, sourcePath: string | null): KichiRuntimeConfig {
|
|
370
|
-
cachedConfig = config;
|
|
371
|
-
cachedConfigPath = sourcePath ?? "";
|
|
372
|
-
try {
|
|
373
|
-
cachedConfigMtime = sourcePath && fs.existsSync(sourcePath)
|
|
374
|
-
? fs.statSync(sourcePath).mtimeMs
|
|
375
|
-
: 0;
|
|
376
|
-
} catch {
|
|
377
|
-
cachedConfigMtime = 0;
|
|
318
|
+
if (typeof data.llmRuntimeEnabled !== "boolean") {
|
|
319
|
+
throw new Error(`Invalid llmRuntimeEnabled in ${STATE_PATH}`);
|
|
378
320
|
}
|
|
379
|
-
return
|
|
321
|
+
return {
|
|
322
|
+
currentEnv: data.currentEnv,
|
|
323
|
+
llmRuntimeEnabled: data.llmRuntimeEnabled,
|
|
324
|
+
};
|
|
380
325
|
}
|
|
381
326
|
|
|
382
|
-
function
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
updateCachedRuntimeConfig(normalizeRuntimeConfig(JSON.parse(raw)), configPath);
|
|
390
|
-
const sourceName = path.basename(configPath);
|
|
391
|
-
pluginApi?.logger.debug(`[kichi] loaded runtime config from ${sourceName}`);
|
|
392
|
-
}
|
|
393
|
-
return cachedConfig!;
|
|
394
|
-
}
|
|
395
|
-
} catch (error) {
|
|
396
|
-
pluginApi?.logger.warn(`[kichi] failed to load runtime config: ${error}`);
|
|
327
|
+
function loadStaticConfig(): KichiStaticConfig {
|
|
328
|
+
const configPath = fileURLToPath(BUNDLED_STATIC_CONFIG_PATH);
|
|
329
|
+
const stat = fs.statSync(configPath);
|
|
330
|
+
if (!cachedStaticConfig || stat.mtimeMs !== cachedStaticConfigMtime) {
|
|
331
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
332
|
+
cachedStaticConfig = normalizeStaticConfig(JSON.parse(raw));
|
|
333
|
+
cachedStaticConfigMtime = stat.mtimeMs;
|
|
397
334
|
}
|
|
398
|
-
return
|
|
335
|
+
return cachedStaticConfig;
|
|
399
336
|
}
|
|
400
337
|
|
|
401
338
|
function sendStatusUpdate(status: ActionResult): void {
|
|
@@ -408,7 +345,7 @@ function sendStatusUpdate(status: ActionResult): void {
|
|
|
408
345
|
}
|
|
409
346
|
|
|
410
347
|
function isLlmRuntimeEnabled(): boolean {
|
|
411
|
-
return
|
|
348
|
+
return readState().llmRuntimeEnabled;
|
|
412
349
|
}
|
|
413
350
|
|
|
414
351
|
function syncFixedStatus(status: ActionResult): void {
|
|
@@ -882,10 +819,6 @@ function normalizeClockConfig(value: unknown): { clock?: ClockConfig; error?: st
|
|
|
882
819
|
};
|
|
883
820
|
}
|
|
884
821
|
|
|
885
|
-
function pickRandomAction(actions: string[]): string {
|
|
886
|
-
return actions[Math.floor(Math.random() * actions.length)];
|
|
887
|
-
}
|
|
888
|
-
|
|
889
822
|
function normalizeMusicTitles(value: unknown): { titles: string[]; invalidTitles: string[] } {
|
|
890
823
|
if (!Array.isArray(value)) {
|
|
891
824
|
return { titles: [], invalidTitles: [] };
|
|
@@ -925,17 +858,32 @@ function normalizeMusicTitles(value: unknown): { titles: string[]; invalidTitles
|
|
|
925
858
|
function buildMusicAlbumToolDescription(): string {
|
|
926
859
|
return [
|
|
927
860
|
"Create a custom Kichi music album.",
|
|
928
|
-
"Query status first, then choose track names from the
|
|
861
|
+
"Query status first, then choose track names from the values injected into this tool schema from the static config bundled with the plugin package.",
|
|
929
862
|
].join("\n");
|
|
930
863
|
}
|
|
931
864
|
|
|
865
|
+
function isKichiEnv(value: unknown): value is KichiEnv {
|
|
866
|
+
return value === "local" || value === "dev" || value === "prod";
|
|
867
|
+
}
|
|
868
|
+
|
|
932
869
|
function buildMusicTitlesDescription(): string {
|
|
933
870
|
return [
|
|
934
|
-
"Track names
|
|
871
|
+
"Track names are injected into this tool schema from the static config bundled with the plugin package.",
|
|
935
872
|
"Use exact names only; the available titles are injected into this tool schema.",
|
|
936
873
|
].join(" ");
|
|
937
874
|
}
|
|
938
875
|
|
|
876
|
+
function buildKichiActionDescription(): string {
|
|
877
|
+
const actions = loadStaticConfig().actions;
|
|
878
|
+
return [
|
|
879
|
+
"Send an action or pose to Kichi world. Use this for explicit Kichi actions and task lifecycle sync.",
|
|
880
|
+
`stand actions: ${actions.stand.join(", ")}`,
|
|
881
|
+
`sit actions: ${actions.sit.join(", ")}`,
|
|
882
|
+
`lay actions: ${actions.lay.join(", ")}`,
|
|
883
|
+
`floor actions: ${actions.floor.join(", ")}`,
|
|
884
|
+
].join("\n");
|
|
885
|
+
}
|
|
886
|
+
|
|
939
887
|
function buildKichiPrompt(): string {
|
|
940
888
|
return [
|
|
941
889
|
"Kichi App status sync is available via `kichi_action` and `kichi_clock`.",
|
|
@@ -960,7 +908,6 @@ const plugin = {
|
|
|
960
908
|
|
|
961
909
|
register(api: OpenClawPluginApi) {
|
|
962
910
|
pluginApi = api;
|
|
963
|
-
ensureRuntimeAlbumConfig();
|
|
964
911
|
registerPluginHooks(api);
|
|
965
912
|
const musicTitleEnum = getMusicTitleEnum();
|
|
966
913
|
|
|
@@ -1014,12 +961,7 @@ const plugin = {
|
|
|
1014
961
|
(params as { tags?: unknown } | null)?.tags,
|
|
1015
962
|
);
|
|
1016
963
|
if (!avatarId) {
|
|
1017
|
-
|
|
1018
|
-
const identity = JSON.parse(fs.readFileSync(IDENTITY_PATH, "utf-8")) as {
|
|
1019
|
-
avatarId?: string;
|
|
1020
|
-
};
|
|
1021
|
-
avatarId = identity.avatarId;
|
|
1022
|
-
} catch {}
|
|
964
|
+
avatarId = service?.readSavedAvatarId() ?? undefined;
|
|
1023
965
|
}
|
|
1024
966
|
if (!avatarId) {
|
|
1025
967
|
return { success: false, error: "No avatarId" };
|
|
@@ -1050,6 +992,40 @@ const plugin = {
|
|
|
1050
992
|
},
|
|
1051
993
|
});
|
|
1052
994
|
|
|
995
|
+
api.registerTool({
|
|
996
|
+
name: "kichi_switch_env",
|
|
997
|
+
description:
|
|
998
|
+
"Switch Kichi runtime environment to local, dev, or prod and reconnect immediately without restarting the gateway.",
|
|
999
|
+
parameters: {
|
|
1000
|
+
type: "object",
|
|
1001
|
+
properties: {
|
|
1002
|
+
env: {
|
|
1003
|
+
type: "string",
|
|
1004
|
+
description: "Target environment: local, dev, or prod",
|
|
1005
|
+
enum: ["local", "dev", "prod"],
|
|
1006
|
+
},
|
|
1007
|
+
},
|
|
1008
|
+
required: ["env"],
|
|
1009
|
+
},
|
|
1010
|
+
execute: async (_toolCallId, params) => {
|
|
1011
|
+
if (!service) {
|
|
1012
|
+
return { success: false, error: "Kichi service is not initialized" };
|
|
1013
|
+
}
|
|
1014
|
+
const env = (params as { env?: unknown } | null)?.env;
|
|
1015
|
+
if (!isKichiEnv(env)) {
|
|
1016
|
+
return { success: false, error: "env must be one of: local, dev, prod" };
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
const status = await service.switchEnvironment(env);
|
|
1020
|
+
scheduleWorkspacePush();
|
|
1021
|
+
return {
|
|
1022
|
+
success: true,
|
|
1023
|
+
env,
|
|
1024
|
+
status,
|
|
1025
|
+
};
|
|
1026
|
+
},
|
|
1027
|
+
});
|
|
1028
|
+
|
|
1053
1029
|
api.registerTool({
|
|
1054
1030
|
name: "kichi_rejoin",
|
|
1055
1031
|
description:
|
|
@@ -1107,8 +1083,7 @@ const plugin = {
|
|
|
1107
1083
|
|
|
1108
1084
|
api.registerTool({
|
|
1109
1085
|
name: "kichi_action",
|
|
1110
|
-
description:
|
|
1111
|
-
"Send an action/pose to Kichi world. Use this for explicit Kichi actions and task lifecycle sync.",
|
|
1086
|
+
description: buildKichiActionDescription(),
|
|
1112
1087
|
parameters: {
|
|
1113
1088
|
type: "object",
|
|
1114
1089
|
properties: {
|
|
@@ -1147,7 +1122,7 @@ const plugin = {
|
|
|
1147
1122
|
}
|
|
1148
1123
|
|
|
1149
1124
|
const normalizedPoseType = poseType as PoseType;
|
|
1150
|
-
const poseActions =
|
|
1125
|
+
const poseActions = loadStaticConfig().actions[normalizedPoseType];
|
|
1151
1126
|
const matched = poseActions.find((entry) => entry.toLowerCase() === action.toLowerCase());
|
|
1152
1127
|
if (!matched) {
|
|
1153
1128
|
return {
|
|
@@ -1436,7 +1411,7 @@ const plugin = {
|
|
|
1436
1411
|
if (normalizedTitles.length === 0) {
|
|
1437
1412
|
return {
|
|
1438
1413
|
success: false,
|
|
1439
|
-
error: "musicTitles must contain at least one valid track name from
|
|
1414
|
+
error: "musicTitles must contain at least one valid track name from the static config bundled with the plugin package",
|
|
1440
1415
|
examples: getMusicTitleExamples(),
|
|
1441
1416
|
};
|
|
1442
1417
|
}
|
|
@@ -1444,7 +1419,7 @@ const plugin = {
|
|
|
1444
1419
|
return {
|
|
1445
1420
|
success: false,
|
|
1446
1421
|
error: `Unknown musicTitles: ${invalidTitles.join(", ")}`,
|
|
1447
|
-
hint: "Use exact track names from the
|
|
1422
|
+
hint: "Use exact track names from the static config bundled with the plugin package",
|
|
1448
1423
|
examples: getMusicTitleExamples(),
|
|
1449
1424
|
};
|
|
1450
1425
|
}
|
package/package.json
CHANGED