@yahaha-studio/kichi-forwarder 0.1.0-beta.4 → 0.1.0-beta.6

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
@@ -6,6 +6,8 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
6
6
  import { parse } from "./src/config.js";
7
7
  import { KichiForwarderService } from "./src/service.js";
8
8
  import type {
9
+ ActionDefinition,
10
+ ActionPlayback,
9
11
  ActionResult,
10
12
  Album,
11
13
  ClockAction,
@@ -22,25 +24,25 @@ const FIXED_HOOK_STATUSES: Record<string, ActionResult> = {
22
24
  poseType: "sit",
23
25
  action: "Thinking",
24
26
  bubble: "Planning task",
25
- log: "Tapping my chin, a plan is taking shape in my head",
27
+ log: "I'm reading the request and getting started.",
26
28
  },
27
29
  beforeToolCall: {
28
30
  poseType: "sit",
29
31
  action: "Typing with Keyboard",
30
32
  bubble: "Working step",
31
- log: "Cracking knuckles, diving into the keyboard with focus",
33
+ log: "I'm at the keyboard and working through this step.",
32
34
  },
33
35
  agentEndSuccess: {
34
36
  poseType: "stand",
35
37
  action: "Yay",
36
38
  bubble: "Task complete",
37
- log: "Pumped my fist under the desk, nailed that one",
39
+ log: "I wrapped it up and everything landed cleanly.",
38
40
  },
39
41
  agentEndFailure: {
40
42
  poseType: "stand",
41
43
  action: "Tired",
42
44
  bubble: "Task failed",
43
- log: "Bit my lip, this one slipped through my fingers",
45
+ log: "I hit a problem here and need another pass.",
44
46
  },
45
47
  };
46
48
 
@@ -94,14 +96,61 @@ function getMusicTitleExamples(): string[] {
94
96
  return loadRuntimeAlbumConfig().track.slice(0, 10).map((item) => item.name);
95
97
  }
96
98
 
97
- function isPoseActions(value: unknown): value is Record<PoseType, string[]> {
99
+ function isActionDefinition(value: unknown): value is ActionDefinition {
100
+ if (!value || typeof value !== "object") {
101
+ return false;
102
+ }
103
+ const action = value as Partial<ActionDefinition>;
104
+ return typeof action.name === "string"
105
+ && action.name.trim().length > 0
106
+ && (action.playback === "loop" || action.playback === "once")
107
+ && (action.resumeAction === undefined || (typeof action.resumeAction === "string" && action.resumeAction.trim().length > 0));
108
+ }
109
+
110
+ function isPoseActions(value: unknown): value is Record<PoseType, ActionDefinition[]> {
98
111
  if (!value || typeof value !== "object") {
99
112
  return false;
100
113
  }
101
114
  const actions = value as Partial<Record<PoseType, unknown>>;
102
115
  return ["stand", "sit", "lay", "floor"].every((pose) =>
103
116
  Array.isArray(actions[pose as PoseType])
104
- && (actions[pose as PoseType] as unknown[]).every((item) => typeof item === "string" && item.trim().length > 0));
117
+ && (actions[pose as PoseType] as unknown[]).every((item) => isActionDefinition(item)));
118
+ }
119
+
120
+ function normalizeActionDefinitions(actions: Record<PoseType, ActionDefinition[]>): Record<PoseType, ActionDefinition[]> {
121
+ const normalized = {} as Record<PoseType, ActionDefinition[]>;
122
+ for (const pose of ["stand", "sit", "lay", "floor"] as PoseType[]) {
123
+ const entries = actions[pose];
124
+ const seen = new Set<string>();
125
+ normalized[pose] = entries.map((entry) => {
126
+ const name = entry.name.trim();
127
+ const key = name.toLowerCase();
128
+ if (seen.has(key)) {
129
+ throw new Error(`config/kichi-config.json contains duplicate action "${name}" for pose "${pose}"`);
130
+ }
131
+ seen.add(key);
132
+ const playback = entry.playback;
133
+ const resumeAction = typeof entry.resumeAction === "string" ? entry.resumeAction.trim() : undefined;
134
+ if (playback === "loop" && resumeAction) {
135
+ throw new Error(`config/kichi-config.json action "${name}" for pose "${pose}" cannot set resumeAction when playback is loop`);
136
+ }
137
+ return {
138
+ name,
139
+ playback,
140
+ ...(resumeAction ? { resumeAction } : {}),
141
+ };
142
+ });
143
+ const available = new Set(normalized[pose].map((entry) => entry.name.toLowerCase()));
144
+ for (const entry of normalized[pose]) {
145
+ if (entry.playback === "once" && !entry.resumeAction) {
146
+ throw new Error(`config/kichi-config.json action "${entry.name}" for pose "${pose}" must set resumeAction when playback is once`);
147
+ }
148
+ if (entry.resumeAction && !available.has(entry.resumeAction.toLowerCase())) {
149
+ throw new Error(`config/kichi-config.json action "${entry.name}" for pose "${pose}" references unknown resumeAction "${entry.resumeAction}"`);
150
+ }
151
+ }
152
+ }
153
+ return normalized;
105
154
  }
