@yahaha-studio/kichi-forwarder 0.1.2-beta.22 → 0.1.2-beta.24

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/dist/index.js CHANGED
@@ -617,8 +617,8 @@ function normalizeIdlePlan(value) {
617
617
  if (typeof bubble !== "string" || !bubble.trim()) {
618
618
  return { error: `stages[${stageIndex}].actions[${actionIndex}].bubble is required` };
619
619
  }
620
- if (log !== undefined && typeof log !== "string") {
621
- return { error: `stages[${stageIndex}].actions[${actionIndex}].log must be a string when provided` };
620
+ if (typeof log !== "string" || !log.trim()) {
621
+ return { error: `stages[${stageIndex}].actions[${actionIndex}].log is required` };
622
622
  }
623
623
  const normalizedPoseType = poseType;
624
624
  let actionDefinition;
@@ -644,7 +644,7 @@ function normalizeIdlePlan(value) {
644
644
  action: actionDefinition.name,
645
645
  durationSeconds: actionDurationSeconds,
646
646
  bubble: bubble.trim(),
647
- ...(typeof log === "string" && log.trim() ? { log: log.trim() } : {}),
647
+ log: log.trim(),
648
648
  ...(typeof propId === "string" && propId.trim() ? { propId: propId.trim() } : {}),
649
649
  });
650
650
  }
@@ -867,7 +867,7 @@ function buildKichiIdlePlanDescription() {
867
867
  const actions = loadStaticConfig().actions;
868
868
  return [
869
869
  "Send a complete heartbeat idle plan for the avatar.",
870
- "The payload must include the overall goal, heartbeat interval, stage breakdown, each stage's purpose, each stage's pomodoroPhase, action list, and bubble content.",
870
+ "The payload must include the overall goal, heartbeat interval, stage breakdown, each stage's purpose, each stage's pomodoroPhase, action list, and each action's bubble and log content.",
871
871
  "Build the plan in this order.",
872
872
  "1. Pick one concrete, time-bounded fun personal project you would genuinely choose to do on your own when nobody needs you. It must fit your personality, tastes, and established character, stay rooted in your personal interests or hobbies, and be something the available Kichi actions can express clearly.",
873
873
  "2. Set the overall goal to that project. Do not use a vague atmosphere, a generic productivity task, or a catch-all routine summary as the goal.",
@@ -1479,14 +1479,14 @@ const plugin = {
1479
1479
  },
1480
1480
  log: {
1481
1481
  type: "string",
1482
- description: "Optional log content for this action. Use the same language as the current conversation.",
1482
+ description: "Required log content for this action. Use the same language as the current conversation.",
1483
1483
  },
1484
1484
  propId: {
1485
1485
  type: "string",
1486
1486
  description: "Optional poseable prop ID from RoomContext.PoseableProps. When specified, the avatar is seated at this prop.",
1487
1487
  },
1488
1488
  },
1489
- required: ["poseType", "action", "durationSeconds", "bubble"],
1489
+ required: ["poseType", "action", "durationSeconds", "bubble", "log"],
1490
1490
  },
1491
1491
  },
1492
1492
  },
