@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.
@@ -1,6 +1,4 @@
1
- import type { Album } from "./types.js";
2
-
3
- export const DEFAULT_ALBUM_CONFIG: Album = {
1
+ {
4
2
  "albumCount": 10,
5
3
  "trackCount": 58,
6
4
  "track": [
@@ -508,4 +506,4 @@ export const DEFAULT_ALBUM_CONFIG: Album = {
508
506
  ]
509
507
  }
510
508
  ]
511
- };
509
+ }
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 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);
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 = MUSIC_TITLE_LOOKUP.get(key);
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-config.",
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: "Track names chosen from album-config.",
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: MUSIC_TITLE_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 src/album-config.ts",
839
- examples: MUSIC_TITLE_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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yahaha-studio/kichi-forwarder",
3
- "version": "0.0.1-alpha.29",
3
+ "version": "0.0.1-alpha.31",
4
4
  "description": "Forward OpenClaw agent events to external WebSocket server for visualization",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -259,7 +259,7 @@ Parameters:
259
259
 
260
260
  Track source rule:
261
261
 
262
- - `musicTitles` must use exact track names from `src/album-config.ts` -> `DEFAULT_ALBUM_CONFIG.track[].name`
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 `src/album-config.ts` track name list only.
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