@yahaha-studio/kichi-forwarder 0.0.1-alpha.30 → 0.0.1-alpha.32

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
@@ -31,26 +31,31 @@ const FIXED_HOOK_STATUSES: Record<string, ActionResult> = {
31
31
  poseType: "sit",
32
32
  action: "Study Look At",
33
33
  bubble: "Reading request",
34
+ log: "Leaning in, this request looks interesting",
34
35
  },
35
36
  beforePromptBuild: {
36
37
  poseType: "sit",
37
38
  action: "Thinking",
38
39
  bubble: "Planning task",
40
+ log: "Mind pacing, there is a neat angle here",
39
41
  },
40
42
  beforeToolCall: {
41
43
  poseType: "sit",
42
44
  action: "Typing with Keyboard",
43
45
  bubble: "Working step",
46
+ log: "Typing hard, this one is kind of fun",
44
47
  },
45
48
  agentEndSuccess: {
46
49
  poseType: "stand",
47
50
  action: "Yay",
48
51
  bubble: "Task complete",
52
+ log: "Bouncing a little, that landed cleanly",
49
53
  },
50
54
  agentEndFailure: {
51
55
  poseType: "stand",
52
56
  action: "Tired",
53
57
  bubble: "Task failed",
58
+ log: "Shoulders dropped, this one fought back",
54
59
  },
55
60
  };
56
61
 
@@ -58,17 +63,14 @@ const KICHI_WORLD_DIR = path.join(os.homedir(), ".openclaw", "kichi-world");
58
63
  const RUNTIME_CONFIG_PATH = path.join(KICHI_WORLD_DIR, "kichi-runtime-config.json");
59
64
  const LEGACY_SKILLS_CONFIG_PATH = path.join(KICHI_WORLD_DIR, "skills-config.json");
60
65
  const IDENTITY_PATH = path.join(KICHI_WORLD_DIR, "identity.json");
66
+ const RUNTIME_ALBUM_CONFIG_PATH = path.join(KICHI_WORLD_DIR, "album-config.json");
61
67
  const MAX_NOTEBOARD_TEXT_LENGTH = 200;
62
- const ALBUM_CONFIG_PATH = new URL("./config/album-config.json", import.meta.url);
63
- const DEFAULT_ALBUM_CONFIG = loadAlbumConfig();
64
- const MUSIC_TITLE_LOOKUP = new Map(
65
- DEFAULT_ALBUM_CONFIG.track.map((item) => [item.name.toLowerCase(), item.name] as const),
66
- );
67
- const MUSIC_TITLE_ENUM = DEFAULT_ALBUM_CONFIG.track.map((item) => item.name);
68
- const MUSIC_TITLE_EXAMPLES = DEFAULT_ALBUM_CONFIG.track.slice(0, 10).map((item) => item.name);
68
+ const BUNDLED_ALBUM_CONFIG_PATH = new URL("./config/album-config.json", import.meta.url);
69
69
  let cachedConfig: KichiRuntimeConfig | null = null;
70
70
  let cachedConfigMtime = 0;
71
71
  let cachedConfigPath = "";
72
+ let cachedAlbumConfig: Album | null = null;
73
+ let cachedAlbumConfigMtime = 0;
72
74
  let service: KichiForwarderService | null = null;
73
75
  let pluginApi: OpenClawPluginApi | null = null;
74
76
 
@@ -93,15 +95,55 @@ function isAlbumConfig(value: unknown): value is Album {
93
95
  });
94
96
  }
95
97
 
