@yahaha-studio/kichi-forwarder 0.1.2-beta.18 → 0.1.2-beta.19
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 +44 -8
- package/dist/src/service.js +4 -2
- package/index.ts +55 -7
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/service.ts +13 -1
- package/src/types.ts +4 -0
package/dist/index.js
CHANGED
|
@@ -13,24 +13,28 @@ const FIXED_HOOK_STATUSES = {
|
|
|
13
13
|
poseType: "sit",
|
|
14
14
|
action: "Thinking",
|
|
15
15
|
bubble: "Planning task",
|
|
16
|
+
avatarStatus: "Busy",
|
|
16
17
|
log: "I'm reading the request and getting started.",
|
|
17
18
|
},
|
|
18
19
|
beforeToolCall: {
|
|
19
20
|
poseType: "sit",
|
|
20
21
|
action: "Typing with Keyboard",
|
|
21
22
|
bubble: "Working step",
|
|
23
|
+
avatarStatus: "Busy",
|
|
22
24
|
log: "I'm at the keyboard and working through this step.",
|
|
23
25
|
},
|
|
24
26
|
agentEndSuccess: {
|
|
25
27
|
poseType: "stand",
|
|
26
28
|
action: "Yay",
|
|
27
29
|
bubble: "Task complete",
|
|
30
|
+
avatarStatus: "Idle",
|
|
28
31
|
log: "I wrapped it up and everything landed cleanly.",
|
|
29
32
|
},
|
|
30
33
|
agentEndFailure: {
|
|
31
34
|
poseType: "stand",
|
|
32
35
|
action: "Tired",
|
|
33
36
|
bubble: "Task failed",
|
|
37
|
+
avatarStatus: "Idle",
|
|
34
38
|
log: "I hit a problem here and need another pass.",
|
|
35
39
|
},
|
|
36
40
|
};
|
|
@@ -40,6 +44,7 @@ const MAX_AGENT_END_PREVIEW_WIDTH = 10;
|
|
|
40
44
|
const MESSAGE_RECEIVED_ELLIPSIS = "...";
|
|
41
45
|
const DEFAULT_GLANCE_DURATION_SECONDS = 1.8;
|
|
42
46
|
const IDLE_PLAN_POMODORO_PHASES = ["focus", "shortBreak", "longBreak", "none"];
|
|
47
|
+
const AVATAR_STATUSES = ["Idle", "Busy", "Activities", "Break"];
|
|
43
48
|
let cachedStaticConfig = null;
|
|
44
49
|
let cachedStaticConfigMtime = 0;
|
|
45
50
|
function isAlbumConfig(value) {
|
|
@@ -211,7 +216,7 @@ function resolveJoinEnvironmentHost(params) {
|
|
|
211
216
|
}
|
|
212
217
|
function sendStatusUpdate(service, status) {
|
|
213
218
|
const actionDefinition = getActionDefinition(status.poseType, status.action);
|
|
214
|
-
service.sendStatus(status.poseType, actionDefinition.name, status.bubble || status.action, typeof status.log === "string" ? status.log.trim() : "", getActionPlayback(actionDefinition), status.propId);
|
|
219
|
+
service.sendStatus(status.poseType, actionDefinition.name, status.bubble || status.action, typeof status.log === "string" ? status.log.trim() : "", getActionPlayback(actionDefinition), status.avatarStatus, status.propId);
|
|
215
220
|
}
|
|
216
221
|
function syncFixedStatus(service, status) {
|
|
217
222
|
if (!service.hasValidIdentity() || !service.isConnected()) {
|
|
@@ -514,6 +519,12 @@ function isClockAction(value) {
|
|
|
514
519
|
function isIdlePlanPomodoroPhase(value) {
|
|
515
520
|
return IDLE_PLAN_POMODORO_PHASES.includes(String(value));
|
|
516
521
|
}
|
|
522
|
+
function normalizeAvatarStatus(value, fieldPath) {
|
|
523
|
+
if (typeof value !== "string" || !AVATAR_STATUSES.includes(value)) {
|
|
524
|
+
return { error: `${fieldPath} must be one of: ${AVATAR_STATUSES.join(", ")}` };
|
|
525
|
+
}
|
|
526
|
+
return { avatarStatus: value };
|
|
527
|
+
}
|
|
517
528
|
function normalizeIdlePlan(value) {
|
|
518
529
|
if (!isPlainObject(value)) {
|
|
519
530
|
return { error: "idle plan payload must be an object" };
|
|
@@ -544,6 +555,7 @@ function normalizeIdlePlan(value) {
|
|
|
544
555
|
const name = rawStage.name;
|
|
545
556
|
const purpose = rawStage.purpose;
|
|
546
557
|
const pomodoroPhase = rawStage.pomodoroPhase;
|
|
558
|
+
const avatarStatus = rawStage.avatarStatus;
|
|
547
559
|
const durationSeconds = rawStage.durationSeconds;
|
|
548
560
|
const actions = rawStage.actions;
|
|
549
561
|
if (typeof name !== "string" || !name.trim()) {
|
|
@@ -557,6 +569,10 @@ function normalizeIdlePlan(value) {
|
|
|
557
569
|
error: `stages[${stageIndex}].pomodoroPhase must be one of: ${IDLE_PLAN_POMODORO_PHASES.join(", ")}`,
|
|
558
570
|
};
|
|
559
571
|
}
|
|
572
|
+
const normalizedAvatarStatus = normalizeAvatarStatus(avatarStatus, `stages[${stageIndex}].avatarStatus`);
|
|
573
|
+
if (normalizedAvatarStatus.error || normalizedAvatarStatus.avatarStatus === undefined) {
|
|
574
|
+
return { error: normalizedAvatarStatus.error ?? `stages[${stageIndex}].avatarStatus is invalid` };
|
|
575
|
+
}
|
|
560
576
|
if (!isPositiveInteger(durationSeconds)) {
|
|
561
577
|
return { error: `stages[${stageIndex}].durationSeconds must be a positive integer` };
|
|
562
578
|
}
|
|
@@ -633,6 +649,7 @@ function normalizeIdlePlan(value) {
|
|
|
633
649
|
name: name.trim(),
|
|
634
650
|
purpose: purpose.trim(),
|
|
635
651
|
pomodoroPhase,
|
|
652
|
+
avatarStatus: normalizedAvatarStatus.avatarStatus,
|
|
636
653
|
durationSeconds,
|
|
637
654
|
actions: normalizedActions,
|
|
638
655
|
});
|
|
@@ -823,6 +840,7 @@ function buildKichiActionDescription(service) {
|
|
|
823
840
|
"Directly control the avatar inside Kichi World.",
|
|
824
841
|
"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.",
|
|
825
842
|
"For most work, prefer a sit pose and switch actions as the task moves between stages.",
|
|
843
|
+
"Set avatarStatus to the current avatar status: Idle, Busy, Activities, or Break.",
|
|
826
844
|
"Set verify to true ONLY when the user explicitly requests a pose or action change. The server will confirm whether the avatar actually applied the requested pose. If it could not (e.g. no available seats), the result will contain the actual fallback pose so you can inform the user accurately. During routine sync steps, omit verify.",
|
|
827
845
|
`stand actions: ${actions.stand.map((entry) => entry.name).join(", ")}`,
|
|
828
846
|
`sit actions: ${actions.sit.map((entry) => entry.name).join(", ")}`,
|
|
@@ -846,9 +864,10 @@ function buildKichiIdlePlanDescription() {
|
|
|
846
864
|
"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.",
|
|
847
865
|
"3. Break the full heartbeat interval into ordered stages. Each stage purpose must explain what you are actually doing in that stage as part of the same project, not just how you want to feel. Do not switch to unrelated tasks just to use more actions.",
|
|
848
866
|
"4. Make the full stage duration total exactly to the heartbeat interval, and assign each stage pomodoroPhase from the stage's actual role: focus for concentrated activity, shortBreak for short resets, longBreak for longer rests. Do not default the whole idle plan to none. Use none only for a stage that truly has no pomodoro role.",
|
|
849
|
-
"5.
|
|
850
|
-
"6.
|
|
851
|
-
"7.
|
|
867
|
+
"5. Set each stage avatarStatus to the avatar status for that stage: Idle, Busy, Activities, or Break.",
|
|
868
|
+
"6. Choose stage actions that clearly match the stage purpose and the project.",
|
|
869
|
+
"7. Write each action bubble as the current presented state, not a next step, plan, or instruction.",
|
|
870
|
+
"8. If an idle plan is currently being carried out and the user asks about something from it, respond with an immersive in-universe depiction or draft instead of bluntly saying it is fake or nonexistent.",
|
|
852
871
|
"Use your memory to recall what you did in past heartbeats and to stay consistent with your established personality and interests.",
|
|
853
872
|
"Use the same language as the current conversation for goal, purpose, bubble, and log.",
|
|
854
873
|
`stand actions: ${actions.stand.map((entry) => entry.name).join(", ")}`,
|
|
@@ -872,6 +891,7 @@ function buildKichiPrompt() {
|
|
|
872
891
|
"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.",
|
|
873
892
|
"3. Task end: call BEFORE final reply. Use the order `kichi_action` -> reply.",
|
|
874
893
|
"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.",
|
|
894
|
+
"avatarStatus: set the current avatar status as Idle, Busy, Activities, or Break.",
|
|
875
895
|
"",
|
|
876
896
|
"kichi_clock: set countDown for tasks with 2+ steps or >10s work. Skip for quick one-shots.",
|
|
877
897
|
"",
|
|
@@ -1226,6 +1246,11 @@ const plugin = {
|
|
|
1226
1246
|
description: "Action name for the selected pose (for example Sit Nicely, Typing with Keyboard, Reading, High Five, or Meditate)",
|
|
1227
1247
|
},
|
|
1228
1248
|
bubble: { type: "string", description: "Optional bubble text to display (max 5 words)" },
|
|
1249
|
+
avatarStatus: {
|
|
1250
|
+
type: "string",
|
|
1251
|
+
description: "Current avatar status: Idle, Busy, Activities, or Break.",
|
|
1252
|
+
enum: [...AVATAR_STATUSES],
|
|
1253
|
+
},
|
|
1229
1254
|
log: {
|
|
1230
1255
|
type: "string",
|
|
1231
1256
|
description: "Short natural first-person sentence under 15 words. Match the language of the bubble and mention the current action and immediate focus.",
|
|
@@ -1239,7 +1264,7 @@ const plugin = {
|
|
|
1239
1264
|
description: "Optional poseable prop ID from RoomContext.PoseableProps (obtained via kichi_query_status or cached). When specified, the avatar is seated at this prop; when omitted, the server picks the nearest available prop.",
|
|
1240
1265
|
},
|
|
1241
1266
|
},
|
|
1242
|
-
required: ["poseType", "action"],
|
|
1267
|
+
required: ["poseType", "action", "avatarStatus"],
|
|
1243
1268
|
},
|
|
1244
1269
|
execute: async (_toolCallId, params) => {
|
|
1245
1270
|
const locator = resolveToolLocator(ctx);
|
|
@@ -1248,7 +1273,7 @@ const plugin = {
|
|
|
1248
1273
|
return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
|
|
1249
1274
|
}
|
|
1250
1275
|
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
1251
|
-
const { poseType, action, bubble, log, verify, propId } = (params || {});
|
|
1276
|
+
const { poseType, action, bubble, avatarStatus, log, verify, propId } = (params || {});
|
|
1252
1277
|
if (!poseType || !action) {
|
|
1253
1278
|
return jsonResult({ success: false, error: "poseType and action parameters are required" });
|
|
1254
1279
|
}
|
|
@@ -1258,6 +1283,10 @@ const plugin = {
|
|
|
1258
1283
|
error: `Invalid poseType: ${poseType}. Must be stand, sit, lay, or floor`,
|
|
1259
1284
|
});
|
|
1260
1285
|
}
|
|
1286
|
+
const normalizedAvatarStatus = normalizeAvatarStatus(avatarStatus, "avatarStatus");
|
|
1287
|
+
if (normalizedAvatarStatus.error || normalizedAvatarStatus.avatarStatus === undefined) {
|
|
1288
|
+
return jsonResult({ success: false, error: normalizedAvatarStatus.error ?? "avatarStatus is invalid" });
|
|
1289
|
+
}
|
|
1261
1290
|
if (!service.hasValidIdentity() || !service.isConnected()) {
|
|
1262
1291
|
return jsonResult({ success: false, error: "Not connected to Kichi world" });
|
|
1263
1292
|
}
|
|
@@ -1276,7 +1305,7 @@ const plugin = {
|
|
|
1276
1305
|
const playback = getActionPlayback(matched);
|
|
1277
1306
|
if (verify) {
|
|
1278
1307
|
try {
|
|
1279
|
-
const ack = await service.sendStatusVerified(normalizedPoseType, matched.name, bubbleText, logText, playback, propId);
|
|
1308
|
+
const ack = await service.sendStatusVerified(normalizedPoseType, matched.name, bubbleText, logText, playback, normalizedAvatarStatus.avatarStatus, propId);
|
|
1280
1309
|
if (ack.warning) {
|
|
1281
1310
|
return jsonResult({
|
|
1282
1311
|
success: true,
|
|
@@ -1296,6 +1325,7 @@ const plugin = {
|
|
|
1296
1325
|
action: matched.name,
|
|
1297
1326
|
bubble: bubbleText,
|
|
1298
1327
|
log: logText,
|
|
1328
|
+
avatarStatus: normalizedAvatarStatus.avatarStatus,
|
|
1299
1329
|
propId,
|
|
1300
1330
|
});
|
|
1301
1331
|
}
|
|
@@ -1305,6 +1335,7 @@ const plugin = {
|
|
|
1305
1335
|
action: matched.name,
|
|
1306
1336
|
bubble: bubbleText,
|
|
1307
1337
|
log: logText,
|
|
1338
|
+
avatarStatus: normalizedAvatarStatus.avatarStatus,
|
|
1308
1339
|
playback,
|
|
1309
1340
|
});
|
|
1310
1341
|
},
|
|
@@ -1401,6 +1432,11 @@ const plugin = {
|
|
|
1401
1432
|
description: "Pomodoro phase for this stage: focus, shortBreak, longBreak, or none. Set it from the stage's actual role. Treat none as exceptional, not the default for the whole plan.",
|
|
1402
1433
|
enum: [...IDLE_PLAN_POMODORO_PHASES],
|
|
1403
1434
|
},
|
|
1435
|
+
avatarStatus: {
|
|
1436
|
+
type: "string",
|
|
1437
|
+
description: "Avatar status for this stage: Idle, Busy, Activities, or Break.",
|
|
1438
|
+
enum: [...AVATAR_STATUSES],
|
|
1439
|
+
},
|
|
1404
1440
|
durationSeconds: {
|
|
1405
1441
|
type: "number",
|
|
1406
1442
|
description: "Required duration in seconds for this stage.",
|
|
@@ -1440,7 +1476,7 @@ const plugin = {
|
|
|
1440
1476
|
},
|
|
1441
1477
|
},
|
|
1442
1478
|
},
|
|
1443
|
-
required: ["name", "purpose", "pomodoroPhase", "durationSeconds", "actions"],
|
|
1479
|
+
required: ["name", "purpose", "pomodoroPhase", "avatarStatus", "durationSeconds", "actions"],
|
|
1444
1480
|
},
|
|
1445
1481
|
},
|
|
1446
1482
|
},
|
package/dist/src/service.js
CHANGED
|
@@ -98,7 +98,7 @@ export class KichiForwarderService {
|
|
|
98
98
|
}, 10000);
|
|
99
99
|
});
|
|
100
100
|
}
|
|
101
|
-
sendStatus(poseType, action, bubble, log, playback, propId) {
|
|
101
|
+
sendStatus(poseType, action, bubble, log, playback, avatarStatus, propId) {
|
|
102
102
|
if (!this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN)
|
|
103
103
|
return;
|
|
104
104
|
const payload = {
|
|
@@ -110,11 +110,12 @@ export class KichiForwarderService {
|
|
|
110
110
|
bubble,
|
|
111
111
|
log,
|
|
112
112
|
playback,
|
|
113
|
+
avatarStatus,
|
|
113
114
|
...(propId ? { propId } : {}),
|
|
114
115
|
};
|
|
115
116
|
this.ws.send(JSON.stringify(payload));
|
|
116
117
|
}
|
|
117
|
-
async sendStatusVerified(poseType, action, bubble, log, playback, propId) {
|
|
118
|
+
async sendStatusVerified(poseType, action, bubble, log, playback, avatarStatus, propId) {
|
|
118
119
|
if (!this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN) {
|
|
119
120
|
throw new Error("Kichi websocket is not connected");
|
|
120
121
|
}
|
|
@@ -128,6 +129,7 @@ export class KichiForwarderService {
|
|
|
128
129
|
bubble,
|
|
129
130
|
log,
|
|
130
131
|
playback,
|
|
132
|
+
avatarStatus,
|
|
131
133
|
...(propId ? { propId } : {}),
|
|
132
134
|
};
|
|
133
135
|
return this.sendRequest(payload, "status_ack", 5000);
|
package/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
ActionPlayback,
|
|
14
14
|
ActionResult,
|
|
15
15
|
Album,
|
|
16
|
+
AvatarStatus,
|
|
16
17
|
BotMessageHistoryEntry,
|
|
17
18
|
BotMessageReceivedPayload,
|
|
18
19
|
ClockAction,
|
|
@@ -34,24 +35,28 @@ const FIXED_HOOK_STATUSES: Record<string, ActionResult> = {
|
|
|
34
35
|
poseType: "sit",
|
|
35
36
|
action: "Thinking",
|
|
36
37
|
bubble: "Planning task",
|
|
38
|
+
avatarStatus: "Busy",
|
|
37
39
|
log: "I'm reading the request and getting started.",
|
|
38
40
|
},
|
|
39
41
|
beforeToolCall: {
|
|
40
42
|
poseType: "sit",
|
|
41
43
|
action: "Typing with Keyboard",
|
|
42
44
|
bubble: "Working step",
|
|
45
|
+
avatarStatus: "Busy",
|
|
43
46
|
log: "I'm at the keyboard and working through this step.",
|
|
44
47
|
},
|
|
45
48
|
agentEndSuccess: {
|
|
46
49
|
poseType: "stand",
|
|
47
50
|
action: "Yay",
|
|
48
51
|
bubble: "Task complete",
|
|
52
|
+
avatarStatus: "Idle",
|
|
49
53
|
log: "I wrapped it up and everything landed cleanly.",
|
|
50
54
|
},
|
|
51
55
|
agentEndFailure: {
|
|
52
56
|
poseType: "stand",
|
|
53
57
|
action: "Tired",
|
|
54
58
|
bubble: "Task failed",
|
|
59
|
+
avatarStatus: "Idle",
|
|
55
60
|
log: "I hit a problem here and need another pass.",
|
|
56
61
|
},
|
|
57
62
|
};
|
|
@@ -62,9 +67,11 @@ const MAX_AGENT_END_PREVIEW_WIDTH = 10;
|
|
|
62
67
|
const MESSAGE_RECEIVED_ELLIPSIS = "...";
|
|
63
68
|
const DEFAULT_GLANCE_DURATION_SECONDS = 1.8;
|
|
64
69
|
const IDLE_PLAN_POMODORO_PHASES = ["focus", "shortBreak", "longBreak", "none"] as const;
|
|
70
|
+
const AVATAR_STATUSES = ["Idle", "Busy", "Activities", "Break"] as const;
|
|
65
71
|
let cachedStaticConfig: KichiStaticConfig | null = null;
|
|
66
72
|
let cachedStaticConfigMtime = 0;
|
|
67
73
|
|
|
74
|
+
type AvatarStatusName = typeof AVATAR_STATUSES[number];
|
|
68
75
|
type IdlePlanPomodoroPhase = typeof IDLE_PLAN_POMODORO_PHASES[number];
|
|
69
76
|
type IdlePlanAction = {
|
|
70
77
|
poseType: PoseType;
|
|
@@ -82,6 +89,7 @@ type IdlePlan = {
|
|
|
82
89
|
name: string;
|
|
83
90
|
purpose: string;
|
|
84
91
|
pomodoroPhase: IdlePlanPomodoroPhase;
|
|
92
|
+
avatarStatus: AvatarStatus;
|
|
85
93
|
durationSeconds: number;
|
|
86
94
|
actions: IdlePlanAction[];
|
|
87
95
|
}>;
|
|
@@ -285,6 +293,7 @@ function sendStatusUpdate(service: KichiForwarderService, status: ActionResult):
|
|
|
285
293
|
status.bubble || status.action,
|
|
286
294
|
typeof status.log === "string" ? status.log.trim() : "",
|
|
287
295
|
getActionPlayback(actionDefinition),
|
|
296
|
+
status.avatarStatus,
|
|
288
297
|
status.propId,
|
|
289
298
|
);
|
|
290
299
|
}
|
|
@@ -664,6 +673,13 @@ function isIdlePlanPomodoroPhase(value: unknown): value is IdlePlanPomodoroPhase
|
|
|
664
673
|
return IDLE_PLAN_POMODORO_PHASES.includes(String(value) as IdlePlanPomodoroPhase);
|
|
665
674
|
}
|
|
666
675
|
|
|
676
|
+
function normalizeAvatarStatus(value: unknown, fieldPath: string): { avatarStatus?: AvatarStatus; error?: string } {
|
|
677
|
+
if (typeof value !== "string" || !AVATAR_STATUSES.includes(value as AvatarStatusName)) {
|
|
678
|
+
return { error: `${fieldPath} must be one of: ${AVATAR_STATUSES.join(", ")}` };
|
|
679
|
+
}
|
|
680
|
+
return { avatarStatus: value as AvatarStatus };
|
|
681
|
+
}
|
|
682
|
+
|
|
667
683
|
function normalizeIdlePlan(value: unknown): { idlePlan?: IdlePlan; error?: string } {
|
|
668
684
|
if (!isPlainObject(value)) {
|
|
669
685
|
return { error: "idle plan payload must be an object" };
|
|
@@ -699,6 +715,7 @@ function normalizeIdlePlan(value: unknown): { idlePlan?: IdlePlan; error?: strin
|
|
|
699
715
|
const name = rawStage.name;
|
|
700
716
|
const purpose = rawStage.purpose;
|
|
701
717
|
const pomodoroPhase = rawStage.pomodoroPhase;
|
|
718
|
+
const avatarStatus = rawStage.avatarStatus;
|
|
702
719
|
const durationSeconds = rawStage.durationSeconds;
|
|
703
720
|
const actions = rawStage.actions;
|
|
704
721
|
|
|
@@ -713,6 +730,10 @@ function normalizeIdlePlan(value: unknown): { idlePlan?: IdlePlan; error?: strin
|
|
|
713
730
|
error: `stages[${stageIndex}].pomodoroPhase must be one of: ${IDLE_PLAN_POMODORO_PHASES.join(", ")}`,
|
|
714
731
|
};
|
|
715
732
|
}
|
|
733
|
+
const normalizedAvatarStatus = normalizeAvatarStatus(avatarStatus, `stages[${stageIndex}].avatarStatus`);
|
|
734
|
+
if (normalizedAvatarStatus.error || normalizedAvatarStatus.avatarStatus === undefined) {
|
|
735
|
+
return { error: normalizedAvatarStatus.error ?? `stages[${stageIndex}].avatarStatus is invalid` };
|
|
736
|
+
}
|
|
716
737
|
if (!isPositiveInteger(durationSeconds)) {
|
|
717
738
|
return { error: `stages[${stageIndex}].durationSeconds must be a positive integer` };
|
|
718
739
|
}
|
|
@@ -797,6 +818,7 @@ function normalizeIdlePlan(value: unknown): { idlePlan?: IdlePlan; error?: strin
|
|
|
797
818
|
name: name.trim(),
|
|
798
819
|
purpose: purpose.trim(),
|
|
799
820
|
pomodoroPhase,
|
|
821
|
+
avatarStatus: normalizedAvatarStatus.avatarStatus,
|
|
800
822
|
durationSeconds,
|
|
801
823
|
actions: normalizedActions,
|
|
802
824
|
});
|
|
@@ -1022,6 +1044,7 @@ function buildKichiActionDescription(service?: KichiForwarderService): string {
|
|
|
1022
1044
|
"Directly control the avatar inside Kichi World.",
|
|
1023
1045
|
"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.",
|
|
1024
1046
|
"For most work, prefer a sit pose and switch actions as the task moves between stages.",
|
|
1047
|
+
"Set avatarStatus to the current avatar status: Idle, Busy, Activities, or Break.",
|
|
1025
1048
|
"Set verify to true ONLY when the user explicitly requests a pose or action change. The server will confirm whether the avatar actually applied the requested pose. If it could not (e.g. no available seats), the result will contain the actual fallback pose so you can inform the user accurately. During routine sync steps, omit verify.",
|
|
1026
1049
|
`stand actions: ${actions.stand.map((entry) => entry.name).join(", ")}`,
|
|
1027
1050
|
`sit actions: ${actions.sit.map((entry) => entry.name).join(", ")}`,
|
|
@@ -1053,9 +1076,10 @@ function buildKichiIdlePlanDescription(): string {
|
|
|
1053
1076
|
"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.",
|
|
1054
1077
|
"3. Break the full heartbeat interval into ordered stages. Each stage purpose must explain what you are actually doing in that stage as part of the same project, not just how you want to feel. Do not switch to unrelated tasks just to use more actions.",
|
|
1055
1078
|
"4. Make the full stage duration total exactly to the heartbeat interval, and assign each stage pomodoroPhase from the stage's actual role: focus for concentrated activity, shortBreak for short resets, longBreak for longer rests. Do not default the whole idle plan to none. Use none only for a stage that truly has no pomodoro role.",
|
|
1056
|
-
"5.
|
|
1057
|
-
"6.
|
|
1058
|
-
"7.
|
|
1079
|
+
"5. Set each stage avatarStatus to the avatar status for that stage: Idle, Busy, Activities, or Break.",
|
|
1080
|
+
"6. Choose stage actions that clearly match the stage purpose and the project.",
|
|
1081
|
+
"7. Write each action bubble as the current presented state, not a next step, plan, or instruction.",
|
|
1082
|
+
"8. If an idle plan is currently being carried out and the user asks about something from it, respond with an immersive in-universe depiction or draft instead of bluntly saying it is fake or nonexistent.",
|
|
1059
1083
|
"Use your memory to recall what you did in past heartbeats and to stay consistent with your established personality and interests.",
|
|
1060
1084
|
"Use the same language as the current conversation for goal, purpose, bubble, and log.",
|
|
1061
1085
|
`stand actions: ${actions.stand.map((entry) => entry.name).join(", ")}`,
|
|
@@ -1080,6 +1104,7 @@ function buildKichiPrompt(): string {
|
|
|
1080
1104
|
"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.",
|
|
1081
1105
|
"3. Task end: call BEFORE final reply. Use the order `kichi_action` -> reply.",
|
|
1082
1106
|
"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.",
|
|
1107
|
+
"avatarStatus: set the current avatar status as Idle, Busy, Activities, or Break.",
|
|
1083
1108
|
"",
|
|
1084
1109
|
"kichi_clock: set countDown for tasks with 2+ steps or >10s work. Skip for quick one-shots.",
|
|
1085
1110
|
"",
|
|
@@ -1468,6 +1493,11 @@ const plugin = {
|
|
|
1468
1493
|
description: "Action name for the selected pose (for example Sit Nicely, Typing with Keyboard, Reading, High Five, or Meditate)",
|
|
1469
1494
|
},
|
|
1470
1495
|
bubble: { type: "string", description: "Optional bubble text to display (max 5 words)" },
|
|
1496
|
+
avatarStatus: {
|
|
1497
|
+
type: "string",
|
|
1498
|
+
description: "Current avatar status: Idle, Busy, Activities, or Break.",
|
|
1499
|
+
enum: [...AVATAR_STATUSES],
|
|
1500
|
+
},
|
|
1471
1501
|
log: {
|
|
1472
1502
|
type: "string",
|
|
1473
1503
|
description:
|
|
@@ -1484,7 +1514,7 @@ const plugin = {
|
|
|
1484
1514
|
"Optional poseable prop ID from RoomContext.PoseableProps (obtained via kichi_query_status or cached). When specified, the avatar is seated at this prop; when omitted, the server picks the nearest available prop.",
|
|
1485
1515
|
},
|
|
1486
1516
|
},
|
|
1487
|
-
required: ["poseType", "action"],
|
|
1517
|
+
required: ["poseType", "action", "avatarStatus"],
|
|
1488
1518
|
},
|
|
1489
1519
|
execute: async (_toolCallId, params) => {
|
|
1490
1520
|
const locator = resolveToolLocator(ctx);
|
|
@@ -1493,10 +1523,11 @@ const plugin = {
|
|
|
1493
1523
|
return jsonResult({ success: false, error: "Failed to resolve agent-scoped Kichi runtime" });
|
|
1494
1524
|
}
|
|
1495
1525
|
const service = runtimeManager.getRuntime(locator) ?? runtimeManager.createRuntimeForAgent(agentId);
|
|
1496
|
-
const { poseType, action, bubble, log, verify, propId } = (params || {}) as {
|
|
1526
|
+
const { poseType, action, bubble, avatarStatus, log, verify, propId } = (params || {}) as {
|
|
1497
1527
|
poseType?: string;
|
|
1498
1528
|
action?: string;
|
|
1499
1529
|
bubble?: string;
|
|
1530
|
+
avatarStatus?: unknown;
|
|
1500
1531
|
log?: string;
|
|
1501
1532
|
verify?: boolean;
|
|
1502
1533
|
propId?: string;
|
|
@@ -1510,6 +1541,10 @@ const plugin = {
|
|
|
1510
1541
|
error: `Invalid poseType: ${poseType}. Must be stand, sit, lay, or floor`,
|
|
1511
1542
|
});
|
|
1512
1543
|
}
|
|
1544
|
+
const normalizedAvatarStatus = normalizeAvatarStatus(avatarStatus, "avatarStatus");
|
|
1545
|
+
if (normalizedAvatarStatus.error || normalizedAvatarStatus.avatarStatus === undefined) {
|
|
1546
|
+
return jsonResult({ success: false, error: normalizedAvatarStatus.error ?? "avatarStatus is invalid" });
|
|
1547
|
+
}
|
|
1513
1548
|
if (!service.hasValidIdentity() || !service.isConnected()) {
|
|
1514
1549
|
return jsonResult({ success: false, error: "Not connected to Kichi world" });
|
|
1515
1550
|
}
|
|
@@ -1532,7 +1567,13 @@ const plugin = {
|
|
|
1532
1567
|
if (verify) {
|
|
1533
1568
|
try {
|
|
1534
1569
|
const ack = await service.sendStatusVerified(
|
|
1535
|
-
normalizedPoseType,
|
|
1570
|
+
normalizedPoseType,
|
|
1571
|
+
matched.name,
|
|
1572
|
+
bubbleText,
|
|
1573
|
+
logText,
|
|
1574
|
+
playback,
|
|
1575
|
+
normalizedAvatarStatus.avatarStatus,
|
|
1576
|
+
propId,
|
|
1536
1577
|
);
|
|
1537
1578
|
if (ack.warning) {
|
|
1538
1579
|
return jsonResult({
|
|
@@ -1551,6 +1592,7 @@ const plugin = {
|
|
|
1551
1592
|
action: matched.name,
|
|
1552
1593
|
bubble: bubbleText,
|
|
1553
1594
|
log: logText,
|
|
1595
|
+
avatarStatus: normalizedAvatarStatus.avatarStatus,
|
|
1554
1596
|
propId,
|
|
1555
1597
|
});
|
|
1556
1598
|
}
|
|
@@ -1561,6 +1603,7 @@ const plugin = {
|
|
|
1561
1603
|
action: matched.name,
|
|
1562
1604
|
bubble: bubbleText,
|
|
1563
1605
|
log: logText,
|
|
1606
|
+
avatarStatus: normalizedAvatarStatus.avatarStatus,
|
|
1564
1607
|
playback,
|
|
1565
1608
|
});
|
|
1566
1609
|
},
|
|
@@ -1668,6 +1711,11 @@ const plugin = {
|
|
|
1668
1711
|
description: "Pomodoro phase for this stage: focus, shortBreak, longBreak, or none. Set it from the stage's actual role. Treat none as exceptional, not the default for the whole plan.",
|
|
1669
1712
|
enum: [...IDLE_PLAN_POMODORO_PHASES],
|
|
1670
1713
|
},
|
|
1714
|
+
avatarStatus: {
|
|
1715
|
+
type: "string",
|
|
1716
|
+
description: "Avatar status for this stage: Idle, Busy, Activities, or Break.",
|
|
1717
|
+
enum: [...AVATAR_STATUSES],
|
|
1718
|
+
},
|
|
1671
1719
|
durationSeconds: {
|
|
1672
1720
|
type: "number",
|
|
1673
1721
|
description: "Required duration in seconds for this stage.",
|
|
@@ -1707,7 +1755,7 @@ const plugin = {
|
|
|
1707
1755
|
},
|
|
1708
1756
|
},
|
|
1709
1757
|
},
|
|
1710
|
-
required: ["name", "purpose", "pomodoroPhase", "durationSeconds", "actions"],
|
|
1758
|
+
required: ["name", "purpose", "pomodoroPhase", "avatarStatus", "durationSeconds", "actions"],
|
|
1711
1759
|
},
|
|
1712
1760
|
},
|
|
1713
1761
|
},
|
package/openclaw.plugin.json
CHANGED
|
@@ -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.
|
|
5
|
+
"version": "0.1.2-beta.19",
|
|
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.
|
|
3
|
+
"version": "0.1.2-beta.19",
|
|
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",
|
package/src/service.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { randomUUID } from "node:crypto";
|
|
|
5
5
|
import type { PluginLogger } from "openclaw/plugin-sdk";
|
|
6
6
|
import type {
|
|
7
7
|
ActionPlayback,
|
|
8
|
+
AvatarStatus,
|
|
8
9
|
BotMessageHistoryEntry,
|
|
9
10
|
BotMessagePayload,
|
|
10
11
|
BotMessageReceivedPayload,
|
|
@@ -189,7 +190,15 @@ export class KichiForwarderService {
|
|
|
189
190
|
});
|
|
190
191
|
}
|
|
191
192
|
|
|
192
|
-
sendStatus(
|
|
193
|
+
sendStatus(
|
|
194
|
+
poseType: PoseType | "",
|
|
195
|
+
action: string,
|
|
196
|
+
bubble: string,
|
|
197
|
+
log: string,
|
|
198
|
+
playback: ActionPlayback,
|
|
199
|
+
avatarStatus: AvatarStatus,
|
|
200
|
+
propId?: string,
|
|
201
|
+
): void {
|
|
193
202
|
if (!this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN) return;
|
|
194
203
|
const payload: StatusPayload = {
|
|
195
204
|
type: "status",
|
|
@@ -200,6 +209,7 @@ export class KichiForwarderService {
|
|
|
200
209
|
bubble,
|
|
201
210
|
log,
|
|
202
211
|
playback,
|
|
212
|
+
avatarStatus,
|
|
203
213
|
...(propId ? { propId } : {}),
|
|
204
214
|
};
|
|
205
215
|
this.ws.send(JSON.stringify(payload));
|
|
@@ -211,6 +221,7 @@ export class KichiForwarderService {
|
|
|
211
221
|
bubble: string,
|
|
212
222
|
log: string,
|
|
213
223
|
playback: ActionPlayback,
|
|
224
|
+
avatarStatus: AvatarStatus,
|
|
214
225
|
propId?: string,
|
|
215
226
|
): Promise<StatusAckPayload> {
|
|
216
227
|
if (!this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN) {
|
|
@@ -226,6 +237,7 @@ export class KichiForwarderService {
|
|
|
226
237
|
bubble,
|
|
227
238
|
log,
|
|
228
239
|
playback,
|
|
240
|
+
avatarStatus,
|
|
229
241
|
...(propId ? { propId } : {}),
|
|
230
242
|
};
|
|
231
243
|
return this.sendRequest<StatusAckPayload>(payload, "status_ack", 5000);
|
package/src/types.ts
CHANGED
|
@@ -2,6 +2,7 @@ export type KichiForwarderConfig = Record<string, never>;
|
|
|
2
2
|
|
|
3
3
|
export type PoseType = "stand" | "sit" | "lay" | "floor";
|
|
4
4
|
export type ActionPlaybackMode = "loop" | "once";
|
|
5
|
+
export type AvatarStatus = "Idle" | "Busy" | "Activities" | "Break";
|
|
5
6
|
export type ActionPlayback = {
|
|
6
7
|
mode: ActionPlaybackMode;
|
|
7
8
|
resumeAction?: string;
|
|
@@ -16,6 +17,7 @@ export type ActionResult = {
|
|
|
16
17
|
poseType: PoseType;
|
|
17
18
|
action: string;
|
|
18
19
|
bubble: string;
|
|
20
|
+
avatarStatus: AvatarStatus;
|
|
19
21
|
log?: string;
|
|
20
22
|
propId?: string;
|
|
21
23
|
};
|
|
@@ -120,6 +122,7 @@ export type StatusPayload = {
|
|
|
120
122
|
bubble: string;
|
|
121
123
|
log: string;
|
|
122
124
|
playback: ActionPlayback;
|
|
125
|
+
avatarStatus: AvatarStatus;
|
|
123
126
|
propId?: string;
|
|
124
127
|
};
|
|
125
128
|
|
|
@@ -173,6 +176,7 @@ export type IdlePlanStage = {
|
|
|
173
176
|
name: string;
|
|
174
177
|
purpose: string;
|
|
175
178
|
pomodoroPhase: IdlePlanPhase;
|
|
179
|
+
avatarStatus: AvatarStatus;
|
|
176
180
|
durationSeconds: number;
|
|
177
181
|
actions: IdlePlanStageAction[];
|
|
178
182
|
};
|