From 37bca66bda0d518d2ea01b74d2f2f6cdaa05eaf5 Mon Sep 17 00:00:00 2001 From: berce <75228263+bercedev@users.noreply.github.com> Date: Wed, 21 Oct 2026 14:11:38 +0300 Subject: [PATCH 1/6] fix(tasks): read a few more watch-change phrasings The "don't tell/notify me" and "tell me ... changes" matches stop at the end of a clause, tiny/trivial and "don't bother (me) with" read as asking for important changes only, and "tell me about the changes" no longer asks for every change. Closes #489. --- changes/489.fixed.md | 1 + sentient/tasks/scripts.py | 15 +++++++++++---- tests/tasks/test_scripts_triggers_retry.py | 17 +++++++++++++++++ 3 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 changes/489.fixed.md diff --git a/changes/489.fixed.md b/changes/489.fixed.md new file mode 100644 index 000000000..a69a29451 --- /dev/null +++ b/changes/489.fixed.md @@ -0,0 +1 @@ +**Watch tasks understand a few more ways of saying which changes you want.** "Ignore all the tiny changes" or "Please don't bother me with every little change" now asks for important changes only, "Don't notify me at night, but tell me about every change" asks for every change, and a question such as "Could you tell me about the changes today?" no longer switches the watch to every change. diff --git a/sentient/tasks/scripts.py b/sentient/tasks/scripts.py index 3ac2d3916..079e2e79c 100644 --- a/sentient/tasks/scripts.py +++ b/sentient/tasks/scripts.py @@ -499,15 +499,20 @@ def normalize_script( return out +# Text up to the end of a clause: a comma or semicolon, or a full stop, question or exclamation mark followed by +# a space (a URL's dots don't end it). +_WITHIN_CLAUSE = r"(?:[^\n,;.!?]|[.!?](?=\S))" + # The newest request about updates wins. A question about a page is not a request to change its watch setting; # neither is a bare use of "change" as a verb. Requests for every update must say that positively. _NOTEWORTHY_REQUEST = re.compile( r"\b(?:only\s+)?(?:important|big|major|significant|meaningful)\s+(?:\w+\s+)?" r"(?:changes?|updates?|stuff|ones?)\b" - r"|\b(?:ignore|skip|don't|do not)\s+(?:(?:all|the)\s+){0,2}(?:minor|small|little)\s+" - r"(?:changes?|updates?)\b" + r"|\b(?:ignore|skip|(?:don't|do not)(?:\s+bother(?:\s+me)?\s+with)?)\s+(?:(?:all|the|every)\s+){0,2}" + r"(?:minor|small|little|tiny|trivial)\s+(?:changes?|updates?)\b" r"|\b(?:not|never)\s+(?:any|every|all)\s+(?:\w+\s+){0,3}?(?:changes?|updates?)\b" - r"|\b(?:don't|do not)\s+(?:tell me|notify me|let me know|alert me|report)\b[^\n]{0,160}?" + # within one clause: "Don't notify me at night, but tell me about every change" asks for every change + r"|\b(?:don't|do not)\s+(?:tell me|notify me|let me know|alert me|report)\b" + _WITHIN_CLAUSE + r"{0,160}?" r"\b(?:any|every|all)\s+(?:\w+\s+){0,3}?(?:changes?|updates?)\b", re.IGNORECASE, ) @@ -521,7 +526,9 @@ def normalize_script( r"\b(?:any|every|all)\s+(?:\w+\s+){0,3}?(?:changes?|updates?)\b" r"|\b(?:when|whenever|if)\b[^\n]{0,160}?\b(?:changes?|updates?)\b" r"|\b(?:tell me|notify me|let me know|alert me|report)\b" - r"(?!\s+(?:what|which)\s+(?:changes?|updates?)\b)[^\n]{0,160}?\b(?:changes?|updates?)\b" + # "tell me about the changes (today)" asks about particular changes, not for every future one + r"(?!\s+(?:what|which)\s+(?:changes?|updates?)\b|\s+(?:about|of)\s+the\s+(?:\w+\s+)?(?:changes?|updates?)\b)" + + _WITHIN_CLAUSE + r"{0,160}?\b(?:changes?|updates?)\b" r"|\b(?:tell me|notify me|let me know|alert me|report)\b[^\n]{0,160}?\b" r"(?:anything|everything)\s+(?:at\s+all|new)\b", re.IGNORECASE, diff --git a/tests/tasks/test_scripts_triggers_retry.py b/tests/tasks/test_scripts_triggers_retry.py index 4231469bb..1c776d462 100644 --- a/tests/tasks/test_scripts_triggers_retry.py +++ b/tests/tasks/test_scripts_triggers_retry.py @@ -447,11 +447,28 @@ def test_planner_default_uses_the_last_preference_in_one_message(): ("Don't tell me about every change", "noteworthy"), ("Thanks for all the updates", None), ("Can you tell me what changes were made?", None), + # #489 + ("Don't notify me at night, but tell me about every change", "changed"), + ("Ignore all the tiny changes", "noteworthy"), + ("Ignore all trivial changes", "noteworthy"), + ("Don't bother with all the minor changes", "noteworthy"), + ("Please don't bother me with every little change", "noteworthy"), + ("Could you tell me about the changes today?", None), ]) def test_change_preference_requires_an_explicit_watch_request(message, preference): assert scripts._change_preference(message) == preference +@pytest.mark.parametrize(("message", "condition"), [ + ("Don't notify me at night, but tell me about every change", "changed"), + ("Please don't bother me with every little change", "noteworthy"), + ("Could you tell me about the changes today?", "noteworthy"), +]) +def test_planner_default_reads_the_new_phrasings(message, condition): + changed = normalize_script({"code": PAGE_CODE, "condition": "changed"}) + assert scripts.planner_default(changed, message)["condition"] == condition + + async def test_planner_watch_default_gives_later_chat_priority_over_answers(make_app): watch = {**PLAN_SCRIPT, "script": {"code": PAGE_CODE, "condition": "changed", "then": "notify"}} app = await make_app(FakeProvider(json_replies=[watch])) From 194347d5d6925db364dca53cdf10e93273d5ff63 Mon Sep 17 00:00:00 2001 From: berce <75228263+bercedev@users.noreply.github.com> Date: Wed, 21 Oct 2026 18:02:31 +0300 Subject: [PATCH 2/6] fix(tasks): keep "notify me about the changes" as a request for every change The about/of-the-changes exclusion now applies to tell me and report only; notify me, let me know and alert me ask for future changes. --- sentient/tasks/scripts.py | 5 ++++- tests/tasks/test_scripts_triggers_retry.py | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/sentient/tasks/scripts.py b/sentient/tasks/scripts.py index 079e2e79c..bf99b231e 100644 --- a/sentient/tasks/scripts.py +++ b/sentient/tasks/scripts.py @@ -525,10 +525,13 @@ def normalize_script( _EVERY_CHANGE_REQUEST = re.compile( r"\b(?:any|every|all)\s+(?:\w+\s+){0,3}?(?:changes?|updates?)\b" r"|\b(?:when|whenever|if)\b[^\n]{0,160}?\b(?:changes?|updates?)\b" - r"|\b(?:tell me|notify me|let me know|alert me|report)\b" # "tell me about the changes (today)" asks about particular changes, not for every future one + r"|\b(?:tell me|report)\b" r"(?!\s+(?:what|which)\s+(?:changes?|updates?)\b|\s+(?:about|of)\s+the\s+(?:\w+\s+)?(?:changes?|updates?)\b)" + _WITHIN_CLAUSE + r"{0,160}?\b(?:changes?|updates?)\b" + # but "notify me about the changes (as they happen)" asks for the future ones + r"|\b(?:notify me|let me know|alert me)\b(?!\s+(?:what|which)\s+(?:changes?|updates?)\b)" + + _WITHIN_CLAUSE + r"{0,160}?\b(?:changes?|updates?)\b" r"|\b(?:tell me|notify me|let me know|alert me|report)\b[^\n]{0,160}?\b" r"(?:anything|everything)\s+(?:at\s+all|new)\b", re.IGNORECASE, diff --git a/tests/tasks/test_scripts_triggers_retry.py b/tests/tasks/test_scripts_triggers_retry.py index 1c776d462..ffac4ade5 100644 --- a/tests/tasks/test_scripts_triggers_retry.py +++ b/tests/tasks/test_scripts_triggers_retry.py @@ -454,6 +454,9 @@ def test_planner_default_uses_the_last_preference_in_one_message(): ("Don't bother with all the minor changes", "noteworthy"), ("Please don't bother me with every little change", "noteworthy"), ("Could you tell me about the changes today?", None), + ("Notify me about the changes as they happen", "changed"), + ("Alert me of the updates on this page", "changed"), + ("Let me know what changes were made", None), ]) def test_change_preference_requires_an_explicit_watch_request(message, preference): assert scripts._change_preference(message) == preference @@ -463,6 +466,7 @@ def test_change_preference_requires_an_explicit_watch_request(message, preferenc ("Don't notify me at night, but tell me about every change", "changed"), ("Please don't bother me with every little change", "noteworthy"), ("Could you tell me about the changes today?", "noteworthy"), + ("Notify me about the changes as they happen", "changed"), ]) def test_planner_default_reads_the_new_phrasings(message, condition): changed = normalize_script({"code": PAGE_CODE, "condition": "changed"}) From 0c5a8db7665da7c6fcef5ab3d86dc98cdca2d4cf Mon Sep 17 00:00:00 2001 From: berce <75228263+bercedev@users.noreply.github.com> Date: Wed, 21 Oct 2026 19:44:32 +0300 Subject: [PATCH 3/6] fix(tasks): let an important-changes preference outrank later notification wording "notify me about the changes" now only counts as a request for every change when the same message has no important-changes preference, so "Only important changes; notify me about the changes on this page" stays noteworthy. Explicit wording like "tell me about every change" still wins. --- sentient/tasks/scripts.py | 15 +++++++++++---- tests/tasks/test_scripts_triggers_retry.py | 3 +++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/sentient/tasks/scripts.py b/sentient/tasks/scripts.py index bf99b231e..509352021 100644 --- a/sentient/tasks/scripts.py +++ b/sentient/tasks/scripts.py @@ -529,13 +529,17 @@ def normalize_script( r"|\b(?:tell me|report)\b" r"(?!\s+(?:what|which)\s+(?:changes?|updates?)\b|\s+(?:about|of)\s+the\s+(?:\w+\s+)?(?:changes?|updates?)\b)" + _WITHIN_CLAUSE + r"{0,160}?\b(?:changes?|updates?)\b" - # but "notify me about the changes (as they happen)" asks for the future ones - r"|\b(?:notify me|let me know|alert me)\b(?!\s+(?:what|which)\s+(?:changes?|updates?)\b)" - + _WITHIN_CLAUSE + r"{0,160}?\b(?:changes?|updates?)\b" r"|\b(?:tell me|notify me|let me know|alert me|report)\b[^\n]{0,160}?\b" r"(?:anything|everything)\s+(?:at\s+all|new)\b", re.IGNORECASE, ) +# "notify me about the changes (as they happen)" asks for future changes without saying +# which, so it is delivery wording: an "only important changes" in the same message decides. +_CHANGE_NOTIFICATION_REQUEST = re.compile( + r"\b(?:notify me|let me know|alert me)\b(?!\s+(?:what|which)\s+(?:changes?|updates?)\b)" + + _WITHIN_CLAUSE + r"{0,160}?\b(?:changes?|updates?)\b", + re.IGNORECASE, +) _NEGATED_REQUEST_PREFIX = re.compile( r"\b(?:not|never)\s*$|\b(?:don't|do not)\b[^,.!?;]{0,60}$" r"|\b(?:thanks|thank you)\s+for\s*$", @@ -550,8 +554,11 @@ def _change_preference(message: str) -> str | None: noteworthy_matches = list(_NOTEWORTHY_REQUEST.finditer(message)) noteworthy_spans = [match.span() for match in noteworthy_matches] preferences = [(match.start(), "noteworthy") for match in noteworthy_matches] + changed_matches = list(_EVERY_CHANGE_REQUEST.finditer(message)) + if not noteworthy_matches: + changed_matches += _CHANGE_NOTIFICATION_REQUEST.finditer(message) preferences += [ - (match.start(), "changed") for match in _EVERY_CHANGE_REQUEST.finditer(message) + (match.start(), "changed") for match in changed_matches if not any(start <= match.start() < end for start, end in noteworthy_spans) and not _NEGATED_REQUEST_PREFIX.search(message[:match.start()]) ] diff --git a/tests/tasks/test_scripts_triggers_retry.py b/tests/tasks/test_scripts_triggers_retry.py index ffac4ade5..f1533a02b 100644 --- a/tests/tasks/test_scripts_triggers_retry.py +++ b/tests/tasks/test_scripts_triggers_retry.py @@ -457,6 +457,8 @@ def test_planner_default_uses_the_last_preference_in_one_message(): ("Notify me about the changes as they happen", "changed"), ("Alert me of the updates on this page", "changed"), ("Let me know what changes were made", None), + ("Only important changes; notify me about the changes on this page", "noteworthy"), + ("Only important changes; tell me about every change after all", "changed"), ]) def test_change_preference_requires_an_explicit_watch_request(message, preference): assert scripts._change_preference(message) == preference @@ -467,6 +469,7 @@ def test_change_preference_requires_an_explicit_watch_request(message, preferenc ("Please don't bother me with every little change", "noteworthy"), ("Could you tell me about the changes today?", "noteworthy"), ("Notify me about the changes as they happen", "changed"), + ("Only important changes; notify me about the changes on this page", "noteworthy"), ]) def test_planner_default_reads_the_new_phrasings(message, condition): changed = normalize_script({"code": PAGE_CODE, "condition": "changed"}) From 2afa553d5a0ed61f07a6bd89324614db18266cc2 Mon Sep 17 00:00:00 2001 From: berce <75228263+bercedev@users.noreply.github.com> Date: Thu, 22 Oct 2026 01:36:47 +0300 Subject: [PATCH 4/6] fix(tasks): don't read a negated ignore of minor changes as important-only "Don't ignore minor changes; notify me about changes on this page" matched "ignore minor changes" inside the negation, so the message counted as an important-changes preference. A preceding don't, do not or never now keeps ignore/skip from matching. --- sentient/tasks/scripts.py | 4 +++- tests/tasks/test_scripts_triggers_retry.py | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/sentient/tasks/scripts.py b/sentient/tasks/scripts.py index 509352021..a859b562c 100644 --- a/sentient/tasks/scripts.py +++ b/sentient/tasks/scripts.py @@ -508,7 +508,9 @@ def normalize_script( _NOTEWORTHY_REQUEST = re.compile( r"\b(?:only\s+)?(?:important|big|major|significant|meaningful)\s+(?:\w+\s+)?" r"(?:changes?|updates?|stuff|ones?)\b" - r"|\b(?:ignore|skip|(?:don't|do not)(?:\s+bother(?:\s+me)?\s+with)?)\s+(?:(?:all|the|every)\s+){0,2}" + # "don't ignore minor changes" asks for them, so a negated ignore/skip is not this preference + r"|\b(?:(? Date: Thu, 22 Oct 2026 11:47:23 +0300 Subject: [PATCH 5/6] fix(tasks): read "tell me about the changes" as a future request unless it asks about the past "Tell me about the changes as they happen" (from now on, going forward, every time) asks for every change, like "notify me about the changes", so it now goes through the same delivery path. A one-off time (today, so far, since Monday, ...) or a trailing question mark still keeps it a question about past changes. "Don't ignore minor changes" now counts as a request for every change. --- sentient/tasks/scripts.py | 15 +++++++++++++-- tests/tasks/test_scripts_triggers_retry.py | 12 ++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/sentient/tasks/scripts.py b/sentient/tasks/scripts.py index a859b562c..6e1fc491d 100644 --- a/sentient/tasks/scripts.py +++ b/sentient/tasks/scripts.py @@ -532,14 +532,25 @@ def normalize_script( r"(?!\s+(?:what|which)\s+(?:changes?|updates?)\b|\s+(?:about|of)\s+the\s+(?:\w+\s+)?(?:changes?|updates?)\b)" + _WITHIN_CLAUSE + r"{0,160}?\b(?:changes?|updates?)\b" r"|\b(?:tell me|notify me|let me know|alert me|report)\b[^\n]{0,160}?\b" - r"(?:anything|everything)\s+(?:at\s+all|new)\b", + r"(?:anything|everything)\s+(?:at\s+all|new)\b" + # "don't ignore minor changes" asks for those too + r"|\b(?:don't|do not|never)\s+(?:ignore|skip)\s+(?:(?:any|the)\s+)?" + r"(?:minor|small|little|tiny|trivial)\s+(?:changes?|updates?)\b", re.IGNORECASE, ) +# A time that makes "tell me about the changes" a one-off question about changes that already happened. +_ONE_OFF_TIME = ( + r"(?:today|tonight|yesterday|so\s+far|just\s+now|earlier|lately|recently" + r"|this\s+(?:morning|afternoon|evening|week|month)|last\s+\w+|since\s+\w+)" +) # "notify me about the changes (as they happen)" asks for future changes without saying # which, so it is delivery wording: an "only important changes" in the same message decides. +# So is "tell me about the changes", unless a one-off time or a question makes it about past changes. _CHANGE_NOTIFICATION_REQUEST = re.compile( r"\b(?:notify me|let me know|alert me)\b(?!\s+(?:what|which)\s+(?:changes?|updates?)\b)" - + _WITHIN_CLAUSE + r"{0,160}?\b(?:changes?|updates?)\b", + + _WITHIN_CLAUSE + r"{0,160}?\b(?:changes?|updates?)\b" + r"|\b(?:tell me|report)\s+(?:about|of)\s+the\s+(?:\w+\s+)?(?:changes?|updates?)\b" + r"(?!" + _WITHIN_CLAUSE + r"{0,80}?\b" + _ONE_OFF_TIME + r"\b)(?![^\n]*\?\s*$)", re.IGNORECASE, ) _NEGATED_REQUEST_PREFIX = re.compile( diff --git a/tests/tasks/test_scripts_triggers_retry.py b/tests/tasks/test_scripts_triggers_retry.py index 79a736246..0e8bc079b 100644 --- a/tests/tasks/test_scripts_triggers_retry.py +++ b/tests/tasks/test_scripts_triggers_retry.py @@ -461,6 +461,16 @@ def test_planner_default_uses_the_last_preference_in_one_message(): ("Only important changes; tell me about every change after all", "changed"), ("Don't ignore minor changes; notify me about changes on this page", "changed"), ("Never skip the small updates, let me know about updates here", "changed"), + ("Don't ignore minor changes", "changed"), + # "tell me about the changes" asks for future changes unless it is a one-off question + ("Tell me about the changes as they happen", "changed"), + ("Tell me about the changes from now on", "changed"), + ("Tell me about the changes going forward", "changed"), + ("Tell me about the new changes every time", "changed"), + ("Tell me about the changes so far", None), + ("Tell me about the changes since Monday", None), + ("Tell me about the changes on the page?", None), + ("Only important changes; tell me about the changes as they happen", "noteworthy"), ]) def test_change_preference_requires_an_explicit_watch_request(message, preference): assert scripts._change_preference(message) == preference @@ -472,6 +482,8 @@ def test_change_preference_requires_an_explicit_watch_request(message, preferenc ("Could you tell me about the changes today?", "noteworthy"), ("Notify me about the changes as they happen", "changed"), ("Only important changes; notify me about the changes on this page", "noteworthy"), + ("Tell me about the changes as they happen", "changed"), + ("Don't ignore minor changes", "changed"), ]) def test_planner_default_reads_the_new_phrasings(message, condition): changed = normalize_script({"code": PAGE_CODE, "condition": "changed"}) From 16973b88f3c43a2e1609e672be8b1e22e25cef7d Mon Sep 17 00:00:00 2001 From: berce <75228263+bercedev@users.noreply.github.com> Date: Thu, 22 Oct 2026 11:55:40 +0300 Subject: [PATCH 6/6] fix(tasks): keep past-tense "tell me about the changes" a question and scope its question mark to the clause "Tell me about the changes that were made" has no time word but asks about past changes, so past-tense wording (that were/have been/happened, were made) now counts like a one-off time. The question-mark check looked at the end of the whole message, so "Tell me about the changes as they happen. Can you do that?" lost its request; it now looks only at the request's own clause. --- sentient/tasks/scripts.py | 11 +++++++---- tests/tasks/test_scripts_triggers_retry.py | 5 +++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/sentient/tasks/scripts.py b/sentient/tasks/scripts.py index 6e1fc491d..ae181d081 100644 --- a/sentient/tasks/scripts.py +++ b/sentient/tasks/scripts.py @@ -538,19 +538,22 @@ def normalize_script( r"(?:minor|small|little|tiny|trivial)\s+(?:changes?|updates?)\b", re.IGNORECASE, ) -# A time that makes "tell me about the changes" a one-off question about changes that already happened. +# A time or past-tense wording that makes "tell me about the changes" a one-off question about changes +# that already happened. _ONE_OFF_TIME = ( r"(?:today|tonight|yesterday|so\s+far|just\s+now|earlier|lately|recently" - r"|this\s+(?:morning|afternoon|evening|week|month)|last\s+\w+|since\s+\w+)" + r"|this\s+(?:morning|afternoon|evening|week|month)|last\s+\w+|since\s+\w+" + r"|(?:that|which)\s+(?:were|was|have\s+been|has\s+been|had\s+been|happened)|(?:were|was)\s+made)" ) # "notify me about the changes (as they happen)" asks for future changes without saying # which, so it is delivery wording: an "only important changes" in the same message decides. -# So is "tell me about the changes", unless a one-off time or a question makes it about past changes. +# So is "tell me about the changes", unless a one-off time or a question in the same clause makes it +# about past changes. _CHANGE_NOTIFICATION_REQUEST = re.compile( r"\b(?:notify me|let me know|alert me)\b(?!\s+(?:what|which)\s+(?:changes?|updates?)\b)" + _WITHIN_CLAUSE + r"{0,160}?\b(?:changes?|updates?)\b" r"|\b(?:tell me|report)\s+(?:about|of)\s+the\s+(?:\w+\s+)?(?:changes?|updates?)\b" - r"(?!" + _WITHIN_CLAUSE + r"{0,80}?\b" + _ONE_OFF_TIME + r"\b)(?![^\n]*\?\s*$)", + r"(?!" + _WITHIN_CLAUSE + r"{0,80}?\b" + _ONE_OFF_TIME + r"\b)(?!" + _WITHIN_CLAUSE + r"{0,160}?\?)", re.IGNORECASE, ) _NEGATED_REQUEST_PREFIX = re.compile( diff --git a/tests/tasks/test_scripts_triggers_retry.py b/tests/tasks/test_scripts_triggers_retry.py index 0e8bc079b..1891f2c3e 100644 --- a/tests/tasks/test_scripts_triggers_retry.py +++ b/tests/tasks/test_scripts_triggers_retry.py @@ -470,6 +470,9 @@ def test_planner_default_uses_the_last_preference_in_one_message(): ("Tell me about the changes so far", None), ("Tell me about the changes since Monday", None), ("Tell me about the changes on the page?", None), + ("Tell me about the changes that were made", None), + ("Tell me about the changes that have been made to the page", None), + ("Tell me about the changes as they happen. Can you do that?", "changed"), ("Only important changes; tell me about the changes as they happen", "noteworthy"), ]) def test_change_preference_requires_an_explicit_watch_request(message, preference): @@ -484,6 +487,8 @@ def test_change_preference_requires_an_explicit_watch_request(message, preferenc ("Only important changes; notify me about the changes on this page", "noteworthy"), ("Tell me about the changes as they happen", "changed"), ("Don't ignore minor changes", "changed"), + ("Tell me about the changes that were made", "noteworthy"), + ("Tell me about the changes as they happen. Can you do that?", "changed"), ]) def test_planner_default_reads_the_new_phrasings(message, condition): changed = normalize_script({"code": PAGE_CODE, "condition": "changed"})