@yahaha-studio/kichi-forwarder 0.0.1-alpha.26 → 0.0.1-alpha.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.ts CHANGED
@@ -8,8 +8,6 @@ import type {
8
8
  ActionResult,
9
9
  ClockAction,
10
10
  ClockConfig,
11
- CreateNotesBoardNote,
12
- CreateNotesBoardNoteResultPayload,
13
11
  KichiRuntimeConfig,
14
12
  KichiForwarderConfig,
15
13
  PomodoroPhase,
@@ -25,6 +23,34 @@ const DEFAULT_ACTIONS: KichiRuntimeConfig["actions"] = {
25
23
 
26
24
  const DEFAULT_RUNTIME_CONFIG: KichiRuntimeConfig = {
27
25
  actions: DEFAULT_ACTIONS,
26
+ llmRuntimeEnabled: true,
27
+ };
28
+ const FIXED_HOOK_STATUSES: Record<string, ActionResult> = {
29
+ messageReceived: {
30
+ poseType: "sit",
31
+ action: "Study Look At",
32
+ bubble: "Reading request",
33
+ },
34
+ beforePromptBuild: {
35
+ poseType: "sit",
36
+ action: "Thinking",
37
+ bubble: "Planning task",
38
+ },
39
+ beforeToolCall: {
40
+ poseType: "sit",
41
+ action: "Typing with Keyboard",
42
+ bubble: "Working step",
43
+ },
44
+ agentEndSuccess: {
45
+ poseType: "stand",
46
+ action: "Yay",
47
+ bubble: "Task complete",
48
+ },
49
+ agentEndFailure: {
50
+ poseType: "stand",
51
+ action: "Tired",
52
+ bubble: "Task failed",
53
+ },
28
54
  };
29
55
 
30
56
  const KICHI_WORLD_DIR = path.join(os.homedir(), ".openclaw", "kichi-world");
@@ -57,6 +83,7 @@ function normalizeRuntimeConfig(value: unknown): KichiRuntimeConfig {
57
83
  const raw = value && typeof value === "object" ? (value as Partial<KichiRuntimeConfig>) : {};
58
84
  const actions = raw.actions;
59
85
  return {
86
+ llmRuntimeEnabled: typeof raw.llmRuntimeEnabled === "boolean" ? raw.llmRuntimeEnabled : true,
60
87
  actions: {
61
88
  stand: sanitizeActions(actions?.stand, DEFAULT_ACTIONS.stand),
62
89
  sit: sanitizeActions(actions?.sit, DEFAULT_ACTIONS.sit),
@@ -180,10 +207,33 @@ function resolveStatusSourceId(ctx?: { agentId?: string; sessionKey?: string }):
180
207
  return undefined;
181
208
  }
182
209
 
210
+ function isLlmRuntimeEnabled(): boolean {
211
+ return loadRuntimeConfig().llmRuntimeEnabled;
212
+ }
213
+
214
+ function syncFixedStatus(status: ActionResult, log = ""): void {
215
+ if (!service?.hasValidIdentity() || !service?.isConnected()) {
216
+ return;
217
+ }
218
+ sendStatusAndRemember(status, log);
219
+ }
220
+
221
+ function buildToolExecutionLog(toolName: string, params: unknown, agentId?: string): string {
222
+ const paramsText = stringifyParamsForLog(params);
223
+ const prefix = typeof agentId === "string" && agentId.trim() ? `[${agentId.trim()}] ` : "";
224
+ return truncateLog(`${prefix}exec tool: ${toolName}, params: ${paramsText}`, 300);
225
+ }
226
+
183
227
  async function handleMessageReceivedHook(
184
- _event: any,
228
+ event: { content?: string },
185
229
  _ctx?: { agentId?: string; sessionKey?: string },
186
230
  ): Promise<void> {
231
+ if (!isLlmRuntimeEnabled()) {
232
+ const preview = typeof event?.content === "string" && event.content.trim()
233
+ ? truncateLog(`message received: ${event.content.trim()}`, 220)
234
+ : "message received";
235
+ syncFixedStatus(FIXED_HOOK_STATUSES.messageReceived, preview);
236
+ }
187
237
  return;
188
238
  }
189
239
 
@@ -192,18 +242,41 @@ function registerPluginHooks(api: OpenClawPluginApi): void {
192
242
  if (!service?.hasValidIdentity() || !service?.isConnected()) {
193
243
  return;
194
244
  }
245
+ if (!isLlmRuntimeEnabled()) {
246
+ syncFixedStatus(FIXED_HOOK_STATUSES.beforePromptBuild);
247
+ return;
248
+ }
195
249
  return {
196
250
  prependContext: buildKichiPrompt(),
197
251
  };
198
252
  });
199
253
 
200
254
  api.on("before_tool_call", (event, ctx) => {
255
+ if (!isLlmRuntimeEnabled()) {
256
+ syncFixedStatus(
257
+ FIXED_HOOK_STATUSES.beforeToolCall,
258
+ buildToolExecutionLog(event.toolName, event.params, ctx?.agentId),
259
+ );
260
+ return;
261
+ }
201
262
  forwardToolCallLog(event.toolName, event.params, ctx?.agentId);
202
263
  });
203
264
 
204
265
  api.on("message_received", async (event, ctx) => {
205
266
  await handleMessageReceivedHook(event, ctx);
206
267
  });
268
+
269
+ api.on("agent_end", (event) => {
270
+ if (isLlmRuntimeEnabled()) {
271
+ return;
272
+ }
273
+ syncFixedStatus(
274
+ event.success ? FIXED_HOOK_STATUSES.agentEndSuccess : FIXED_HOOK_STATUSES.agentEndFailure,
275
+ event.success
276
+ ? "task finished"
277
+ : truncateLog(`task failed: ${event.error ?? "unknown error"}`, 220),
278
+ );
279
+ });
207
280
  }
208
281
 
209
282
  function isPlainObject(value: unknown): value is Record<string, unknown> {
@@ -345,45 +418,6 @@ function pickRandomAction(actions: string[]): string {
345
418
  return actions[Math.floor(Math.random() * actions.length)];
346
419
  }
347
420
 
348
- function truncateNoteData(
349
- data: string,
350
- maxLen = 500,
351
- ): { data: string; dataTruncated: boolean } {
352
- if (data.length <= maxLen) {
353
- return { data, dataTruncated: false };
354
- }
355
- return { data: `${data.slice(0, maxLen)}...`, dataTruncated: true };
356
- }
357
-
358
- function summarizeCreatedNote(note: CreateNotesBoardNote) {
359
- const { data, dataTruncated } = truncateNoteData(note.data);
360
- return {
361
- id: note.id,
362
- ownerName: note.ownerName,
363
- createTime: note.createTime,
364
- data,
365
- dataTruncated,
366
- };
367
- }
368
-
369
- function buildMutationSummary(result: CreateNotesBoardNoteResultPayload): string {
370
- if (!result.success) {
371
- const parts = ["Mutation failed"];
372
- if ("errorCode" in result && result.errorCode) {
373
- parts.push(`error=${result.errorCode}`);
374
- }
375
- if (typeof result.remaining === "number") {
376
- parts.push(`remaining=${result.remaining}`);
377
- }
378
- if (result.resetAtUtc) {
379
- parts.push(`resetAtUtc=${result.resetAtUtc}`);
380
- }
381
- return parts.join(", ");
382
- }
383
-
384
- const text = truncateNoteData(result.note.data, 120).data.replace(/\s+/g, " ").trim();
385
- return `${result.propId} -> ${result.note.id} by ${result.note.ownerName}: ${text}`;
386
- }
387
421
 
388
422
  function buildKichiPrompt(): string {
389
423
  return [
@@ -391,23 +425,17 @@ function buildKichiPrompt(): string {
391
425
  "",
392
426
  "When to use `kichi_action`:",
393
427
  "- Task start: User gives you a new task to work on",
428
+ "- Step switch: Moving from one meaningful step or subtask to another within the current task",
394
429
  "- Task switch: Moving from one distinct task to another",
395
- "- Major milestone: Completed a significant phase",
396
430
  "- Task end (highest priority): Before the final user-visible reply of this turn, MUST call `kichi_action` exactly once",
397
431
  "- Required order at task end: 1) call `kichi_action` 2) send final reply",
398
- "- Trivial-operation skip applies only to Task start / Task switch / Major milestone, NOT Task end",
399
- "",
400
- "How to choose parameters:",
401
- "- Choose poseType, action, and bubble that match your actual current activity",
402
- "- Use available actions from the configured action list for each poseType",
403
- "- bubble should be 2-5 words describing what you're doing now",
432
+ "- Trivial-operation skip applies only to Task start / Step switch / Task switch, NOT Task end",
404
433
  "",
405
434
  "When to use `kichi_clock`:",
406
- "- Default behavior: For most tasks, set a `countDown` at task start to plan your time.",
407
- "- Prefer using it whenever work has 2+ steps or is likely to take more than a brief moment (~10s).",
435
+ "- For tasks with 2+ meaningful steps or work likely to take more than a brief moment (~10s), set a `countDown` at task start.",
436
+ "- Skip clock only for truly quick one-shot operations.",
408
437
  "- If duration is uncertain, start with a reasonable estimate and adjust as work progresses.",
409
438
  "- If user requests a timer style, follow it (`pomodoro`, `countDown`, or `countUp`).",
410
- "- Skip only for truly quick one-shot operations (for example a single file read or one simple command).",
411
439
  "",
412
440
  "Skip all sync if:",
413
441
  "- User says 'don't sync to Kichi' or similar",
@@ -705,7 +733,7 @@ const plugin = {
705
733
  });
706
734
 
707
735
  api.registerTool({
708
- name: "kichi_noteboard_query",
736
+ name: "kichi_query_status",
709
737
  description:
710
738
  "Query Kichi note boards for the current mate. Use this before creating a new note, especially when you may want to relate it to an existing note.",
711
739
  parameters: {
@@ -755,18 +783,13 @@ const plugin = {
755
783
  type: "string",
756
784
  description: "Note content to create. Maximum 200 characters.",
757
785
  },
758
- requestId: {
759
- type: "string",
760
- description: "Optional request ID for tracing or deduplication.",
761
- },
762
786
  },
763
787
  required: ["propId", "data"],
764
788
  },
765
789
  execute: async (_toolCallId, params) => {
766
- const { propId, data, requestId } = (params || {}) as {
790
+ const { propId, data } = (params || {}) as {
767
791
  propId?: unknown;
768
792
  data?: unknown;
769
- requestId?: unknown;
770
793
  };
771
794
  if (typeof propId !== "string" || !propId.trim()) {
772
795
  return { success: false, error: "propId is required" };
@@ -780,38 +803,13 @@ const plugin = {
780
803
  error: `data must be ${MAX_NOTEBOARD_TEXT_LENGTH} characters or fewer`,
781
804
  };
782
805
  }
783
- if (requestId !== undefined && typeof requestId !== "string") {
784
- return { success: false, error: "requestId must be a string when provided" };
785
- }
786
806
  if (!service?.hasValidIdentity() || !service?.isConnected()) {
787
807
  return { success: false, error: "Not connected to Kichi world" };
788
808
  }
789
809
 
790
810
  try {
791
- const result = await service.createNotesBoardNote(
792
- propId.trim(),
793
- data.trim(),
794
- typeof requestId === "string" ? requestId : undefined,
795
- );
796
- if (!result.success) {
797
- return {
798
- ...result,
799
- summary: buildMutationSummary(result),
800
- };
801
- }
802
-
803
- return {
804
- success: true,
805
- requestId: result.requestId,
806
- mateId: result.mateId,
807
- spaceId: result.spaceId,
808
- propId: result.propId,
809
- dailyLimit: result.dailyLimit,
810
- remaining: result.remaining,
811
- resetAtUtc: result.resetAtUtc,
812
- note: summarizeCreatedNote(result.note),
813
- summary: buildMutationSummary(result),
814
- };
811
+ service.createNotesBoardNote(propId.trim(), data.trim());
812
+ return { success: true };
815
813
  } catch (error) {
816
814
  return {
817
815
  success: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yahaha-studio/kichi-forwarder",
3
- "version": "0.0.1-alpha.26",
3
+ "version": "0.0.1-alpha.27",
4
4
  "description": "Forward OpenClaw agent events to external WebSocket server for visualization",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -28,8 +28,7 @@ Treat these as required completion checks for plugin setup:
28
28
 
29
29
  1. Plugin exists and is enabled: `plugins.entries.kichi-forwarder.enabled: true`.
30
30
  2. Workspace `HEARTBEAT.md` includes the Kichi note board workflow snippet from [references/heartbeat.md](references/heartbeat.md).
31
- 3. Heartbeat cadence is set to `10m` by default.
32
- 4. Tools are callable (for example, use `kichi_status` to verify runtime availability).
31
+ 3. Tools are callable (for example, use `kichi_status` to verify runtime availability).
33
32
 
34
33
  ## Heartbeat Integration
35
34
 
@@ -38,10 +37,14 @@ This plugin only provides websocket tools. It does not edit workspace files itse
38
37
  If setup is missing required heartbeat content:
39
38
 
40
39
  1. Update workspace `HEARTBEAT.md`.
41
- 2. Update OpenClaw heartbeat cadence if needed.
40
+ 2. Keep the existing OpenClaw heartbeat cadence unless the user explicitly wants a different interval.
42
41
  3. Do not claim the plugin edited `HEARTBEAT.md` automatically.
43
42
 
44
- For heartbeat-specific note triage, workflow steps, snippet text, and cadence command, follow [references/heartbeat.md](references/heartbeat.md).
43
+ For heartbeat-specific note triage, workflow steps, and snippet text, follow [references/heartbeat.md](references/heartbeat.md).
44
+
45
+ ## LLM Runtime
46
+
47
+ Runtime config supports `llmRuntimeEnabled` in `kichi-runtime-config.json` (default: `true`). When enabled, sync status uses LLM-driven prompts (may consume extra tokens). When disabled, sync uses fixed English text.
45
48
 
46
49
  ## Tool Selection Flow
47
50
 
@@ -87,7 +90,7 @@ When user asks to call `kichi_leave`:
87
90
 
88
91
  1. Call `kichi_leave`.
89
92
  2. Remove Kichi note board heartbeat workflow from workspace `HEARTBEAT.md`.
90
- 3. Revert heartbeat cadence if it was Kichi-specific.
93
+ 3. Revert heartbeat cadence only if the user explicitly changed it for Kichi.
91
94
  4. Do not claim the plugin removed heartbeat settings automatically.
92
95
 
93
96
  ### kichi_rejoin
@@ -166,18 +169,18 @@ kichi_clock(action: "set", clock: { mode: "countUp", elapsedSeconds: 0 })
166
169
  kichi_clock(action: "stop")
167
170
  ```
168
171
 
169
- ### kichi_noteboard_query
172
+ ### kichi_query_status
170
173
 
171
174
  Query boards first:
172
175
 
173
176
  ```text
174
- kichi_noteboard_query()
177
+ kichi_query_status()
175
178
  ```
176
179
 
177
180
  Optional:
178
181
 
179
182
  ```text
180
- kichi_noteboard_query(requestId: "trace-id")
183
+ kichi_query_status(requestId: "trace-id")
181
184
  ```
182
185
 
183
186
  Each returned note includes `creatorName`, `isFromOwner`, `isCreatedByCurrentMate`, `createTime`, `updateTime`, and `data`.
@@ -233,7 +236,7 @@ Plugin data directory:
233
236
  Files:
234
237
 
235
238
  - `identity.json`: `mateId`, `authKey`
236
- - `kichi-runtime-config.json`: runtime action list
239
+ - `kichi-runtime-config.json`: runtime action list and `llmRuntimeEnabled`
237
240
  - `skills-config.json`: legacy filename still readable for backward compatibility
238
241
 
239
242
  ## Runtime Behavior
@@ -5,7 +5,6 @@
5
5
  If `plugins.entries.kichi-forwarder.enabled: true` and workspace `HEARTBEAT.md` does not include Kichi note board workflow:
6
6
 
7
7
  1. Add the snippet from `HEARTBEAT.md Snippet` section below.
8
- 2. Set heartbeat cadence to `10m` by default.
9
8
 
10
9
  ## Workflow Boundary
11
10
 
@@ -14,7 +13,7 @@ This plugin only provides websocket tools. It does not edit workspace files itse
14
13
  If user wants recurring note board checks:
15
14
 
16
15
  1. Update workspace `HEARTBEAT.md`.
17
- 2. Update OpenClaw heartbeat cadence if needed.
16
+ 2. Keep the existing OpenClaw heartbeat cadence unless the user explicitly wants a different interval.
18
17
  3. Do not claim the plugin edited `HEARTBEAT.md` automatically.
19
18
 
20
19
  ## Definitions
@@ -75,9 +74,3 @@ Use this exact flow:
75
74
  - Respect `dailyLimit`, `remaining`, `resetAtUtc`.
76
75
  - Reply `HEARTBEAT_OK` only when no note is created in this run.
77
76
  ```
78
-
79
- Suggested cadence:
80
-
81
- ```bash
82
- openclaw config set agents.defaults.heartbeat.every "10m"
83
- ```
@@ -11,30 +11,29 @@ Save `mateId` to `identity.json` before using `kichi_join`:
11
11
  }
12
12
  ```
13
13
 
14
- Install and enable:
14
+ Install:
15
15
 
16
16
  ```bash
17
17
  openclaw plugins install @yahaha-studio/kichi-forwarder@latest
18
- openclaw plugins enable kichi-forwarder
19
18
  ```
20
19
 
21
- After enabling, OpenClaw gateway will restart automatically. Plugin tools (`kichi_join`, `kichi_rejoin`, etc.) become available after the restart completes.
20
+ For npm-installed plugins, OpenClaw installs and enables the plugin through `plugins install`. If the Gateway is already running with the default config reload behavior, the required plugin reload/restart is handled there; otherwise restart the Gateway once after install. Plugin tools (`kichi_join`, `kichi_rejoin`, etc.) become available after that restart/reload completes.
22
21
 
23
22
  Required post-install integration:
24
23
 
25
24
  1. Add the Kichi note board workflow snippet to workspace `HEARTBEAT.md` (see [heartbeat.md](heartbeat.md)).
26
- 2. Set heartbeat cadence to `10m` by default.
27
- 3. Verify tools are callable (for example, call `kichi_status`).
25
+ 2. Verify tools are callable (for example, call `kichi_status`).
28
26
 
29
27
  Note: this plugin does not edit workspace files automatically. Do not claim plugin-side auto-write of `HEARTBEAT.md`.
30
28
 
29
+ You can update workspace `HEARTBEAT.md` before or after plugin install. Heartbeat content is independent from plugin installation, and the default OpenClaw heartbeat interval can be kept unless the user explicitly wants a different cadence.
30
+
31
31
  If the registry install cannot be resolved, install from source:
32
32
 
33
33
  ```bash
34
34
  git clone https://github.com/XiaoxinShi001/yahaha_focus_forwarder_alpha
35
35
  cd yahaha_focus_forwarder_alpha
36
36
  openclaw plugins install .
37
- openclaw plugins enable kichi-forwarder
38
37
  ```
39
38
 
40
39
  ## Files
package/src/service.ts CHANGED
@@ -10,7 +10,6 @@ import type {
10
10
  ClockPayload,
11
11
  KichiConnectionStatus,
12
12
  CreateNotesBoardNotePayload,
13
- CreateNotesBoardNoteResultPayload,
14
13
  KichiForwarderConfig,
15
14
  KichiIdentity,
16
15
  PoseType,
@@ -22,7 +21,7 @@ import type {
22
21
  const IDENTITY_DIR = path.join(os.homedir(), ".openclaw", "kichi-world");
23
22
  const IDENTITY_PATH = path.join(IDENTITY_DIR, "identity.json");
24
23
  const MAX_NOTEBOARD_TEXT_LENGTH = 200;
25
-
24
+
26
25
  export class KichiForwarderService {
27
26
  private ws: WebSocket | null = null;
28
27
  private stopped = false;
@@ -40,14 +39,14 @@ export class KichiForwarderService {
40
39
  >();
41
40
 
42
41
  constructor(private config: KichiForwarderConfig, private logger: Logger) {}
43
-
44
- async start(): Promise<void> {
45
- if (!this.config.enabled) return;
46
- this.identity = this.loadIdentity();
47
- this.stopped = false;
48
- this.connect();
49
- }
50
-
42
+
43
+ async start(): Promise<void> {
44
+ if (!this.config.enabled) return;
45
+ this.identity = this.loadIdentity();
46
+ this.stopped = false;
47
+ this.connect();
48
+ }
49
+
51
50
  async stop(): Promise<void> {
52
51
  this.stopped = true;
53
52
  if (this.reconnectTimeout) clearTimeout(this.reconnectTimeout);
@@ -55,7 +54,7 @@ export class KichiForwarderService {
55
54
  this.ws?.close();
56
55
  this.ws = null;
57
56
  }
58
-
57
+
59
58
  async join(
60
59
  mateId: string,
61
60
  botName: string,
@@ -71,10 +70,10 @@ export class KichiForwarderService {
71
70
  } else {
72
71
  this.ws?.once("open", sendJoin);
73
72
  }
74
- setTimeout(() => { if (this.joinResolve) { this.joinResolve = null; resolve(null); } }, 10000);
75
- });
76
- }
77
-
73
+ setTimeout(() => { if (this.joinResolve) { this.joinResolve = null; resolve(null); } }, 10000);
74
+ });
75
+ }
76
+
78
77
  private connect(): void {
79
78
  if (this.stopped) return;
80
79
  this.ws = new WebSocket(this.config.wsUrl);
@@ -107,12 +106,12 @@ export class KichiForwarderService {
107
106
  this.logger.info(`Joined as ${this.identity.mateId}`);
108
107
  this.joinResolve?.(msg.authKey);
109
108
  this.joinResolve = null;
110
- } else if (msg.type === "rejoin_failed" || msg.type === "auth_error") {
111
- // AuthKey invalid/expired, clear it
112
- this.logger.warn(`Auth failed: ${msg.reason || "unknown"}`);
113
- this.clearAuthKey();
114
- } else if (msg.type === "leave_ack") {
115
- this.logger.info("Left Kichi world");
109
+ } else if (msg.type === "rejoin_failed" || msg.type === "auth_error") {
110
+ // AuthKey invalid/expired, clear it
111
+ this.logger.warn(`Auth failed: ${msg.reason || "unknown"}`);
112
+ this.clearAuthKey();
113
+ } else if (msg.type === "leave_ack") {
114
+ this.logger.info("Left Kichi world");
116
115
  }
117
116
  } catch (e) {
118
117
  this.logger.warn(`Failed to parse message: ${e}`);
@@ -195,7 +194,7 @@ export class KichiForwarderService {
195
194
  }
196
195
  });
197
196
  }
198
-
197
+
199
198
  private loadIdentity(): KichiIdentity | null {
200
199
  try {
201
200
  if (!fs.existsSync(IDENTITY_PATH)) return null;
@@ -211,23 +210,23 @@ export class KichiForwarderService {
211
210
  } catch (e) {
212
211
  this.logger.warn(`Failed to load identity: ${e}`);
213
212
  return null;
214
- }
215
- }
216
-
213
+ }
214
+ }
215
+
217
216
  private saveIdentity(): void {
218
217
  if (!this.identity?.mateId) return;
219
218
  try {
220
219
  if (!fs.existsSync(IDENTITY_DIR)) fs.mkdirSync(IDENTITY_DIR, { recursive: true, mode: 0o700 });
221
220
  fs.writeFileSync(IDENTITY_PATH, JSON.stringify(this.identity, null, 2), { mode: 0o600 });
222
- } catch (e) {
223
- this.logger.error(`Failed to save identity: ${e}`);
224
- }
225
- }
226
-
221
+ } catch (e) {
222
+ this.logger.error(`Failed to save identity: ${e}`);
223
+ }
224
+ }
225
+
227
226
  private clearAuthKey(): void {
228
- if (!this.identity) return;
229
- this.identity.authKey = undefined;
230
- this.saveIdentity();
227
+ if (!this.identity) return;
228
+ this.identity.authKey = undefined;
229
+ this.saveIdentity();
231
230
  this.logger.info("AuthKey cleared");
232
231
  }
233
232
 
@@ -242,7 +241,7 @@ export class KichiForwarderService {
242
241
  this.logger.debug(`Sent rejoin for ${this.identity.mateId}`);
243
242
  return true;
244
243
  }
245
-
244
+
246
245
  sendStatus(poseType: PoseType | "", action: string, bubble: string, log: string): void {
247
246
  if (!this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN) return;
248
247
  const payload: StatusPayload = {
@@ -291,19 +290,15 @@ export class KichiForwarderService {
291
290
  }
292
291
 
293
292
  const payload: QueryNotesBoardPayload = {
294
- type: "query_notes_board",
293
+ type: "query_status",
295
294
  requestId: requestId?.trim() || randomUUID(),
296
295
  mateId: identity.mateId,
297
296
  authKey: identity.authKey,
298
297
  };
299
- return this.sendRequest<QueryNotesBoardResultPayload>(payload, "query_notes_board_result");
298
+ return this.sendRequest<QueryNotesBoardResultPayload>(payload, "query_status_result");
300
299
  }
301
300
 
302
- async createNotesBoardNote(
303
- propId: string,
304
- data: string,
305
- requestId?: string,
306
- ): Promise<CreateNotesBoardNoteResultPayload> {
301
+ createNotesBoardNote(propId: string, data: string): void {
307
302
  const identity = this.requireIdentity();
308
303
  if (!identity) {
309
304
  throw new Error("Missing Kichi identity");
@@ -313,18 +308,18 @@ export class KichiForwarderService {
313
308
  throw new Error(`Note content must be ${MAX_NOTEBOARD_TEXT_LENGTH} characters or fewer`);
314
309
  }
315
310
 
311
+ if (this.ws?.readyState !== WebSocket.OPEN) {
312
+ throw new Error("Kichi websocket is not connected");
313
+ }
314
+
316
315
  const payload: CreateNotesBoardNotePayload = {
317
316
  type: "create_notes_board_note",
318
- requestId: requestId?.trim() || randomUUID(),
319
317
  mateId: identity.mateId,
320
318
  authKey: identity.authKey,
321
319
  propId,
322
320
  data,
323
321
  };
324
- return this.sendRequest<CreateNotesBoardNoteResultPayload>(
325
- payload,
326
- "create_notes_board_note_result",
327
- );
322
+ this.ws.send(JSON.stringify(payload));
328
323
  }
329
324
 
330
325
  isConnected(): boolean { return this.ws?.readyState === WebSocket.OPEN && !!this.identity?.authKey; }
@@ -414,15 +409,15 @@ export class KichiForwarderService {
414
409
  return new Promise((resolve) => {
415
410
  const handler = (data: WebSocket.Data) => {
416
411
  try {
417
- const msg = JSON.parse(data.toString());
418
- if (msg.type === "leave_ack") {
419
- this.ws?.off("message", handler);
420
- this.clearAuthKey();
421
- resolve(true);
422
- }
423
- } catch (e) {
424
- this.logger.warn(`Failed to parse leave response: ${e}`);
425
- }
412
+ const msg = JSON.parse(data.toString());
413
+ if (msg.type === "leave_ack") {
414
+ this.ws?.off("message", handler);
415
+ this.clearAuthKey();
416
+ resolve(true);
417
+ }
418
+ } catch (e) {
419
+ this.logger.warn(`Failed to parse leave response: ${e}`);
420
+ }
426
421
  };
427
422
  this.ws!.on("message", handler);
428
423
  this.ws!.send(
@@ -431,4 +426,4 @@ export class KichiForwarderService {
431
426
  setTimeout(() => { this.ws?.off("message", handler); resolve(false); }, 10000);
432
427
  });
433
428
  }
434
- }
429
+ }
package/src/types.ts CHANGED
@@ -13,6 +13,7 @@ export type ActionResult = {
13
13
 
14
14
  export type KichiRuntimeConfig = {
15
15
  actions: Record<PoseType, string[]>;
16
+ llmRuntimeEnabled: boolean;
16
17
  };
17
18
 
18
19
  // Backward-compatible alias for older imports.
@@ -141,14 +142,14 @@ export type QueryNotesBoard = {
141
142
  };
142
143
 
143
144
  export type QueryNotesBoardPayload = {
144
- type: "query_notes_board";
145
+ type: "query_status";
145
146
  requestId: string;
146
147
  mateId: string;
147
148
  authKey: string;
148
149
  };
149
150
 
150
151
  export type QueryNotesBoardSuccessPayload = {
151
- type: "query_notes_board_result";
152
+ type: "query_status_result";
152
153
  requestId: string;
153
154
  success: true;
154
155
  mateId: string;
@@ -160,7 +161,7 @@ export type QueryNotesBoardSuccessPayload = {
160
161
  };
161
162
 
162
163
  export type QueryNotesBoardFailurePayload = {
163
- type: "query_notes_board_result";
164
+ type: "query_status_result";
164
165
  requestId: string;
165
166
  } & KichiErrorResult;
166
167
 
@@ -170,41 +171,8 @@ export type QueryNotesBoardResultPayload =
170
171
 
171
172
  export type CreateNotesBoardNotePayload = {
172
173
  type: "create_notes_board_note";
173
- requestId: string;
174
174
  mateId: string;
175
175
  authKey: string;
176
176
  propId: string;
177
177
  data: string;
178
178
  };
179
-
180
- export type CreateNotesBoardNote = {
181
- id: string;
182
- ownerName: string;
183
- createTime: string;
184
- data: string;
185
- };
186
-
187
- type NotesBoardMutationSuccessPayloadBase = {
188
- requestId: string;
189
- success: true;
190
- mateId: string;
191
- spaceId?: string;
192
- propId: string;
193
- dailyLimit?: number;
194
- remaining?: number;
195
- resetAtUtc?: string;
196
- note: CreateNotesBoardNote;
197
- };
198
-
199
- export type CreateNotesBoardNoteSuccessPayload = NotesBoardMutationSuccessPayloadBase & {
200
- type: "create_notes_board_note_result";
201
- };
202
-
203
- export type CreateNotesBoardNoteFailurePayload = {
204
- type: "create_notes_board_note_result";
205
- requestId: string;
206
- } & KichiErrorResult;
207
-
208
- export type CreateNotesBoardNoteResultPayload =
209
- | CreateNotesBoardNoteSuccessPayload
210
- | CreateNotesBoardNoteFailurePayload;