@chatpanel/events 0.18.1 → 0.19.0

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.
Files changed (2) hide show
  1. package/package.json +2 -2
  2. package/schedule.js +54 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@chatpanel/events",
3
- "version": "0.18.1",
4
- "description": "The canonical ChatPanel event-log and capability contracts \u2014 typed durable facts, clock-free deterministic linearization, schema upcasting, and the invariants the replay harness asserts. Pure, dependency-free ESM shared by the ChatPanel extension, gateway and bridge.",
3
+ "version": "0.19.0",
4
+ "description": "The canonical ChatPanel event-log and capability contracts — typed durable facts, clock-free deterministic linearization, schema upcasting, and the invariants the replay harness asserts. Pure, dependency-free ESM shared by the ChatPanel extension, gateway and bridge.",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "exports": {
package/schedule.js CHANGED
@@ -187,6 +187,60 @@ function speakerAllowed(want, speaker, ctx) {
187
187
  return true;
188
188
  }
189
189
 
190
+ /**
191
+ * Words a sentence does not end on.
192
+ *
193
+ * Not a grammar: a short list of the tails that mean "the speaker is mid-thought". A caption
194
+ * ending on one of these is a line that has not finished arriving.
195
+ */
196
+ export const DANGLING_TAILS = Object.freeze(new Set([
197
+ // conjunctions and subordinators — the reason, the contrast, the condition all come AFTER
198
+ 'and', 'or', 'but', 'so', 'because', 'cause', 'cos', 'since', 'although', 'though', 'while',
199
+ 'whereas', 'unless', 'until', 'if', 'when', 'whenever', 'that', 'which', 'who', 'whom',
200
+ 'whose', 'than', 'as', 'like',
201
+ // prepositions and particles
202
+ 'to', 'of', 'in', 'on', 'at', 'by', 'for', 'from', 'with', 'without', 'about', 'into',
203
+ 'onto', 'over', 'under', 'between', 'through', 'during', 'against', 'per',
204
+ // determiners that must be followed by something. Only the ones that genuinely cannot end
205
+ // a sentence — 'this', 'some' and 'both' all can ("take both"), so they are not here.
206
+ 'a', 'an', 'the', 'my', 'our', 'your', 'their', 'its',
207
+ // auxiliaries and copulas left hanging
208
+ 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'am', 'do', 'does', 'did', 'have', 'has',
209
+ 'had', 'will', 'would', 'can', 'could', 'should', 'shall', 'may', 'might', 'must',
210
+ // fillers a speaker trails off on. Pronouns are NOT here: "we should ship it" is a whole
211
+ // thought, and treating every object pronoun as a trailing word made the common case wait.
212
+ 'um', 'uh', 'er',
213
+ ]));
214
+
215
+ /**
216
+ * Does this line read as a FINISHED thought?
217
+ *
218
+ * A live caption is delivered while it is still being spoken and GROWS across flushes, so a
219
+ * trigger that matched can be holding half a sentence: "the product is just amazing because"
220
+ * — with the reason, the only part worth acting on, still unsaid. The caller uses this to
221
+ * decide whether to read the transcript now or wait for the rest of it.
222
+ *
223
+ * DEFAULT TRUE, deliberately. Speech-to-text frequently emits no punctuation at all, so
224
+ * requiring a full stop would make every job wait every time — and the complaint this exists
225
+ * to fix is about missing context, not about speed. Only a line that ends the way an
226
+ * unfinished one ends is treated as unfinished.
227
+ *
228
+ * Pure and synchronous, like every other predicate in this file: the waiting is the caller's
229
+ * problem, and it is the only part that needs a platform.
230
+ */
231
+ export function utteranceLooksComplete(text) {
232
+ const t = String(text || '').trim();
233
+ if (!t) return false;
234
+ // Sentence-final punctuation, optionally inside a closing quote or bracket.
235
+ if (/[.!?…]["'\u2019\u201d)\]]*$/.test(t)) return true;
236
+ // A comma, dash or colon at the end is a speaker who is explicitly not done.
237
+ if (/[,;:\-\u2013\u2014]$/.test(t)) return false;
238
+ const tokens = t.toLowerCase().match(/[\p{L}\p{N}']+/gu) || [];
239
+ const last = tokens[tokens.length - 1];
240
+ if (!last) return true;
241
+ return !DANGLING_TAILS.has(last);
242
+ }
243
+
190
244
  export const timerTrigger = defineTrigger({
191
245
  id: 'timer:schedule',
192
246
  label: 'On a schedule',