@chatpanel/events 0.27.0 → 0.28.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.
- package/package.json +1 -1
- package/schedule.js +5 -0
- package/voice-intents.js +45 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chatpanel/events",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
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.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
package/schedule.js
CHANGED
|
@@ -222,6 +222,11 @@ export const DANGLING_TAILS = Object.freeze(new Set([
|
|
|
222
222
|
'and', 'or', 'but', 'so', 'because', 'cause', 'cos', 'since', 'although', 'though', 'while',
|
|
223
223
|
'whereas', 'unless', 'until', 'if', 'when', 'whenever', 'that', 'which', 'who', 'whom',
|
|
224
224
|
'whose', 'than', 'as', 'like',
|
|
225
|
+
// 'what' ends a clause only in a handful of set phrases ("guess what") and otherwise means
|
|
226
|
+
// the object of the thought is still coming — "…testing to see what". Live captions cut
|
|
227
|
+
// exactly there and punctuate it, which is how a sentence ABOUT the product became an
|
|
228
|
+
// address to it (see isAddressed in voice-intents.js).
|
|
229
|
+
'what',
|
|
225
230
|
// prepositions and particles
|
|
226
231
|
'to', 'of', 'in', 'on', 'at', 'by', 'for', 'from', 'with', 'without', 'about', 'into',
|
|
227
232
|
'onto', 'over', 'under', 'between', 'through', 'during', 'against', 'per',
|
package/voice-intents.js
CHANGED
|
@@ -502,24 +502,66 @@ export function refinementStream({ onChange = null } = {}) {
|
|
|
502
502
|
*/
|
|
503
503
|
const NOUN_MARKERS = /^(?:the|a|an|our|your|their|my|this|that|these|those|about|on|in|of|with|via|using|called|named|to)$/i;
|
|
504
504
|
|
|
505
|
+
// Adverbs that sit between a subject and its verb — "chat panel ACTUALLY helps us".
|
|
506
|
+
const SUBJECT_ADVERBS = /^(?:actually|really|also|always|never|just|only|still|often|usually|basically|literally|probably|certainly|definitely|now|then|even|apparently|obviously)$/i;
|
|
507
|
+
// Verb forms that make whatever comes before them the SUBJECT of a claim rather than the
|
|
508
|
+
// person being spoken to. A closed list on purpose: the general rule ("any third-person verb")
|
|
509
|
+
// cannot be told from an imperative without a parser, and guessing wrong here costs a command.
|
|
510
|
+
const SUBJECT_VERBS = /^(?:is|isn't|was|wasn't|are|aren't|were|weren't|has|hasn't|have|had|does|doesn't|did|didn't|can|can't|cannot|could|couldn't|will|won't|would|wouldn't|should|shouldn't|shall|may|might|must|seems|helps|lets|gives|allows|works|looks|means|needs|wants|keeps|makes|shows|tells|comes|goes|takes|runs|becomes|provides|supports)$/i;
|
|
511
|
+
// …unless a pronoun follows, which turns the same auxiliary into a question aimed at us:
|
|
512
|
+
// "chatpanel, can YOU set a timer" against "chat panel can help us".
|
|
513
|
+
const QUESTION_PRONOUNS = /^(?:you|we|i|they|it|there|he|she)$/i;
|
|
514
|
+
|
|
515
|
+
const bare = (t) => String(t?.w || '').replace(/[^\p{L}\p{N}']/gu, '');
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Is the wake phrase the SUBJECT of the sentence rather than the person being addressed?
|
|
519
|
+
*
|
|
520
|
+
* "…what chat panel actually helps us to monitor" is a sentence ABOUT the product, and it
|
|
521
|
+
* fired a request. Nothing in the words before the name says so — the giveaway is what comes
|
|
522
|
+
* after it: a vocative is followed by a comma, an imperative or a question word, while a
|
|
523
|
+
* subject is followed by its verb.
|
|
524
|
+
*/
|
|
525
|
+
function readsAsSubject(raw, tokens, endIdx) {
|
|
526
|
+
const next = tokens[endIdx + 1];
|
|
527
|
+
if (!next) return false; // nothing after the name at all — not a claim about it
|
|
528
|
+
// Punctuation between the name and what follows is the vocative comma (or a sentence
|
|
529
|
+
// break). Either way the name stands alone, which subjects do not do.
|
|
530
|
+
if (/[.!?,;:–—-]/.test(raw.slice(tokens[endIdx].end, next.start))) return false;
|
|
531
|
+
let j = endIdx + 1;
|
|
532
|
+
if (SUBJECT_ADVERBS.test(bare(tokens[j])) && tokens[j + 1]) j += 1;
|
|
533
|
+
if (!SUBJECT_VERBS.test(bare(tokens[j]))) return false;
|
|
534
|
+
return !QUESTION_PRONOUNS.test(bare(tokens[j + 1]));
|
|
535
|
+
}
|
|
536
|
+
|
|
505
537
|
function isAddressed(raw, tokens, i, n = 0) {
|
|
506
538
|
// The fuzzy match is generous enough to SWALLOW a leading article: "a chat panel" squashes
|
|
507
539
|
// to "achatpanel", one edit from "chatpanel", so the determiner ends up inside the matched
|
|
508
540
|
// span instead of before it. Check the first matched token too, or "a chat panel would be
|
|
509
541
|
// useful here" reads as an address purely because the "a" was absorbed.
|
|
510
542
|
if (n > 0 && NOUN_MARKERS.test(String(tokens[i].w || '').replace(/[^\p{L}\p{N}]/gu, ''))) return false;
|
|
543
|
+
// Whatever came before it, a name followed by its own verb is being TALKED ABOUT.
|
|
544
|
+
if (readsAsSubject(raw, tokens, i + n)) return false;
|
|
511
545
|
if (i === 0) return true; // nothing before it — it opens the utterance
|
|
512
546
|
const prev = tokens[i - 1];
|
|
513
547
|
const word = String(prev.w || '').replace(/[^\p{L}\p{N}]/gu, '');
|
|
514
548
|
if (NOUN_MARKERS.test(word)) return false; // "the chat panel" — a thing, not a listener
|
|
549
|
+
// "hey chatpanel", "ok chatpanel" — an address word is how people open one.
|
|
550
|
+
if (/^(?:ok|okay|hey|hi|yo|hello|so|um|uh)$/i.test(word)) return true;
|
|
515
551
|
// Punctuation before it is the vocative comma or a sentence break — "…here. Okay, chat
|
|
516
552
|
// panel", "so I was thinking. ChatPanel, what did we decide?" — and both mean a fresh
|
|
517
553
|
// address rather than a continuing noun phrase. Measured from the previous token's START,
|
|
518
554
|
// because the tokenizer keeps trailing punctuation ON the token ("thinking."), so the gap
|
|
519
555
|
// between tokens is only the space and the full stop would be missed.
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
556
|
+
const gap = raw.slice(prev.start, tokens[i].start);
|
|
557
|
+
if (!/[.!?,;:]["'’”)\]]*\s*$/.test(gap)) return false;
|
|
558
|
+
// …but ONLY a break the speaker actually made. The transcriber invents full stops, and it
|
|
559
|
+
// invents them mid-clause: "…another round of testing to see what. Chat panel actually
|
|
560
|
+
// helps us to monitor" is one sentence about the product, cut in half by a machine, and the
|
|
561
|
+
// half-stop made the second half read as a fresh address. A break is only a break when the
|
|
562
|
+
// words before it are a finished thought — the same test that decides when a command has
|
|
563
|
+
// stopped growing, for exactly the same reason.
|
|
564
|
+
return !/[.!?…]/.test(gap) || commandLooksFinished(raw.slice(0, prev.end));
|
|
523
565
|
}
|
|
524
566
|
|
|
525
567
|
/**
|