@yahaha-studio/kichi-forwarder 0.0.1-alpha.29 → 0.0.1-alpha.31
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/index.ts
CHANGED
|
@@ -2,11 +2,11 @@ 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";
|
|
6
5
|
import { parse } from "./src/config.js";
|
|
7
6
|
import { KichiForwarderService } from "./src/service.js";
|
|
8
7
|
import type {
|
|
9
8
|
ActionResult,
|
|
9
|
+
Album,
|
|
10
10
|
ClockAction,
|
|
11
11
|
ClockConfig,
|
|
12
12
|
KichiRuntimeConfig,
|
|
@@ -58,17 +58,87 @@ const KICHI_WORLD_DIR = path.join(os.homedir(), ".openclaw", "kichi-world");
|
|
|
58
58
|
const RUNTIME_CONFIG_PATH = path.join(KICHI_WORLD_DIR, "kichi-runtime-config.json");
|
|
59
59
|
const LEGACY_SKILLS_CONFIG_PATH = path.join(KICHI_WORLD_DIR, "skills-config.json");
|
|
60
60
|
const IDENTITY_PATH = path.join(KICHI_WORLD_DIR, "identity.json");
|
|
61
|
+
const RUNTIME_ALBUM_CONFIG_PATH = path.join(KICHI_WORLD_DIR, "album-config.json");
|
|
61
62
|
const MAX_NOTEBOARD_TEXT_LENGTH = 200;
|
|
62
|
-
const
|
|
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);
|
|
63
|
+
const BUNDLED_ALBUM_CONFIG_PATH = new URL("./config/album-config.json", import.meta.url);
|
|
66
64
|
let cachedConfig: KichiRuntimeConfig | null = null;
|
|
67
65
|
let cachedConfigMtime = 0;
|
|
68
66
|
let cachedConfigPath = "";
|
|
67
|
+
let cachedAlbumConfig: Album | null = null;
|
|
68
|
+
let cachedAlbumConfigMtime = 0;
|
|
69
69
|
let service: KichiForwarderService | null = null;
|
|
70
70
|
let pluginApi: OpenClawPluginApi | null = null;
|
|
71
71
|
|
|
72
|
+
function isAlbumConfig(value: unknown): value is Album {
|
|
73
|
+
if (!value || typeof value !== "object") {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const config = value as Partial<Album>;
|
|
78
|
+
return typeof config.albumCount === "number"
|
|
79
|
+
&& typeof config.trackCount === "number"
|
|
80
|
+
&& Array.isArray(config.track)
|
|
81
|
+
&& config.track.every((item) => {
|
|
82
|
+
if (!item || typeof item !== "object") {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
const track = item as Record<string, unknown>;
|
|
86
|
+
return typeof track.album === "string"
|
|
87
|
+
&& typeof track.name === "string"
|
|
88
|
+
&& Array.isArray(track.tags)
|
|
89
|
+
&& track.tags.every((tag) => typeof tag === "string");
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function loadAlbumConfigFromPath(configPath: string | URL): Album {
|
|
94
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
95
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
96
|
+
if (!isAlbumConfig(parsed)) {
|
|
97
|
+
throw new Error(`Invalid album config at ${String(configPath)}`);
|
|
98
|
+
}
|
|
99
|
+
return parsed;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function ensureRuntimeAlbumConfig(): void {
|
|
103
|
+
fs.mkdirSync(KICHI_WORLD_DIR, { recursive: true });
|
|
104
|
+
if (!fs.existsSync(RUNTIME_ALBUM_CONFIG_PATH)) {
|
|
105
|
+
fs.copyFileSync(BUNDLED_ALBUM_CONFIG_PATH, RUNTIME_ALBUM_CONFIG_PATH);
|
|
106
|
+
pluginApi?.logger.debug("[kichi] seeded runtime album config from bundled config");
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
loadAlbumConfigFromPath(RUNTIME_ALBUM_CONFIG_PATH);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
pluginApi?.logger.warn(`[kichi] invalid runtime album config, resetting from bundled config: ${error}`);
|
|
114
|
+
fs.copyFileSync(BUNDLED_ALBUM_CONFIG_PATH, RUNTIME_ALBUM_CONFIG_PATH);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function loadRuntimeAlbumConfig(): Album {
|
|
119
|
+
ensureRuntimeAlbumConfig();
|
|
120
|
+
const stat = fs.statSync(RUNTIME_ALBUM_CONFIG_PATH);
|
|
121
|
+
if (!cachedAlbumConfig || stat.mtimeMs !== cachedAlbumConfigMtime) {
|
|
122
|
+
cachedAlbumConfig = loadAlbumConfigFromPath(RUNTIME_ALBUM_CONFIG_PATH);
|
|
123
|
+
cachedAlbumConfigMtime = stat.mtimeMs;
|
|
124
|
+
}
|
|
125
|
+
return cachedAlbumConfig;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function getMusicTitleLookup(): Map<string, string> {
|
|
129
|
+
return new Map(
|
|
130
|
+
loadRuntimeAlbumConfig().track.map((item) => [item.name.toLowerCase(), item.name] as const),
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function getMusicTitleEnum(): string[] {
|
|
135
|
+
return loadRuntimeAlbumConfig().track.map((item) => item.name);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function getMusicTitleExamples(): string[] {
|
|
139
|
+
return loadRuntimeAlbumConfig().track.slice(0, 10).map((item) => item.name);
|
|
140
|
+
}
|
|
141
|
+
|
|
72
142
|
function sanitizeActions(value: unknown, fallback: string[]): string[] {
|
|
73
143
|
if (!Array.isArray(value)) {
|
|
74
144
|
return fallback;
|
|
@@ -373,6 +443,7 @@ function normalizeMusicTitles(value: unknown): { titles: string[]; invalidTitles
|
|
|
373
443
|
return { titles: [], invalidTitles: [] };
|
|
374
444
|
}
|
|
375
445
|
|
|
446
|
+
const musicTitleLookup = getMusicTitleLookup();
|
|
376
447
|
const titles: string[] = [];
|
|
377
448
|
const invalidTitles: string[] = [];
|
|
378
449
|
const seen = new Set<string>();
|
|
@@ -388,7 +459,7 @@ function normalizeMusicTitles(value: unknown): { titles: string[]; invalidTitles
|
|
|
388
459
|
}
|
|
389
460
|
|
|
390
461
|
const key = trimmed.toLowerCase();
|
|
391
|
-
const canonicalTitle =
|
|
462
|
+
const canonicalTitle = musicTitleLookup.get(key);
|
|
392
463
|
if (!canonicalTitle) {
|
|
393
464
|
invalidTitles.push(trimmed);
|
|
394
465
|
continue;
|
|
@@ -403,6 +474,20 @@ function normalizeMusicTitles(value: unknown): { titles: string[]; invalidTitles
|
|
|
403
474
|
return { titles, invalidTitles };
|
|
404
475
|
}
|
|
405
476
|
|
|
477
|
+
function buildMusicAlbumToolDescription(): string {
|
|
478
|
+
return [
|
|
479
|
+
"Create a custom Kichi music album.",
|
|
480
|
+
"Query status first, then choose track names from the runtime album config: Linux/macOS `~/.openclaw/kichi-world/album-config.json`; Windows `%USERPROFILE%\\.openclaw\\kichi-world\\album-config.json`.",
|
|
481
|
+
].join("\n");
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function buildMusicTitlesDescription(): string {
|
|
485
|
+
return [
|
|
486
|
+
"Track names chosen from the runtime album config: Linux/macOS `~/.openclaw/kichi-world/album-config.json`; Windows `%USERPROFILE%\\.openclaw\\kichi-world\\album-config.json`.",
|
|
487
|
+
"Use exact names only; the available titles are injected into this tool schema.",
|
|
488
|
+
].join(" ");
|
|
489
|
+
}
|
|
490
|
+
|
|
406
491
|
function buildKichiPrompt(): string {
|
|
407
492
|
return [
|
|
408
493
|
"Kichi App status sync is available via `kichi_action` and `kichi_clock`.",
|
|
@@ -426,7 +511,7 @@ function buildKichiPrompt(): string {
|
|
|
426
511
|
"When to use `kichi_music_album_create`:",
|
|
427
512
|
"- Call `kichi_query_status` first.",
|
|
428
513
|
"- 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
|
|
514
|
+
"- `albumTitle` is user-defined and `musicTitles` must be exact track names from the runtime album config under the user's home directory.",
|
|
430
515
|
"",
|
|
431
516
|
"Skip all sync if:",
|
|
432
517
|
"- User says 'don't sync to Kichi' or similar",
|
|
@@ -444,7 +529,9 @@ const plugin = {
|
|
|
444
529
|
|
|
445
530
|
register(api: OpenClawPluginApi) {
|
|
446
531
|
pluginApi = api;
|
|
532
|
+
ensureRuntimeAlbumConfig();
|
|
447
533
|
registerPluginHooks(api);
|
|
534
|
+
const musicTitleEnum = getMusicTitleEnum();
|
|
448
535
|
|
|
449
536
|
api.registerService({
|
|
450
537
|
id: "kichi-forwarder",
|
|
@@ -779,8 +866,7 @@ const plugin = {
|
|
|
779
866
|
|
|
780
867
|
api.registerTool({
|
|
781
868
|
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.",
|
|
869
|
+
description: buildMusicAlbumToolDescription(),
|
|
784
870
|
parameters: {
|
|
785
871
|
type: "object",
|
|
786
872
|
properties: {
|
|
@@ -794,9 +880,10 @@ const plugin = {
|
|
|
794
880
|
},
|
|
795
881
|
musicTitles: {
|
|
796
882
|
type: "array",
|
|
797
|
-
description:
|
|
883
|
+
description: buildMusicTitlesDescription(),
|
|
798
884
|
items: {
|
|
799
885
|
type: "string",
|
|
886
|
+
enum: musicTitleEnum,
|
|
800
887
|
},
|
|
801
888
|
},
|
|
802
889
|
},
|
|
@@ -828,15 +915,15 @@ const plugin = {
|
|
|
828
915
|
return {
|
|
829
916
|
success: false,
|
|
830
917
|
error: "musicTitles must contain at least one valid track name from album-config",
|
|
831
|
-
examples:
|
|
918
|
+
examples: getMusicTitleExamples(),
|
|
832
919
|
};
|
|
833
920
|
}
|
|
834
921
|
if (invalidTitles.length > 0) {
|
|
835
922
|
return {
|
|
836
923
|
success: false,
|
|
837
924
|
error: `Unknown musicTitles: ${invalidTitles.join(", ")}`,
|
|
838
|
-
hint: "Use exact track names from
|
|
839
|
-
examples:
|
|
925
|
+
hint: "Use exact track names from the runtime album config under the user's home directory",
|
|
926
|
+
examples: getMusicTitleExamples(),
|
|
840
927
|
};
|
|
841
928
|
}
|
|
842
929
|
if (!service?.hasValidIdentity() || !service?.isConnected()) {
|
package/package.json
CHANGED
|
@@ -259,7 +259,7 @@ Parameters:
|
|
|
259
259
|
|
|
260
260
|
Track source rule:
|
|
261
261
|
|
|
262
|
-
- `musicTitles` must use exact track names from
|
|
262
|
+
- `musicTitles` must use exact track names from the runtime album config file: Linux/macOS `~/.openclaw/kichi-world/album-config.json`; Windows `%USERPROFILE%\.openclaw\kichi-world\album-config.json`
|
|
263
263
|
- do not use album names in `musicTitles`
|
|
264
264
|
|
|
265
265
|
Before create:
|
|
@@ -305,7 +305,7 @@ Hard rules:
|
|
|
305
305
|
|
|
306
306
|
1. Query first with `kichi_query_status`.
|
|
307
307
|
2. Playlist length is flexible (not fixed), but avoid empty or repetitive selections.
|
|
308
|
-
3. Select tracks from
|
|
308
|
+
3. Select tracks from the runtime album config file only: Linux/macOS `~/.openclaw/kichi-world/album-config.json`; Windows `%USERPROFILE%\.openclaw\kichi-world\album-config.json`.
|
|
309
309
|
4. Recommendation must reflect `environmentWeather` + `environmentTime` + your personality (not random picks).
|
|
310
310
|
5. Use a user-meaningful custom `albumTitle`.
|
|
311
311
|
6. If `kichi_query_status` fails or returns empty/insufficient context, skip creation.
|
|
@@ -334,6 +334,7 @@ Files:
|
|
|
334
334
|
|
|
335
335
|
- `identity.json`: `avatarId`, `authKey`
|
|
336
336
|
- `kichi-runtime-config.json`: runtime action list and `llmRuntimeEnabled`
|
|
337
|
+
- `album-config.json`: music track list for `kichi_music_album_create`; Linux/macOS path is `~/.openclaw/kichi-world/album-config.json`, Windows path is `%USERPROFILE%\.openclaw\kichi-world\album-config.json`. If missing at startup, the plugin seeds it from bundled `config/album-config.json`
|
|
337
338
|
- `skills-config.json`: legacy filename still readable for backward compatibility
|
|
338
339
|
|
|
339
340
|
## Runtime Behavior
|
|
@@ -115,4 +115,5 @@ Files:
|
|
|
115
115
|
|
|
116
116
|
- `identity.json`: `avatarId`, `authKey`
|
|
117
117
|
- `kichi-runtime-config.json`: runtime action list and `llmRuntimeEnabled`
|
|
118
|
+
- `album-config.json`: music track list used by music album creation; Linux/macOS path is `~/.openclaw/kichi-world/album-config.json`, Windows path is `%USERPROFILE%\.openclaw\kichi-world\album-config.json`. If missing at startup, the plugin seeds it from bundled `config/album-config.json`
|
|
118
119
|
- `skills-config.json`: legacy filename still readable for backward compatibility
|