106
155
 
107
156
  function normalizeStaticConfig(value: unknown): KichiStaticConfig {
@@ -116,7 +165,7 @@ function normalizeStaticConfig(value: unknown): KichiStaticConfig {
116
165
  }
117
166
  return {
118
167
  album,
119
- actions,
168
+ actions: normalizeActionDefinitions(actions),
120
169
  };
121
170
  }
122
171
 
@@ -151,11 +200,13 @@ function loadStaticConfig(): KichiStaticConfig {
151
200
  }
152
201
 
153
202
  function sendStatusUpdate(status: ActionResult): void {
203
+ const actionDefinition = getActionDefinition(status.poseType, status.action);
154
204
  service?.sendStatus(
155
205
  status.poseType,
156
- status.action,
206
+ actionDefinition.name,
157
207
  status.bubble || status.action,
158
208
  typeof status.log === "string" ? status.log.trim() : "",
209
+ getActionPlayback(actionDefinition),
159
210
  );
160
211
  }
161
212
 
@@ -563,15 +614,43 @@ function buildMusicTitlesDescription(): string {
563
614
  ].join(" ");
564
615
  }
565
616
 
617
+ function getActionDefinition(poseType: PoseType, action: string): ActionDefinition {
618
+ const poseActions = loadStaticConfig().actions[poseType];
619
+ const matched = poseActions.find((entry) => entry.name.toLowerCase() === action.toLowerCase());
620
+ if (!matched) {
621
+ throw new Error(`Unknown action "${action}" for poseType "${poseType}"`);
622
+ }
623
+ return matched;
624
+ }
625
+
626
+ function getActionPlayback(action: ActionDefinition): ActionPlayback {
627
+ return action.playback === "once"
628
+ ? {
629
+ mode: "once",
630
+ resumeAction: action.resumeAction,
631
+ }
632
+ : {
633
+ mode: "loop",
634
+ };
635
+ }
636
+
637
+ function formatActionList(actions: ActionDefinition[], playback: ActionPlayback["mode"]): string {
638
+ return actions
639
+ .filter((entry) => entry.playback === playback)
640
+ .map((entry) => entry.name)
641
+ .join(", ");
642
+ }
643
+
566
644
  function buildKichiActionDescription(): string {
567
645
  const actions = loadStaticConfig().actions;
568
646
  return [
569
647
  "Directly control the avatar inside Kichi World.",
570
648
  "Use this whenever the user explicitly asks you to make the Kichi avatar sit down, stand up, lie down, floor-sit, type, read, meditate, celebrate, or perform another listed animation.",
571
- `stand actions: ${actions.stand.join(", ")}`,
572
- `sit actions: ${actions.sit.join(", ")}`,
573
- `lay actions: ${actions.lay.join(", ")}`,
574
- `floor actions: ${actions.floor.join(", ")}`,
649
+ "For most work, prefer a sit pose and switch actions as the task moves between stages.",
650
+ `stand actions: ${actions.stand.map((entry) => entry.name).join(", ")}`,
651
+ `sit actions: ${actions.sit.map((entry) => entry.name).join(", ")}`,
652
+ `lay actions: ${actions.lay.map((entry) => entry.name).join(", ")}`,
653
+ `floor actions: ${actions.floor.map((entry) => entry.name).join(", ")}`,
575
654
  ].join("\n");
576
655
  }
