Skip to content

Commit 6eb1dff

Browse files
feat(kiloclaw): integrate Stream Chat for real-time messaging (#1573)
## Summary - Integrate Stream Chat as a real-time messaging channel for KiloClaw instances. On first provision, the DO auto-creates a Stream Chat bot user, human user, and default messaging channel via the Stream Chat REST API (lightweight `jose`-based JWT auth, no Node SDK dependency for CF Workers compatibility). - Add a new `ChatTab` React component to the KiloClaw dashboard using `stream-chat-react`, with bot online/offline presence indicator and dark theme styling. - Wire Stream Chat credentials through the full stack: DO persisted state, config-writer (OpenClaw plugin + channel config), env vars (sensitive bot token encrypted in transit), platform API route, tRPC router, and React hook. - Update both Dockerfiles to install the `openclaw-channel-streamchat` plugin from a GitHub fork (https://github.com/Kilo-Org/openclaw-channel-streamchat/tree/catrielmuller/attach-support). ## Verification - [x] `pnpm format:check` passes - [x] `pnpm lint` passes across all workspaceshttps://github.com/Kilo-Org/openclaw-channel-streamchat/tree/catrielmuller/attach-support - [x] `pnpm typecheck` passes for root project and kiloclaw workspace (pre-existing cloud-agent-next drizzle-orm type errors are unrelated, also present on main) - [x] Stream Chat client unit tests exist (`kiloclaw/src/stream-chat/client.test.ts` — covers JWT generation, upsertUsers, getOrCreateChannel, and full setupDefaultStreamChatChannel flow) - [x] Config-writer tests exist (`kiloclaw/controller/src/config-writer.test.ts` — covers Stream Chat channel configuration, missing env vars, and plugin path deduplication) ## Visual Changes <img width="2239" height="1359" alt="image" src="https://github.com/user-attachments/assets/4dfc5f83-6916-493d-ac21-44cdc335f11d" /> <img width="2261" height="1290" alt="image" src="https://github.com/user-attachments/assets/69780f7e-f2c0-4091-83b7-4bff0514d174" /> <img width="2277" height="1621" alt="image" src="https://github.com/user-attachments/assets/4f1cc7a9-fb62-493a-8976-15b471e1e502" /> <img width="2277" height="1621" alt="image" src="https://github.com/user-attachments/assets/a97a05b4-8ec5-44a8-9711-4226816c87cd" /> ## Reviewer Notes - The Stream Chat API secret never reaches Fly Machines — all admin operations (user creation, channel creation, token generation) happen server-side in the CF Worker DO. Only per-user JWTs are forwarded. - Stream Chat setup is best-effort during provisioning: failure is logged but does not block instance creation, so existing instances are unaffected. - The `openclaw-channel-streamchat` plugin is installed from a GitHub fork branch (`catrielmuller/attach-support`) — this should be updated to a stable release before merging to production. - The `stream-chat` and `stream-chat-react` npm packages are added to the root `package.json` for the Next.js frontend.
2 parents 627fbcb + 3b2a7ab commit 6eb1dff

File tree

32 files changed

+17978
-31
lines changed

32 files changed

+17978
-31
lines changed

kiloclaw/.dev.vars.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,7 @@ NEXT_PUBLIC_POSTHOG_KEY=phc_GK2Pxl0HPj5ZPfwhLRjXrtdz8eD7e9MKnXiFrOqnB6z
7676
# In wrangler dev, it connects to the remote Postgres via the Hyperdrive service.
7777
# To use a local Postgres instead, add "localConnectionString" to the hyperdrive
7878
# config in wrangler.jsonc (see https://developers.cloudflare.com/hyperdrive/configuration/local-development/).
79+
80+
# StreamChat API Key
81+
STREAM_CHAT_API_KEY=...
82+
STREAM_CHAT_API_SECRET=...

kiloclaw/AGENTS.md

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -229,27 +229,31 @@ User config is transported to the machine via environment variables set in the F
229229

230230
**Encrypted (stored as `KILOCLAW_ENC_{name}`, decrypted to `{name}` at boot):**
231231

232-
| Env var (after decrypt) | Source | Purpose |
233-
| ------------------------ | ---------------------------- | --------------------------- |
234-
| `KILOCODE_API_KEY` | User config (DO) | KiloCode API authentication |
235-
| `OPENCLAW_GATEWAY_TOKEN` | Derived from sandboxId | Per-user gateway auth |
236-
| `TELEGRAM_BOT_TOKEN` | Decrypted channel token | Telegram channel |
237-
| `DISCORD_BOT_TOKEN` | Decrypted channel token | Discord channel |
238-
| `SLACK_BOT_TOKEN` | Decrypted channel token | Slack channel |
239-
| `SLACK_APP_TOKEN` | Decrypted channel token | Slack channel |
240-
| User encrypted secrets | Decrypted from RSA envelopes | User-provided credentials |
232+
| Env var (after decrypt) | Source | Purpose |
233+
| ---------------------------- | --------------------------------- | ------------------------------ |
234+
| `KILOCODE_API_KEY` | User config (DO) | KiloCode API authentication |
235+
| `OPENCLAW_GATEWAY_TOKEN` | Derived from sandboxId | Per-user gateway auth |
236+
| `TELEGRAM_BOT_TOKEN` | Decrypted channel token | Telegram channel |
237+
| `DISCORD_BOT_TOKEN` | Decrypted channel token | Discord channel |
238+
| `SLACK_BOT_TOKEN` | Decrypted channel token | Slack channel |
239+
| `SLACK_APP_TOKEN` | Decrypted channel token | Slack channel |
240+
| `STREAM_CHAT_BOT_USER_TOKEN` | Auto-provisioned (Stream Chat DO) | Stream Chat bot authentication |
241+
| User encrypted secrets | Decrypted from RSA envelopes | User-provided credentials |
241242

242243
**Plaintext (stored as-is in config.env):**
243244

244-
| Env var | Source | Purpose |
245-
| -------------------------- | --------------------------------- | ------------------------------------ |
246-
| `KILOCODE_DEFAULT_MODEL` | User config (DO) | Default model for agents |
247-
| `KILOCODE_MODELS_JSON` | User config (DO), JSON-serialized | Available model list |
248-
| `KILOCODE_API_BASE_URL` | Worker env | API base URL override |
249-
| `AUTO_APPROVE_DEVICES` | Hardcoded `true` | Skip device pairing |
250-
| `TELEGRAM_DM_POLICY` | Worker env | Telegram DM policy |
251-
| `DISCORD_DM_POLICY` | Worker env | Discord DM policy |
252-
| `OPENCLAW_ALLOWED_ORIGINS` | Worker env | Control UI WebSocket allowed origins |
245+
| Env var | Source | Purpose |
246+
| -------------------------------- | --------------------------------- | ------------------------------------ |
247+
| `KILOCODE_DEFAULT_MODEL` | User config (DO) | Default model for agents |
248+
| `KILOCODE_MODELS_JSON` | User config (DO), JSON-serialized | Available model list |
249+
| `KILOCODE_API_BASE_URL` | Worker env | API base URL override |
250+
| `AUTO_APPROVE_DEVICES` | Hardcoded `true` | Skip device pairing |
251+
| `TELEGRAM_DM_POLICY` | Worker env | Telegram DM policy |
252+
| `DISCORD_DM_POLICY` | Worker env | Discord DM policy |
253+
| `OPENCLAW_ALLOWED_ORIGINS` | Worker env | Control UI WebSocket allowed origins |
254+
| `STREAM_CHAT_API_KEY` | Auto-provisioned (Stream Chat DO) | Stream Chat app key |
255+
| `STREAM_CHAT_BOT_USER_ID` | Auto-provisioned (Stream Chat DO) | Per-user bot identifier |
256+
| `STREAM_CHAT_DEFAULT_CHANNEL_ID` | Auto-provisioned (Stream Chat DO) | Default channel ID for the user |
253257

254258
### AI Provider Selection
255259

kiloclaw/Dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,12 @@ RUN npm install -g mcporter@0.7.3
5454
# Install summarize (web page summarization CLI)
5555
RUN npm install -g @steipete/summarize@0.12.0
5656

57+
# Install Stream Chat channel plugin for OpenClaw (installed from GitHub, not npm)
58+
RUN npm install -g github:Kilo-Org/openclaw-channel-streamchat#catrielmuller/attach-support
59+
5760
# Install Kilo CLI (agentic coding assistant for the terminal)
5861
RUN npm install -g @kilocode/cli@7.0.46
5962

60-
6163
# Install Go (available at runtime for users to `go install` additional tools)
6264
ENV GO_VERSION=1.26.0
6365
RUN ARCH="$(dpkg --print-architecture)" \

kiloclaw/Dockerfile.local

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ RUN apt-get update \
3030
&& curl -fsSL https://downloads.1password.com/linux/keys/1password.asc \
3131
| gpg --dearmor --output /usr/share/debsig/keyrings/AC2D62742012EA22/debsig.gpg \
3232
&& apt-get update \
33-
&& apt-get install -y --no-install-recommends gh 1password-cli=2.32.1-1 \
33+
&& apt-get install -y --no-install-recommends gh 1password-cli=2.33.0-1 \
3434
&& apt-get purge -y xz-utils \
3535
&& apt-get autoremove -y \
3636
&& rm -rf /var/lib/apt/lists/* \
@@ -49,14 +49,17 @@ RUN npm install -g /tmp/openclaw.tgz \
4949
# Install ClawHub CLI
5050
RUN npm install -g clawhub
5151

52-
# Install QMD
53-
RUN npm install -g @tobilu/qmd
54-
5552
# Install mcporter (MCP server tooling)
5653
RUN npm install -g mcporter@0.7.3
5754

5855
# Install summarize (web page summarization CLI)
59-
RUN npm install -g @steipete/summarize@0.11.1
56+
RUN npm install -g @steipete/summarize@0.12.0
57+
58+
# Install Stream Chat channel plugin for OpenClaw (installed from GitHub, not npm)
59+
RUN npm install -g github:Kilo-Org/openclaw-channel-streamchat#catrielmuller/attach-support
60+
61+
# Install Kilo CLI (agentic coding assistant for the terminal)
62+
RUN npm install -g @kilocode/cli@7.0.46
6063

6164
# Install Go (available at runtime for users to `go install` additional tools)
6265
ENV GO_VERSION=1.26.0
@@ -73,7 +76,7 @@ RUN ARCH="$(dpkg --print-architecture)" \
7376
# so user-installed tools persist across restarts and are included in snapshots.
7477
# - npm/Node (installed to /usr/local) and apt packages (/usr/bin) are unaffected.
7578
ENV PATH="/usr/local/go/bin:/root/go/bin:$PATH"
76-
RUN GOBIN=/usr/local/bin go install github.com/steipete/gogcli/cmd/gog@v0.11.0 \
79+
RUN GOBIN=/usr/local/bin go install github.com/steipete/gogcli/cmd/gog@v0.12.0 \
7780
&& GOBIN=/usr/local/bin go install github.com/steipete/goplaces/cmd/goplaces@v0.3.0 \
7881
&& GOBIN=/usr/local/bin go install github.com/Hyaxia/blogwatcher/cmd/blogwatcher@v0.0.2 \
7982
&& GOBIN=/usr/local/bin go install github.com/xdevplatform/xurl@v1.0.3 \
@@ -111,7 +114,7 @@ RUN mkdir -p /root/.openclaw \
111114

112115
# Copy helper scripts (used at runtime by the controller/gateway)
113116
# Build cache bust: 2026-03-17-v65-remove-startup-script
114-
RUN echo "9"
117+
RUN echo "10"
115118
COPY openclaw-pairing-list.js /usr/local/bin/openclaw-pairing-list.js
116119
COPY openclaw-device-pairing-list.js /usr/local/bin/openclaw-device-pairing-list.js
117120

@@ -126,4 +129,6 @@ COPY container/TOOLS.md /usr/local/share/kiloclaw/TOOLS.md
126129
EXPOSE 18789
127130

128131
# Entrypoint: start the KiloClaw controller daemon directly.
132+
# All bootstrap logic (env decryption, onboard/doctor, config patching,
133+
# feature flags) lives in the controller's bootstrap module — no shell wrapper needed.
129134
CMD ["node", "/usr/local/bin/kiloclaw-controller.js"]

kiloclaw/controller/src/config-writer.test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,68 @@ describe('generateBaseConfig', () => {
437437
expect(config.channels.slack).toBeUndefined();
438438
});
439439

440+
// ─── Stream Chat (default channel) ───────────────────────────────────────
441+
442+
it('configures Stream Chat channel and plugin when all three vars are set', () => {
443+
const { deps } = fakeDeps();
444+
const env = {
445+
...minimalEnv(),
446+
STREAM_CHAT_API_KEY: 'sc-api-key',
447+
STREAM_CHAT_BOT_USER_ID: 'bot-sandbox-abc',
448+
STREAM_CHAT_BOT_USER_TOKEN: 'sc-bot-token',
449+
};
450+
const config = generateBaseConfig(env, '/tmp/openclaw.json', deps);
451+
452+
expect(config.channels.streamchat.apiKey).toBe('sc-api-key');
453+
expect(config.channels.streamchat.botUserId).toBe('bot-sandbox-abc');
454+
expect(config.channels.streamchat.botUserToken).toBe('sc-bot-token');
455+
expect(config.channels.streamchat.botUserName).toBe('KiloClaw');
456+
expect(config.channels.streamchat.enabled).toBe(true);
457+
expect(config.plugins.entries.streamchat.enabled).toBe(true);
458+
expect(config.plugins.load.paths).toContain(
459+
'/usr/local/lib/node_modules/@wunderchat/openclaw-channel-streamchat'
460+
);
461+
});
462+
463+
it('does not configure Stream Chat when any of the three required vars is missing', () => {
464+
const cases = [
465+
{ STREAM_CHAT_API_KEY: 'key', STREAM_CHAT_BOT_USER_ID: 'bot' },
466+
{ STREAM_CHAT_API_KEY: 'key', STREAM_CHAT_BOT_USER_TOKEN: 'token' },
467+
{ STREAM_CHAT_BOT_USER_ID: 'bot', STREAM_CHAT_BOT_USER_TOKEN: 'token' },
468+
];
469+
470+
for (const partial of cases) {
471+
const { deps } = fakeDeps();
472+
const env = { ...minimalEnv(), ...partial };
473+
const config = generateBaseConfig(env, '/tmp/openclaw.json', deps);
474+
expect(config.channels.streamchat).toBeUndefined();
475+
}
476+
});
477+
478+
it('does not duplicate the plugin path on repeated generateBaseConfig calls', () => {
479+
const existing = JSON.stringify({
480+
channels: { streamchat: { apiKey: 'old-key', enabled: true } },
481+
plugins: {
482+
load: {
483+
paths: ['/usr/local/lib/node_modules/@wunderchat/openclaw-channel-streamchat'],
484+
},
485+
entries: { streamchat: { enabled: true } },
486+
},
487+
});
488+
const { deps } = fakeDeps(existing);
489+
const env = {
490+
...minimalEnv(),
491+
STREAM_CHAT_API_KEY: 'sc-api-key',
492+
STREAM_CHAT_BOT_USER_ID: 'bot-sandbox-abc',
493+
STREAM_CHAT_BOT_USER_TOKEN: 'sc-bot-token',
494+
};
495+
const config = generateBaseConfig(env, '/tmp/openclaw.json', deps);
496+
497+
const pluginPath = '/usr/local/lib/node_modules/@wunderchat/openclaw-channel-streamchat';
498+
const paths = config.plugins.load.paths as string[];
499+
expect(paths.filter(p => p === pluginPath)).toHaveLength(1);
500+
});
501+
440502
it('does not set gateway auth when OPENCLAW_GATEWAY_TOKEN is missing', () => {
441503
const { deps } = fakeDeps();
442504
const env = { ...minimalEnv() };

kiloclaw/controller/src/config-writer.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,30 @@ export function generateBaseConfig(
279279
config.plugins.entries.slack.enabled = true;
280280
}
281281

282+
// Stream Chat default channel (auto-provisioned at provision time)
283+
if (env.STREAM_CHAT_API_KEY && env.STREAM_CHAT_BOT_USER_ID && env.STREAM_CHAT_BOT_USER_TOKEN) {
284+
config.channels.streamchat = config.channels.streamchat ?? {};
285+
config.channels.streamchat.apiKey = env.STREAM_CHAT_API_KEY;
286+
config.channels.streamchat.botUserId = env.STREAM_CHAT_BOT_USER_ID;
287+
config.channels.streamchat.botUserToken = env.STREAM_CHAT_BOT_USER_TOKEN;
288+
config.channels.streamchat.botUserName = 'KiloClaw';
289+
config.channels.streamchat.enabled = true;
290+
291+
config.plugins = config.plugins ?? {};
292+
config.plugins.load = config.plugins.load ?? {};
293+
config.plugins.load.paths = Array.isArray(config.plugins.load.paths)
294+
? config.plugins.load.paths
295+
: [];
296+
const pluginPath = '/usr/local/lib/node_modules/@wunderchat/openclaw-channel-streamchat';
297+
if (!(config.plugins.load.paths as string[]).includes(pluginPath)) {
298+
(config.plugins.load.paths as string[]).push(pluginPath);
299+
}
300+
301+
config.plugins.entries = config.plugins.entries ?? {};
302+
config.plugins.entries.streamchat = config.plugins.entries.streamchat ?? {};
303+
config.plugins.entries.streamchat.enabled = true;
304+
}
305+
282306
// Webhook hooks configuration (required for Gmail push notifications via gog).
283307
// hooks.token authenticates incoming hook requests from gog's --hook-token.
284308
// The gmail preset maps gog's gmailHookPayload into OpenClaw's expected format.

kiloclaw/src/durable-objects/kiloclaw-instance/config.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,17 @@ export async function buildUserEnvVars(
154154
plainEnv.KILOCLAW_GMAIL_LAST_HISTORY_ID = state.gmailLastHistoryId;
155155
}
156156

157+
// Stream Chat default channel (auto-provisioned at first provision).
158+
// API key and bot user ID are plaintext; bot user token is sensitive.
159+
if (state.streamChatApiKey && state.streamChatBotUserId && state.streamChatBotUserToken) {
160+
plainEnv.STREAM_CHAT_API_KEY = state.streamChatApiKey;
161+
plainEnv.STREAM_CHAT_BOT_USER_ID = state.streamChatBotUserId;
162+
sensitive.STREAM_CHAT_BOT_USER_TOKEN = state.streamChatBotUserToken;
163+
if (state.streamChatChannelId) {
164+
plainEnv.STREAM_CHAT_DEFAULT_CHANNEL_ID = state.streamChatChannelId;
165+
}
166+
}
167+
157168
// Get the env encryption key from the App DO, creating it if needed.
158169
const appStub = env.KILOCLAW_APP.get(env.KILOCLAW_APP.idFromName(state.userId));
159170
const { key: envKey, secretsVersion } = await appStub.ensureEnvKey(state.userId);

kiloclaw/src/durable-objects/kiloclaw-instance/index.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ import {
6161
markRestartSuccessful,
6262
} from './reconcile';
6363
import { restoreFromPostgres, markDestroyedInPostgresHelper } from './postgres';
64+
import {
65+
setupDefaultStreamChatChannel,
66+
createShortLivedUserToken,
67+
deactivateStreamChatUsers,
68+
} from '../../stream-chat/client';
6469
import { writeEvent } from '../../utils/analytics';
6570
import type { KiloClawEventData, KiloClawEventName } from '../../utils/analytics';
6671

@@ -384,6 +389,35 @@ export class KiloClawInstance extends DurableObject<KiloClawEnv> {
384389
}
385390
this.s.loaded = true;
386391

392+
// Set up the default Stream Chat channel on first provision (best-effort).
393+
// The bot and channel are created server-side here so the API secret never
394+
// reaches the Fly Machine. Failure is non-fatal: the instance will start
395+
// without the Stream Chat channel rather than blocking provisioning.
396+
if (isNew && this.env.STREAM_CHAT_API_KEY && this.env.STREAM_CHAT_API_SECRET) {
397+
try {
398+
const streamChat = await setupDefaultStreamChatChannel(
399+
this.env.STREAM_CHAT_API_KEY,
400+
this.env.STREAM_CHAT_API_SECRET,
401+
sandboxId
402+
);
403+
this.s.streamChatApiKey = streamChat.apiKey;
404+
this.s.streamChatBotUserId = streamChat.botUserId;
405+
this.s.streamChatBotUserToken = streamChat.botUserToken;
406+
this.s.streamChatChannelId = streamChat.channelId;
407+
await this.persist({
408+
streamChatApiKey: streamChat.apiKey,
409+
streamChatBotUserId: streamChat.botUserId,
410+
streamChatBotUserToken: streamChat.botUserToken,
411+
streamChatChannelId: streamChat.channelId,
412+
});
413+
console.log('[DO] Stream Chat default channel provisioned:', streamChat.channelId);
414+
} catch (err) {
415+
doWarn(this.s, 'Stream Chat default channel setup failed (non-fatal)', {
416+
error: toLoggable(err),
417+
});
418+
}
419+
}
420+
387421
if (isNew) {
388422
await this.scheduleAlarm();
389423
}
@@ -1124,6 +1158,22 @@ export class KiloClawInstance extends DurableObject<KiloClawEnv> {
11241158
value: machineUptimeMs,
11251159
});
11261160

1161+
// Best-effort: deactivate Stream Chat users so any captured tokens become useless.
1162+
// Failure is non-fatal — worst case is the same as pre-deactivation behavior.
1163+
if (this.env.STREAM_CHAT_API_KEY && this.env.STREAM_CHAT_API_SECRET && this.s.sandboxId) {
1164+
try {
1165+
await deactivateStreamChatUsers(
1166+
this.env.STREAM_CHAT_API_KEY,
1167+
this.env.STREAM_CHAT_API_SECRET,
1168+
[this.s.sandboxId, `bot-${this.s.sandboxId}`]
1169+
);
1170+
} catch (err) {
1171+
doWarn(this.s, 'Stream Chat user deactivation failed (non-fatal)', {
1172+
error: toLoggable(err),
1173+
});
1174+
}
1175+
}
1176+
11271177
const flyConfig = getFlyConfig(this.env, this.s);
11281178
const destroyRctx = createReconcileContext(this.s, this.env, 'destroy');
11291179
await tryDeleteMachine(flyConfig, this.ctx, this.s, destroyRctx);
@@ -1215,6 +1265,38 @@ export class KiloClawInstance extends DurableObject<KiloClawEnv> {
12151265
};
12161266
}
12171267

1268+
async getStreamChatCredentials(): Promise<{
1269+
apiKey: string;
1270+
userId: string;
1271+
userToken: string;
1272+
channelId: string;
1273+
} | null> {
1274+
await this.loadState();
1275+
1276+
if (
1277+
!this.s.streamChatApiKey ||
1278+
!this.env.STREAM_CHAT_API_SECRET ||
1279+
!this.s.streamChatChannelId ||
1280+
!this.s.sandboxId
1281+
) {
1282+
return null;
1283+
}
1284+
1285+
// Mint a short-lived token on every request so that revoked users lose
1286+
// access when the token expires, without requiring an app-secret rotation.
1287+
const userToken = await createShortLivedUserToken(
1288+
this.env.STREAM_CHAT_API_SECRET,
1289+
this.s.sandboxId
1290+
);
1291+
1292+
return {
1293+
apiKey: this.s.streamChatApiKey,
1294+
userId: this.s.sandboxId,
1295+
userToken,
1296+
channelId: this.s.streamChatChannelId,
1297+
};
1298+
}
1299+
12181300
async getDebugState(): Promise<{
12191301
userId: string | null;
12201302
sandboxId: string | null;

kiloclaw/src/durable-objects/kiloclaw-instance/state.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ export async function loadState(ctx: DurableObjectState, s: InstanceMutableState
7979
// Legacy instances pre-dating this field treat absence as already-sent
8080
// to avoid spurious emails after deploy.
8181
s.instanceReadyEmailSent = 'instanceReadyEmailSent' in raw ? d.instanceReadyEmailSent : true;
82+
s.streamChatApiKey = d.streamChatApiKey;
83+
s.streamChatBotUserId = d.streamChatBotUserId;
84+
s.streamChatBotUserToken = d.streamChatBotUserToken;
85+
s.streamChatChannelId = d.streamChatChannelId;
8286
} else {
8387
const hasAnyData = entries.size > 0;
8488
if (hasAnyData) {
@@ -146,6 +150,10 @@ export function resetMutableState(s: InstanceMutableState): void {
146150
s.preRestoreStatus = null;
147151
s.pendingRestoreVolumeId = null;
148152
s.instanceReadyEmailSent = false;
153+
s.streamChatApiKey = null;
154+
s.streamChatBotUserId = null;
155+
s.streamChatBotUserToken = null;
156+
s.streamChatChannelId = null;
149157
s.lastLiveCheckAt = null;
150158
s.restartingAt = null;
151159
s.loaded = false;
@@ -207,6 +215,10 @@ export function createMutableState(): InstanceMutableState {
207215
preRestoreStatus: null,
208216
pendingRestoreVolumeId: null,
209217
instanceReadyEmailSent: false,
218+
streamChatApiKey: null,
219+
streamChatBotUserId: null,
220+
streamChatBotUserToken: null,
221+
streamChatChannelId: null,
210222
lastLiveCheckAt: null,
211223
};
212224
}

0 commit comments

Comments
 (0)