@folotoy/folotoy-openclaw-plugin 0.5.9 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/config.d.ts CHANGED
@@ -27,11 +27,19 @@ export type FlatChannelConfig = {
27
27
  mqtt_port?: number;
28
28
  summary_enabled?: boolean;
29
29
  summary_max_chars?: number;
30
+ sentence_split_enabled?: boolean;
31
+ sentence_split_delimiters?: string;
32
+ soothing_loop_enabled?: boolean;
33
+ soothing_loop_interval_ms?: number;
30
34
  };
31
35
  export declare const DEFAULT_API_URL = "https://api.folotoy.cn";
32
36
  export declare const DEFAULT_MQTT_HOST: string;
33
37
  export declare const DEFAULT_MQTT_PORT = 1883;
34
38
  export declare const DEFAULT_SUMMARY_ENABLED = true;
35
39
  export declare const DEFAULT_SUMMARY_MAX_CHARS = 200;
40
+ export declare const DEFAULT_SENTENCE_SPLIT_ENABLED = true;
41
+ export declare const DEFAULT_SENTENCE_SPLIT_DELIMITERS = "\uFF01\u3002\uFF1F\uFF1B!.?;~";
42
+ export declare const DEFAULT_SOOTHING_LOOP_ENABLED = true;
43
+ export declare const DEFAULT_SOOTHING_LOOP_INTERVAL_MS = 3000;
36
44
  export declare function flatToPluginConfig(flat: FlatChannelConfig): PluginConfig;
37
45
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,KAAK,CAAA;IACX,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,QAAQ,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,eAAe,GAAG,eAAe,CAAA;IACvC,IAAI,EAAE;QACJ,IAAI,EAAE,MAAM,CAAA;QACZ,IAAI,EAAE,MAAM,CAAA;KACb,CAAA;CACF,CAAA;AAED,8DAA8D;AAC9D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B,CAAA;AAED,eAAO,MAAM,eAAe,2BAA2B,CAAA;AACvD,eAAO,MAAM,iBAAiB,QAAgD,CAAA;AAC9E,eAAO,MAAM,iBAAiB,OAAO,CAAA;AACrC,eAAO,MAAM,uBAAuB,OAAO,CAAA;AAC3C,eAAO,MAAM,yBAAyB,MAAM,CAAA;AAE5C,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,GAAG,YAAY,CAaxE"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,KAAK,CAAA;IACX,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,QAAQ,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,eAAe,GAAG,eAAe,CAAA;IACvC,IAAI,EAAE;QACJ,IAAI,EAAE,MAAM,CAAA;QACZ,IAAI,EAAE,MAAM,CAAA;KACb,CAAA;CACF,CAAA;AAED,8DAA8D;AAC9D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,sBAAsB,CAAC,EAAE,OAAO,CAAA;IAChC,yBAAyB,CAAC,EAAE,MAAM,CAAA;IAClC,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B,yBAAyB,CAAC,EAAE,MAAM,CAAA;CACnC,CAAA;AAED,eAAO,MAAM,eAAe,2BAA2B,CAAA;AACvD,eAAO,MAAM,iBAAiB,QAAkD,CAAA;AAChF,eAAO,MAAM,iBAAiB,OAAO,CAAA;AACrC,eAAO,MAAM,uBAAuB,OAAO,CAAA;AAC3C,eAAO,MAAM,yBAAyB,MAAM,CAAA;AAC5C,eAAO,MAAM,8BAA8B,OAAO,CAAA;AAClD,eAAO,MAAM,iCAAiC,kCAAc,CAAA;AAC5D,eAAO,MAAM,6BAA6B,OAAO,CAAA;AACjD,eAAO,MAAM,iCAAiC,OAAO,CAAA;AAErD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,GAAG,YAAY,CAaxE"}
package/dist/config.js CHANGED
@@ -1,8 +1,12 @@
1
1
  export const DEFAULT_API_URL = 'https://api.folotoy.cn';
2
- export const DEFAULT_MQTT_HOST = process.env.FOLOTOY_MQTT_HOST ?? 'f.qrc92.cn';
2
+ export const DEFAULT_MQTT_HOST = process.env.FOLOTOY_MQTT_HOST ?? 'f.folotoy.cn';
3
3
  export const DEFAULT_MQTT_PORT = 1883;
4
4
  export const DEFAULT_SUMMARY_ENABLED = true;
5
5
  export const DEFAULT_SUMMARY_MAX_CHARS = 200;
6
+ export const DEFAULT_SENTENCE_SPLIT_ENABLED = true;
7
+ export const DEFAULT_SENTENCE_SPLIT_DELIMITERS = '!。?;!.?;~';
8
+ export const DEFAULT_SOOTHING_LOOP_ENABLED = true;
9
+ export const DEFAULT_SOOTHING_LOOP_INTERVAL_MS = 3000;
6
10
  export function flatToPluginConfig(flat) {
7
11
  const flow = flat.flow ?? 'direct';
8
12
  const auth = flow === 'api'
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAkCA,MAAM,CAAC,MAAM,eAAe,GAAG,wBAAwB,CAAA;AACvD,MAAM,CAAC,MAAM,iBAAiB,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,YAAY,CAAA;AAC9E,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAA;AACrC,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAA;AAC3C,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,CAAA;AAE5C,MAAM,UAAU,kBAAkB,CAAC,IAAuB;IACxD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,QAAQ,CAAA;IAClC,MAAM,IAAI,GAAG,IAAI,KAAK,KAAK;QACzB,CAAC,CAAC,EAAE,IAAI,EAAE,KAAc,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,eAAe,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,EAAE,EAAE;QAC5H,CAAC,CAAC,EAAE,IAAI,EAAE,QAAiB,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,EAAE,EAAE,CAAA;IAEvF,OAAO;QACL,IAAI;QACJ,IAAI,EAAE;YACJ,IAAI,EAAE,IAAI,CAAC,SAAS,IAAI,iBAAiB;YACzC,IAAI,EAAE,IAAI,CAAC,SAAS,IAAI,iBAAiB;SAC1C;KACF,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAsCA,MAAM,CAAC,MAAM,eAAe,GAAG,wBAAwB,CAAA;AACvD,MAAM,CAAC,MAAM,iBAAiB,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,cAAc,CAAA;AAChF,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAA;AACrC,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAA;AAC3C,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,CAAA;AAC5C,MAAM,CAAC,MAAM,8BAA8B,GAAG,IAAI,CAAA;AAClD,MAAM,CAAC,MAAM,iCAAiC,GAAG,WAAW,CAAA;AAC5D,MAAM,CAAC,MAAM,6BAA6B,GAAG,IAAI,CAAA;AACjD,MAAM,CAAC,MAAM,iCAAiC,GAAG,IAAI,CAAA;AAErD,MAAM,UAAU,kBAAkB,CAAC,IAAuB;IACxD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,QAAQ,CAAA;IAClC,MAAM,IAAI,GAAG,IAAI,KAAK,KAAK;QACzB,CAAC,CAAC,EAAE,IAAI,EAAE,KAAc,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,eAAe,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,EAAE,EAAE;QAC5H,CAAC,CAAC,EAAE,IAAI,EAAE,QAAiB,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,EAAE,EAAE,CAAA;IAEvF,OAAO;QACL,IAAI;QACJ,IAAI,EAAE;YACJ,IAAI,EAAE,IAAI,CAAC,SAAS,IAAI,iBAAiB;YACzC,IAAI,EAAE,IAAI,CAAC,SAAS,IAAI,iBAAiB;SAC1C;KACF,CAAA;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAgC,MAAM,0BAA0B,CAAA;AA+T/F,wBAAgB,gBAAgB,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE;;;EAczF;yBAEe,KAAK,iBAAiB;AAAtC,wBAGC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAgC,MAAM,0BAA0B,CAAA;AAyb/F,wBAAgB,gBAAgB,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE;;;EAczF;yBAEe,KAAK,iBAAiB;AAAtC,wBAGC"}
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { resolveCredentials, createMqttClient, buildInboundTopic, buildOutboundTopic, buildNotificationTopic } from './mqtt.js';
2
- import { pickSoothingReply } from './soothing.js';
3
- import { DEFAULT_MQTT_HOST, DEFAULT_MQTT_PORT, DEFAULT_SUMMARY_ENABLED, DEFAULT_SUMMARY_MAX_CHARS, flatToPluginConfig } from './config.js';
2
+ import { createSoothingPicker } from './soothing.js';
3
+ import { stripMarkdown } from './strip-markdown.js';
4
+ import { DEFAULT_MQTT_HOST, DEFAULT_MQTT_PORT, DEFAULT_SUMMARY_ENABLED, DEFAULT_SUMMARY_MAX_CHARS, DEFAULT_SENTENCE_SPLIT_ENABLED, DEFAULT_SENTENCE_SPLIT_DELIMITERS, DEFAULT_SOOTHING_LOOP_ENABLED, DEFAULT_SOOTHING_LOOP_INTERVAL_MS, flatToPluginConfig } from './config.js';
4
5
  // Per-account MQTT clients and msgId counters
5
6
  const activeClients = new Map();
6
7
  // Subagent reference, set at plugin registration
@@ -30,6 +31,10 @@ const folotoyChannel = {
30
31
  mqtt_port: { type: 'number', default: DEFAULT_MQTT_PORT },
31
32
  summary_enabled: { type: 'boolean', default: DEFAULT_SUMMARY_ENABLED },
32
33
  summary_max_chars: { type: 'number', default: DEFAULT_SUMMARY_MAX_CHARS },
34
+ sentence_split_enabled: { type: 'boolean', default: DEFAULT_SENTENCE_SPLIT_ENABLED },
35
+ sentence_split_delimiters: { type: 'string', default: DEFAULT_SENTENCE_SPLIT_DELIMITERS },
36
+ soothing_loop_enabled: { type: 'boolean', default: DEFAULT_SOOTHING_LOOP_ENABLED },
37
+ soothing_loop_interval_ms: { type: 'number', default: DEFAULT_SOOTHING_LOOP_INTERVAL_MS },
33
38
  },
34
39
  },