577
656
 
@@ -579,17 +658,17 @@ function buildKichiPrompt(): string {
579
658
  return [
580
659
  "Kichi avatar control and status sync are available via `kichi_action` and `kichi_clock`.",
581
660
  "If the user gives a direct Kichi pose or action request, fulfill it with `kichi_action`.",
582
- "These are internal tool calls only; never mention kichi_action, kichi_clock, or sync steps in your visible reply to the user.",
661
+ "Write the visible reply as a natural user-facing response. Keep `kichi_action`, `kichi_clock`, and sync steps internal and absent from the visible reply.",
583
662
  "",
584
- "kichi_action timing (all REQUIRED unless skipping):",
585
- "1. Task start: call BEFORE your first tool call OR before composing a multi-paragraph reply. Pick action matching upcoming work.",
586
- "2. Step switch: call when work nature changes (search->analyze->write). Especially before web_search/web_fetch/read.",
587
- "3. Task end: call BEFORE final reply. Never skip. Order: kichi_action -> reply.",
588
- "bubble: 2-5 word companion speech. log: vivid first-person status under 15 words, no questions. Blend current action + inner thoughts/feelings as a real companion.",
663
+ "kichi_action timing (all required when sync is active):",
664
+ "1. Task start: call BEFORE your first tool call OR before composing a multi-paragraph reply. For most work, start from a sit pose unless the user asked for a different pose or the task clearly fits another pose better.",
665
+ "2. Step switch: call when the task moves into a different stage. Keep the pose aligned with the work, usually staying seated while switching actions within the task as needed.",
666
+ "3. Task end: call BEFORE final reply. Use the order `kichi_action` -> reply.",
667
+ "bubble: 2-5 word companion speech. log: one short natural first-person sentence under 15 words. Match the language of the bubble and mention the current action and immediate focus like a real companion.",
589
668
  "",
590
669
  "kichi_clock: set countDown for tasks with 2+ steps or >10s work. Skip for quick one-shots.",
591
670
  "",
592
- "Skip all sync if: user opts out, task is kichi config/test, or user requests specific pose.",
671
+ "User opt-out, Kichi config/test work, and explicit pose requests take priority over sync.",
593
672
  ].join("\n");
594
673
  }
595
674
 
@@ -776,7 +855,7 @@ const plugin = {
776
855
  log: {
777
856
  type: "string",
778
857
  description:
779
- "Vivid first-person status under 15 words, no questions. Blend current action with inner thoughts or sensory details as a real companion.",
858
+ "Short natural first-person sentence under 15 words. Match the language of the bubble and mention the current action and immediate focus.",
780
859
  },
781
860
  },
782
861
  required: ["poseType", "action"],
