Skip to content

feat: PR 6 — Stream rip + final validation#2958

Merged
iscekic merged 89 commits intofeat/kilo-chat-migration-pr1from
feat/kilo-chat-migration-pr6
Apr 30, 2026
Merged

feat: PR 6 — Stream rip + final validation#2958
iscekic merged 89 commits intofeat/kilo-chat-migration-pr1from
feat/kilo-chat-migration-pr6

Conversation

@iscekic
Copy link
Copy Markdown
Contributor

@iscekic iscekic commented Apr 30, 2026

Summary

Hard cutover from Stream.io to kilo-chat. After this lands, Stream is gone everywhere — mobile, web, kiloclaw.

  • Mobile (Phase 14 / Tasks 51–53): delete old chat components (chat.tsx, chat-shell, chat-avatar, chat-theme, chat-hooks, chat-placeholder, notification-prompt, use-kiloclaw-latest-message, [instance-id].tsx); drop useStreamChatCredentials hook; drop stream-chat + stream-chat-expo deps and the stream-chat-react-native-core patch (also drops the @gorhom/bottom-sheet patch since that came in transitively via stream-chat-expo).
  • Web (Phase 15 / Tasks 54–57 + cleanup): delete getStreamChatCredentials and sendChatMessage tRPC procedures and their tests; delete /api/kiloclaw/chat-credentials route; strip Stream methods from kiloclaw-internal/user clients and types; replace ChatTab.tsx with a thin redirect to /claw/kilo-chat?sandboxId=…; drop useClawStreamChatCredentials and useStreamChatCredentials hooks; delete claw-chat.css and its imports; drop stream-chat and stream-chat-react from apps/web/package.json.
  • services/kiloclaw (Phase 16 / Tasks 58–61): delete src/stream-chat/; strip Stream provision/destroy/restart blocks, mutable state fields, persisted-state schema fields, env keys, and /stream-chat-credentials and /send-chat-message routes from the instance DO and routes; remove the controller config-writer's Stream channel/plugin block; drop STREAM_CHAT_* from kiloclaw types and regenerate worker-configuration.d.ts.

Two small inline fixes were unavoidable for typecheck to stay green after Task 51's deletions:

  • apps/mobile/src/components/home/kiloclaw-card.tsx — drop the useKiloClawLatestMessage-driven message preview/timestamp UI now that the hook is gone.
  • apps/mobile/src/lib/hooks/use-unread-counts.ts — was reading row.channelId (renamed to badgeBucket in PR 4); now parses kiloclaw:{sandboxId}:{conversationId} and sums counts per sandboxId.

Test plan

  • root validate green (typecheck + tests)
  • per-package vitest suites: kiloclaw (1663), notifications (12), event-service (16), kilo-chat (246), web (4488)
  • mobile typecheck + lint clean
  • manual QA on staging — see "Manual QA" below

Pre-existing failures (also fail on feat/kilo-chat-migration-pr5d HEAD; not introduced here): root pnpm lint reports 2 errors in packages/kilo-chat/src/utils.ts:21 (non-null assertion); mobile pnpm check:unused reports 2 unused deps + 14 unused exports + 2 unused types.

Manual QA (run on staging mobile dev client and web)

  • Foreground bot message → in-app realtime, no banner
  • Background → banner + badge++ + tap deep-links
  • Two conversations: viewing X, message in Y → push for Y, no push for X
  • Two devices same user: iPad foregrounded on conv X → no push on either device for X (per-user presence)
  • Web tab focused on conv → no mobile push; web tab hidden → mobile push
  • 5 rapid messages → 5 pushes
  • Edit/delete/react → no push
  • Empty state on brand-new sandbox → "Start a conversation" CTA → creates and navigates

Post-merge (manual)

  • Delete STREAM_CHAT_API_KEY and STREAM_CHAT_API_SECRET from Cloudflare Secrets Store
  • Decommission Stream.io project / billing
  • Optional: remove STREAM_CHAT_* lines from local services/kiloclaw/.dev.vars so wrangler types stops adding them to worker-configuration.d.ts

Stack

Based on #2953 (PR 5d). Merge PRs 1 → 2 → 3 → 4 → 4.5 → 5a → 5b → 5c → 5d → 6 in order.

iscekic added 30 commits April 29, 2026 17:28
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).
…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.
Introduces a single app-shell EventServiceProvider that owns the
EventServiceClient and KiloChatClient for all authenticated routes.
Mounted in (app)/layout.tsx so platform/instance/conversation presence
subscriptions and the kilo-chat UI share one WebSocket.