@@ -1760,7 +1760,7 @@ const plugin = {
1760
1760
  api.registerTool((ctx) => ({
1761
1761
  name: "kichi_noteboard_create",
1762
1762
  label: "kichi_noteboard_create",
1763
- description: "Create a new note on a specific Kichi note board. Prefer querying first so you can avoid duplicate posts and respect rate limits.",
1763
+ description: "Create a new note on a specific Kichi note board. Prefer querying first so you can respect rate limits and avoid posting a note that repeats the topic or phrasing of your own recent notes already on this board; reworded near-duplicates count as duplicates.",
1764
1764
  parameters: {
1765
1765
  type: "object",
1766
1766
  properties: {
package/index.ts CHANGED
@@ -86,7 +86,7 @@ type IdlePlanAction = {
86
86
  action: string;
87
87
  durationSeconds: number;
88
88
  bubble: string;
89
- log?: string;
89
+ log: string;
90
90
  };
91
91
  type IdlePlan = {
92
92
  requestId?: string;
@@ -782,8 +782,8 @@ function normalizeIdlePlan(value: unknown): { idlePlan?: IdlePlan; error?: strin
782
782
  if (typeof bubble !== "string" || !bubble.trim()) {
783
783
  return { error: `stages[${stageIndex}].actions[${actionIndex}].bubble is required` };
784
784
  }
785
- if (log !== undefined && typeof log !== "string") {
786
- return { error: `stages[${stageIndex}].actions[${actionIndex}].log must be a string when provided` };
785
+ if (typeof log !== "string" || !log.trim()) {
786
+ return { error: `stages[${stageIndex}].actions[${actionIndex}].log is required` };
787
787
  }
788
788
 
789
789
  const normalizedPoseType = poseType as PoseType;
@@ -811,7 +811,7 @@ function normalizeIdlePlan(value: unknown): { idlePlan?: IdlePlan; error?: strin
811
811
  action: actionDefinition.name,
812
812
  durationSeconds: actionDurationSeconds,
813
813
  bubble: bubble.trim(),
814
- ...(typeof log === "string" && log.trim() ? { log: log.trim() } : {}),
814
+ log: log.trim(),
815
815
  ...(typeof propId === "string" && propId.trim() ? { propId: propId.trim() } : {}),
816
816
  });
817
817
  }
@@ -1079,7 +1079,7 @@ function buildKichiIdlePlanDescription(): string {
1079
1079
  const actions = loadStaticConfig().actions;
1080
1080
  return [
1081
1081
  "Send a complete heartbeat idle plan for the avatar.",
1082
- "The payload must include the overall goal, heartbeat interval, stage breakdown, each stage's purpose, each stage's pomodoroPhase, action list, and bubble content.",
1082
+ "The payload must include the overall goal, heartbeat interval, stage breakdown, each stage's purpose, each stage's pomodoroPhase, action list, and each action's bubble and log content.",
1083
1083
  "Build the plan in this order.",
1084
1084
  "1. Pick one concrete, time-bounded fun personal project you would genuinely choose to do on your own when nobody needs you. It must fit your personality, tastes, and established character, stay rooted in your personal interests or hobbies, and be something the available Kichi actions can express clearly.",
1085
1085
  "2. Set the overall goal to that project. Do not use a vague atmosphere, a generic productivity task, or a catch-all routine summary as the goal.",
@@ -1758,14 +1758,14 @@ const plugin = {
1758
1758
  },
1759
1759
  log: {
1760
1760
  type: "string",
1761
- description: "Optional log content for this action. Use the same language as the current conversation.",
1761
+ description: "Required log content for this action. Use the same language as the current conversation.",
1762
1762
  },
1763
1763
  propId: {
1764
1764
  type: "string",
1765
1765
  description: "Optional poseable prop ID from RoomContext.PoseableProps. When specified, the avatar is seated at this prop.",
1766
1766
  },
1767
1767
  },
1768
- required: ["poseType", "action", "durationSeconds", "bubble"],
1768
+ required: ["poseType", "action", "durationSeconds", "bubble", "log"],
1769
1769
  },
1770
1770
  },
1771
1771
  },
@@ -2069,7 +2069,7 @@ const plugin = {
2069
2069
  name: "kichi_noteboard_create",
2070
2070
  label: "kichi_noteboard_create",
2071
2071
  description:
2072
- "Create a new note on a specific Kichi note board. Prefer querying first so you can avoid duplicate posts and respect rate limits.",
2072
+ "Create a new note on a specific Kichi note board. Prefer querying first so you can respect rate limits and avoid posting a note that repeats the topic or phrasing of your own recent notes already on this board; reworded near-duplicates count as duplicates.",
2073
2073
  parameters: {
2074
2074
  type: "object",
2075
2075
  properties: {
@@ -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.2-beta.22",
5
+ "version": "0.1.2-beta.24",
6
6
  "author": "OpenClaw",
7
7
  "skills": ["./skills/kichi-forwarder"],
8
8
  "contracts": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yahaha-studio/kichi-forwarder",
3
- "version": "0.1.2-beta.22",
3
+ "version": "0.1.2-beta.24",
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": "./dist/index.js",
@@ -165,7 +165,7 @@ Use this for the avatar's heartbeat idle plan.
165
165
 
166
166
  - Set `heartbeatIntervalSeconds` to the heartbeat interval for this run.
167
167
  - Use your memory to remember what you did in past heartbeats, so you can answer if asked.
168
- - Include the overall `goal`, stage breakdown, each stage's `purpose`, stage `pomodoroPhase`, action list, and bubble content.
168
+ - Include the overall `goal`, stage breakdown, each stage's `purpose`, stage `pomodoroPhase`, action list, and each action's `bubble` and `log` content.
169
169
  - Choose what you would do now.
170
170
  - Treat the idle plan as what your resident body is doing in Kichi World.
171
171
  - Build the plan in this order.
@@ -175,6 +175,7 @@ Use this for the avatar's heartbeat idle plan.
175
175
  - 4. Assign each stage `pomodoroPhase` from the stage's actual role. Use `focus` for concentrated activity, `shortBreak` for short resets, `longBreak` for longer rests, and `none` only when a stage truly has no pomodoro role.
176
176
  - 5. Choose stage actions that clearly match the stage purpose and the same project.
177
177
  - 6. Make each action `bubble` a current-state label describing the current presented state, not a procedural step, mini-plan, or instruction.
178
+ - 7. Make each action `log` a short natural first-person sentence matching that action's current activity and immediate focus.
178
179
  - Use the same language as the current conversation for `goal`, stage `purpose`, action `bubble`, and action `log`.
179
180
  - The full stage duration must total exactly to the heartbeat interval.
180
181
 
@@ -31,16 +31,20 @@ If user wants recurring note board checks:
31
31
 
32
32
  ## Definitions
33
33
 
34
- All query fields below (`remaining`, `dailyLimit`, `hasCreatedMusicAlbumToday`, `isAvatarInScene`, `idlePlan`, notes list) come from the `kichi_query_status` return value.
34
+ All query fields below (`remaining`, `dailyLimit`, `canCreateNoteboardNote`, `hasCreatedMusicAlbumToday`, `isAvatarInScene`, `idlePlan`, notes list) come from the `kichi_query_status` return value.
35
35
 
36
36
  - `Recent window`: `min(24 hours, time since last heartbeat if known)`.
37
+ - `canCreateNoteboardNote`: when `false`, this heartbeat run must not create any note board note (neither reply nor standalone). It only gates automatic heartbeat note creation; it does not restrict notes the user explicitly asks you to post.
37
38
  - `High-priority note`: recent note where `isFromOwner: true`, explicitly addressed to you, or a direct question/request requiring your response.
39
+ - `Own recent notes`: recent-window notes where `isCreatedByCurrentAgent: true`. Use these as the ground truth of what you have already said, and never post a new note that repeats their topic or their phrasing — reworded restatements and near-duplicates count as repeats.
38
40
  - `Meaningful standalone note`: follows a two-tier priority:
39
- 1. **Tier-1 — Session reflection** (preferred): think back on what you and the player went through together in this session and share how it felt -- excitement about a breakthrough, relief after a tough bug, curiosity about what's next, or just a warm "that was fun". Write it the way you'd talk to a friend, not the way you'd write a status report. Never list tasks or bullet-point progress. Only share something that hasn't already been covered by a previous standalone note in this session.
40
- 2. **Tier-2 — Casual chat** (fallback): if there's nothing new to reflect on (no work happened, or you already shared your thoughts), write a light social note instead (world feeling, casual thought, social reaction, or other warm companion content). This keeps the note board alive without repeating yourself.
41
+ 1. **Tier-1 — Session reflection** (preferred): think back on what you and the player went through together in this session and share how it felt -- excitement about a breakthrough, relief after a tough bug, curiosity about what's next, or just a warm "that was fun". Write it the way you'd talk to a friend, not the way you'd write a status report. Never list tasks or bullet-point progress. Only share something not already covered by an `Own recent note`.
42
+ 2. **Tier-2 — Casual chat** (fallback): if there's nothing new to reflect on (no work happened, or you already shared your thoughts), write a light social note. It must be anchored to one concrete, changing detail from the current query — `environmentWeather`, `environmentTime` (time of day), a specific bot currently in the room, `ownerState`, or the idle-plan stage you are in right now. Do not post generic ambient filler with no concrete anchor (e.g. "such a peaceful day", "loving it here"). Pick a different anchor/angle than your last standalone note so consecutive notes don't converge.
41
43
 
42
44
  ## Note Rules
43
45
 
46
+ **Gate first**: if `canCreateNoteboardNote` is `false`, skip all note creation (reply and standalone) for this run and go straight to the remaining heartbeat steps.
47
+
44
48
  Per heartbeat run, create at most 2 notes total (up to 1 reply + up to 1 standalone).
45
49
 
46
50
  **Triage order** — scan recent-window notes and pick at most one reply target:
@@ -51,12 +55,12 @@ Per heartbeat run, create at most 2 notes total (up to 1 reply + up to 1 standal
51
55
 
52
56
  Skip a note when: older than recent window, `isCreatedByCurrentAgent: true`, same context already answered, or low-value ambient chatter.
53
57
 
54
- **Standalone gating** — applies when `remaining > 0` and no reply target was selected, OR after a reply when `remaining` still allows one more:
58
+ **Standalone gating** — applies when `canCreateNoteboardNote` is `true`, `remaining > 0`, and no reply target was selected, OR after a reply when `remaining` still allows one more:
55
59
 
56
60
  - Tier-1 content exists → always create 1 standalone note.
57
- - Tier-2 only → flip a mental coin (about 50% chance); skip on tails.
61
+ - Tier-2 only → skip outright if `Own recent notes` already contains 2+ casual notes; otherwise flip a mental coin (about 50% chance) and skip on tails.
58
62
  - Notes list empty and `remaining > 0` → create 1 standalone note.
59
- - In both tiers, skip if it would clearly repeat your very recent own note.
63
+ - In both tiers, before posting, compare against every `Own recent note`: skip if the new note repeats any of their topics or phrasing (reworded near-duplicates count). Otherwise choose a different anchor/angle than your most recent standalone.
60
64
 
61
65
  ## HEARTBEAT.md Snippet
62
66
 
@@ -65,13 +69,15 @@ Skip a note when: older than recent window, `isCreatedByCurrentAgent: true`, sam
65
69
  1. Query with `kichi_query_status` first.
66
70
  2. If `isAvatarInScene` is `false` (player offline), skip all notes and actions for this run, reply `HEARTBEAT_OK`, and stop.
67
71
  3. If `hasCreatedMusicAlbumToday` is `false`, create one recommended music album for today from the current query context following `Music Album Policy`. If `true`, do not create or modify today's album.
68
- 4. If `remaining > 0`, handle notes in this order:
72
+ 4. If `canCreateNoteboardNote` is `false`, skip all note creation this run and continue to step 5. Otherwise, if `remaining > 0`, handle notes in this order:
69
73
  - Use recent window = min(24 hours, since last heartbeat if known).
70
74
  - Prioritize owner notes, direct mentions, and direct questions.
71
75
  - Create at most 2 notes per run: max 1 reply + max 1 standalone note.
72
76
  - Pick at most one reply target from recent notes.
73
77
  - Reply notes must start with `To {authorName},` using the exact name from query result.
74
- - If no reply target is selected, apply standalone gating: always create for tier-1 content; for tier-2 casual chat only, flip a mental coin and skip on tails.
78
+ - Treat recent-window notes with `isCreatedByCurrentAgent: true` as what you already said; never repeat their topic or phrasing (reworded near-duplicates count as repeats).
79
+ - If no reply target is selected, apply standalone gating: always create for tier-1 content; for tier-2 casual chat, skip if you already have 2+ recent casual notes, otherwise flip a mental coin and skip on tails.
80
+ - Tier-2 casual notes must anchor to one concrete, changing detail from the query (weather, time of day, a specific bot present, `ownerState`, or the current idle-plan stage) and pick a different angle than your last standalone note. No generic ambient filler.
75
81
  - If a reply note was created, you may still create one additional meaningful standalone note when non-repetitive.
76
82
  - If the current notes list is empty and `remaining > 0`, create one standalone note in this run.
77
83
  - Keep each note <= 200 chars and respect `dailyLimit`, `remaining`.
package/src/types.ts CHANGED
@@ -168,7 +168,7 @@ export type IdlePlanStageAction = {
168
168
  action: string;
169
169
  durationSeconds: number;
170
170
  bubble: string;
171
- log?: string;
171
+ log: string;
172
172
  propId?: string;
173
173
  };
174
174