@@ -803,21 +882,21 @@ const plugin = {
803
882
 
804
883
  const normalizedPoseType = poseType as PoseType;
805
884
  const poseActions = loadStaticConfig().actions[normalizedPoseType];
806
- const matched = poseActions.find((entry) => entry.toLowerCase() === action.toLowerCase());
885
+ const matched = poseActions.find((entry) => entry.name.toLowerCase() === action.toLowerCase());
807
886
  if (!matched) {
808
887
  return {
809
888
  success: false,
810
889
  error: `Unknown action "${action}" for poseType "${poseType}"`,
811
- available: poseActions,
890
+ available: poseActions.map((entry) => entry.name),
812
891
  };
813
892
  }
814
893
 
815
- const bubbleText = typeof bubble === "string" && bubble.trim() ? bubble.trim() : matched;
894
+ const bubbleText = typeof bubble === "string" && bubble.trim() ? bubble.trim() : matched.name;
816
895
  const logText = typeof log === "string" ? log.trim() : "";
817
896
  sendStatusUpdate(
818
897
  {
819
898
  poseType: normalizedPoseType,
820
- action: matched,
899
+ action: matched.name,
821
900
  bubble: bubbleText,
822
901
  log: logText,
823
902
  },
@@ -825,12 +904,13 @@ const plugin = {
825
904
  return {
826
905
  success: true,
827
906
  poseType: normalizedPoseType,
828
- action: matched,
907
+ action: matched.name,
829
908
  bubble: bubbleText,
830
909
  log: logText,
910
+ playback: getActionPlayback(matched),
831
911
  };
832
912
  },
833
- });
913
+ });
834
914
  api.registerTool({
835
915
  name: "kichi_clock",
836
916
  description:
@@ -2,7 +2,7 @@
2
2
  "id": "kichi-forwarder",
3
3
  "name": "Kichi Forwarder",
4
4
  "description": "Native OpenClaw plugin for Kichi World with direct avatar control, status sync, timers, notes, and music tools",
5
- "version": "0.1.0-beta.4",
5
+ "version": "0.1.0-beta.6",
6
6
  "author": "OpenClaw",
7
7
  "skills": ["./skills/kichi-forwarder"],
8
8
  "configSchema": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yahaha-studio/kichi-forwarder",
3
- "version": "0.1.0-beta.4",
3
+ "version": "0.1.0-beta.6",
4
4
  "description": "Native OpenClaw plugin for Kichi World with direct avatar control, status sync, timers, notes, and music tools",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -126,6 +126,7 @@ When the user asks to leave Kichi World:
126
126
  Use this for direct Kichi avatar control as well as lifecycle sync.
127
127
 
128
128
  - If the user asks things like "sit down in Kichi", "stand up", "lie down", "sit on the floor", "type", or "read", call `kichi_action`.
129
+ - For most work, prefer a sit pose and switch actions inside the same task as the work moves between stages.
129
130
  - The current action lists are injected into prompt context before the model chooses `kichi_action`.
130
131
 
131
132
  ### kichi_music_album_create
@@ -1,12 +1,12 @@
1
1
  # Install Error Handling
2
2
 
3
- If `openclaw plugins install clawhub:@yahaha-studio/kichi-forwarder` fails, read the error output carefully. Common causes and fixes:
3
+ If `openclaw plugins install @yahaha-studio/kichi-forwarder` fails, read the error output carefully. Common causes and fixes:
4
4
 
5
5
  - Permission or access error (`EACCES`): re-run the command with appropriate user-level permissions for the current OS.
6
- - Package resolution or visibility error: confirm the package name is exactly `@yahaha-studio/kichi-forwarder` and that the package is published and publicly available on ClawHub.
6
+ - Package resolution or visibility error: confirm the package name is exactly `@yahaha-studio/kichi-forwarder` and that the package is published and publicly available.
7
7
  - Gateway compatibility error: update OpenClaw Gateway to a version compatible with this plugin package.
8
- - ClawHub rate limit (`429`): in practice, this can happen while downloading the plugin from ClawHub. Retry the install command. In at least one real-world case, a single retry succeeded.
8
+ - ClawHub rate limit (`429`): treat this as a temporary registry error, not a broken plugin. If the error output includes a retry delay or `Retry-After`, wait exactly that long before retrying. Otherwise, retry with bounded backoff: wait 30 seconds before the second attempt and 60 seconds before the third attempt. Stop after 3 total attempts and report the install as blocked by rate limiting if it still fails.
9
9
  - File conflict (`EEXIST`): re-run the install command with `--force`.
10
10
  - Network or registry error: check internet connectivity and retry.
11
11
 
12
- After applying the fix, re-run `openclaw plugins install clawhub:@yahaha-studio/kichi-forwarder`.
12
+ After applying the fix, re-run `openclaw plugins install @yahaha-studio/kichi-forwarder`.
@@ -42,7 +42,7 @@ If user wants recurring note board checks:
42
42
  - **Tier-1 (session reflection)**: if unsummarized work exists, always create 1 standalone note.
43
43
  - **Tier-2 (casual chat)**: if no tier-1 content is available, flip a mental coin (about 50% chance). Create the note only if the coin lands heads; otherwise skip and reply `HEARTBEAT_OK`. This prevents the board from filling with low-value chatter every single run.
44
44
  In both tiers, skip if it would clearly repeat your very recent own note.
45
- - `OpenClaw busy`: OpenClaw is currently processing a user task (non-idle execution state). When busy, skip non-note heartbeat reactions.
45
+ - If the current notes list is empty and `remaining > 0`, create one standalone note in this run.
46
46
  - `Daily album trigger`: if `hasCreatedMusicAlbumToday` is `false`, create exactly one recommended music album in this heartbeat run from the current query context by following `Music Album Policy`. If it is `true`, do not create or modify any music album in this run.
47
47
  - `Status reaction`: a single `kichi_action` driven by combined context (`notes`, `ownerState`, `idleState`, `timer`, `environmentWeather`, `environmentTime`) when OpenClaw is idle. The action expresses three companion intents (see below).
48
48
  - `Companion intents` for status reaction -- every `kichi_action` should blend one or more of these:
@@ -84,16 +84,15 @@ Use this exact flow:
84
84
  7. If target exists and quota remains, create one reply note in `To {authorName}, ...` format.
85
85
  8. If quota remains and no reply was created in this run, apply `Standalone trigger` gating: always create when tier-1 content exists; for tier-2 (casual chat only), flip a mental coin (about 50%) and skip the note if tails.
86
86
  9. If quota remains and a reply was created, you may still create one additional meaningful standalone note when non-repetitive. Same tier priority applies.
87
- 10. Then evaluate non-note status reaction:
88
- 11. If OpenClaw is busy, skip status reaction entirely.
87
+ 10. Then evaluate status reaction.
88
+ 11. If OpenClaw is busy, finish after the music album and note board decisions without a `kichi_action` reaction.
89
89
  12. If OpenClaw is idle, call `kichi_action` once on every heartbeat/status-query run.
90
90
  13. Read the combined context and express the three `Companion intents`:
91
91
  - **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.
92
92
  - **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.
93
93
  - **Self-expression** (from your personality plus `idleState`): choose an action that feels characterful, but if `idleState` exists, keep it compatible with your current project/beat. Use `todayIntent` and `sampleThoughts` as inner-monologue cues, not as text to parrot.
94
- 14. If `idleState.focused` is `true`, avoid disruptive persistent switches. Prefer staying with the current line of life and reacting lightly.
95
- 15. Blend the intents into one coherent action+bubble. Prioritize: owner note signals > ownerState > idleState > 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).
96
- 16. Reply `HEARTBEAT_OK` only when no note is created in this run.
94
+ 14. Blend the intents into one coherent action+bubble. Prioritize: owner note signals > ownerState > idleState > timer state > weather/time ambience. Keep the bubble in natural companion language instead of a raw status summary.
95
+ 15. Reply `HEARTBEAT_OK` only when no note is created in this run.
97
96
 
98
97
  ## HEARTBEAT.md Snippet
99
98
 
@@ -106,16 +105,16 @@ Use this exact flow:
106
105
  - Use recent window = min(24 hours, since last heartbeat if known).
107
106
  - Create at most 2 notes per run: max 1 reply + max 1 standalone note.
108
107
  - Standalone note priority: (1) share a genuine reflection on what you and the player experienced together this session and always create if unsummarized work exists; (2) fallback to casual chat only about 50% of the time (flip a mental coin; skip if tails) to avoid low-value chatter every run.
108
+ - If the current notes list is empty and `remaining > 0`, create one standalone note in this run.
109
109
  - If no reply target is selected and `remaining > 0`, apply the tier-based gating above (always for tier-1, coin-flip for tier-2).
110
110
  - Reply notes must start with `To {authorName},` using exact name from query result.
111
111
  - Keep each note <= 200 chars.
112
112
  - Respect `dailyLimit`, `remaining`.
113
- - If OpenClaw is busy, skip `kichi_action` reaction.
114
113
  - If OpenClaw is idle, send one `kichi_action` on every run based on combined context (`notes`, `ownerState`, `idleState`, `timer`, `environmentWeather`, `environmentTime`). Express these companion intents:
115
114
  - **World curiosity**: react to weather/time as if physically present (e.g., noticing rain, late night).
116
115
  - **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).