35
40
  uiHints: {
@@ -42,6 +47,10 @@ const folotoyChannel = {
42
47
  mqtt_port: { label: 'MQTT Port' },
43
48
  summary_enabled: { label: 'Enable Summary' },
44
49
  summary_max_chars: { label: 'Summary Max Characters' },
50
+ sentence_split_enabled: { label: 'Enable Sentence Splitting' },
51
+ sentence_split_delimiters: { label: 'Sentence Delimiters' },
52
+ soothing_loop_enabled: { label: 'Enable Soothing Loop' },
53
+ soothing_loop_interval_ms: { label: 'Soothing Loop Interval (ms)' },
45
54
  },
46
55
  },
47
56
  config: {
@@ -86,6 +95,10 @@ const folotoyChannel = {
86
95
  });
87
96
  const summaryEnabled = account.summary_enabled ?? DEFAULT_SUMMARY_ENABLED;
88
97
  const summaryMaxChars = account.summary_max_chars ?? DEFAULT_SUMMARY_MAX_CHARS;
98
+ const sentenceSplitEnabled = account.sentence_split_enabled ?? DEFAULT_SENTENCE_SPLIT_ENABLED;
99
+ const sentenceSplitDelimiters = account.sentence_split_delimiters ?? DEFAULT_SENTENCE_SPLIT_DELIMITERS;
100
+ const soothingLoopEnabled = account.soothing_loop_enabled ?? DEFAULT_SOOTHING_LOOP_ENABLED;
101
+ const soothingLoopIntervalMs = account.soothing_loop_interval_ms ?? DEFAULT_SOOTHING_LOOP_INTERVAL_MS;
89
102
  client.on('message', (_topic, payload) => {
90
103
  let msg;
91
104
  try {
@@ -98,12 +111,14 @@ const folotoyChannel = {
98
111
  return;
99
112
  const { msgId, inputParams: { text, recording_id } } = msg;
100
113
  let order = 1;
114
+ // Create a per-message picker that cycles through candidates without repeats
115
+ const soothingPick = createSoothingPicker(text);
101
116
  // Send a quick soothing acknowledgment before AI processing (order=1).
102
117
  // AI replies continue from order=2.
103
118
  const ackMsg = {
104
119
  msgId,
105
120
  identifier: 'chat_output',
106
- outParams: { content: pickSoothingReply(text), recording_id, order, is_finished: false },
121
+ outParams: { content: soothingPick(), recording_id, order, is_finished: false },
107
122
  };
108
123
  client.publish(outboundTopic, JSON.stringify(ackMsg));
109
124
  const peer = { kind: 'direct', id: credentials.toy_sn };
@@ -127,13 +142,120 @@ const folotoyChannel = {
127
142
  });
128
143
  // dispatch using dispatchReplyFromConfig (full agent capabilities including tools)
129
144
  void (async () => {
130
- const replyChunks = [];
145
+ // Sentence-level streaming: buffer incoming text and flush complete
146
+ // sentences (delimited by punctuation) to the toy immediately.
147
+ const sentenceDelimiterRe = sentenceSplitEnabled
148
+ ? new RegExp(`[${sentenceSplitDelimiters.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&')}]`)
149
+ : null;
150
+ let sentenceBuffer = '';
151
+ let totalSentChars = 0;
152
+ let firstSentenceFlushed = false;
153
+ let llmStarted = false;
154
+ // Soothing loop: check every 200ms if LLM has started responding.
155
+ // If not, send a soothing reply at a pace matching TTS playback
156
+ // (controlled by soothingLoopIntervalMs). Keeps running until LLM
157
+ // returns its first chunk — no fixed max count.
158
+ let soothingCheckTimer = null;
159
+ let lastSoothingSentAt = 0;
160
+ const stopSoothingLoop = () => {
161
+ if (soothingCheckTimer) {
162
+ clearInterval(soothingCheckTimer);
163
+ soothingCheckTimer = null;
164
+ }
165
+ };
166
+ if (soothingLoopEnabled) {
167
+ soothingCheckTimer = setInterval(() => {
168
+ if (llmStarted) {
169
+ stopSoothingLoop();
170
+ return;
171
+ }
172
+ const now = Date.now();
173
+ if (now - lastSoothingSentAt >= soothingLoopIntervalMs) {
174
+ lastSoothingSentAt = now;
175
+ order++;
176
+ const extraAck = {
177
+ msgId,
178
+ identifier: 'chat_output',
179
+ outParams: { content: soothingPick(), recording_id, order, is_finished: false },
180
+ };
181
+ client.publish(outboundTopic, JSON.stringify(extraAck));
182
+ }
183
+ }, 200);
184
+ }
185
+ // First sentence: split purely by punctuation (fast first response).
186
+ // Subsequent sentences: require a minimum length that grows with each
187
+ // sentence (base 20 chars, +10 per sentence), so later chunks are longer.
188
+ let sentenceCount = 0;
189
+ const minLenForSentence = () => {
190
+ if (sentenceCount === 0)
191
+ return 0; // first sentence: no minimum
192
+ return Math.min(100, 20 + (sentenceCount - 1) * 10);
193
+ };
194
+ const FORCE_FLUSH_LEN = 200; // force flush if buffer has no delimiter for this many chars
195
+ const publishSentence = (sentence) => {
196
+ sentenceCount++;
197
+ if (!firstSentenceFlushed)
198
+ firstSentenceFlushed = true;
199
+ order++;
200
+ totalSentChars += sentence.length;
201
+ const outMsg = {
202
+ msgId,
203
+ identifier: 'chat_output',
204
+ outParams: { content: sentence, recording_id, order, is_finished: false },
205
+ };
206
+ client.publish(outboundTopic, JSON.stringify(outMsg));
207
+ };
208
+ const flushSentences = () => {
209
+ if (!sentenceDelimiterRe)
210
+ return;
211
+ while (true) {
212
+ const minLen = minLenForSentence();
213
+ // Search for a delimiter that is at or beyond the minimum length
214
+ let idx = -1;
215
+ const searchFrom = Math.max(0, minLen - 1);
216
+ if (searchFrom < sentenceBuffer.length) {
217
+ const sub = sentenceBuffer.slice(searchFrom);
218
+ const match = sub.search(sentenceDelimiterRe);
219
+ if (match !== -1)
220
+ idx = searchFrom + match;
221
+ }
222
+ if (idx !== -1) {
223
+ const sentence = sentenceBuffer.slice(0, idx + 1).trim();
224
+ sentenceBuffer = sentenceBuffer.slice(idx + 1);
225
+ if (sentence)
226
+ publishSentence(sentence);
227
+ continue;
228
+ }
229
+ // No delimiter found — force flush if buffer exceeds limit
230
+ if (sentenceBuffer.length >= FORCE_FLUSH_LEN) {
231
+ // Try to break at the last space/comma for a cleaner cut
232
+ let cutIdx = FORCE_FLUSH_LEN;
233
+ const lastSpace = sentenceBuffer.lastIndexOf(' ', FORCE_FLUSH_LEN);
234
+ const lastComma = sentenceBuffer.lastIndexOf(',', FORCE_FLUSH_LEN);
235
+ const lastBreak = Math.max(lastSpace, lastComma);
236
+ if (lastBreak > FORCE_FLUSH_LEN / 2)
237
+ cutIdx = lastBreak + 1;
238
+ const sentence = sentenceBuffer.slice(0, cutIdx).trim();
239
+ sentenceBuffer = sentenceBuffer.slice(cutIdx);
240
+ if (sentence)
241
+ publishSentence(sentence);
242
+ continue;
243
+ }
244
+ break;
245
+ }
246
+ };
131
247
  const dispatcher = {
132
248
  sendToolResult: () => true,
133
249
  sendBlockReply: () => true,
134
250
  sendFinalReply: (payload) => {
135
- if (payload.text)
136
- replyChunks.push(payload.text);
251
+ if (payload.text) {
252
+ if (!llmStarted) {
253
+ llmStarted = true;
254
+ stopSoothingLoop();
255
+ }
256
+ sentenceBuffer += stripMarkdown(payload.text);
257
+ flushSentences();
258
+ }
137
259
  return true;
138
260
  },
139
261
  waitForIdle: async () => { },
@@ -149,50 +271,53 @@ const folotoyChannel = {
149
271
  dispatcher,
150
272
  }),
151
273
  });
152
- let finalText = replyChunks.join('');
153
- // Summarize if enabled and text exceeds threshold
154
- if (summaryEnabled && subagent && finalText.length > summaryMaxChars) {
155
- try {
156
- const sessionKey = `folotoy-summary-${accountId}-${Date.now()}`;
157
- const { runId } = await subagent.run({
158
- sessionKey,
159
- message: [
160
- `You are an assistant that summarizes texts concisely while keeping the most important information.`,
161
- `Summarize the text to approximately ${summaryMaxChars} characters.`,
162
- `Maintain the original tone and style. Reply only with the summary, without additional explanations.`,
163
- ``,
164
- `<text_to_summarize>`,
165
- finalText,
166
- `</text_to_summarize>`,
167
- ].join('\n'),
168
- deliver: false,
169
- });
170
- const result = await subagent.waitForRun({ runId, timeoutMs: 30_000 });
171
- if (result.status === 'ok') {
172
- const { messages } = await subagent.getSessionMessages({ sessionKey, limit: 1 });
173
- const lastMsg = messages[messages.length - 1];
174
- const summaryText = lastMsg?.content ?? lastMsg?.text;
175
- if (summaryText)
176
- finalText = summaryText;
274
+ // Flush remaining buffer (text after the last punctuation)
275
+ let remaining = sentenceBuffer.trim();
276
+ if (remaining) {
277
+ // Summarize remaining text if enabled and total response exceeds threshold
278
+ if (summaryEnabled && subagent && (totalSentChars + remaining.length) > summaryMaxChars) {
279
+ try {
280
+ const summarySessionKey = `folotoy-summary-${accountId}-${Date.now()}`;
281
+ const { runId } = await subagent.run({
282
+ sessionKey: summarySessionKey,
283
+ message: [
284
+ `You are an assistant that summarizes texts concisely while keeping the most important information.`,
285
+ `Summarize the text to approximately ${Math.max(50, summaryMaxChars - totalSentChars)} characters.`,
286
+ `Maintain the original tone and style. Reply only with the summary, without additional explanations.`,
287
+ ``,
288
+ `<text_to_summarize>`,
289
+ remaining,
290
+ `</text_to_summarize>`,
291
+ ].join('\n'),
292
+ deliver: false,
293
+ });
294
+ const result = await subagent.waitForRun({ runId, timeoutMs: 30_000 });
295
+ if (result.status === 'ok') {
296
+ const { messages } = await subagent.getSessionMessages({ sessionKey: summarySessionKey, limit: 1 });
297
+ const lastMsg = messages[messages.length - 1];
298
+ const summaryText = lastMsg?.content ?? lastMsg?.text;
299
+ if (summaryText)
300
+ remaining = summaryText;
301
+ }
302
+ await subagent.deleteSession({ sessionKey: summarySessionKey }).catch(() => { });
303
+ }
304
+ catch (err) {
305
+ log?.warn?.(`Summary failed, truncating text: ${String(err)}`);
306
+ const maxRemaining = Math.max(50, summaryMaxChars - totalSentChars);
307
+ remaining = `${remaining.slice(0, maxRemaining - 3)}...`;
177
308
  }
178
- await subagent.deleteSession({ sessionKey }).catch(() => { });
179
- }
180
- catch (err) {
181
- log?.warn?.(`Summary failed, truncating text: ${String(err)}`);
182
- finalText = `${finalText.slice(0, summaryMaxChars - 3)}...`;
183
309
  }
184
- }
185
- if (finalText) {
186
310
  order++;
187
311
  const outMsg = {
188
312
  msgId,
189
313
  identifier: 'chat_output',
190
- outParams: { content: finalText, recording_id, order, is_finished: false },
314
+ outParams: { content: remaining, recording_id, order, is_finished: false },
191
315
  };
192
316
  client.publish(outboundTopic, JSON.stringify(outMsg));
193
317
  }
194
318
  }
195
319
  finally {
320
+ stopSoothingLoop();
196
321
  order++;
197
322
  const finishMsg = {
198
323
  msgId,
@@ -232,7 +357,7 @@ const folotoyChannel = {
232
357
  const pad = (n) => String(n).padStart(2, '0');
233
358
  const nowLocal = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}T${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}${tzSuffix}`;
234
359
  return [
235
- `[FoloToy] This message is from a FoloToy toy (SN: ${sn}). Current time: ${nowLocal} (${tz}).`,
360
+ `[FoloToy] This message is from a FoloToy toy (SN: ${sn}). Current time: ${nowLocal} (${tz}). IMPORTANT: Your reply will be converted to speech (TTS). Use plain text only — no markdown, no bullet points, no numbered lists, no code blocks, no URLs. Write in a natural, conversational tone.`,
236
361
  [
237
362
  `To set reminders/timers, use the cron tool with action="add". IMPORTANT:`,
238
363
  `- schedule.at MUST use the same timezone offset as current time (${tzSuffix}), NEVER use "Z"`,
@@ -272,7 +397,7 @@ const folotoyChannel = {
272
397
  const notifMsg = {
273
398
  msgId,
274
399
  identifier: 'send_notification',
275
- outParams: { text },
400
+ outParams: { text: stripMarkdown(text) },
276
401
  };
277
402
  entry.client.publish(notificationTopic, JSON.stringify(notifMsg));
278
403
  return { channel: 'folotoy', messageId: String(msgId) };
@@ -289,7 +414,7 @@ export function sendNotification({ text, accountId }) {
289
414
  const notifMsg = {
290
415
  msgId,
291
416
  identifier: 'send_notification',
292
- outParams: { text },
417
+ outParams: { text: stripMarkdown(text) },
293
418
  };
294
419
  entry.client.publish(notificationTopic, JSON.stringify(notifMsg));
295
420
  return { channel: 'folotoy', messageId: String(msgId) };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -4,6 +4,16 @@
4
4
  * 12 intent categories, 5 candidates each — one is chosen at random.
5
5
  * Principles: convey "I heard you + I'm working on it", no time promises.
6
6
  */
7
+ /**
8
+ * Creates a soothing reply picker that guarantees no repeats until all
9
+ * candidates in the matched category are exhausted, then reshuffles.
10
+ *
11
+ * Usage:
12
+ * const picker = createSoothingPicker(text)
13
+ * picker() // first reply (no repeat)
14
+ * picker() // second reply (different from first)
15
+ */
16
+ export declare function createSoothingPicker(text?: string): () => string;
7
17
  /** Returns one randomly chosen soothing reply matching the input intent. */
8
- export declare function pickSoothingReply(text: string): string;
18
+ export declare function pickSoothingReply(text?: string): string;
9
19
  //# sourceMappingURL=soothing.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"soothing.d.ts","sourceRoot":"","sources":["../src/soothing.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA+IH,4EAA4E;AAC5E,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOtD"}
1
+ {"version":3,"file":"soothing.d.ts","sourceRoot":"","sources":["../src/soothing.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAoJH;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,MAAM,CAmBhE;AAED,4EAA4E;AAC5E,wBAAgB,iBAAiB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAEvD"}
package/dist/soothing.js CHANGED
@@ -139,16 +139,44 @@ const DEFAULT_CANDIDATES = [
139
139
  '嗯,给我一点时间想想',
140
140
  '让我好好想想...',
141
141
  ];
142
- function pickRandom(arr) {
143
- return arr[Math.floor(Math.random() * arr.length)];
142
+ /** Fisher-Yates shuffle (in-place) */
143
+ function shuffle(arr) {
144
+ for (let i = arr.length - 1; i > 0; i--) {
145
+ const j = Math.floor(Math.random() * (i + 1));
146
+ [arr[i], arr[j]] = [arr[j], arr[i]];
147
+ }
148
+ return arr;
144
149
  }
145
- /** Returns one randomly chosen soothing reply matching the input intent. */
146
- export function pickSoothingReply(text) {
147
- for (const rule of INTENT_RULES) {
148
- if (rule.pattern.test(text)) {
149
- return pickRandom(rule.candidates);
150
+ /**
151
+ * Creates a soothing reply picker that guarantees no repeats until all
152
+ * candidates in the matched category are exhausted, then reshuffles.
153
+ *
154
+ * Usage:
155
+ * const picker = createSoothingPicker(text)
156
+ * picker() // first reply (no repeat)
157
+ * picker() // second reply (different from first)
158
+ */
159
+ export function createSoothingPicker(text) {
160
+ let candidates;
161
+ if (text) {
162
+ for (const rule of INTENT_RULES) {
163
+ if (rule.pattern.test(text)) {
164
+ candidates = rule.candidates;
165
+ break;
166
+ }
150
167
  }
151
168
  }
152
- return pickRandom(DEFAULT_CANDIDATES);
169
+ const pool = candidates ?? DEFAULT_CANDIDATES;
170
+ let queue = [];
171
+ return () => {
172
+ if (queue.length === 0) {
173
+ queue = shuffle([...pool]);
174
+ }
175
+ return queue.pop();
176
+ };
177
+ }
178
+ /** Returns one randomly chosen soothing reply matching the input intent. */
179
+ export function pickSoothingReply(text) {
180
+ return createSoothingPicker(text)();
153
181
  }
154
182
  //# sourceMappingURL=soothing.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"soothing.js","sourceRoot":"","sources":["../src/soothing.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,YAAY,GAAqD;IACrE;QACE,SAAS;QACT,OAAO,EAAE,gBAAgB;QACzB,UAAU,EAAE;YACV,uBAAuB;YACvB,oBAAoB;YACpB,sBAAsB;YACtB,kBAAkB;YAClB,aAAa;SACd;KACF;IACD;QACE,SAAS;QACT,OAAO,EAAE,eAAe;QACxB,UAAU,EAAE;YACV,kBAAkB;YAClB,cAAc;YACd,gBAAgB;YAChB,oBAAoB;YACpB,gBAAgB;SACjB;KACF;IACD;QACE,UAAU;QACV,OAAO,EAAE,YAAY;QACrB,UAAU,EAAE;YACV,iBAAiB;YACjB,cAAc;YACd,kBAAkB;YAClB,eAAe;YACf,iBAAiB;SAClB;KACF;IACD;QACE,SAAS;QACT,OAAO,EAAE,WAAW;QACpB,UAAU,EAAE;YACV,cAAc;YACd,eAAe;YACf,aAAa;YACb,gBAAgB;YAChB,iBAAiB;SAClB;KACF;IACD;QACE,YAAY;QACZ,OAAO,EAAE,cAAc;QACvB,UAAU,EAAE;YACV,aAAa;YACb,cAAc;YACd,gBAAgB;YAChB,aAAa;YACb,YAAY;SACb;KACF;IACD;QACE,SAAS;QACT,OAAO,EAAE,gBAAgB;QACzB,UAAU,EAAE;YACV,aAAa;YACb,cAAc;YACd,iBAAiB;YACjB,oBAAoB;YACpB,cAAc;SACf;KACF;IACD;QACE,YAAY;QACZ,OAAO,EAAE,mBAAmB;QAC5B,UAAU,EAAE;YACV,eAAe;YACf,aAAa;YACb,iBAAiB;YACjB,cAAc;YACd,YAAY;SACb;KACF;IACD;QACE,YAAY;QACZ,OAAO,EAAE,kBAAkB;QAC3B,UAAU,EAAE;YACV,YAAY;YACZ,aAAa;YACb,WAAW;YACX,cAAc;YACd,UAAU;SACX;KACF;IACD;QACE,aAAa;QACb,OAAO,EAAE,yBAAyB;QAClC,UAAU,EAAE;YACV,eAAe;YACf,eAAe;YACf,gBAAgB;YAChB,eAAe;YACf,gBAAgB;SACjB;KACF;IACD;QACE,aAAa;QACb,OAAO,EAAE,oBAAoB;QAC7B,UAAU,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC;KAC9C;IACD;QACE,aAAa;QACb,OAAO,EAAE,mBAAmB;QAC5B,UAAU,EAAE;YACV,aAAa;YACb,WAAW;YACX,YAAY;YACZ,UAAU;YACV,YAAY;SACb;KACF;IACD;QACE,UAAU;QACV,OAAO,EAAE,oBAAoB;QAC7B,UAAU,EAAE;YACV,YAAY;YACZ,UAAU;YACV,UAAU;YACV,YAAY;YACZ,QAAQ;SACT;KACF;CACF,CAAA;AAED,MAAM,kBAAkB,GAAG;IACzB,UAAU;IACV,cAAc;IACd,SAAS;IACT,YAAY;IACZ,WAAW;CACZ,CAAA;AAED,SAAS,UAAU,CAAI,GAAQ;IAC7B,OAAO,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,CAAM,CAAA;AACzD,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;QAChC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC5B,OAAO,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;QACpC,CAAC;IACH,CAAC;IACD,OAAO,UAAU,CAAC,kBAAkB,CAAC,CAAA;AACvC,CAAC"}
1
+ {"version":3,"file":"soothing.js","sourceRoot":"","sources":["../src/soothing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Strip Markdown / rich-text formatting for TTS.
3
+ *
4
+ * Designed to be safe for streaming chunks — removes syntax characters
5
+ * individually rather than matching complete open/close patterns,
6
+ * so partial markdown across chunks is handled correctly.
7
+ * Plain text without markdown passes through unchanged.
8
+ */
9
+ export declare function stripMarkdown(text: string): string;
10
+ //# sourceMappingURL=strip-markdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strip-markdown.d.ts","sourceRoot":"","sources":["../src/strip-markdown.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAmClD"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Strip Markdown / rich-text formatting for TTS.
3
+ *
4
+ * Designed to be safe for streaming chunks — removes syntax characters
5
+ * individually rather than matching complete open/close patterns,
6
+ * so partial markdown across chunks is handled correctly.
7
+ * Plain text without markdown passes through unchanged.
8
+ */
9
+ export function stripMarkdown(text) {
10
+ return (text
11
+ // Code blocks (``` ... ```) — remove fences and content when complete
12
+ .replace(/```[\s\S]*?```/g, '')
13
+ // Remaining fences (incomplete code block in a chunk)
14
+ .replace(/```/g, '')
15
+ // Inline code backticks
16
+ .replace(/`/g, '')
17
+ // Images ![alt](url) — keep alt text
18
+ .replace(/!\[([^\]]*)\]\([^)]*\)/g, '$1')
19
+ // Links [text](url) — keep text
20
+ .replace(/\[([^\]]*)\]\([^)]*\)/g, '$1')
21
+ // Bare URLs (https://...)
22
+ .replace(/https?:\/\/\S+/g, '')
23
+ // Heading markers
24
+ .replace(/^#{1,6}\s*/gm, '')
25
+ // Bold/italic/strikethrough markers (*, _, ~)
26
+ .replace(/[*_~]+/g, '')
27
+ // Blockquote markers
28
+ .replace(/^>\s?/gm, '')
29
+ // Horizontal rules (---, ***, ___)
30
+ .replace(/^[-]{3,}\s*$/gm, '')
31
+ // Unordered list markers (- / + at line start, * already removed above)
32
+ .replace(/^[\s]*[-+]\s+/gm, '')
33
+ // Ordered list markers (1. / 2. ...)
34
+ .replace(/^[\s]*\d+\.\s+/gm, '')
35
+ // HTML tags
36
+ .replace(/<[^>]+>/g, '')
37
+ // Collapse multiple spaces
38
+ .replace(/ {2,}/g, ' ')
39
+ // Collapse multiple blank lines
40
+ .replace(/\n{3,}/g, '\n\n')
41
+ .trim());
42
+ }
43
+ //# sourceMappingURL=strip-markdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strip-markdown.js","sourceRoot":"","sources":["../src/strip-markdown.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO,CACL,IAAI;QACF,sEAAsE;SACrE,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC;QAC/B,sDAAsD;SACrD,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;QACpB,wBAAwB;SACvB,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;QAClB,qCAAqC;SACpC,OAAO,CAAC,yBAAyB,EAAE,IAAI,CAAC;QACzC,gCAAgC;SAC/B,OAAO,CAAC,wBAAwB,EAAE,IAAI,CAAC;QACxC,0BAA0B;SACzB,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC;QAC/B,kBAAkB;SACjB,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC;QAC5B,8CAA8C;SAC7C,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC;QACvB,qBAAqB;SACpB,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC;QACvB,mCAAmC;SAClC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC;QAC9B,wEAAwE;SACvE,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC;QAC/B,qCAAqC;SACpC,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC;QAChC,YAAY;SACX,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;QACxB,2BAA2B;SAC1B,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC;QACvB,gCAAgC;SAC/B,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC;SAC1B,IAAI,EAAE,CACV,CAAA;AACH,CAAC"}
@@ -2,7 +2,7 @@
2
2
  "id": "folotoy-openclaw-plugin",
3
3
  "name": "FoloToy",
4
4
  "description": "Empower your FoloToy with OpenClaw AI capabilities.",
5
- "version": "0.5.9",
5
+ "version": "0.6.2",
6
6
  "channels": ["folotoy"],
7
7
  "configSchema": {
8
8
  "type": "object",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@folotoy/folotoy-openclaw-plugin",
3
- "version": "0.5.9",
3
+ "version": "0.6.2",
4
4
  "description": "Empower your FoloToy with OpenClaw AI capabilities.",
5
5
  "keywords": [
6
6
  "folotoy",
@@ -55,6 +55,11 @@ let gotSoothing = false
55
55
  let gotReply = false
56
56
  let gotFinish = false
57
57
  let gotNotification = false
58
+ let soothingCount = 0
59
+ let firstReplyAt = 0
60
+ const soothingTimestamps = []
61
+ const replyTimestamps = []
62
+ const startTime = Date.now()
58
63
 
59
64
  function validateOutboundMessage(msg) {
60
65
  if (msg.identifier !== 'chat_output') {
@@ -90,8 +95,8 @@ function printSummary() {
90
95
  console.log('='.repeat(60))
91
96
 
92
97
  const checks = [
93
- ['Soothing ack (order=1)', gotSoothing],
94
- ['AI reply (order=2+)', gotReply],
98
+ ['Soothing ack received', gotSoothing],
99
+ ['AI reply received', gotReply],
95
100
  ['Finish message (is_finished=true)', gotFinish],
96
101
  ]
97
102
 
@@ -99,6 +104,27 @@ function printSummary() {
99
104
  console.log(` ${ok ? '✅' : '❌'} ${label}`)
100
105
  }
101
106
 
107
+ console.log('\n Soothing stats:')
108
+ console.log(` Total soothing messages: ${soothingCount}`)
109
+ if (soothingTimestamps.length > 0) {
110
+ console.log(` Soothing timestamps: ${soothingTimestamps.map(t => `+${t}ms`).join(', ')}`)
111
+ if (soothingTimestamps.length > 1) {
112
+ const intervals = soothingTimestamps.slice(1).map((t, i) => t - soothingTimestamps[i])
113
+ console.log(` Intervals between soothing: ${intervals.map(t => `${t}ms`).join(', ')}`)
114
+ }
115
+ }
116
+ if (firstReplyAt) {
117
+ console.log(` First LLM reply at: +${firstReplyAt}ms`)
118
+ const lastSoothing = soothingTimestamps[soothingTimestamps.length - 1] || 0
119
+ if (lastSoothing && firstReplyAt > lastSoothing) {
120
+ console.log(` Gap (last soothing → first reply): ${firstReplyAt - lastSoothing}ms`)
121
+ }
122
+ console.log(` No soothing after first LLM reply: ${soothingTimestamps.every(t => t < firstReplyAt) ? '✅ YES' : '❌ NO'}`)
123
+ }
124
+ if (replyTimestamps.length > 0) {
125
+ console.log(` Total LLM reply chunks: ${replyTimestamps.length}`)
126
+ }
127
+
102
128
  if (mode === 'reminder') {
103
129
  console.log(` ${gotNotification ? '✅' : 'ℹ️ '} Notification on event/post${gotNotification ? '' : ' (not received within timeout — may arrive later)'}`)
104
130
  }
@@ -157,12 +183,29 @@ client.on('message', (topic, payload) => {
157
183
 
158
184
  if (topic === outboundTopic) {
159
185
  const p = msg.outParams
186
+ const elapsed = Date.now() - startTime
160
187
  const label = p?.is_finished ? 'FINISH' : `REPLY(order=${p?.order})`
161
- console.log(`\n[OUTBOUND ${label}] ${p?.content || '(empty)'}`)
188
+ console.log(`\n[OUTBOUND ${label}] +${elapsed}ms | ${p?.content || '(empty)'}`)
162
189
  validateOutboundMessage(msg)
163
190
 
164
- if (p?.order === 1 && !p?.is_finished) gotSoothing = true
165
- if (p?.order >= 2 && !p?.is_finished && p?.content) gotReply = true
191
+ if (!p?.is_finished && p?.content) {
192
+ // Detect soothing vs LLM reply: soothing messages arrive before LLM starts
193
+ if (!firstReplyAt) {
194
+ // All messages before the first LLM sentence are soothing
195
+ // We'll determine this retrospectively, for now track all
196
+ }
197
+ // Heuristic: soothing messages are short Chinese phrases ending with ... or ~
198
+ const isSoothing = /[.…~]$/.test(p.content) && p.content.length < 30
199
+ if (isSoothing) {
200
+ soothingCount++
201
+ soothingTimestamps.push(elapsed)
202
+ gotSoothing = true
203
+ } else {
204
+ if (!firstReplyAt) firstReplyAt = elapsed
205
+ replyTimestamps.push(elapsed)
206
+ gotReply = true
207
+ }
208
+ }
166
209
  if (p?.is_finished) {
167
210
  gotFinish = true
168
211
  if (mode === 'chat') {
package/src/config.ts CHANGED
@@ -30,13 +30,21 @@ export type FlatChannelConfig = {
30
30
  mqtt_port?: number
31
31
  summary_enabled?: boolean
32
32
  summary_max_chars?: number
33
+ sentence_split_enabled?: boolean
34
+ sentence_split_delimiters?: string
35
+ soothing_loop_enabled?: boolean
36
+ soothing_loop_interval_ms?: number
33
37
  }
34
38
 
35
39
  export const DEFAULT_API_URL = 'https://api.folotoy.cn'
36
- export const DEFAULT_MQTT_HOST = process.env.FOLOTOY_MQTT_HOST ?? 'f.qrc92.cn'
40
+ export const DEFAULT_MQTT_HOST = process.env.FOLOTOY_MQTT_HOST ?? 'f.folotoy.cn'
37
41
  export const DEFAULT_MQTT_PORT = 1883
38
42
  export const DEFAULT_SUMMARY_ENABLED = true
39
43
  export const DEFAULT_SUMMARY_MAX_CHARS = 200
44
+ export const DEFAULT_SENTENCE_SPLIT_ENABLED = true
45
+ export const DEFAULT_SENTENCE_SPLIT_DELIMITERS = '!。?;!.?;~'
46
+ export const DEFAULT_SOOTHING_LOOP_ENABLED = true
47
+ export const DEFAULT_SOOTHING_LOOP_INTERVAL_MS = 3000
40
48
 
41
49
  export function flatToPluginConfig(flat: FlatChannelConfig): PluginConfig {
42
50
  const flow = flat.flow ?? 'direct'
package/src/index.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import type { OpenClawPluginApi, ChannelPlugin, PluginRuntime } from 'openclaw/plugin-sdk/core'
2
2
  import { resolveCredentials, createMqttClient, buildInboundTopic, buildOutboundTopic, buildNotificationTopic } from './mqtt.js'
3
- import { pickSoothingReply } from './soothing.js'
4
- import { DEFAULT_MQTT_HOST, DEFAULT_MQTT_PORT, DEFAULT_SUMMARY_ENABLED, DEFAULT_SUMMARY_MAX_CHARS, flatToPluginConfig } from './config.js'
3
+ import { createSoothingPicker } from './soothing.js'
4
+ import { stripMarkdown } from './strip-markdown.js'
5
+ import { DEFAULT_MQTT_HOST, DEFAULT_MQTT_PORT, DEFAULT_SUMMARY_ENABLED, DEFAULT_SUMMARY_MAX_CHARS, DEFAULT_SENTENCE_SPLIT_ENABLED, DEFAULT_SENTENCE_SPLIT_DELIMITERS, DEFAULT_SOOTHING_LOOP_ENABLED, DEFAULT_SOOTHING_LOOP_INTERVAL_MS, flatToPluginConfig } from './config.js'
5
6
  import type { FlatChannelConfig } from './config.js'
6
7
  import type { MqttClient } from 'mqtt'
7
8
 
@@ -54,6 +55,10 @@ const folotoyChannel: ChannelPlugin<FlatChannelConfig> = {
54
55
  mqtt_port: { type: 'number', default: DEFAULT_MQTT_PORT },
55
56
  summary_enabled: { type: 'boolean', default: DEFAULT_SUMMARY_ENABLED },
56
57
  summary_max_chars: { type: 'number', default: DEFAULT_SUMMARY_MAX_CHARS },
58
+ sentence_split_enabled: { type: 'boolean', default: DEFAULT_SENTENCE_SPLIT_ENABLED },
59
+ sentence_split_delimiters: { type: 'string', default: DEFAULT_SENTENCE_SPLIT_DELIMITERS },
60
+ soothing_loop_enabled: { type: 'boolean', default: DEFAULT_SOOTHING_LOOP_ENABLED },
61
+ soothing_loop_interval_ms: { type: 'number', default: DEFAULT_SOOTHING_LOOP_INTERVAL_MS },
57
62
  },
58
63
  },
59
64
  uiHints: {
@@ -66,6 +71,10 @@ const folotoyChannel: ChannelPlugin<FlatChannelConfig> = {
66
71
  mqtt_port: { label: 'MQTT Port' },
67
72
  summary_enabled: { label: 'Enable Summary' },
68
73
  summary_max_chars: { label: 'Summary Max Characters' },
74
+ sentence_split_enabled: { label: 'Enable Sentence Splitting' },
75
+ sentence_split_delimiters: { label: 'Sentence Delimiters' },
76
+ soothing_loop_enabled: { label: 'Enable Soothing Loop' },
77
+ soothing_loop_interval_ms: { label: 'Soothing Loop Interval (ms)' },
69
78
  },
70
79
  },
71
80
  config: {
@@ -115,6 +124,10 @@ const folotoyChannel: ChannelPlugin<FlatChannelConfig> = {
115
124
 
116
125
  const summaryEnabled = account.summary_enabled ?? DEFAULT_SUMMARY_ENABLED
117
126
  const summaryMaxChars = account.summary_max_chars ?? DEFAULT_SUMMARY_MAX_CHARS
127
+ const sentenceSplitEnabled = account.sentence_split_enabled ?? DEFAULT_SENTENCE_SPLIT_ENABLED
128
+ const sentenceSplitDelimiters = account.sentence_split_delimiters ?? DEFAULT_SENTENCE_SPLIT_DELIMITERS
129
+ const soothingLoopEnabled = account.soothing_loop_enabled ?? DEFAULT_SOOTHING_LOOP_ENABLED
130
+ const soothingLoopIntervalMs = account.soothing_loop_interval_ms ?? DEFAULT_SOOTHING_LOOP_INTERVAL_MS
118
131
 
119
132
  client.on('message', (_topic, payload) => {
120
133
  let msg: InboundMessage
@@ -128,12 +141,15 @@ const folotoyChannel: ChannelPlugin<FlatChannelConfig> = {
128
141
  const { msgId, inputParams: { text, recording_id } } = msg
129
142
  let order = 1
130
143
 
144
+ // Create a per-message picker that cycles through candidates without repeats
145
+ const soothingPick = createSoothingPicker(text)
146
+
131
147
  // Send a quick soothing acknowledgment before AI processing (order=1).
132
148
  // AI replies continue from order=2.
133
149
  const ackMsg: OutboundMessage = {
134
150
  msgId,
135
151
  identifier: 'chat_output',
136
- outParams: { content: pickSoothingReply(text), recording_id, order, is_finished: false },
152
+ outParams: { content: soothingPick(), recording_id, order, is_finished: false },
137
153
  }
138
154
  client.publish(outboundTopic, JSON.stringify(ackMsg))
139
155
 
@@ -160,12 +176,116 @@ const folotoyChannel: ChannelPlugin<FlatChannelConfig> = {
160
176
 
161
177
  // dispatch using dispatchReplyFromConfig (full agent capabilities including tools)
162
178
  void (async () => {
163
- const replyChunks: string[] = []
179
+ // Sentence-level streaming: buffer incoming text and flush complete
180
+ // sentences (delimited by punctuation) to the toy immediately.
181
+ const sentenceDelimiterRe = sentenceSplitEnabled
182
+ ? new RegExp(`[${sentenceSplitDelimiters.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&')}]`)
183
+ : null
184
+ let sentenceBuffer = ''
185
+ let totalSentChars = 0
186
+ let firstSentenceFlushed = false
187
+ let llmStarted = false
188
+
189
+ // Soothing loop: check every 200ms if LLM has started responding.
190
+ // If not, send a soothing reply at a pace matching TTS playback
191
+ // (controlled by soothingLoopIntervalMs). Keeps running until LLM
192
+ // returns its first chunk — no fixed max count.
193
+ let soothingCheckTimer: ReturnType<typeof setInterval> | null = null
194
+ let lastSoothingSentAt = 0
195
+ const stopSoothingLoop = () => {
196
+ if (soothingCheckTimer) { clearInterval(soothingCheckTimer); soothingCheckTimer = null }
197
+ }
198
+ if (soothingLoopEnabled) {
199
+ soothingCheckTimer = setInterval(() => {
200
+ if (llmStarted) {
201
+ stopSoothingLoop()
202
+ return
203
+ }
204
+ const now = Date.now()
205
+ if (now - lastSoothingSentAt >= soothingLoopIntervalMs) {
206
+ lastSoothingSentAt = now
207
+ order++
208
+ const extraAck: OutboundMessage = {
209
+ msgId,
210
+ identifier: 'chat_output',
211
+ outParams: { content: soothingPick(), recording_id, order, is_finished: false },
212
+ }
213
+ client.publish(outboundTopic, JSON.stringify(extraAck))
214
+ }
215
+ }, 200)
216
+ }
217
+
218
+ // First sentence: split purely by punctuation (fast first response).
219
+ // Subsequent sentences: require a minimum length that grows with each
220
+ // sentence (base 20 chars, +10 per sentence), so later chunks are longer.
221
+ let sentenceCount = 0
222
+ const minLenForSentence = () => {
223
+ if (sentenceCount === 0) return 0 // first sentence: no minimum
224
+ return Math.min(100, 20 + (sentenceCount - 1) * 10)
225
+ }
226
+
227
+ const FORCE_FLUSH_LEN = 200 // force flush if buffer has no delimiter for this many chars
228
+
229
+ const publishSentence = (sentence: string) => {
230
+ sentenceCount++
231
+ if (!firstSentenceFlushed) firstSentenceFlushed = true
232
+ order++
233
+ totalSentChars += sentence.length
234
+ const outMsg: OutboundMessage = {
235
+ msgId,
236
+ identifier: 'chat_output',
237
+ outParams: { content: sentence, recording_id, order, is_finished: false },
238
+ }
239
+ client.publish(outboundTopic, JSON.stringify(outMsg))
240
+ }
241
+
242
+ const flushSentences = () => {
243
+ if (!sentenceDelimiterRe) return
244
+ while (true) {
245
+ const minLen = minLenForSentence()
246
+ // Search for a delimiter that is at or beyond the minimum length
247
+ let idx = -1
248
+ const searchFrom = Math.max(0, minLen - 1)
249
+ if (searchFrom < sentenceBuffer.length) {
250
+ const sub = sentenceBuffer.slice(searchFrom)
251
+ const match = sub.search(sentenceDelimiterRe)
252
+ if (match !== -1) idx = searchFrom + match
253
+ }
254
+ if (idx !== -1) {
255
+ const sentence = sentenceBuffer.slice(0, idx + 1).trim()
256
+ sentenceBuffer = sentenceBuffer.slice(idx + 1)
257
+ if (sentence) publishSentence(sentence)
258
+ continue
259
+ }
260
+ // No delimiter found — force flush if buffer exceeds limit
261
+ if (sentenceBuffer.length >= FORCE_FLUSH_LEN) {
262
+ // Try to break at the last space/comma for a cleaner cut
263
+ let cutIdx = FORCE_FLUSH_LEN
264
+ const lastSpace = sentenceBuffer.lastIndexOf(' ', FORCE_FLUSH_LEN)
265
+ const lastComma = sentenceBuffer.lastIndexOf(',', FORCE_FLUSH_LEN)
266
+ const lastBreak = Math.max(lastSpace, lastComma)
267
+ if (lastBreak > FORCE_FLUSH_LEN / 2) cutIdx = lastBreak + 1
268
+ const sentence = sentenceBuffer.slice(0, cutIdx).trim()
269
+ sentenceBuffer = sentenceBuffer.slice(cutIdx)
270
+ if (sentence) publishSentence(sentence)
271
+ continue
272
+ }
273
+ break
274
+ }
275
+ }
276
+
164
277
  const dispatcher = {
165
278
  sendToolResult: () => true,
166
279
  sendBlockReply: () => true,
167
280
  sendFinalReply: (payload: { text?: string }) => {
168
- if (payload.text) replyChunks.push(payload.text)
281
+ if (payload.text) {
282
+ if (!llmStarted) {
283
+ llmStarted = true
284
+ stopSoothingLoop()
285
+ }
286
+ sentenceBuffer += stripMarkdown(payload.text)
287
+ flushSentences()
288
+ }
169
289
  return true
170
290
  },
171
291
  waitForIdle: async () => {},
@@ -183,49 +303,51 @@ const folotoyChannel: ChannelPlugin<FlatChannelConfig> = {
183
303
  }),
184
304
  })
185
305
 
186
- let finalText = replyChunks.join('')
187
-
188
- // Summarize if enabled and text exceeds threshold
189
- if (summaryEnabled && subagent && finalText.length > summaryMaxChars) {
190
- try {
191
- const sessionKey = `folotoy-summary-${accountId}-${Date.now()}`
192
- const { runId } = await subagent.run({
193
- sessionKey,
194
- message: [
195
- `You are an assistant that summarizes texts concisely while keeping the most important information.`,
196
- `Summarize the text to approximately ${summaryMaxChars} characters.`,
197
- `Maintain the original tone and style. Reply only with the summary, without additional explanations.`,
198
- ``,
199
- `<text_to_summarize>`,
200
- finalText,
201
- `</text_to_summarize>`,
202
- ].join('\n'),
203
- deliver: false,
204
- })
205
- const result = await subagent.waitForRun({ runId, timeoutMs: 30_000 })
206
- if (result.status === 'ok') {
207
- const { messages } = await subagent.getSessionMessages({ sessionKey, limit: 1 })
208
- const lastMsg = messages[messages.length - 1] as { content?: string; text?: string } | undefined
209
- const summaryText = lastMsg?.content ?? lastMsg?.text
210
- if (summaryText) finalText = summaryText
306
+ // Flush remaining buffer (text after the last punctuation)
307
+ let remaining = sentenceBuffer.trim()
308
+ if (remaining) {
309
+ // Summarize remaining text if enabled and total response exceeds threshold
310
+ if (summaryEnabled && subagent && (totalSentChars + remaining.length) > summaryMaxChars) {
311
+ try {
312
+ const summarySessionKey = `folotoy-summary-${accountId}-${Date.now()}`
313
+ const { runId } = await subagent.run({
314
+ sessionKey: summarySessionKey,
315
+ message: [
316
+ `You are an assistant that summarizes texts concisely while keeping the most important information.`,
317
+ `Summarize the text to approximately ${Math.max(50, summaryMaxChars - totalSentChars)} characters.`,
318
+ `Maintain the original tone and style. Reply only with the summary, without additional explanations.`,
319
+ ``,
320
+ `<text_to_summarize>`,
321
+ remaining,
322
+ `</text_to_summarize>`,
323
+ ].join('\n'),
324
+ deliver: false,
325
+ })
326
+ const result = await subagent.waitForRun({ runId, timeoutMs: 30_000 })
327
+ if (result.status === 'ok') {
328
+ const { messages } = await subagent.getSessionMessages({ sessionKey: summarySessionKey, limit: 1 })
329
+ const lastMsg = messages[messages.length - 1] as { content?: string; text?: string } | undefined
330
+ const summaryText = lastMsg?.content ?? lastMsg?.text
331
+ if (summaryText) remaining = summaryText
332
+ }
333
+ await subagent.deleteSession({ sessionKey: summarySessionKey }).catch(() => {})
334
+ } catch (err) {
335
+ log?.warn?.(`Summary failed, truncating text: ${String(err)}`)
336
+ const maxRemaining = Math.max(50, summaryMaxChars - totalSentChars)
337
+ remaining = `${remaining.slice(0, maxRemaining - 3)}...`
211
338
  }
212
- await subagent.deleteSession({ sessionKey }).catch(() => {})
213
- } catch (err) {
214
- log?.warn?.(`Summary failed, truncating text: ${String(err)}`)
215
- finalText = `${finalText.slice(0, summaryMaxChars - 3)}...`
216
339
  }
217
- }
218
340
 
219
- if (finalText) {
220
341
  order++
221
342
  const outMsg: OutboundMessage = {
222
343
  msgId,
223
344
  identifier: 'chat_output',
224
- outParams: { content: finalText, recording_id, order, is_finished: false },
345
+ outParams: { content: remaining, recording_id, order, is_finished: false },
225
346
  }
226
347
  client.publish(outboundTopic, JSON.stringify(outMsg))
227
348
  }
228
349
  } finally {
350
+ stopSoothingLoop()
229
351
  order++
230
352
  const finishMsg: OutboundMessage = {
231
353
  msgId,
@@ -268,7 +390,7 @@ const folotoyChannel: ChannelPlugin<FlatChannelConfig> = {
268
390
  const pad = (n: number) => String(n).padStart(2, '0')
269
391
  const nowLocal = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}T${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}${tzSuffix}`
270
392
  return [
271
- `[FoloToy] This message is from a FoloToy toy (SN: ${sn}). Current time: ${nowLocal} (${tz}).`,
393
+ `[FoloToy] This message is from a FoloToy toy (SN: ${sn}). Current time: ${nowLocal} (${tz}). IMPORTANT: Your reply will be converted to speech (TTS). Use plain text only — no markdown, no bullet points, no numbered lists, no code blocks, no URLs. Write in a natural, conversational tone.`,
272
394
  [
273
395
  `To set reminders/timers, use the cron tool with action="add". IMPORTANT:`,
274
396
  `- schedule.at MUST use the same timezone offset as current time (${tzSuffix}), NEVER use "Z"`,
@@ -309,7 +431,7 @@ const folotoyChannel: ChannelPlugin<FlatChannelConfig> = {
309
431
  const notifMsg: NotificationMessage = {
310
432
  msgId,
311
433
  identifier: 'send_notification',
312
- outParams: { text },
434
+ outParams: { text: stripMarkdown(text) },
313
435
  }
314
436
  entry.client.publish(notificationTopic, JSON.stringify(notifMsg))
315
437
  return { channel: 'folotoy', messageId: String(msgId) }
@@ -327,7 +449,7 @@ export function sendNotification({ text, accountId }: { text: string; accountId?
327
449
  const notifMsg: NotificationMessage = {
328
450
  msgId,
329
451
  identifier: 'send_notification',
330
- outParams: { text },
452
+ outParams: { text: stripMarkdown(text) },
331
453
  }
332
454
  entry.client.publish(notificationTopic, JSON.stringify(notifMsg))
333
455
  return { channel: 'folotoy', messageId: String(msgId) }
package/src/soothing.ts CHANGED
@@ -142,16 +142,46 @@ const DEFAULT_CANDIDATES = [
142
142
  '让我好好想想...',
143
143
  ]
144
144
 
145
- function pickRandom<T>(arr: T[]): T {
146
- return arr[Math.floor(Math.random() * arr.length)] as T
145
+ /** Fisher-Yates shuffle (in-place) */
146
+ function shuffle<T>(arr: T[]): T[] {
147
+ for (let i = arr.length - 1; i > 0; i--) {
148
+ const j = Math.floor(Math.random() * (i + 1))
149
+ ;[arr[i], arr[j]] = [arr[j]!, arr[i]!]
150
+ }
151
+ return arr
147
152
  }
148
153
 
149
- /** Returns one randomly chosen soothing reply matching the input intent. */
150
- export function pickSoothingReply(text: string): string {
151
- for (const rule of INTENT_RULES) {
152
- if (rule.pattern.test(text)) {
153
- return pickRandom(rule.candidates)
154
+ /**
155
+ * Creates a soothing reply picker that guarantees no repeats until all
156
+ * candidates in the matched category are exhausted, then reshuffles.
157
+ *
158
+ * Usage:
159
+ * const picker = createSoothingPicker(text)
160
+ * picker() // first reply (no repeat)
161
+ * picker() // second reply (different from first)
162
+ */
163
+ export function createSoothingPicker(text?: string): () => string {
164
+ let candidates: string[] | undefined
165
+ if (text) {
166
+ for (const rule of INTENT_RULES) {
167
+ if (rule.pattern.test(text)) {
168
+ candidates = rule.candidates
169
+ break
170
+ }
154
171
  }
155
172
  }
156
- return pickRandom(DEFAULT_CANDIDATES)
173
+ const pool = candidates ?? DEFAULT_CANDIDATES
174
+
175
+ let queue: string[] = []
176
+ return () => {
177
+ if (queue.length === 0) {
178
+ queue = shuffle([...pool])
179
+ }
180
+ return queue.pop()!
181
+ }
182
+ }
183
+
184
+ /** Returns one randomly chosen soothing reply matching the input intent. */
185
+ export function pickSoothingReply(text?: string): string {
186
+ return createSoothingPicker(text)()
157
187
  }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Strip Markdown / rich-text formatting for TTS.
3
+ *
4
+ * Designed to be safe for streaming chunks — removes syntax characters
5
+ * individually rather than matching complete open/close patterns,
6
+ * so partial markdown across chunks is handled correctly.
7
+ * Plain text without markdown passes through unchanged.
8
+ */
9
+ export function stripMarkdown(text: string): string {
10
+ return (
11
+ text
12
+ // Code blocks (``` ... ```) — remove fences and content when complete
13
+ .replace(/```[\s\S]*?```/g, '')
14
+ // Remaining fences (incomplete code block in a chunk)
15
+ .replace(/```/g, '')
16
+ // Inline code backticks
17
+ .replace(/`/g, '')
18
+ // Images ![alt](url) — keep alt text
19
+ .replace(/!\[([^\]]*)\]\([^)]*\)/g, '$1')
20
+ // Links [text](url) — keep text
21
+ .replace(/\[([^\]]*)\]\([^)]*\)/g, '$1')
22
+ // Bare URLs (https://...)
23
+ .replace(/https?:\/\/\S+/g, '')
24
+ // Heading markers
25
+ .replace(/^#{1,6}\s*/gm, '')
26
+ // Bold/italic/strikethrough markers (*, _, ~)
27
+ .replace(/[*_~]+/g, '')
28
+ // Blockquote markers
29
+ .replace(/^>\s?/gm, '')
30
+ // Horizontal rules (---, ***, ___)
31
+ .replace(/^[-]{3,}\s*$/gm, '')
32
+ // Unordered list markers (- / + at line start, * already removed above)
33
+ .replace(/^[\s]*[-+]\s+/gm, '')
34
+ // Ordered list markers (1. / 2. ...)
35
+ .replace(/^[\s]*\d+\.\s+/gm, '')
36
+ // HTML tags
37
+ .replace(/<[^>]+>/g, '')
38
+ // Collapse multiple spaces
39
+ .replace(/ {2,}/g, ' ')
40
+ // Collapse multiple blank lines
41
+ .replace(/\n{3,}/g, '\n\n')
42
+ .trim()
43
+ )
44
+ }