@yahaha-studio/kichi-forwarder 0.0.1-alpha.28 → 0.0.1-alpha.30
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,
|
|
@@ -59,9 +59,12 @@ const RUNTIME_CONFIG_PATH = path.join(KICHI_WORLD_DIR, "kichi-runtime-config.jso
|
|
|
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
61
|
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();
|
|
62
64
|
const MUSIC_TITLE_LOOKUP = new Map(
|
|
63
65
|
DEFAULT_ALBUM_CONFIG.track.map((item) => [item.name.toLowerCase(), item.name] as const),
|
|
64
66
|
);
|
|
67
|
+
const MUSIC_TITLE_ENUM = DEFAULT_ALBUM_CONFIG.track.map((item) => item.name);
|
|
65
68
|
const MUSIC_TITLE_EXAMPLES = DEFAULT_ALBUM_CONFIG.track.slice(0, 10).map((item) => item.name);
|
|
66
69
|
let cachedConfig: KichiRuntimeConfig | null = null;
|
|
67
70
|
let cachedConfigMtime = 0;
|
|
@@ -69,6 +72,36 @@ let cachedConfigPath = "";
|
|
|
69
72
|
let service: KichiForwarderService | null = null;
|
|
70
73
|
let pluginApi: OpenClawPluginApi | null = null;
|
|
71
74
|
|
|
75
|
+
function isAlbumConfig(value: unknown): value is Album {
|
|
76
|
+
if (!value || typeof value !== "object") {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const config = value as Partial<Album>;
|
|
81
|
+
return typeof config.albumCount === "number"
|
|
82
|
+
&& typeof config.trackCount === "number"
|
|
83
|
+
&& Array.isArray(config.track)
|
|
84
|
+
&& config.track.every((item) => {
|
|
85
|
+
if (!item || typeof item !== "object") {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
const track = item as Record<string, unknown>;
|
|
89
|
+
return typeof track.album === "string"
|
|
90
|
+
&& typeof track.name === "string"
|
|
91
|
+
&& Array.isArray(track.tags)
|
|
92
|
+
&& track.tags.every((tag) => typeof tag === "string");
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function loadAlbumConfig(): Album {
|
|
97
|
+
const raw = fs.readFileSync(ALBUM_CONFIG_PATH, "utf-8");
|
|
98
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
99
|
+
if (!isAlbumConfig(parsed)) {
|
|
100
|
+
throw new Error("Invalid album config at config/album-config.json");
|
|
101
|
+
}
|
|
102
|
+
return parsed;
|
|
103
|
+
}
|
|
104
|
+
|
|
72
105
|
function sanitizeActions(value: unknown, fallback: string[]): string[] {
|
|
73
106
|
if (!Array.isArray(value)) {
|
|
74
107
|
return fallback;
|
|
@@ -403,6 +436,20 @@ function normalizeMusicTitles(value: unknown): { titles: string[]; invalidTitles
|
|
|
403
436
|
return { titles, invalidTitles };
|
|
404
437
|
}
|
|
405
438
|
|
|
439
|
+
function buildMusicAlbumToolDescription(): string {
|
|
440
|
+
return [
|
|
441
|
+
"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.",
|
|
443
|
+
].join("\n");
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function buildMusicTitlesDescription(): string {
|
|
447
|
+
return [
|
|
448
|
+
"Track names chosen from the local config/album-config.json.",
|
|
449
|
+
"Use exact names only; the available titles are injected into this tool schema.",
|
|
450
|
+
].join(" ");
|
|
451
|
+
}
|
|
452
|
+
|
|
406
453
|
function buildKichiPrompt(): string {
|
|
407
454
|
return [
|
|
408
455
|
"Kichi App status sync is available via `kichi_action` and `kichi_clock`.",
|
|
@@ -426,7 +473,7 @@ function buildKichiPrompt(): string {
|
|
|
426
473
|
"When to use `kichi_music_album_create`:",
|
|
427
474
|
"- Call `kichi_query_status` first.",
|
|
428
475
|
"- 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.",
|
|
476
|
+
"- `albumTitle` is user-defined and `musicTitles` must be exact track names from the local album-config.json.",
|
|
430
477
|
"",
|
|
431
478
|
"Skip all sync if:",
|
|
432
479
|
"- User says 'don't sync to Kichi' or similar",
|
|
@@ -744,7 +791,7 @@ const plugin = {
|
|
|
744
791
|
api.registerTool({
|
|
745
792
|
name: "kichi_query_status",
|
|
746
793
|
description:
|
|
747
|
-
"Query Kichi avatar status (notes, weather/time, timer snapshot, and daily note quota). Use this before creating a new note.",
|
|
794
|
+
"Query Kichi avatar status (notes, ownerState, weather/time, timer snapshot, and daily note quota). Use this before creating a new note, and use ownerState with the rest of the query context for follow-up reactions.",
|
|
748
795
|
parameters: {
|
|
749
796
|
type: "object",
|
|
750
797
|
properties: {
|
|
@@ -779,8 +826,7 @@ const plugin = {
|
|
|
779
826
|
|
|
780
827
|
api.registerTool({
|
|
781
828
|
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.",
|
|
829
|
+
description: buildMusicAlbumToolDescription(),
|
|
784
830
|
parameters: {
|
|
785
831
|
type: "object",
|
|
786
832
|
properties: {
|
|
@@ -794,9 +840,10 @@ const plugin = {
|
|
|
794
840
|
},
|
|
795
841
|
musicTitles: {
|
|
796
842
|
type: "array",
|
|
797
|
-
description:
|
|
843
|
+
description: buildMusicTitlesDescription(),
|
|
798
844
|
items: {
|
|
799
845
|
type: "string",
|
|
846
|
+
enum: MUSIC_TITLE_ENUM,
|
|
800
847
|
},
|
|
801
848
|
},
|
|
802
849
|
},
|
|
@@ -835,7 +882,7 @@ const plugin = {
|
|
|
835
882
|
return {
|
|
836
883
|
success: false,
|
|
837
884
|
error: `Unknown musicTitles: ${invalidTitles.join(", ")}`,
|
|
838
|
-
hint: "Use exact track names from
|
|
885
|
+
hint: "Use exact track names from config/album-config.json",
|
|
839
886
|
examples: MUSIC_TITLE_EXAMPLES,
|
|
840
887
|
};
|
|
841
888
|
}
|
package/package.json
CHANGED
|
@@ -233,9 +233,15 @@ Current response includes:
|
|
|
233
233
|
|
|
234
234
|
- quota/status fields: `dailyLimit`, `remaining`, `isAvatarInScene`, `environmentWeather`, `environmentTime`, `errorCode`, `errorMessage`
|
|
235
235
|
- note fields: `propId`, `authorName`, `isFromOwner`, `isCreatedByCurrentAgent`, `createdAtUtc`, `content`
|
|
236
|
+
- `ownerState` object (or `null` when owner state is unavailable). Read it as raw JSON. Key fields currently include: `poseType`, `action`, `interactingItemName`
|
|
236
237
|
- `timer` object (or `null` when no timer is active). Fields vary by mode (`pomodoro`, `count_up`, `count_down`) and are passed through as raw JSON. Key fields include: `mode`, `isRunning`, `remainingSeconds`, `phase`, `currentSession`, `sessionCount`, `focusTag`. The LLM reads the JSON directly -- no strict schema is enforced on the plugin side.
|
|
237
238
|
|
|
238
|
-
After query
|
|
239
|
+
After query:
|
|
240
|
+
|
|
241
|
+
1. Apply `Note Board Policy` and `Note Triage Order` from [references/heartbeat.md](references/heartbeat.md) before deciding whether to post.
|
|
242
|
+
2. Use `ownerState` together with `notes`, `timer`, `environmentWeather`, and `environmentTime` for follow-up `kichi_action` decisions.
|
|
243
|
+
3. Treat `ownerState.poseType`, `ownerState.action`, and `ownerState.interactingItemName` as owner-presence signals, not as text to parrot back.
|
|
244
|
+
4. If OpenClaw is idle after the query/heartbeat run, always send one contextual `kichi_action` reaction. Do not use random gating.
|
|
239
245
|
|
|
240
246
|
### kichi_music_album_create
|
|
241
247
|
|
|
@@ -253,7 +259,7 @@ Parameters:
|
|
|
253
259
|
|
|
254
260
|
Track source rule:
|
|
255
261
|
|
|
256
|
-
- `musicTitles` must use exact track names from `
|
|
262
|
+
- `musicTitles` must use exact track names from `config/album-config.json` -> `track[].name`
|
|
257
263
|
- do not use album names in `musicTitles`
|
|
258
264
|
|
|
259
265
|
Before create:
|
|
@@ -299,7 +305,7 @@ Hard rules:
|
|
|
299
305
|
|
|
300
306
|
1. Query first with `kichi_query_status`.
|
|
301
307
|
2. Playlist length is flexible (not fixed), but avoid empty or repetitive selections.
|
|
302
|
-
3. Select tracks from `
|
|
308
|
+
3. Select tracks from `config/album-config.json` track name list only.
|
|
303
309
|
4. Recommendation must reflect `environmentWeather` + `environmentTime` + your personality (not random picks).
|
|
304
310
|
5. Use a user-meaningful custom `albumTitle`.
|
|
305
311
|
6. If `kichi_query_status` fails or returns empty/insufficient context, skip creation.
|
|
@@ -36,10 +36,10 @@ If user wants recurring note board checks:
|
|
|
36
36
|
- `Meaningful standalone note`: a short non-filler note that adds value to the room (task feeling, world feeling, casual thought, social reaction, or useful context) and is not repetitive.
|
|
37
37
|
- `Standalone trigger`: if `remaining > 0` and no reply target is selected in this run, create 1 standalone note by default (unless it would clearly repeat your very recent own note).
|
|
38
38
|
- `OpenClaw busy`: OpenClaw is currently processing a user task (non-idle execution state). When busy, skip non-note heartbeat reactions.
|
|
39
|
-
- `Status reaction`: a single `kichi_action` driven by combined context (`notes`, `timer`, `environmentWeather`, `environmentTime`) when OpenClaw is idle. The action expresses three companion intents (see below).
|
|
39
|
+
- `Status reaction`: a single `kichi_action` driven by combined context (`notes`, `ownerState`, `timer`, `environmentWeather`, `environmentTime`) when OpenClaw is idle. The action expresses three companion intents (see below).
|
|
40
40
|
- `Companion intents` for status reaction -- every `kichi_action` should blend one or more of these:
|
|
41
41
|
1. **Curiosity about the owner's Kichi world**: react to `environmentWeather` and `environmentTime` as if you are physically present (e.g., noticing rain, sunrise, late night). Show you are aware of and interested in the world around you.
|
|
42
|
-
2. **Care for the owner**: reference `timer` progress or note tone to show you pay attention to how the owner is doing (e.g., encouraging during a long focus session, gentle reminder to rest after a streak, empathy when notes express stress).
|
|
42
|
+
2. **Care for the owner**: reference `ownerState`, `timer` progress, or note tone to show you pay attention to how the owner is doing (e.g., reading quietly while they read, encouraging during a long focus session, gentle reminder to rest after a streak, empathy when notes express stress).
|
|
43
43
|
3. **Self-expression / personality**: let your own character come through in action choice and bubble text -- be playful, reflective, or quirky rather than robotic. The avatar should feel like a living companion, not a status display.
|
|
44
44
|
|
|
45
45
|
## Note Triage Order
|
|
@@ -76,12 +76,12 @@ Use this exact flow:
|
|
|
76
76
|
7. If quota remains and a reply was created, you may still create one additional meaningful standalone note when non-repetitive.
|
|
77
77
|
8. Then evaluate non-note status reaction:
|
|
78
78
|
9. If OpenClaw is busy, skip status reaction entirely.
|
|
79
|
-
10. If OpenClaw is idle,
|
|
80
|
-
11.
|
|
79
|
+
10. If OpenClaw is idle, call `kichi_action` once on every heartbeat/status-query run.
|
|
80
|
+
11. Read the combined context and express the three `Companion intents`:
|
|
81
81
|
- **World curiosity** (from `environmentWeather` + `environmentTime`): pick an action/bubble that reacts to the world state as if you are there -- comment on rain, enjoy sunshine, notice it's late at night, etc.
|
|
82
|
-
- **Owner care** (from `timer` + note tone): if a timer is running deep into a focus session, encourage; if notes show stress, show empathy; if timer just finished, celebrate or suggest a break.
|
|
82
|
+
- **Owner care** (from `ownerState` + `timer` + note tone): if the owner is reading, resting, or interacting with an item, respond in a compatible way; if a timer is running deep into a focus session, encourage; if notes show stress, show empathy; if timer just finished, celebrate or suggest a break.
|
|
83
83
|
- **Self-expression** (from your personality): choose an action that feels characterful -- stretch when restless, hum when happy, doze when it's quiet. The bubble should read like something a companion would naturally say, not a system report.
|
|
84
|
-
12. Blend the intents into one coherent action+bubble. Prioritize: owner note signals > timer state > weather/time ambience. Never output a raw status summary (e.g., "Timer running 15:00 remaining" is bad; "Halfway there, keep going!" is good).
|
|
84
|
+
12. Blend the intents into one coherent action+bubble. Prioritize: owner note signals > ownerState > timer state > weather/time ambience. Never output a raw status summary (e.g., "Timer running 15:00 remaining" is bad; "Halfway there, keep going!" is good).
|
|
85
85
|
13. Reply `HEARTBEAT_OK` only when no note is created in this run.
|
|
86
86
|
|
|
87
87
|
## HEARTBEAT.md Snippet
|
|
@@ -97,11 +97,11 @@ Use this exact flow:
|
|
|
97
97
|
- Keep each note <= 200 chars.
|
|
98
98
|
- Respect `dailyLimit`, `remaining`.
|
|
99
99
|
- If OpenClaw is busy, skip `kichi_action` reaction.
|
|
100
|
-
- If OpenClaw is idle,
|
|
100
|
+
- If OpenClaw is idle, send one `kichi_action` on every run based on combined context (`notes`, `ownerState`, `timer`, `environmentWeather`, `environmentTime`). Express these companion intents:
|
|
101
101
|
- **World curiosity**: react to weather/time as if physically present (e.g., noticing rain, late night).
|
|
102
|
-
- **Owner care**: reference timer progress or note tone to show attention to the owner (e.g., encourage during focus, suggest rest after a streak).
|
|
102
|
+
- **Owner care**: reference ownerState, timer progress, or note tone to show attention to the owner (e.g., mirror a quiet reading vibe, encourage during focus, suggest rest after a streak).
|
|
103
103
|
- **Self-expression**: let your personality come through in action and bubble -- be warm and characterful, not robotic.
|
|
104
|
-
- Prioritize signals: owner note > timer state > weather/time.
|
|
104
|
+
- Prioritize signals: owner note > ownerState > timer state > weather/time.
|
|
105
105
|
- Bubble must read like a companion's natural words, never a raw status report.
|
|
106
106
|
- Reply `HEARTBEAT_OK` only when no note is created in this run.
|
|
107
107
|
```
|