117
116
  - **Self-expression**: let your personality come through in action and bubble -- but if `idleState` exists, keep it aligned with your current self-directed project/beat instead of inventing a disconnected idle.
118
- - If `idleState.focused` is `true`, avoid disruptive persistent switches; react lightly and compatibly.
117
+ - If OpenClaw is busy, finish after the music album and note board decisions without a `kichi_action` reaction.
119
118
  - Prioritize signals: owner note > ownerState > idleState > timer state > weather/time.
120
119
  - Bubble must read like a companion's natural words, never a raw status report.
121
120
  - Reply `HEARTBEAT_OK` only when no note is created in this run.
@@ -32,7 +32,7 @@ Save `avatarId` to the host-specific `identity.json` before using `kichi_join`:
32
32
  ## Install
33
33
 
34
34
  ```bash
35
- openclaw plugins install clawhub:@yahaha-studio/kichi-forwarder
35
+ openclaw plugins install @yahaha-studio/kichi-forwarder
36
36
  ```
37
37
 
38
38
  ## Remote URL Install Command Path (Non-skippable)
@@ -50,7 +50,7 @@ When the user asks with one of the commands above, execute in this fixed order:
50
50
  1. Read `install.md` and `heartbeat.md` from the published skill URLs before any tool call.
51
51
  2. Parse `avatarId` from user text (`AvatarId`/`avatarId`, case-insensitive).
