@yahaha-studio/kichi-forwarder 0.0.1-alpha.25 → 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");
@@ -32,13 +58,6 @@ const RUNTIME_CONFIG_PATH = path.join(KICHI_WORLD_DIR, "kichi-runtime-config.jso
32
58
  const LEGACY_SKILLS_CONFIG_PATH = path.join(KICHI_WORLD_DIR, "skills-config.json");
33
59
  const IDENTITY_PATH = path.join(KICHI_WORLD_DIR, "identity.json");
34
60
  const MAX_NOTEBOARD_TEXT_LENGTH = 200;
35
- const MESSAGE_RECEIVED_BUBBLES = [
36
- "(`・ω・�?�?,
37
- "( ̄^�?�?,
38
- "(〃・ิ‿・�?�?,
39
- "(≧∀�?�?,
40
- ];
41
-
42
61
  let cachedConfig: KichiRuntimeConfig | null = null;
43
62
  let cachedConfigMtime = 0;
44
63
  let cachedConfigPath = "";
@@ -64,6 +83,7 @@ function normalizeRuntimeConfig(value: unknown): KichiRuntimeConfig {
64
83
  const raw = value && typeof value === "object" ? (value as Partial<KichiRuntimeConfig>) : {};
65
84
  const actions = raw.actions;
66
85
  return {
86
+ llmRuntimeEnabled: typeof raw.llmRuntimeEnabled === "boolean" ? raw.llmRuntimeEnabled : true,
67
87
  actions: {
68
88
  stand: sanitizeActions(actions?.stand, DEFAULT_ACTIONS.stand),
69
89
  sit: sanitizeActions(actions?.sit, DEFAULT_ACTIONS.sit),
@@ -187,22 +207,34 @@ function resolveStatusSourceId(ctx?: { agentId?: string; sessionKey?: string }):
187
207
  return undefined;
188
208
  }
189
209
 
190
- async function handleMessageReceivedHook(
191
- event: any,
192
- ctx?: { agentId?: string; sessionKey?: string },
193
- ): Promise<void> {
210
+ function isLlmRuntimeEnabled(): boolean {
211
+ return loadRuntimeConfig().llmRuntimeEnabled;
212
+ }
213
+
214
+ function syncFixedStatus(status: ActionResult, log = ""): void {
194
215
  if (!service?.hasValidIdentity() || !service?.isConnected()) {
195
216
  return;
196
217
  }
218
+ sendStatusAndRemember(status, log);
219
+ }
197
220
 
198
- const sourceId = resolveStatusSourceId(ctx);
199
- const content =
200
- typeof event?.content === "string" && event.content.trim()
201
- ? event.content.trim()
202
- : JSON.stringify(event ?? "new message");
203
- const bubble = pickRandomAction(MESSAGE_RECEIVED_BUBBLES);
204
- const context = `${sourceId ? `[${sourceId}] ` : ""}Received: ${content}; bubble=${bubble}`;
205
- service.sendStatus("", "", bubble, prefixLogTimestamp(truncateLog(context)));
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
+
227
+ async function handleMessageReceivedHook(
228
+ event: { content?: string },
229
+ _ctx?: { agentId?: string; sessionKey?: string },
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
+ }
237
+ return;
206
238
  }
207
239
 
208
240
  function registerPluginHooks(api: OpenClawPluginApi): void {
@@ -210,18 +242,41 @@ function registerPluginHooks(api: OpenClawPluginApi): void {
210
242
  if (!service?.hasValidIdentity() || !service?.isConnected()) {
211
243
  return;
212
244
  }
245
+ if (!isLlmRuntimeEnabled()) {
246
+ syncFixedStatus(FIXED_HOOK_STATUSES.beforePromptBuild);
247
+ return;
248
+ }
213
249
  return {
214
250
  prependContext: buildKichiPrompt(),
215
251
  };
216
252
  });
217
253
 
218
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
+ }
219
262
  forwardToolCallLog(event.toolName, event.params, ctx?.agentId);
220
263
  });
221
264
 
222
265
  api.on("message_received", async (event, ctx) => {
223
266
  await handleMessageReceivedHook(event, ctx);
224
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
+ });
225
280
  }
226
281
 
