@yahaha-studio/kichi-forwarder 0.1.0-beta.6 → 0.1.0-beta.8
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/README.md +1 -0
- package/config/kichi-config.json +11 -11
- package/index.ts +312 -5
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/skills/kichi-forwarder/SKILL.md +20 -0
- package/skills/kichi-forwarder/references/error.md +9 -2
- package/skills/kichi-forwarder/references/heartbeat.md +34 -22
- package/skills/kichi-forwarder/references/install.md +26 -7
- package/src/service.ts +14 -0
- package/src/types.ts +33 -12
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ It can directly control your companion's avatar in Kichi, show what it is doing,
|
|
|
13
13
|
- Bring your OpenClaw companion into Kichi
|
|
14
14
|
- Directly control the avatar's poses and actions in Kichi
|
|
15
15
|
- Keep its visible state in sync while it works
|
|
16
|
+
- Plan human-like idle routines during heartbeat windows
|
|
16
17
|
- Let it leave notes for you in Kichi
|
|
17
18
|
- Let it recommend music in Kichi
|
|
18
19
|
|
package/config/kichi-config.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
{
|
|
5
5
|
"name": "High Five",
|
|
6
6
|
"playback": "once",
|
|
7
|
-
"resumeAction": "
|
|
7
|
+
"resumeAction": "Idle Backup Hands"
|
|
8
8
|
},
|
|
9
9
|
{
|
|
10
10
|
"name": "Listen Music",
|
|
@@ -13,17 +13,17 @@
|
|
|
13
13
|
{
|
|
14
14
|
"name": "Arm Stretch",
|
|
15
15
|
"playback": "once",
|
|
16
|
-
"resumeAction": "
|
|
16
|
+
"resumeAction": "Idle Backup Hands"
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
"name": "Backbend Stretch",
|
|
20
20
|
"playback": "once",
|
|
21
|
-
"resumeAction": "
|
|
21
|
+
"resumeAction": "Idle Backup Hands"
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
"name": "Making Selfie",
|
|
25
25
|
"playback": "once",
|
|
26
|
-
"resumeAction": "
|
|
26
|
+
"resumeAction": "Idle Backup Hands"
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
29
|
"name": "Arms Crossed",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
{
|
|
33
33
|
"name": "Epiphany",
|
|
34
34
|
"playback": "once",
|
|
35
|
-
"resumeAction": "
|
|
35
|
+
"resumeAction": "Idle Backup Hands"
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
"name": "Angry",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
{
|
|
42
42
|
"name": "Yay",
|
|
43
43
|
"playback": "once",
|
|
44
|
-
"resumeAction": "
|
|
44
|
+
"resumeAction": "Idle Backup Hands"
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
"name": "Dance",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
{
|
|
71
71
|
"name": "Curtsy",
|
|
72
72
|
"playback": "once",
|
|
73
|
-
"resumeAction": "
|
|
73
|
+
"resumeAction": "Idle Backup Hands"
|
|
74
74
|
},
|
|
75
75
|
{
|
|
76
76
|
"name": "Stand Writing",
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
{
|
|
116
116
|
"name": "No",
|
|
117
117
|
"playback": "once",
|
|
118
|
-
"resumeAction": "
|
|
118
|
+
"resumeAction": "Idle Backup Hands"
|
|
119
119
|
},
|
|
120
120
|
{
|
|
121
121
|
"name": "Panic",
|
|
@@ -124,7 +124,7 @@
|
|
|
124
124
|
{
|
|
125
125
|
"name": "Playful Point Up",
|
|
126
126
|
"playback": "once",
|
|
127
|
-
"resumeAction": "
|
|
127
|
+
"resumeAction": "Idle Backup Hands"
|
|
128
128
|
},
|
|
129
129
|
{
|
|
130
130
|
"name": "Rub Hands",
|
|
@@ -133,12 +133,12 @@
|
|
|
133
133
|
{
|
|
134
134
|
"name": "Run Jump",
|
|
135
135
|
"playback": "once",
|
|
136
|
-
"resumeAction": "
|
|
136
|
+
"resumeAction": "Idle Backup Hands"
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
139
|
"name": "Star Showing",
|
|
140
140
|
"playback": "once",
|
|
141
|
-
"resumeAction": "
|
|
141
|
+
"resumeAction": "Idle Backup Hands"
|
|
142
142
|
},
|
|
143
143
|
{
|
|
144
144
|
"name": "Walk",
|
package/index.ts
CHANGED
|
@@ -52,10 +52,33 @@ const MAX_NOTEBOARD_TEXT_LENGTH = 200;
|
|
|
52
52
|
const MAX_MESSAGE_RECEIVED_PREVIEW_WIDTH = 20;
|
|
53
53
|
const MAX_AGENT_END_PREVIEW_WIDTH = 10;
|
|
54
54
|
const MESSAGE_RECEIVED_ELLIPSIS = "...";
|
|
55
|
+
const IDLE_PLAN_POMODORO_PHASES = ["focus", "shortBreak", "longBreak", "none"] as const;
|
|
55
56
|
let cachedStaticConfig: KichiStaticConfig | null = null;
|
|
56
57
|
let cachedStaticConfigMtime = 0;
|
|
57
58
|
let service: KichiForwarderService | null = null;
|
|
58
59
|
let pluginApi: OpenClawPluginApi | null = null;
|
|
60
|
+
|
|
61
|
+
type IdlePlanPomodoroPhase = typeof IDLE_PLAN_POMODORO_PHASES[number];
|
|
62
|
+
type IdlePlanAction = {
|
|
63
|
+
poseType: PoseType;
|
|
64
|
+
action: string;
|
|
65
|
+
durationSeconds: number;
|
|
66
|
+
bubble: string;
|
|
67
|
+
log?: string;
|
|
68
|
+
};
|
|
69
|
+
type IdlePlan = {
|
|
70
|
+
requestId?: string;
|
|
71
|
+
heartbeatIntervalSeconds: number;
|
|
72
|
+
goal: string;
|
|
73
|
+
totalDurationSeconds: number;
|
|
74
|
+
stages: Array<{
|
|
75
|
+
name: string;
|
|
76
|
+
purpose: string;
|
|
77
|
+
pomodoroPhase: IdlePlanPomodoroPhase;
|
|
78
|
+
durationSeconds: number;
|
|
79
|
+
actions: IdlePlanAction[];
|
|
80
|
+
}>;
|
|
81
|
+
};
|
|
59
82
|
|
|
60
83
|
function isAlbumConfig(value: unknown): value is Album {
|
|
61
84
|
if (!value || typeof value !== "object") {
|
|
@@ -432,9 +455,166 @@ function isClockAction(value: unknown): value is ClockAction {
|
|
|
432
455
|
return ["set", "stop"].includes(String(value));
|
|
433
456
|
}
|
|
434
457
|
|
|
458
|
+
function isIdlePlanPomodoroPhase(value: unknown): value is IdlePlanPomodoroPhase {
|
|
459
|
+
return IDLE_PLAN_POMODORO_PHASES.includes(String(value) as IdlePlanPomodoroPhase);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function normalizeIdlePlan(value: unknown): { idlePlan?: IdlePlan; error?: string } {
|
|
463
|
+
if (!isPlainObject(value)) {
|
|
464
|
+
return { error: "idle plan payload must be an object" };
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const requestId = value.requestId;
|
|
468
|
+
const heartbeatIntervalSeconds = value.heartbeatIntervalSeconds;
|
|
469
|
+
const goal = value.goal;
|
|
470
|
+
const stages = value.stages;
|
|
471
|
+
|
|
472
|
+
if (requestId !== undefined && typeof requestId !== "string") {
|
|
473
|
+
return { error: "requestId must be a string when provided" };
|
|
474
|
+
}
|
|
475
|
+
if (!isPositiveInteger(heartbeatIntervalSeconds)) {
|
|
476
|
+
return { error: "heartbeatIntervalSeconds must be a positive integer" };
|
|
477
|
+
}
|
|
478
|
+
if (typeof goal !== "string" || !goal.trim()) {
|
|
479
|
+
return { error: "goal is required" };
|
|
480
|
+
}
|
|
481
|
+
if (!Array.isArray(stages) || stages.length === 0) {
|
|
482
|
+
return { error: "stages must contain at least one stage" };
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const normalizedStages: IdlePlan["stages"] = [];
|
|
486
|
+
let totalDurationSeconds = 0;
|
|
487
|
+
|
|
488
|
+
for (let stageIndex = 0; stageIndex < stages.length; stageIndex += 1) {
|
|
489
|
+
const rawStage = stages[stageIndex];
|
|
490
|
+
if (!isPlainObject(rawStage)) {
|
|
491
|
+
return { error: `stages[${stageIndex}] must be an object` };
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const name = rawStage.name;
|
|
495
|
+
const purpose = rawStage.purpose;
|
|
496
|
+
const pomodoroPhase = rawStage.pomodoroPhase;
|
|
497
|
+
const durationSeconds = rawStage.durationSeconds;
|
|
498
|
+
const actions = rawStage.actions;
|
|
499
|
+
|
|
500
|
+
if (typeof name !== "string" || !name.trim()) {
|
|
501
|
+
return { error: `stages[${stageIndex}].name is required` };
|
|
502
|
+
}
|
|
503
|
+
if (typeof purpose !== "string" || !purpose.trim()) {
|
|
504
|
+
return { error: `stages[${stageIndex}].purpose is required` };
|
|
505
|
+
}
|
|
506
|
+
if (!isIdlePlanPomodoroPhase(pomodoroPhase)) {
|
|
507
|
+
return {
|
|
508
|
+
error: `stages[${stageIndex}].pomodoroPhase must be one of: ${IDLE_PLAN_POMODORO_PHASES.join(", ")}`,
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
if (!isPositiveInteger(durationSeconds)) {
|
|
512
|
+
return { error: `stages[${stageIndex}].durationSeconds must be a positive integer` };
|
|
513
|
+
}
|
|
514
|
+
if (!Array.isArray(actions) || actions.length === 0) {
|
|
515
|
+
return { error: `stages[${stageIndex}].actions must contain at least one action` };
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const normalizedActions: IdlePlanAction[] = [];
|
|
519
|
+
let stageActionDurationSeconds = 0;
|
|
520
|
+
|
|
521
|
+
for (let actionIndex = 0; actionIndex < actions.length; actionIndex += 1) {
|
|
522
|
+
const rawAction = actions[actionIndex];
|
|
523
|
+
if (!isPlainObject(rawAction)) {
|
|
524
|
+
return { error: `stages[${stageIndex}].actions[${actionIndex}] must be an object` };
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const poseType = rawAction.poseType;
|
|
528
|
+
const action = rawAction.action;
|
|
529
|
+
const actionDurationSeconds = rawAction.durationSeconds;
|
|
530
|
+
const bubble = rawAction.bubble;
|
|
531
|
+
const log = rawAction.log;
|
|
532
|
+
|
|
533
|
+
if (!["stand", "sit", "lay", "floor"].includes(String(poseType))) {
|
|
534
|
+
return {
|
|
535
|
+
error: `stages[${stageIndex}].actions[${actionIndex}].poseType must be stand, sit, lay, or floor`,
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
if (typeof action !== "string" || !action.trim()) {
|
|
539
|
+
return { error: `stages[${stageIndex}].actions[${actionIndex}].action is required` };
|
|
540
|
+
}
|
|
541
|
+
if (!isPositiveInteger(actionDurationSeconds)) {
|
|
542
|
+
return {
|
|
543
|
+
error: `stages[${stageIndex}].actions[${actionIndex}].durationSeconds must be a positive integer`,
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
if (typeof bubble !== "string" || !bubble.trim()) {
|
|
547
|
+
return { error: `stages[${stageIndex}].actions[${actionIndex}].bubble is required` };
|
|
548
|
+
}
|
|
549
|
+
if (log !== undefined && typeof log !== "string") {
|
|
550
|
+
return { error: `stages[${stageIndex}].actions[${actionIndex}].log must be a string when provided` };
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const normalizedPoseType = poseType as PoseType;
|
|
554
|
+
let actionDefinition: ActionDefinition;
|
|
555
|
+
try {
|
|
556
|
+
actionDefinition = getActionDefinition(normalizedPoseType, action.trim());
|
|
557
|
+
} catch (error) {
|
|
558
|
+
return {
|
|
559
|
+
error: error instanceof Error
|
|
560
|
+
? error.message
|
|
561
|
+
: `Invalid action in stages[${stageIndex}].actions[${actionIndex}]`,
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const playback = getActionPlayback(actionDefinition);
|
|
566
|
+
if (playback.mode === "once" && actionDurationSeconds > 30) {
|
|
567
|
+
return {
|
|
568
|
+
error: `stages[${stageIndex}].actions[${actionIndex}] uses once action "${actionDefinition.name}" for ${actionDurationSeconds} seconds; once actions must stay at 30 seconds or less`,
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
stageActionDurationSeconds += actionDurationSeconds;
|
|
573
|
+
normalizedActions.push({
|
|
574
|
+
poseType: normalizedPoseType,
|
|
575
|
+
action: actionDefinition.name,
|
|
576
|
+
durationSeconds: actionDurationSeconds,
|
|
577
|
+
bubble: bubble.trim(),
|
|
578
|
+
...(typeof log === "string" && log.trim() ? { log: log.trim() } : {}),
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (stageActionDurationSeconds !== durationSeconds) {
|
|
583
|
+
return {
|
|
584
|
+
error: `stages[${stageIndex}] action durations must equal stage duration exactly (${stageActionDurationSeconds} !== ${durationSeconds})`,
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
totalDurationSeconds += durationSeconds;
|
|
589
|
+
normalizedStages.push({
|
|
590
|
+
name: name.trim(),
|
|
591
|
+
purpose: purpose.trim(),
|
|
592
|
+
pomodoroPhase,
|
|
593
|
+
durationSeconds,
|
|
594
|
+
actions: normalizedActions,
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (totalDurationSeconds !== heartbeatIntervalSeconds) {
|
|
599
|
+
return {
|
|
600
|
+
error: `idle plan total duration must equal heartbeatIntervalSeconds exactly (${totalDurationSeconds} !== ${heartbeatIntervalSeconds})`,
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
return {
|
|
605
|
+
idlePlan: {
|
|
606
|
+
...(typeof requestId === "string" && requestId.trim() ? { requestId: requestId.trim() } : {}),
|
|
607
|
+
heartbeatIntervalSeconds,
|
|
608
|
+
goal: goal.trim(),
|
|
609
|
+
totalDurationSeconds,
|
|
610
|
+
stages: normalizedStages,
|
|
611
|
+
},
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
|
|
435
615
|
|
|
436
616
|
function isPomodoroPhase(value: unknown): value is PomodoroPhase {
|
|
437
|
-
return ["
|
|
617
|
+
return ["focus", "shortBreak", "longBreak"].includes(String(value));
|
|
438
618
|
}
|
|
439
619
|
|
|
440
620
|
function getPomodoroPhaseDuration(
|
|
@@ -470,7 +650,7 @@ function normalizeClockConfig(value: unknown): { clock?: ClockConfig; error?: st
|
|
|
470
650
|
const longBreakSeconds = value.longBreakSeconds;
|
|
471
651
|
const sessionCount = value.sessionCount;
|
|
472
652
|
const currentSession = value.currentSession ?? 1;
|
|
473
|
-
const phase = value.phase ?? "
|
|
653
|
+
const phase = value.phase ?? "focus";
|
|
474
654
|
|
|
475
655
|
if (!isPositiveInteger(kichiSeconds)) {
|
|
476
656
|
return { error: "clock.kichiSeconds must be a positive integer" };
|
|
@@ -491,7 +671,7 @@ function normalizeClockConfig(value: unknown): { clock?: ClockConfig; error?: st
|
|
|
491
671
|
return { error: "clock.currentSession cannot be greater than clock.sessionCount" };
|
|
492
672
|
}
|
|
493
673
|
if (!isPomodoroPhase(phase)) {
|
|
494
|
-
return { error: "clock.phase must be
|
|
674
|
+
return { error: "clock.phase must be focus, shortBreak, or longBreak" };
|
|
495
675
|
}
|
|
496
676
|
|
|
497
677
|
const defaultRemainingSeconds = getPomodoroPhaseDuration(
|
|
@@ -654,6 +834,28 @@ function buildKichiActionDescription(): string {
|
|
|
654
834
|
].join("\n");
|
|
655
835
|
}
|
|
656
836
|
|
|
837
|
+
function buildKichiIdlePlanDescription(): string {
|
|
838
|
+
const actions = loadStaticConfig().actions;
|
|
839
|
+
return [
|
|
840
|
+
"Send a complete heartbeat idle plan for the avatar.",
|
|
841
|
+
"The payload must include the overall goal, heartbeat interval, stage breakdown, each stage's purpose, each stage's pomodoroPhase, action list, and bubble content.",
|
|
842
|
+
"Shape the goal and stage purposes around one concrete leisure activity you would genuinely choose to do on your own when nobody needs you, in a way that fits your personality, tastes, and established character.",
|
|
843
|
+
"Keep the whole plan centered on that leisure activity, rooted in your personal interests or hobbies.",
|
|
844
|
+
"Do not use a vague atmosphere, a generic productivity task, or a catch-all routine summary as the goal.",
|
|
845
|
+
"Each stage purpose must explain what you are actually doing in that stage, not just how you want to feel.",
|
|
846
|
+
"Make every stage support the same leisure activity instead of switching to unrelated tasks just to use more actions.",
|
|
847
|
+
"Choose a leisure activity that the available Kichi actions can express clearly, instead of starting from abstract mood text and forcing actions to fit afterward.",
|
|
848
|
+
"Each action bubble must describe the current presented state, not a next step, plan, or instruction.",
|
|
849
|
+
"Use the same language as the current conversation for goal, purpose, bubble, and log.",
|
|
850
|
+
"Assign each stage pomodoroPhase from the stage's actual role: focus for concentrated activity, shortBreak for short resets, longBreak for longer rests.",
|
|
851
|
+
"Do not default the whole idle plan to none. Use none only for a stage that truly has no pomodoro role.",
|
|
852
|
+
`stand actions: ${actions.stand.map((entry) => entry.name).join(", ")}`,
|
|
853
|
+
`sit actions: ${actions.sit.map((entry) => entry.name).join(", ")}`,
|
|
854
|
+
`lay actions: ${actions.lay.map((entry) => entry.name).join(", ")}`,
|
|
855
|
+
`floor actions: ${actions.floor.map((entry) => entry.name).join(", ")}`,
|
|
856
|
+
].join("\n");
|
|
857
|
+
}
|
|
858
|
+
|
|
657
859
|
function buildKichiPrompt(): string {
|
|
658
860
|
return [
|
|
659
861
|
"Kichi avatar control and status sync are available via `kichi_action` and `kichi_clock`.",
|
|
@@ -911,6 +1113,111 @@ const plugin = {
|
|
|
911
1113
|
};
|
|
912
1114
|
},
|
|
913
1115
|
});
|
|
1116
|
+
api.registerTool({
|
|
1117
|
+
name: "kichi_idle_plan",
|
|
1118
|
+
description: buildKichiIdlePlanDescription(),
|
|
1119
|
+
parameters: {
|
|
1120
|
+
type: "object",
|
|
1121
|
+
properties: {
|
|
1122
|
+
requestId: {
|
|
1123
|
+
type: "string",
|
|
1124
|
+
description: "Optional request ID for tracing or deduplication.",
|
|
1125
|
+
},
|
|
1126
|
+
heartbeatIntervalSeconds: {
|
|
1127
|
+
type: "number",
|
|
1128
|
+
description: "Required heartbeat interval in seconds. The plan must total exactly to this value.",
|
|
1129
|
+
},
|
|
1130
|
+
goal: {
|
|
1131
|
+
type: "string",
|
|
1132
|
+
description: "Overall goal for the full interval. Set it as one concrete leisure activity you would genuinely choose to do on your own, rooted in your personal interests or hobbies. Do not use a vague atmosphere, a generic productivity task, or a catch-all routine summary. Use the same language as the current conversation.",
|
|
1133
|
+
},
|
|
1134
|
+
stages: {
|
|
1135
|
+
type: "array",
|
|
1136
|
+
description: "Ordered plan stages covering the full heartbeat interval.",
|
|
1137
|
+
items: {
|
|
1138
|
+
type: "object",
|
|
1139
|
+
properties: {
|
|
1140
|
+
name: {
|
|
1141
|
+
type: "string",
|
|
1142
|
+
description: "Stage name.",
|
|
1143
|
+
},
|
|
1144
|
+
purpose: {
|
|
1145
|
+
type: "string",
|
|
1146
|
+
description: "Explain what you are actually doing in this stage. Keep it supporting the same leisure activity instead of switching to unrelated tasks. Do not use pure mood-regulation or atmosphere text. Use the same language as the current conversation.",
|
|
1147
|
+
},
|
|
1148
|
+
pomodoroPhase: {
|
|
1149
|
+
type: "string",
|
|
1150
|
+
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.",
|
|
1151
|
+
enum: [...IDLE_PLAN_POMODORO_PHASES],
|
|
1152
|
+
},
|
|
1153
|
+
durationSeconds: {
|
|
1154
|
+
type: "number",
|
|
1155
|
+
description: "Required duration in seconds for this stage.",
|
|
1156
|
+
},
|
|
1157
|
+
actions: {
|
|
1158
|
+
type: "array",
|
|
1159
|
+
description: "Action list for this stage.",
|
|
1160
|
+
items: {
|
|
1161
|
+
type: "object",
|
|
1162
|
+
properties: {
|
|
1163
|
+
poseType: {
|
|
1164
|
+
type: "string",
|
|
1165
|
+
description: "Pose type for this action: stand, sit, lay, or floor.",
|
|
1166
|
+
},
|
|
1167
|
+
action: {
|
|
1168
|
+
type: "string",
|
|
1169
|
+
description: "Action name for the selected pose. Must match the bundled Kichi action list.",
|
|
1170
|
+
},
|
|
1171
|
+
durationSeconds: {
|
|
1172
|
+
type: "number",
|
|
1173
|
+
description: "Required duration in seconds for this action.",
|
|
1174
|
+
},
|
|
1175
|
+
bubble: {
|
|
1176
|
+
type: "string",
|
|
1177
|
+
description: "State-style bubble content for this action. Describe the current presented state you are in, not a next step, plan, or instruction. Use the same language as the current conversation.",
|
|
1178
|
+
},
|
|
1179
|
+
log: {
|
|
1180
|
+
type: "string",
|
|
1181
|
+
description: "Optional log content for this action. Use the same language as the current conversation.",
|
|
1182
|
+
},
|
|
1183
|
+
},
|
|
1184
|
+
required: ["poseType", "action", "durationSeconds", "bubble"],
|
|
1185
|
+
},
|
|
1186
|
+
},
|
|
1187
|
+
},
|
|
1188
|
+
required: ["name", "purpose", "pomodoroPhase", "durationSeconds", "actions"],
|
|
1189
|
+
},
|
|
1190
|
+
},
|
|
1191
|
+
},
|
|
1192
|
+
required: ["heartbeatIntervalSeconds", "goal", "stages"],
|
|
1193
|
+
},
|
|
1194
|
+
execute: async (_toolCallId, params) => {
|
|
1195
|
+
const { idlePlan, error } = normalizeIdlePlan(params);
|
|
1196
|
+
if (!idlePlan) {
|
|
1197
|
+
return { success: false, error: error ?? "Invalid idle plan payload" };
|
|
1198
|
+
}
|
|
1199
|
+
if (!service?.hasValidIdentity() || !service?.isConnected()) {
|
|
1200
|
+
return { success: false, error: "Not connected to Kichi world" };
|
|
1201
|
+
}
|
|
1202
|
+
const sent = service.sendIdlePlan({
|
|
1203
|
+
...(idlePlan.requestId ? { requestId: idlePlan.requestId } : {}),
|
|
1204
|
+
heartbeatIntervalSeconds: idlePlan.heartbeatIntervalSeconds,
|
|
1205
|
+
goal: idlePlan.goal,
|
|
1206
|
+
stages: idlePlan.stages,
|
|
1207
|
+
});
|
|
1208
|
+
if (!sent) {
|
|
1209
|
+
return { success: false, error: "Failed to send idle plan payload" };
|
|
1210
|
+
}
|
|
1211
|
+
return {
|
|
1212
|
+
success: true,
|
|
1213
|
+
...(idlePlan.requestId ? { requestId: idlePlan.requestId } : {}),
|
|
1214
|
+
heartbeatIntervalSeconds: idlePlan.heartbeatIntervalSeconds,
|
|
1215
|
+
totalDurationSeconds: idlePlan.totalDurationSeconds,
|
|
1216
|
+
goal: idlePlan.goal,
|
|
1217
|
+
stages: idlePlan.stages,
|
|
1218
|
+
};
|
|
1219
|
+
},
|
|
1220
|
+
});
|
|
914
1221
|
api.registerTool({
|
|
915
1222
|
name: "kichi_clock",
|
|
916
1223
|
description:
|
|
@@ -960,7 +1267,7 @@ const plugin = {
|
|
|
960
1267
|
},
|
|
961
1268
|
phase: {
|
|
962
1269
|
type: "string",
|
|
963
|
-
description: "Pomodoro phase:
|
|
1270
|
+
description: "Pomodoro phase: focus, shortBreak, or longBreak",
|
|
964
1271
|
},
|
|
965
1272
|
durationSeconds: {
|
|
966
1273
|
type: "number",
|
|
@@ -1026,7 +1333,7 @@ const plugin = {
|
|
|
1026
1333
|
api.registerTool({
|
|
1027
1334
|
name: "kichi_query_status",
|
|
1028
1335
|
description:
|
|
1029
|
-
"Query Kichi avatar status (notes, ownerState,
|
|
1336
|
+
"Query Kichi avatar status (notes, ownerState, idlePlan, weather/time, timer snapshot, daily note quota, and `hasCreatedMusicAlbumToday`). Use this before creating a new note or daily recommended music album. For heartbeat planning, use the returned idlePlan as reference when shaping the next idle plan.",
|
|
1030
1337
|
parameters: {
|
|
1031
1338
|
type: "object",
|
|
1032
1339
|
properties: {
|
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.0-beta.
|
|
5
|
+
"version": "0.1.0-beta.8",
|
|
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.
|
|
3
|
+
"version": "0.1.0-beta.8",
|
|
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",
|
|
@@ -48,6 +48,7 @@ For install/onboarding/connect requests:
|
|
|
48
48
|
|
|
49
49
|
- Read and follow `install.md` before any tool call.
|
|
50
50
|
- Before calling `kichi_status` or `kichi_join`, you MUST update workspace `HEARTBEAT.md` by following `heartbeat.md`.
|
|
51
|
+
- If plugin installation fails with `429`, treat it as a ClawHub routing failure and switch immediately to the non-ClawHub archive install path from `install.md`/`error.md`. Do not wait and do not retry the same bare package command.
|
|
51
52
|
|
|
52
53
|
## LLM Runtime
|
|
53
54
|
|
|
@@ -129,6 +130,25 @@ Use this for direct Kichi avatar control as well as lifecycle sync.
|
|
|
129
130
|
- For most work, prefer a sit pose and switch actions inside the same task as the work moves between stages.
|
|
130
131
|
- The current action lists are injected into prompt context before the model chooses `kichi_action`.
|
|
131
132
|
|
|
133
|
+
### kichi_idle_plan
|
|
134
|
+
|
|
135
|
+
Use this for the avatar's heartbeat idle plan.
|
|
136
|
+
|
|
137
|
+
- Set `heartbeatIntervalSeconds` to the heartbeat interval for this run.
|
|
138
|
+
- Use the previous `idlePlan` only as optional reference.
|
|
139
|
+
- Include the overall `goal`, stage breakdown, each stage's `purpose`, stage `pomodoroPhase`, action list, and bubble content.
|
|
140
|
+
- Shape `goal` and stage `purpose` around one concrete leisure activity you would genuinely choose to do on your own when nobody needs you, in a way that fits your personality, tastes, and established character.
|
|
141
|
+
- Keep the whole plan centered on that leisure activity, rooted in your personal interests or hobbies.
|
|
142
|
+
- Do not use a vague atmosphere, weather feeling, generic productivity task, or catch-all routine summary as `goal`.
|
|
143
|
+
- Make each stage `purpose` explain what you are doing in that stage. Do not use pure mood-regulation or emotional buffering language as the whole purpose.
|
|
144
|
+
- Make each stage support the same leisure activity instead of switching to unrelated tasks just to use more actions.
|
|
145
|
+
- Choose what you would do now.
|
|
146
|
+
- Use the same language as the current conversation for `goal`, stage `purpose`, action `bubble`, and action `log`.
|
|
147
|
+
- Choose a leisure activity that the available Kichi action list can express clearly. Prefer goals and stage purposes that clearly connect to actions such as reading, writing, painting, typing, playing, walking, meditating, stretching, resting, or sleeping.
|
|
148
|
+
- Make each action `bubble` a current-state label describing the current presented state, not a procedural step, mini-plan, or instruction.
|
|
149
|
+
- 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.
|
|
150
|
+
- The full stage duration must total exactly to the heartbeat interval.
|
|
151
|
+
|
|
132
152
|
### kichi_music_album_create
|
|
133
153
|
|
|
134
154
|
```text
|
|
@@ -5,8 +5,15 @@ If `openclaw plugins install @yahaha-studio/kichi-forwarder` fails, read the err
|
|
|
5
5
|
- Permission or access error (`EACCES`): re-run the command with appropriate user-level permissions for the current OS.
|
|
6
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`):
|
|
8
|
+
- ClawHub rate limit (`429`): do not retry the same bare package command. `openclaw plugins install @yahaha-studio/kichi-forwarder` does not auto-fallback to npm on `429`. Bypass ClawHub immediately:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm pack @yahaha-studio/kichi-forwarder
|
|
12
|
+
openclaw plugins install ./yahaha-studio-kichi-forwarder-<version>.tgz
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Use the exact `.tgz` filename printed by `npm pack`.
|
|
9
16
|
- File conflict (`EEXIST`): re-run the install command with `--force`.
|
|
10
17
|
- Network or registry error: check internet connectivity and retry.
|
|
11
18
|
|
|
12
|
-
After applying the fix,
|
|
19
|
+
After applying the fix, continue with the successful install path you used. For `429`, that means the local `.tgz` archive install, not the original bare package command.
|
|
@@ -44,11 +44,17 @@ If user wants recurring note board checks:
|
|
|
44
44
|
In both tiers, skip if it would clearly repeat your very recent own note.
|
|
45
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
|
-
- `
|
|
48
|
-
- `
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
- `Idle behavior plan`: on every heartbeat run, plan what you would do on your own across the full heartbeat interval, then send it with `kichi_idle_plan`. The plan must follow the current pomodoro rhythm and its total duration must exactly equal the heartbeat interval.
|
|
48
|
+
- `Idle plan reference rule`: use the previous `idlePlan` only as optional reference.
|
|
49
|
+
- `Idle plan content`: include the overall goal, stage breakdown, each stage's purpose, each stage's `pomodoroPhase`, stage action list, and bubble content.
|
|
50
|
+
- `Idle plan expression rule`: shape the overall goal and each stage purpose around one concrete leisure activity you would genuinely choose to do on your own when nobody needs you, in a way that fits your personality, tastes, and established character.
|
|
51
|
+
- `Idle plan goal rule`: keep the whole plan centered on that leisure activity, rooted in your personal interests or hobbies. Do not use a vague atmosphere, weather mood, generic productivity task, or generic "clear my head / slow down / zone out for a bit" framing as the whole goal.
|
|
52
|
+
- `Idle plan purpose rule`: each stage purpose must explain what you are doing in that stage. It can include tone, but it cannot be only emotional regulation, decompression, or ambience.
|
|
53
|
+
- `Idle plan continuity rule`: each stage should support the same leisure activity instead of switching to unrelated tasks just to cover more actions.
|
|
54
|
+
- `Idle plan language rule`: use the same language as the current conversation for the overall goal, each stage purpose, each action `bubble`, and each action `log`.
|
|
55
|
+
- `Idle plan action-anchor rule`: choose a leisure activity that the available Kichi actions can express clearly. Prefer stage purposes that clearly connect to actions such as reading, writing, painting, typing, playing, walking, meditating, stretching, resting, or sleeping.
|
|
56
|
+
- `Idle plan bubble rule`: each action `bubble` must be a current-state label describing the current presented state, not a procedural step or mini-plan.
|
|
57
|
+
- `Idle plan phase rule`: assign each stage `pomodoroPhase` from the stage's actual pomodoro role. Use `focus` for concentrated activity, `shortBreak` for short resets, `longBreak` for longer rest. Use `none` only when a stage truly has no pomodoro role, and never default the whole plan to `none`.
|
|
52
58
|
|
|
53
59
|
## Note Triage Order
|
|
54
60
|
|
|
@@ -77,28 +83,33 @@ Use this exact flow:
|
|
|
77
83
|
|
|
78
84
|
1. Call `kichi_query_status`.
|
|
79
85
|
2. If query fails, report error and stop.
|
|
80
|
-
3. If `isAvatarInScene` is `false`, the player is offline. Do **not** call any further tools (`kichi_noteboard_create`, `
|
|
86
|
+
3. If `isAvatarInScene` is `false`, the player is offline. Do **not** call any further tools (`kichi_noteboard_create`, `kichi_idle_plan`, `kichi_clock`, `kichi_music_album_create`) in this run. Reply `HEARTBEAT_OK` and stop.
|
|
81
87
|
4. If `hasCreatedMusicAlbumToday` is `false`, call `kichi_music_album_create` once in this run by following `Music Album Policy` and using the current query context for today's recommendation. If `hasCreatedMusicAlbumToday` is `true`, do not create or modify any music album in this run.
|
|
82
88
|
5. If `remaining == 0`, create no notes. Reply `HEARTBEAT_OK` unless user asked for forced attempt.
|
|
83
89
|
6. From recent notes, pick at most one highest-priority reply target.
|
|
84
90
|
7. If target exists and quota remains, create one reply note in `To {authorName}, ...` format.
|
|
85
91
|
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
92
|
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.
|
|
88
|
-
11.
|
|
89
|
-
12.
|
|
90
|
-
13.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
10. Plan the avatar's full heartbeat-interval idle routine.
|
|
94
|
+
11. Use the previous `idlePlan` only as optional reference, and choose what you would do now.
|
|
95
|
+
12. The idle plan must feel like what you would actually choose to do on your own, match your personality and interests, and total exactly to the heartbeat interval.
|
|
96
|
+
13. Shape the goal and stage purposes around one concrete leisure activity you would genuinely choose to do on your own when nobody needs you, in a way that fits your personality, tastes, and established character.
|
|
97
|
+
14. Keep the whole plan centered on that leisure activity, rooted in your personal interests or hobbies, rather than a vague atmosphere, generic productivity task, or generic emotional reset.
|
|
98
|
+
15. Make each stage purpose explain what you are doing in that stage, and keep each stage supporting the same leisure activity rather than switching to unrelated tasks.
|
|
99
|
+
16. Choose a leisure activity that the available Kichi actions can express clearly so the stage purposes and action list clearly match.
|
|
100
|
+
17. Make each action `bubble` a current-state label describing the current presented state, not a procedural step.
|
|
101
|
+
18. Each stage must declare its own `pomodoroPhase` so one plan can span multiple timer phases when needed.
|
|
102
|
+
19. Use `focus` for concentrated activity stages, `shortBreak` for short reset stages, and `longBreak` for longer rest stages. Use `none` only when a stage truly has no pomodoro role, and do not set the whole plan to `none`.
|
|
103
|
+
20. Send that plan with `kichi_idle_plan`. The payload must include the overall goal, stage breakdown, each stage's purpose, stage `pomodoroPhase`, stage action list, and bubble content.
|
|
104
|
+
21. Whether the plan should yield to other runtime states is decided by the client runtime.
|
|
105
|
+
22. Reply `HEARTBEAT_OK` only when no note is created in this run.
|
|
96
106
|
|
|
97
107
|
## HEARTBEAT.md Snippet
|
|
98
108
|
|
|
99
109
|
```md
|
|
100
110
|
## Kichi Note Board
|
|
101
111
|
- Query with `kichi_query_status` first.
|
|
112
|
+
- Use the previous `idlePlan` only as optional reference, and choose what you would do now.
|
|
102
113
|
- If `isAvatarInScene` is `false` (player offline), skip all notes and actions for this run.
|
|
103
114
|
- 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.
|
|
104
115
|
- Prioritize owner notes, direct mentions, and direct questions.
|
|
@@ -110,12 +121,13 @@ Use this exact flow:
|
|
|
110
121
|
- Reply notes must start with `To {authorName},` using exact name from query result.
|
|
111
122
|
- Keep each note <= 200 chars.
|
|
112
123
|
- Respect `dailyLimit`, `remaining`.
|
|
113
|
-
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
-
|
|
118
|
-
-
|
|
119
|
-
-
|
|
124
|
+
- On every heartbeat run, plan what you would do on your own across the full heartbeat interval and send it with `kichi_idle_plan`. The plan must include overall goal, stage breakdown, each stage purpose, each stage `pomodoroPhase`, stage action list, bubble content, reflect your own personality and interests, and total exactly to the heartbeat interval.
|
|
125
|
+
- Shape the goal and stage purposes around one concrete leisure activity you would genuinely choose to do on your own when nobody needs you, in a way that fits your personality, tastes, and established character.
|
|
126
|
+
- Keep the whole plan centered on that leisure activity, rooted in your personal interests or hobbies, rather than a vague atmosphere, generic productivity task, or generic emotional reset.
|
|
127
|
+
- Make each stage purpose explain what you are doing in that stage, and keep each stage supporting the same leisure activity rather than switching to unrelated tasks.
|
|
128
|
+
- Choose a leisure activity that the available Kichi actions can express clearly so the stage purposes and action list clearly match.
|
|
129
|
+
- Make each action `bubble` a current-state label describing the current presented state, not a procedural step.
|
|
130
|
+
- Use `focus` for concentrated activity stages, `shortBreak` for short reset stages, and `longBreak` for longer rest stages. Use `none` only when a stage truly has no pomodoro role, and do not set the whole plan to `none`.
|
|
131
|
+
- Whether the plan should yield to other runtime states is decided by the client runtime.
|
|
120
132
|
- Reply `HEARTBEAT_OK` only when no note is created in this run.
|
|
121
133
|
```
|
|
@@ -35,6 +35,24 @@ Save `avatarId` to the host-specific `identity.json` before using `kichi_join`:
|
|
|
35
35
|
openclaw plugins install @yahaha-studio/kichi-forwarder
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
+
If this command fails with `429` or `ClawHub ... failed (429)`, do not retry the same command. Install from a local npm archive instead.
|
|
39
|
+
|
|
40
|
+
## 429 Bypass Install Path
|
|
41
|
+
|
|
42
|
+
1. Download the npm package archive:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm pack @yahaha-studio/kichi-forwarder
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
2. Install the generated local archive with the exact filename printed by `npm pack`:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
openclaw plugins install ./yahaha-studio-kichi-forwarder-<version>.tgz
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
You may also use the exact absolute or relative `.tgz` path that `npm pack` produced. The local archive path bypasses the preferred ClawHub lookup.
|
|
55
|
+
|
|
38
56
|
## Remote URL Install Command Path (Non-skippable)
|
|
39
57
|
|
|
40
58
|
If the user asks:
|
|
@@ -51,13 +69,14 @@ When the user asks with one of the commands above, execute in this fixed order:
|
|
|
51
69
|
2. Parse `avatarId` from user text (`AvatarId`/`avatarId`, case-insensitive).
|
|
52
70
|
3. Resolve the host and write `state.json`.
|
|
53
71
|
4. Run `openclaw plugins install @yahaha-studio/kichi-forwarder`.
|
|
54
|
-
5.
|
|
55
|
-
6.
|
|
56
|
-
7.
|
|
57
|
-
8.
|
|
58
|
-
9.
|
|
59
|
-
10. If
|
|
60
|
-
11.
|
|
72
|
+
5. If step 4 fails with `429`, do not retry the same bare package command. Run `npm pack @yahaha-studio/kichi-forwarder`, then install the generated `.tgz` with `openclaw plugins install <tgz-path>`.
|
|
73
|
+
6. Ensure the plugin is installed and enabled.
|
|
74
|
+
7. Update workspace `HEARTBEAT.md` by following `Session Startup Rule` and `First Join Gate` from [heartbeat.md](heartbeat.md).
|
|
75
|
+
8. If `HEARTBEAT.md` was not updated successfully, report setup as incomplete and stop immediately. Do not continue to `kichi_status` or `kichi_join`.
|
|
76
|
+
9. Call `kichi_status`.
|
|
77
|
+
10. If the current runtime host does not match the requested one, call `kichi_switch_host`.
|
|
78
|
+
11. If `authKey` is missing, call `kichi_join` with parsed `avatarId`, `botName`, `bio`, and `tags`.
|
|
79
|
+
12. Call `kichi_status` again and confirm connection and auth state.
|
|
61
80
|
|
|
62
81
|
## Required Post-install Integration
|
|
63
82
|
|
package/src/service.ts
CHANGED
|
@@ -13,6 +13,8 @@ import type {
|
|
|
13
13
|
CreateNotesBoardNotePayload,
|
|
14
14
|
HookNotifyPayload,
|
|
15
15
|
HookNotifyType,
|
|
16
|
+
IdlePlanContent,
|
|
17
|
+
IdlePlanPayload,
|
|
16
18
|
JoinAckPayload,
|
|
17
19
|
JoinPayload,
|
|
18
20
|
KichiConnectionStatus,
|
|
@@ -158,6 +160,18 @@ export class KichiForwarderService {
|
|
|
158
160
|
this.ws.send(JSON.stringify(payload));
|
|
159
161
|
}
|
|
160
162
|
|
|
163
|
+
sendIdlePlan(payload: IdlePlanContent): boolean {
|
|
164
|
+
if (!this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN) return false;
|
|
165
|
+
const outboundPayload: IdlePlanPayload = {
|
|
166
|
+
type: "kichi_idle_plan",
|
|
167
|
+
avatarId: this.identity.avatarId,
|
|
168
|
+
authKey: this.identity.authKey,
|
|
169
|
+
...payload,
|
|
170
|
+
};
|
|
171
|
+
this.ws.send(JSON.stringify(outboundPayload));
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
|
|
161
175
|
sendClock(action: ClockAction, clock?: ClockConfig, requestId?: string): boolean {
|
|
162
176
|
if (!this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN) return false;
|
|
163
177
|
if (action === "set" && !clock) return false;
|
package/src/types.ts
CHANGED
|
@@ -119,11 +119,42 @@ export type HookNotifyPayload = {
|
|
|
119
119
|
bubble: string;
|
|
120
120
|
};
|
|
121
121
|
|
|
122
|
+
export type IdlePlanPhase = "focus" | "shortBreak" | "longBreak" | "none";
|
|
123
|
+
|
|
124
|
+
export type IdlePlanStageAction = {
|
|
125
|
+
poseType: PoseType;
|
|
126
|
+
action: string;
|
|
127
|
+
durationSeconds: number;
|
|
128
|
+
bubble: string;
|
|
129
|
+
log?: string;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export type IdlePlanStage = {
|
|
133
|
+
name: string;
|
|
134
|
+
purpose: string;
|
|
135
|
+
pomodoroPhase: IdlePlanPhase;
|
|
136
|
+
durationSeconds: number;
|
|
137
|
+
actions: IdlePlanStageAction[];
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export type IdlePlanContent = {
|
|
141
|
+
requestId?: string;
|
|
142
|
+
heartbeatIntervalSeconds: number;
|
|
143
|
+
goal: string;
|
|
144
|
+
stages: IdlePlanStage[];
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export type IdlePlanPayload = IdlePlanContent & {
|
|
148
|
+
type: "kichi_idle_plan";
|
|
149
|
+
avatarId: string;
|
|
150
|
+
authKey: string;
|
|
151
|
+
};
|
|
152
|
+
|
|
122
153
|
export type ClockAction = "set" | "stop";
|
|
123
154
|
|
|
124
155
|
export type ClockMode = "pomodoro" | "countDown" | "countUp";
|
|
125
156
|
|
|
126
|
-
export type PomodoroPhase = "
|
|
157
|
+
export type PomodoroPhase = "focus" | "shortBreak" | "longBreak";
|
|
127
158
|
|
|
128
159
|
export type PomodoroClock = {
|
|
129
160
|
mode: "pomodoro";
|
|
@@ -188,7 +219,7 @@ export type QueryStatusResultPayload = {
|
|
|
188
219
|
notes: QueryStatusNote[];
|
|
189
220
|
ownerState?: QueryStatusOwnerState | null;
|
|
190
221
|
timer?: Record<string, unknown> | null;
|
|
191
|
-
|
|
222
|
+
idlePlan?: IdlePlanContent | null;
|
|
192
223
|
/** All other server fields (timer, environmentWeather, etc.) are passed through to the LLM as-is. */
|
|
193
224
|
[key: string]: unknown;
|
|
194
225
|
};
|
|
@@ -202,16 +233,6 @@ export type QueryStatusOwnerState = {
|
|
|
202
233
|
desktopSummary?: string;
|
|
203
234
|
};
|
|
204
235
|
|
|
205
|
-
export type QueryStatusIdleState = {
|
|
206
|
-
projectId?: string;
|
|
207
|
-
currentBeatId?: string;
|
|
208
|
-
currentPoseType?: string;
|
|
209
|
-
currentAction?: string;
|
|
210
|
-
focused?: boolean;
|
|
211
|
-
todayIntent?: string;
|
|
212
|
-
sampleThoughts?: string[];
|
|
213
|
-
};
|
|
214
|
-
|
|
215
236
|
export type QueryStatusNote = {
|
|
216
237
|
propId: string;
|
|
217
238
|
authorName: string;
|