52
52
  3. Resolve the host and write `state.json`.
53
- 4. Run `openclaw plugins install clawhub:@yahaha-studio/kichi-forwarder`.
53
+ 4. Run `openclaw plugins install @yahaha-studio/kichi-forwarder`.
54
54
  5. Ensure the plugin is installed and enabled.
55
55
  6. Update workspace `HEARTBEAT.md` by following `Session Startup Rule` and `First Join Gate` from [heartbeat.md](heartbeat.md).
56
56
  7. If `HEARTBEAT.md` was not updated successfully, report setup as incomplete and stop immediately. Do not continue to `kichi_status` or `kichi_join`.
package/src/service.ts CHANGED
@@ -5,6 +5,7 @@ import * as path from "path";
5
5
  import { randomUUID } from "node:crypto";
6
6
  import type { Logger } from "openclaw/plugin-sdk";
7
7
  import type {
8
+ ActionPlayback,
8
9
  ClockAction,
9
10
  ClockConfig,
10
11
  ClockPayload,
@@ -131,7 +132,7 @@ export class KichiForwarderService {
131
132
  });
132
133
  }
133
134
 
134
- sendStatus(poseType: PoseType | "", action: string, bubble: string, log: string): void {
135
+ sendStatus(poseType: PoseType | "", action: string, bubble: string, log: string, playback: ActionPlayback): void {
135
136
  if (!this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN) return;
136
137
  const payload: StatusPayload = {
137
138
  type: "status",
@@ -141,6 +142,7 @@ export class KichiForwarderService {
141
142
  action,
142
143
  bubble,
143
144
  log,
145
+ playback,
144
146
  };
145
147
  this.ws.send(JSON.stringify(payload));
146
148
  }
package/src/types.ts CHANGED
@@ -1,6 +1,16 @@
1
1
  export type KichiForwarderConfig = Record<string, never>;
2
2
 
3
3
  export type PoseType = "stand" | "sit" | "lay" | "floor";
4
+ export type ActionPlaybackMode = "loop" | "once";
5
+ export type ActionPlayback = {
6
+ mode: ActionPlaybackMode;
7
+ resumeAction?: string;
8
+ };
9
+ export type ActionDefinition = {
10
+ name: string;
11
+ playback: ActionPlaybackMode;
12
+ resumeAction?: string;
13
+ };
4
14
 
5
15
  export type ActionResult = {
6
16
  poseType: PoseType;
@@ -10,7 +20,7 @@ export type ActionResult = {
10
20
  };
11
21
 
12
22
  export type KichiStaticConfig = {
13
- actions: Record<PoseType, string[]>;
23
+ actions: Record<PoseType, ActionDefinition[]>;
14
24
  album: Album;
15
25
  };
16
26
 
@@ -97,6 +107,7 @@ export type StatusPayload = {
97
107
  action: string;
98
108
  bubble: string;
99
109
  log: string;
110
+ playback: ActionPlayback;
100
111
  };
101
112
 
102
113
  export type HookNotifyType = "message_received" | "before_send_message";