From 91414a7dc4c5192cf7686e5443e2efd34cf5dbd3 Mon Sep 17 00:00:00 2001 From: fitz123 Date: Sat, 4 Apr 2026 15:36:21 +0300 Subject: [PATCH 1/2] recovered: uncommitted changes from crashed session --- docs/plans/completed/080-no-reply-trim.md | 52 +++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 docs/plans/completed/080-no-reply-trim.md diff --git a/docs/plans/completed/080-no-reply-trim.md b/docs/plans/completed/080-no-reply-trim.md new file mode 100644 index 0000000..b797801 --- /dev/null +++ b/docs/plans/completed/080-no-reply-trim.md @@ -0,0 +1,52 @@ +# Plan: Fix NO_REPLY check to use trim + word-boundary regex + +GitHub issue: #80 + +## Problem + +When a cron LLM response starts with `NO_REPLY` but includes additional text (e.g. `NO_REPLY\n\nExplanation...`), the exact match `output === "NO_REPLY"` fails and the entire response gets delivered to the user. + +Real example from bedtime-reminder cron: +``` +NO_REPLY + +Завтра (1 апреля) нет событий с конкретным временем... +``` + +## Root cause + +`bot/src/cron-runner.ts:394`: +```ts +if (cron.type === "llm" && output === "NO_REPLY") { +``` + +Exact match doesn't handle trailing whitespace or extra text after `NO_REPLY`. + +## Fix + +Change line 394 from: +```ts +if (cron.type === "llm" && output === "NO_REPLY") { +``` +to: +```ts +if (cron.type === "llm" && /^NO_REPLY\b/.test(output.trim())) { +``` + +Uses `\b` (word boundary) instead of `startsWith` — this catches `NO_REPLY`, `NO_REPLY: reason`, `NO_REPLY\ntext`, but correctly rejects `NO_REPLY_EXTRA` (underscore is a word character, so no boundary). + +Also check `bot/src/stream-relay.ts` and `bot/src/message-queue.ts` for similar NO_REPLY checks — apply the same pattern everywhere. + +## Files to change + +- [ ] `bot/src/cron-runner.ts` — line 394, fix the check +- [ ] Search all `NO_REPLY` checks in `bot/src/` — apply same fix if exact match found +- [ ] Add/update tests for NO_REPLY with trailing text, whitespace, and clean NO_REPLY + +## Tests + +- [ ] `NO_REPLY` exact — should be swallowed +- [ ] `NO_REPLY\n\nSome text` — should be swallowed +- [ ] ` NO_REPLY ` — should be swallowed +- [ ] `NO_REPLY_EXTRA` — should NOT be swallowed (`\b` correctly rejects this — underscore is a word character) +- [ ] Regular output — should be delivered From bd0dec6e419a0a8279ef8f66087df34e44264420 Mon Sep 17 00:00:00 2001 From: fitz123 Date: Sat, 4 Apr 2026 23:45:44 +0300 Subject: [PATCH 2/2] docs: add context injection section to README architecture Document that each message includes metadata (time, chat type, topic name, sender, reactions) so the agent is context-aware. Co-Authored-By: Claude Opus 4.6 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 1298ebd..f0b1d54 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,8 @@ Both platforms share one Session Manager and use the same stream-relay logic via **Message queue** sits between platform bots and Session Manager. Rapid messages are debounced (3s window) into a single prompt. Messages arriving while Claude is processing are collected (up to 20) and delivered as a combined followup after the current turn completes. +**Context injection:** Each message includes metadata — current time, chat type (DM/group/topic), topic name, sender username, and emoji reactions. The agent knows where it is, when it is, and who it's talking to. Reactions are delivered as messages so the agent can respond to a thumbs-up or a ❤️ without the user typing anything. + **Cron jobs** run separately via launchd plists. Each plist calls `run-cron.sh `, which invokes `cron-runner.ts` to spawn a one-shot `claude -p` session with the cron's prompt. **Config:** `config.yaml` defines agents (workspace + model) and bindings (chatId/channelId -> agentId). User-specific overrides live in `config.local.yaml` (gitignored, deep-merged over `config.yaml`). At least one platform (Telegram or Discord) must be configured. Tokens are read from macOS Keychain at runtime.