Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions docs/content/docs/(configuration)/config.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -266,14 +266,13 @@ Most config values are hot-reloaded when their files change. Spacebot watches `c
| Identity files (SOUL.md, etc.) | Yes | Next channel message renders new identity |
| Skills (SKILL.md files) | Yes | Next message / worker spawn sees new skills |
| Bindings | Yes | Next message routes using new bindings |
| Discord/Slack permissions | Yes | Next message checks new permission rules |
| Messaging adapters (Discord/Slack/Telegram/Twitch/Email/Webhook) | Yes | Adapter runtime is reconciled live; changed instances are restarted or removed without a full process restart |

### What Needs Restart

| Setting | Why |
|---------|-----|
| LLM API keys | Provider clients are initialized once (applies to `secret:`, `env:`, and literal values) |
| Messaging adapters (Discord token, webhook bind/port) | Adapter connections are long-lived |
| Agent topology (adding/removing `[[agents]]`) | Databases and event buses are per-agent |
| Database paths | Connections are opened once at startup |
| System prompts | Compiled into the binary via `include_str!` |
Expand Down
13 changes: 13 additions & 0 deletions src/config/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::secrets::store::{InstancePattern, SecretField, SystemSecrets};

use chrono_tz::Tz;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};

use std::collections::HashMap;
use std::path::{Path, PathBuf};
Expand Down Expand Up @@ -1554,6 +1555,18 @@ pub fn binding_runtime_adapter_key(platform: &str, adapter: Option<&str>) -> Str
platform.to_string()
}

/// Build the persisted token filename for a named Twitch adapter instance.
pub fn named_twitch_token_file_name(name: &str) -> String {
let safe_name: String = name
.chars()

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.

Worth bounding safe_name length to avoid path/filename length issues if someone gives a very long instance name.

Suggested change
.chars()
let safe_name: String = name
.chars()
.map(|ch| if ch.is_ascii_alphanumeric() { ch } else { '_' })
.take(64)
.collect();

.map(|ch| if ch.is_ascii_alphanumeric() { ch } else { '_' })
.collect();
let hash = Sha256::digest(name.as_bytes());
let hash_prefix = hex::encode(&hash[..8]);

format!("twitch_token_{safe_name}_{hash_prefix}.json")
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

/// Match a binding's adapter selector against an inbound message adapter.
pub(super) fn binding_adapter_matches(binding: &Binding, message: &crate::InboundMessage) -> bool {
match (&binding.adapter, message.adapter_selector()) {
Expand Down
Loading