96
- function loadAlbumConfig(): Album {
97
- const raw = fs.readFileSync(ALBUM_CONFIG_PATH, "utf-8");
98
+ function loadAlbumConfigFromPath(configPath: string | URL): Album {
99
+ const raw = fs.readFileSync(configPath, "utf-8");
98
100
  const parsed = JSON.parse(raw) as unknown;
99
101
  if (!isAlbumConfig(parsed)) {
100
- throw new Error("Invalid album config at config/album-config.json");
102
+ throw new Error(`Invalid album config at ${String(configPath)}`);
101
103
  }
102
104
  return parsed;
103
105
  }
104
106
 
107
+ function ensureRuntimeAlbumConfig(): void {
108
+ fs.mkdirSync(KICHI_WORLD_DIR, { recursive: true });
109
+ if (!fs.existsSync(RUNTIME_ALBUM_CONFIG_PATH)) {
110
+ fs.copyFileSync(BUNDLED_ALBUM_CONFIG_PATH, RUNTIME_ALBUM_CONFIG_PATH);
111
+ pluginApi?.logger.debug("[kichi] seeded runtime album config from bundled config");
112
+ return;
113
+ }
114
+
115
+ try {
116
+ loadAlbumConfigFromPath(RUNTIME_ALBUM_CONFIG_PATH);
117
+ } catch (error) {
118
+ pluginApi?.logger.warn(`[kichi] invalid runtime album config, resetting from bundled config: ${error}`);
119
+ fs.copyFileSync(BUNDLED_ALBUM_CONFIG_PATH, RUNTIME_ALBUM_CONFIG_PATH);
120
+ }
121
+ }
122
+
123
+ function loadRuntimeAlbumConfig(): Album {
124
+ ensureRuntimeAlbumConfig();
125
+ const stat = fs.statSync(RUNTIME_ALBUM_CONFIG_PATH);
126
+ if (!cachedAlbumConfig || stat.mtimeMs !== cachedAlbumConfigMtime) {
127
+ cachedAlbumConfig = loadAlbumConfigFromPath(RUNTIME_ALBUM_CONFIG_PATH);
128
+ cachedAlbumConfigMtime = stat.mtimeMs;
129
+ }
130
+ return cachedAlbumConfig;
131
+ }
132
+
133
+ function getMusicTitleLookup(): Map<string, string> {
134
+ return new Map(
135
+ loadRuntimeAlbumConfig().track.map((item) => [item.name.toLowerCase(), item.name] as const),
136
+ );
137
+ }
138
+
139
+ function getMusicTitleEnum(): string[] {
140
+ return loadRuntimeAlbumConfig().track.map((item) => item.name);
141
+ }
142
+
143
+ function getMusicTitleExamples(): string[] {
144
+ return loadRuntimeAlbumConfig().track.slice(0, 10).map((item) => item.name);
145
+ }
146
+
105
147
  function sanitizeActions(value: unknown, fallback: string[]): string[] {
106
148
  if (!Array.isArray(value)) {
107
149
  return fallback;
@@ -186,10 +228,13 @@ function syncFixedStatus(status: ActionResult): void {
186
228
  return;
187
229
  }
188
230
  const bubbleText = status.bubble.trim() || status.action;
231
+ const logText = typeof status.log === "string" && status.log.trim()
232
+ ? status.log.trim()
233
+ : bubbleText;
189
234
  sendStatusUpdate({
190
235
  ...status,
191
236
  bubble: bubbleText,
192
- log: bubbleText,
237
+ log: logText,
193
238
  });
194
239
  }
195
240
 
@@ -406,6 +451,7 @@ function normalizeMusicTitles(value: unknown): { titles: string[]; invalidTitles
406
451
  return { titles: [], invalidTitles: [] };
407
452
  }
408
453
 
454
+ const musicTitleLookup = getMusicTitleLookup();
409
455
  const titles: string[] = [];
410
456
  const invalidTitles: string[] = [];
411
457
  const seen = new Set<string>();
@@ -421,7 +467,7 @@ function normalizeMusicTitles(value: unknown): { titles: string[]; invalidTitles
421
467
  }
422
468
 
423
469
  const key = trimmed.toLowerCase();
424
- const canonicalTitle = MUSIC_TITLE_LOOKUP.get(key);
470
+ const canonicalTitle = musicTitleLookup.get(key);
425
471
  if (!canonicalTitle) {
426
472
  invalidTitles.push(trimmed);
427
473
  continue;
@@ -439,13 +485,13 @@ function normalizeMusicTitles(value: unknown): { titles: string[]; invalidTitles
439
485
  function buildMusicAlbumToolDescription(): string {
440
486
  return [
441
487
  "Create a custom Kichi music album.",
442
- "Query status first, then choose track names from the local config/album-config.json that match weather, time, and personality.",
488
+ "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`.",
443
489
  ].join("\n");
444
490
  }
445
491
 
446
492
  function buildMusicTitlesDescription(): string {
447
493
  return [
448
- "Track names chosen from the local config/album-config.json.",
494
+ "Track names chosen from the runtime album config: Linux/macOS `~/.openclaw/kichi-world/album-config.json`; Windows `%USERPROFILE%\\.openclaw\\kichi-world\\album-config.json`.",
449
495
  "Use exact names only; the available titles are injected into this tool schema.",
450
496
  ].join(" ");
451
497
  }
@@ -462,7 +508,9 @@ function buildKichiPrompt(): string {
462
508
  "- Required order at task end: 1) call `kichi_action` 2) send final reply",
463
509
  "- Trivial-operation skip applies only to Task start / Step switch / Task switch, NOT Task end",
464
510
  "- `bubble`: short natural companion speech, not a raw status report",
465
- "- `log`: optional first-person diary-style note about the current operation, action, status, mood, feeling, or feedback; keep it within 20 words",
511
+ "- `log`: optional first-person inner reaction or personality-forward feedback about the moment; keep it within 20 words",
512
+ "- `log` should blend the feeling of the chosen action with your personal reaction, not a dry summary of work steps",
513
+ "- Good pattern for `log`: action feel first, personality feedback second. Example: `Typing fast, this one is fun`",
466
514
  "",
467
515
  "When to use `kichi_clock`:",
468
516
  "- For tasks with 2+ meaningful steps or work likely to take more than a brief moment (~10s), set a `countDown` at task start.",
@@ -473,7 +521,7 @@ function buildKichiPrompt(): string {
473
521
  "When to use `kichi_music_album_create`:",
474
522
  "- Call `kichi_query_status` first.",
475
523
  "- Recommend a variable-length playlist based on weather, time, and your own personality.",
476
- "- `albumTitle` is user-defined and `musicTitles` must be exact track names from the local album-config.json.",
524
+ "- `albumTitle` is user-defined and `musicTitles` must be exact track names from the runtime album config under the user's home directory.",
477
525
  "",
478
526
  "Skip all sync if:",
479
527
  "- User says 'don't sync to Kichi' or similar",
@@ -491,7 +539,9 @@ const plugin = {
491
539
 
492
540
  register(api: OpenClawPluginApi) {
493
541
  pluginApi = api;
542
+ ensureRuntimeAlbumConfig();
494
543
  registerPluginHooks(api);
544
+ const musicTitleEnum = getMusicTitleEnum();
495
545
 
496
546
  api.registerService({
497
547
  id: "kichi-forwarder",
@@ -620,7 +670,7 @@ const plugin = {
620
670
  log: {
621
671
  type: "string",
622
672
  description:
623
- "Optional first-person log about the current operation, action, status, mood, or feedback (max 20 words)",
673
+ "Optional first-person log that blends the chosen action feeling with personality-forward feedback, not a dry work summary (max 20 words)",
624
674
  },
625
675
  },
626
676
  required: ["poseType", "action"],
@@ -843,7 +893,7 @@ const plugin = {
843
893
  description: buildMusicTitlesDescription(),
844
894
  items: {
845
895
  type: "string",
846
- enum: MUSIC_TITLE_ENUM,
896
+ enum: musicTitleEnum,
847
897
  },
848
898
  },
849
899
  },
@@ -875,15 +925,15 @@ const plugin = {
875
925
  return {
876
926
  success: false,
877
927
  error: "musicTitles must contain at least one valid track name from album-config",
878
- examples: MUSIC_TITLE_EXAMPLES,
928
+ examples: getMusicTitleExamples(),
879
929
  };
880
930
  }
881
931
  if (invalidTitles.length > 0) {
882
932
  return {
883
933
  success: false,
884
934
  error: `Unknown musicTitles: ${invalidTitles.join(", ")}`,
885
- hint: "Use exact track names from config/album-config.json",
886
- examples: MUSIC_TITLE_EXAMPLES,
935
+ hint: "Use exact track names from the runtime album config under the user's home directory",
936
+ examples: getMusicTitleExamples(),
887
937
  };
888
938
  }
889
939
  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.30",
3
+ "version": "0.0.1-alpha.32",
4
4
  "description": "Forward OpenClaw agent events to external WebSocket server for visualization",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -155,14 +155,16 @@ kichi_action(
155
155
  poseType: "sit",
156
156
  action: "Typing with Keyboard",
157
157
  bubble: "Working now",
158
- log: "Need to sum up news, let me sit and think it through"
158
+ log: "Typing hard, this one has some bite"
159
159
  )
160
160
  ```
161
161
 
162
162
  - `poseType`: `stand`, `sit`, `lay`, `floor`
163
163
  - `action`: must be in configured action list for that pose
164
164
  - `bubble`: optional text, recommended 2-5 words
165
- - `log`: optional first-person diary-style note about current operation/action/status/mood, max 20 words
165
+ - `log`: optional first-person inner reaction or personality-forward feedback, max 20 words
166
+ - `log` should blend the chosen action feeling with your personal reaction; do not use it as a dry work-summary field
167
+ - Recommended pattern: action feel first, personality feedback second. Example: `Typing hard, this one has some bite`
166
168
 
167
169
  ### kichi_clock
168
170
 
@@ -259,7 +261,7 @@ Parameters:
259
261
 
260
262
  Track source rule:
261
263
 
262
- - `musicTitles` must use exact track names from `config/album-config.json` -> `track[].name`
264
+ - `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
265
  - do not use album names in `musicTitles`
264
266
 
265
267
  Before create:
@@ -305,7 +307,7 @@ Hard rules:
305
307
 
306
308
  1. Query first with `kichi_query_status`.
307
309
  2. Playlist length is flexible (not fixed), but avoid empty or repetitive selections.
308
- 3. Select tracks from `config/album-config.json` track name list only.
310
+ 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
311
  4. Recommendation must reflect `environmentWeather` + `environmentTime` + your personality (not random picks).
310
312
  5. Use a user-meaningful custom `albumTitle`.
311
313
  6. If `kichi_query_status` fails or returns empty/insufficient context, skip creation.
@@ -334,6 +336,7 @@ Files:
334
336
 
335
337
  - `identity.json`: `avatarId`, `authKey`
336
338
  - `kichi-runtime-config.json`: runtime action list and `llmRuntimeEnabled`
339
+ - `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
340
  - `skills-config.json`: legacy filename still readable for backward compatibility
338
341
 
339
342
  ## 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