227
282
  function isPlainObject(value: unknown): value is Record<string, unknown> {
@@ -363,45 +418,6 @@ function pickRandomAction(actions: string[]): string {
363
418
  return actions[Math.floor(Math.random() * actions.length)];
364
419
  }
365
420
 
366
- function truncateNoteData(
367
- data: string,
368
- maxLen = 500,
369
- ): { data: string; dataTruncated: boolean } {
370
- if (data.length <= maxLen) {
371
- return { data, dataTruncated: false };
372
- }
373
- return { data: `${data.slice(0, maxLen)}...`, dataTruncated: true };
374
- }
375
-
376
- function summarizeCreatedNote(note: CreateNotesBoardNote) {
377
- const { data, dataTruncated } = truncateNoteData(note.data);
378
- return {
379
- id: note.id,
380
- ownerName: note.ownerName,
381
- createTime: note.createTime,
382
- data,
383
- dataTruncated,
384
- };
385
- }
386
-
387
- function buildMutationSummary(result: CreateNotesBoardNoteResultPayload): string {
388
- if (!result.success) {
389
- const parts = ["Mutation failed"];
390
- if ("errorCode" in result && result.errorCode) {
391
- parts.push(`error=${result.errorCode}`);
392
- }
393
- if (typeof result.remaining === "number") {
394
- parts.push(`remaining=${result.remaining}`);
395
- }
396
- if (result.resetAtUtc) {
397
- parts.push(`resetAtUtc=${result.resetAtUtc}`);
398
- }
399
- return parts.join(", ");
400
- }
401
-
402
- const text = truncateNoteData(result.note.data, 120).data.replace(/\s+/g, " ").trim();
403
- return `${result.propId} -> ${result.note.id} by ${result.note.ownerName}: ${text}`;
404
- }
405
421
 
406
422
  function buildKichiPrompt(): string {
407
423
  return [
@@ -409,23 +425,17 @@ function buildKichiPrompt(): string {
409
425
  "",
410
426
  "When to use `kichi_action`:",
411
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",
412
429
  "- Task switch: Moving from one distinct task to another",
413
- "- Major milestone: Completed a significant phase",
414
430
  "- Task end (highest priority): Before the final user-visible reply of this turn, MUST call `kichi_action` exactly once",
415
431
  "- Required order at task end: 1) call `kichi_action` 2) send final reply",
416
- "- Trivial-operation skip applies only to Task start / Task switch / Major milestone, NOT Task end",
417
- "",
418
- "How to choose parameters:",
419
- "- Choose poseType, action, and bubble that match your actual current activity",
420
- "- Use available actions from the configured action list for each poseType",
421
- "- 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",
422
433
  "",
423
434
  "When to use `kichi_clock`:",
424
- "- Default behavior: For most tasks, set a `countDown` at task start to plan your time.",
425
- "- 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.",
426
437
  "- If duration is uncertain, start with a reasonable estimate and adjust as work progresses.",
427
438
  "- If user requests a timer style, follow it (`pomodoro`, `countDown`, or `countUp`).",
428
- "- Skip only for truly quick one-shot operations (for example a single file read or one simple command).",
429
439
  "",
430
440
  "Skip all sync if:",
431
441
  "- User says 'don't sync to Kichi' or similar",
@@ -723,7 +733,7 @@ const plugin = {
723
733
  });
724
734
 
725
735
  api.registerTool({
726
- name: "kichi_noteboard_query",
736
+ name: "kichi_query_status",
727
737
  description:
728
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.",
729
739
  parameters: {
@@ -773,18 +783,13 @@ const plugin = {
773
783
  type: "string",
774
784
  description: "Note content to create. Maximum 200 characters.",
775
785
  },
776
- requestId: {
777
- type: "string",
778
- description: "Optional request ID for tracing or deduplication.",
779
- },
780
786
  },
781
787
  required: ["propId", "data"],
782
788
  },
783
789
  execute: async (_toolCallId, params) => {
784
- const { propId, data, requestId } = (params || {}) as {
790
+ const { propId, data } = (params || {}) as {
785
791
  propId?: unknown;
786
792
  data?: unknown;
787
- requestId?: unknown;
788
793
  };
789
794
  if (typeof propId !== "string" || !propId.trim()) {
790
795
  return { success: false, error: "propId is required" };
@@ -798,38 +803,13 @@ const plugin = {
798
803
  error: `data must be ${MAX_NOTEBOARD_TEXT_LENGTH} characters or fewer`,
799
804
  };
800
805
  }
801
- if (requestId !== undefined && typeof requestId !== "string") {
802
- return { success: false, error: "requestId must be a string when provided" };
803
- }
804
806
  if (!service?.hasValidIdentity() || !service?.isConnected()) {
805
807
  return { success: false, error: "Not connected to Kichi world" };
806
808
  }
807
809
 
808
810
  try {
809
- const result = await service.createNotesBoardNote(
810
- propId.trim(),
811
- data.trim(),
812
- typeof requestId === "string" ? requestId : undefined,
813
- );
814
- if (!result.success) {
815
- return {
816
- ...result,
817
- summary: buildMutationSummary(result),
818
- };
819
- }
820
-
821
- return {
822
- success: true,
823
- requestId: result.requestId,
824
- mateId: result.mateId,
825
- spaceId: result.spaceId,
826
- propId: result.propId,
827
- dailyLimit: result.dailyLimit,
828
- remaining: result.remaining,
829
- resetAtUtc: result.resetAtUtc,
830
- note: summarizeCreatedNote(result.note),
831
- summary: buildMutationSummary(result),
832
- };
811
+ service.createNotesBoardNote(propId.trim(), data.trim());
812
+ return { success: true };
833
813
  } catch (error) {
834
814
  return {
835
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.25",
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;