feat(notifications,kilo-chat): PR 3 — notifications rewrite + push on message.created#2918
Merged
iscekic merged 19 commits intofeat/kilo-chat-migration-pr1from Apr 30, 2026
Merged
Conversation
…rpose); update all consumers
The badge_counts.badge_bucket column is a free-form string. To prevent namespace collisions as more surfaces start emitting badge updates (per-instance today, per-conversation later), centralize bucket-key derivation in @kilocode/notifications and route NotificationChannelDO through it. Mirrors the presence-context builders in @kilocode/event-service. Safe to introduce now without a data migration because PR 2's migration already wipes badge_counts.
…-chat producer Adds kiloclawInstanceContext and kiloclawConversationContext path builders to @kilocode/event-service, replacing hardcoded template literals in kilo-chat's event-push.ts and its test so all callers share a single source of truth.
When a chat message is persisted, fire-and-forget a call to NOTIFICATIONS.sendPushForConversation so non-sender human members of the conversation receive a push. Runs after realtime/event-service delivery inside postCommitFanOut, with errors swallowed so push failures cannot fail the send. - Skip when there are no other human recipients or no sandboxId. - senderUserId = callerId for human senders, null for bot senders. - title is "<sandboxLabel> · <conversationTitle>"; bodyPreview is the first 200 chars of the concatenated text blocks. - Add @kilocode/notifications workspace dep and layer the RPC method shape into Env via bindings.d.ts. - Add a notifications-stub worker to the vitest config so tests can spy on env.NOTIFICATIONS.sendPushForConversation, and globally mock sandbox-lookup in setup.ts (it imports pg via @kilocode/db).
Contributor
Code Review SummaryStatus: 2 Issues Found | Recommendation: Address before merge Overview
Fix these issues in Kilo Cloud Issue Details (click to expand)WARNING
Other Observations (not in diff)Issues found in unchanged code that cannot receive inline comments:
Files Reviewed (1 file)
Carried forward from previous review:
Reviewed by gpt-5.5-2026-04-23 · 692,149 tokens |
…es, fix test mock - Remove `stream-chat` from `services/notifications/package.json`; the Stream webhook (its only consumer) was deleted earlier in the stack. - Regenerate `worker-configuration.d.ts` so the workerd runtime types match the current toolchain (sibling services were on `1.20260312.1`; this one had drifted to `1.20251217.0` from a stale local cache). - Fix the global test mock to reference the renamed `badge_counts` table; the setup file was authored against the pre-rename name and never matched. - Tidy two pre-existing lint nits in the new test files (`import type` for type-only import, drop unused `cols` parameter).
…leak - Switch `NotificationsService` from default-only to a named class export with a separate default. `services/kilo-chat/wrangler.jsonc` binds via `entrypoint: "NotificationsService"`, which resolves named module exports. The default-only form (`export default class NotificationsService`) exports under the `default` key — kilo-chat's RPC binding would not have resolved at deploy. Mirrors the existing pattern in `services/kilo-chat/src/index.ts` (`KiloChatService`). - `dispatchPush` now uses a two-stage idempotency record (`pending` → `delivered`). The badge increment was previously non-idempotent: an Expo failure returned `failed` without writing the idempotency key, so upstream retries (which the design explicitly invites) re-ran the increment before the next send and inflated the badge by one per retry. The `pending` marker is written before the increment and short-circuits the increment on retry; the `delivered` marker is only written on success. - `setAlarm` is now gated on `getAlarm() === null`. Calling `setAlarm` unconditionally on each successful push — as the previous code did — replaces the pending alarm and pushes the cleanup forward indefinitely on a conversation receiving more than one push per `IDEM_TTL_MS`, leaking expired idempotency entries. Adds two test cases covering the badge-retry and alarm-reset paths.
- Schedule the cleanup alarm when writing the `pending` marker, not only on `delivered`. Without this, an Expo failure followed by no further push activity for the conversation leaves the `pending` record in DO storage forever (no alarm was ever set to prune it). - After the alarm fires, reschedule for the earliest remaining record's expiry instead of leaving the alarm slot empty. Otherwise a quiet conversation strands its younger entries until some unrelated future dispatch wakes the DO up. Both paths go through a small `ensureCleanupAlarm` helper that gates on `getAlarm() === null` so a busy conversation still doesn't push the alarm forward on every call.
The kiloclaw-scoped presence paths are literally `/presence` prefixed
onto the kiloclaw event-context paths. Build them by composition so the
`/kiloclaw/{sandboxId}[/{conversationId}]` segment shape is defined in
exactly one place — `kiloclaw-contexts.ts`.
Pure refactor; same string output, template-literal types still narrow
to the same shape.
This was referenced Apr 29, 2026
Base automatically changed from
feat/kilo-chat-migration-pr2
to
feat/kilo-chat-migration-pr1
April 30, 2026 12:53
…to feat/kilo-chat-migration-pr3 # Conflicts: # packages/notifications/src/badge-buckets.ts # pnpm-lock.yaml # services/notifications/package.json # services/notifications/src/dos/NotificationChannelDO.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Third PR in the kilo-chat migration stack (Phases 3 and 5 of the plan). Rewires the
notificationsworker as aWorkerEntrypointand wireskilo-chatto publish a push notification whenever a message is persisted.Stacked on: #2914 (PR 2 — DB rename
channel_badge_counts → badge_counts).Notifications service (Phase 3)
EVENT_SERVICEservice binding to the worker; dropsSTREAM_CHAT_API_SECRET.EVENT_SERVICEand aSELFself-binding for entrypoint RPC tests.NotificationChannelDOwith a genericdispatchPushprimitive: idempotency (1h TTL alarm-pruned), presence skip viaEVENT_SERVICE.isUserInContext, token lookup, badge math, Expo dispatch, receipts queue.failedoutcome leaves no idempotency key, so upstream can retry.WorkerEntrypointNotificationsService.sendPushForConversation(sender-exclude + dedupe + per-recipient DO fan-out) with a puresendPushForConversationCorefor unit testability.badgeBucketForInstancehelper from@kilocode/notifications(the rewritten DO no longer writes per-sandbox bucket rows).services/notifications/src/routes/webhooks.ts(Stream webhook gone).EVENT_SERVICEbinding and enablescloudflare:testtypes sopnpm typecheckis clean at HEAD.Kilo-chat producer (Phase 5)
kiloclawInstanceContext,kiloclawConversationContext) to@kilocode/event-service; migratesevent-push.tsto use them. Single source of truth shared by producers/consumers.fetchSandboxLabelhelper.NOTIFICATIONSservice binding (entrypoint: NotificationsService).message.created,createMessageFornow fans out a push viaNOTIFICATIONS.sendPushForConversationfor non-sender human members. Failures are logged, never block the send.Per the plan: deleting the Stream webhook route here means Stream messages stop generating push notifications during the soak window before PR 6's mobile cutover. Stream is being decommissioned anyway; flagging explicitly.
Test plan
services/notifications:pnpm typecheck— cleanservices/notifications:pnpm test— 8/8 pass (dispatch-push + send-push-for-conversation)services/kilo-chat:pnpm typecheck— cleanservices/kilo-chat:pnpm test— 246/246 pass (incl. 3 new push-notifications cases: negative no-recipients, positive multi-human, failure isolation)packages/event-service:pnpm typecheck— clean