KiloChatLayout now consumes the global clients via useEventServiceClient()
instead of spinning up its own pair, and the getToken prop is removed from
KiloChatLayoutProps (along with both call sites). The local
useEventService(getToken) factory is dead code and has been deleted;
useInstanceContext / useConversationContext stay since they take
EventServiceClient as a parameter.
Thin hook that subscribes the global EventServiceClient to a single
context for the lifetime of the calling component, gated by an `active`
flag. Will back upcoming platform- and instance-level presence
indicators.
…eSubscription

- Drop dead getToken field from KiloChatContextValue (no consumers).
- Remove useInstanceContext / useConversationContext hooks; both call
  sites now use the shared usePresenceSubscription primitive directly.
- Harden usePresenceSubscription against empty-string contexts.
- usePresenceSubscription: accept 'string | null' instead of empty-string
  sentinel; update call sites (KiloChatLayout, MessageArea, useInstancePresence)
- kilo-chat router: validate expiresAt with z.iso.datetime()
- kilo-chat-router test: verify the JWT payload (kiloUserId, tokenSource,
  version) and that expiresAt lands in the expected ~1h window
- MessageArea: comment distinguishing the always-on chat-event subscription
  from the visibility-gated presence subscription
Multiple consumers can now independently hold the same context without
trampling each other. The wire context.subscribe/context.unsubscribe
messages are only sent on the 0->1 and 1->0 refcount transitions; the
intermediate churn stays client-side.

Resubscribe-on-reconnect dedupes by context key.

Tests cover: double-subscribe collapses to a single wire send, partial
unsubscribe keeps the context alive, last-consumer-out releases it,
mixed batches only send newly-active contexts, unknown-context
unsubscribes are no-ops, and reconnect resubscribes each context once.
iscekic added 21 commits April 30, 2026 13:03
Implement MessageList using FlashList v2 with maintainVisibleContentPosition
and startRenderingFromBottom for chat layout; wire fetchOlder via onStartReached.
- Drop double-cast `as unknown as Href` in favor of `as Href`
- Use themed `Text` from `@/components/ui/text` and local `useKiloChatClient`
  re-export in `MessageBubble`
- Switch `crypto.randomUUID()` to `expo-crypto`'s `Crypto.randomUUID` to
  match existing usage in `cloud-agent-runtime.ts`
@iscekic iscekic self-assigned this Apr 30, 2026
{message}
</div>
);
router.replace(`/claw/kilo-chat?sandboxId=${sandboxId}`);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Org chat redirects to the personal chat route

ClawChatPage is shared by both personal and organization chat pages, but this redirect always sends users to /claw/kilo-chat. When an org user opens /organizations/:id/claw/chat, they get moved into the personal KiloClaw chat layout instead of /organizations/:id/claw/kilo-chat, so org chat is inaccessible from this legacy tab route. Pass the context/base path through instead of hard-coding the personal route.

@kilo-code-bot
Copy link
Copy Markdown
Contributor

kilo-code-bot Bot commented Apr 30, 2026

Code Review Summary

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/app/(app)/claw/components/ChatTab.tsx 7 Shared org chat page redirects to the personal /claw/kilo-chat route, making org chat inaccessible from /organizations/:id/claw/chat.
Other Observations (not in diff)

Issues found in unchanged code that cannot receive inline comments:

File Line Issue
services/kiloclaw/worker-configuration.d.ts 37 Generated Worker types still include STREAM_CHAT_API_KEY / STREAM_CHAT_API_SECRET even though the runtime config no longer declares them; this is stale cleanup but not a runtime blocker.
Files Reviewed (53 files)
  • apps/mobile/package.json - 0 issues
  • apps/mobile/src/app/(app)/_layout.tsx - 0 issues
  • apps/mobile/src/app/(app)/chat/[instance-id].tsx - 0 issues
  • apps/mobile/src/components/home/kiloclaw-card.tsx - 0 issues
  • apps/mobile/src/components/kilo-chat/hooks/use-conversations.ts - 0 issues
  • apps/mobile/src/components/kilo-chat/hooks/use-messages.ts - 0 issues
  • apps/mobile/src/components/kilo-chat/kilo-chat-provider.tsx - 0 issues
  • apps/mobile/src/components/kiloclaw/chat-avatar.tsx - 0 issues
  • apps/mobile/src/components/kiloclaw/chat-hooks.ts - 0 issues
  • apps/mobile/src/components/kiloclaw/chat-placeholder.tsx - 0 issues
  • apps/mobile/src/components/kiloclaw/chat-shell.tsx - 0 issues
  • apps/mobile/src/components/kiloclaw/chat-theme.ts - 0 issues
  • apps/mobile/src/components/kiloclaw/chat.tsx - 0 issues
  • apps/mobile/src/components/kiloclaw/notification-prompt.tsx - 0 issues
  • apps/mobile/src/components/kiloclaw/onboarding-flow.tsx - 0 issues
  • apps/mobile/src/lib/hooks/use-kiloclaw-latest-message.ts - 0 issues
  • apps/mobile/src/lib/hooks/use-kiloclaw-queries.ts - 0 issues
  • apps/mobile/src/lib/hooks/use-unread-counts.ts - 0 issues
  • apps/mobile/src/lib/last-active-instance.ts - 0 issues
  • apps/web/package.json - 0 issues
  • apps/web/src/app/(app)/claw/claw-chat.css - 0 issues
  • apps/web/src/app/(app)/claw/components/ChatTab.tsx - 1 issue
  • apps/web/src/app/(app)/claw/components/ClawChatPage.tsx - 0 issues
  • apps/web/src/app/(app)/claw/hooks/useClawHooks.ts - 0 issues
  • apps/web/src/app/(app)/claw/layout.tsx - 0 issues
  • apps/web/src/app/(app)/organizations/[id]/claw/layout.tsx - 0 issues
  • apps/web/src/app/api/kiloclaw/chat-credentials/route.ts - 0 issues
  • apps/web/src/hooks/useKiloClaw.ts - 0 issues
  • apps/web/src/lib/kiloclaw/kiloclaw-internal-client.ts - 0 issues
  • apps/web/src/lib/kiloclaw/kiloclaw-user-client.ts - 0 issues
  • apps/web/src/lib/kiloclaw/types.ts - 0 issues
  • apps/web/src/routers/kiloclaw-router.ts - 0 issues
  • apps/web/src/routers/kiloclaw-send-chat-message.test.ts - 0 issues
  • apps/web/src/routers/organizations/organization-kiloclaw-router.ts - 0 issues
  • package.json - 0 issues
  • patches/@gorhom__bottom-sheet@5.1.8.patch - 0 issues
  • patches/stream-chat-react-native-core.patch - 0 issues
  • pnpm-lock.yaml - 0 issues
  • services/kiloclaw/controller/src/config-writer.test.ts - 0 issues
  • services/kiloclaw/controller/src/config-writer.ts - 0 issues
  • services/kiloclaw/src/durable-objects/kiloclaw-instance.test.ts - 0 issues
  • services/kiloclaw/src/durable-objects/kiloclaw-instance/config.ts - 0 issues
  • services/kiloclaw/src/durable-objects/kiloclaw-instance/index.ts - 0 issues
  • services/kiloclaw/src/durable-objects/kiloclaw-instance/state.ts - 0 issues
  • services/kiloclaw/src/durable-objects/kiloclaw-instance/types.ts - 0 issues
  • services/kiloclaw/src/gateway/env.ts - 0 issues
  • services/kiloclaw/src/routes/kiloclaw.ts - 0 issues
  • services/kiloclaw/src/routes/platform.ts - 0 issues
  • services/kiloclaw/src/schemas/instance-config.ts - 0 issues
  • services/kiloclaw/src/stream-chat/client.test.ts - 0 issues
  • services/kiloclaw/src/stream-chat/client.ts - 0 issues
  • services/kiloclaw/src/types.ts - 0 issues
  • services/kiloclaw/worker-configuration.d.ts - 0 issues

Fix these issues in Kilo Cloud


Reviewed by gpt-5.5-20260423 · 2,421,365 tokens

Base automatically changed from feat/kilo-chat-migration-pr5d to feat/kilo-chat-migration-pr1 April 30, 2026 13:42
…to resolve/pr2958-conflicts

# Conflicts:
#	apps/mobile/src/app/(app)/_layout.tsx
#	apps/mobile/src/components/kilo-chat/hooks/use-conversations.ts
#	apps/mobile/src/components/kilo-chat/hooks/use-messages.ts
#	apps/mobile/src/components/kilo-chat/kilo-chat-provider.tsx
#	apps/mobile/src/components/kiloclaw/chat.tsx
#	apps/mobile/src/lib/hooks/use-unread-counts.ts
#	apps/web/src/app/(app)/claw/layout.tsx
#	apps/web/src/app/(app)/organizations/[id]/claw/layout.tsx
#	packages/kilo-chat/src/utils.ts
#	pnpm-lock.yaml
#	services/kilo-chat/wrangler.jsonc
@iscekic iscekic merged commit a67ddf9 into feat/kilo-chat-migration-pr1 Apr 30, 2026
1 check passed
@iscekic iscekic deleted the feat/kilo-chat-migration-pr6 branch April 30, 2026 13:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant