Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b5aa3b5
Add bridgeadapter RemoteMessage/RemoteEdit and refactor
batuhan Mar 8, 2026
252b825
Use bridgeadapter and consolidate utilities
batuhan Mar 8, 2026
246729e
Refactor: extract helpers and consolidate logic
batuhan Mar 8, 2026
49a01a5
Refactor stream/session helpers and subagent logic
batuhan Mar 8, 2026
e324fee
Add bridgeadapter and toolspec helpers
batuhan Mar 8, 2026
fa299ba
Centralize canonical parsers and web search
batuhan Mar 8, 2026
4f6c1a9
Centralize DM chat info & debounced edits
batuhan Mar 8, 2026
8d688ad
Remove stray blank lines
batuhan Mar 8, 2026
fba1eb2
Add bridgeadapter helpers and assorted refactors
batuhan Mar 8, 2026
6b0516d
Use toolspec helpers for tool schemas
batuhan Mar 8, 2026
faa919f
Use strings.CutPrefix/CutSuffix for trimming
batuhan Mar 8, 2026
8971935
Update main.go
batuhan Mar 8, 2026
9483ea2
Add BaseMessageMetadata and central SendViaPortal
batuhan Mar 8, 2026
6b67d5a
Use BaseMessageMetadata in MessageMetadata
batuhan Mar 8, 2026
34de988
Unify prompt text appending
batuhan Mar 8, 2026
bcad817
Dereference logger and remove unused imports
batuhan Mar 8, 2026
69e60f1
Embed BaseMessageMetadata and guard nil bridge
batuhan Mar 8, 2026
7acbcf6
Fix various bugs and improve robustness
batuhan Mar 8, 2026
6baaf0f
Fix token counting, metadata copy, and misc bugs
batuhan Mar 8, 2026
79aa2fc
Simplify AI room config and provisioning
batuhan Mar 8, 2026
5e7539f
Remove legacy AI room controls
batuhan Mar 8, 2026
ad49f94
Remove remaining legacy room metadata
batuhan Mar 8, 2026
06b3b96
Merge origin/main into cleanup branch
batuhan Mar 8, 2026
15a9a38
Remove dead legacy connector code
batuhan Mar 8, 2026
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Batteries included - one click setup (for [Beeper Plus](https://www.beeper.com/p

Coming soon to Beeper Desktop as an experiment. Join the [Developer Community](beeper://connect) on [Matrix](https://matrix.to/#/#beeper-developers:beeper.com?via=beeper.com) for early access.

Connect all your chats with one click and manage your inbox with agents. Supports image generation, reminders, web search, and memory. Create basic AI Chats to talk to models with no tools and customizable system prompt.
Connect all your chats with one click and manage your inbox with agents. Supports image generation, reminders, web search, and memory. Create direct model chats for simple conversations or agent chats for richer workflows.

Made by humans using agentic coding.

Expand All @@ -20,7 +20,7 @@ Experimental Matrix ↔ AI bridge for Beeper, built on top of [mautrix/bridgev2]
- Per-model chats (each model shows up as its own contact)
- Streaming responses
- Multimodal input (images, PDFs, audio, video) when supported by the model
- Per-room settings (model, temperature, system prompt, context limits, tools)
- Ghost-based chat targeting for models and agents
- Login flows for Beeper, Magic Proxy, or custom (BYOK)
- OpenClaw-style memory search (stored in the bridge DB)

Expand Down
56 changes: 2 additions & 54 deletions docs/matrix-ai-matrix-spec-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ This document specifies a Matrix transport profile for real-time AI:
- primary: ephemeral events (`com.beeper.ai.stream_event` with AI SDK `UIMessageChunk`)
- fallback: debounced `m.replace` timeline edits when ephemeral delivery is unavailable
- `com.beeper.ai.*` timeline projection events (tool call/result, compaction status, etc).
- `com.beeper.ai.*` state events (room settings/capabilities).
- standard Matrix room features for capability advertising.
- Tool approvals (MCP approvals + selected builtin tools).
- Auxiliary `com.beeper.ai*` keys used for routing/metadata.

Expand Down Expand Up @@ -82,9 +82,6 @@ Authoritative identifiers are defined in `pkg/matrixevents/matrixevents.go`.
| `m.room.message` | message | timeline | Canonical assistant message carrier (`com.beeper.ai`) | [Canonical](#canonical) |
| `com.beeper.ai.stream_event` | ephemeral | ephemeral | Streaming `UIMessageChunk` deltas | [Streaming](#streaming) |
| `com.beeper.ai.compaction_status` | message | timeline | Context compaction lifecycle/status | [Projections](#projection-compaction) |
| `com.beeper.ai.room_capabilities` | state | state | Producer-controlled capabilities and effective settings | [State](#state-room-capabilities) |
| `com.beeper.ai.room_settings` | state | state | User-editable room settings | [State](#state-room-settings) |
| `com.beeper.ai.model_capabilities` | state | state | Per-model capabilities (e.g. supported features) | — |
| `com.beeper.ai.agents` | state | state | Agent definitions for the room | — |

### Content Keys (Inside Standard Events)
Expand Down Expand Up @@ -288,56 +285,7 @@ Example:

<a id="state"></a>
## State Events
State events broadcast room configuration and capabilities.

<a id="state-room-capabilities"></a>
### `com.beeper.ai.room_capabilities`
Producer-controlled capabilities and effective settings.

Fields (see `RoomCapabilitiesEventContent` in `pkg/connector/events.go`):
- `capabilities?: ModelCapabilities`
- `available_tools?: ToolInfo[]`
- `reasoning_effort_options?: { value: string, label: string }[]`
- `provider?: string`
- `effective_settings?: object`

Example:
```json
{
"capabilities": {
"supports_reasoning": true,
"supports_tool_calling": true
},
"available_tools": [
{"name": "web_search", "display_name": "Web Search", "type": "provider", "enabled": true, "available": true}
],
"provider": "beeper"
}
```

<a id="state-room-settings"></a>
### `com.beeper.ai.room_settings`
User-editable room settings.

Fields (see `RoomSettingsEventContent` in `pkg/connector/events.go`):
- `model?: string`
- `system_prompt?: string`
- `temperature?: number`
- `max_context_messages?: number`
- `max_completion_tokens?: number`
- `reasoning_effort?: string`
- `agent_id?: string`
- `emit_thinking?: boolean`
- `emit_tool_args?: boolean`

Example:
```json
{
"model": "openai/gpt-5",
"temperature": 0.7,
"agent_id": "boss"
}
```
This bridge no longer uses custom room state for editable AI configuration. Room target selection is determined by ghost identity and membership, while room-level capability advertising uses standard Matrix room features.

<a id="approvals"></a>
## Tool Approvals
Expand Down
39 changes: 6 additions & 33 deletions docs/msc/com.beeper.mscXXXX-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ This is a profile document, not a new MSC. It specifies which commands ai-bridge

## Motivation

Text-based bot commands (`!ai model gpt-4o`, `!ai reset`) have several problems:
Text-based bot commands (`!ai status`, `!ai reset`) have several problems:

- **Undiscoverable:** Users must read documentation or type `!ai help` to learn available commands. There is no in-client autocomplete or parameter hinting.
- **Fragile parsing:** Free-text command parsing leads to ambiguous inputs and poor error messages. Typed parameters eliminate this class of bugs.
Expand Down Expand Up @@ -36,22 +36,6 @@ The bot MUST broadcast one state event per command when it joins a room. The `st
```

```json
{
"type": "org.matrix.msc4391.command_description",
"state_key": "model",
"content": {
"description": "Get or set the AI model",
"arguments": {
"model_id": {
"description": "Model identifier (e.g. gpt-4o, claude-sonnet)",
"required": false,
"type": "string"
}
}
}
}
```

### Structured Invocation

When a client sends a command, it MUST include the `org.matrix.msc4391.command` field in the message content:
Expand All @@ -61,12 +45,10 @@ When a client sends a command, it MUST include the `org.matrix.msc4391.command`
"type": "m.room.message",
"content": {
"msgtype": "m.text",
"body": "!ai model gpt-4o",
"body": "!ai status",
"org.matrix.msc4391.command": {
"command": "model",
"arguments": {
"model_id": "gpt-4o"
}
"command": "status",
"arguments": {}
}
}
}
Expand All @@ -80,19 +62,10 @@ Commands broadcast by ai-bridge:

| Command | Description | Arguments |
|---------|-------------|-----------|
| `new` | Create a new chat of the same type | `agent?: string` |
| `status` | Show current session status | — |
| `model` | Get or set the AI model | `model_id?: string` |
| `reset` | Start a new session/thread | — |
| `stop` | Abort current run and clear queue | — |
| `think` | Get or set thinking level | `level?: off\|minimal\|low\|medium\|high\|xhigh` |
| `verbose` | Get or set verbosity | `level?: off\|on\|full` |
| `reasoning` | Get or set reasoning visibility | `level?: off\|on\|low\|medium\|high\|xhigh` |
| `elevated` | Get or set elevated access | `level?: off\|on\|ask\|full` |
| `activation` | Set group activation policy | `policy: mention\|always` |
| `send` | Allow/deny sending messages | `mode: on\|off\|inherit` |
| `queue` | Inspect or configure message queue | `action?: status\|reset\|<mode>` |
| `whoami` | Show your Matrix user ID | — |
| `last-heartbeat` | Show last heartbeat event | — |

Dynamic commands from integrations and modules are also broadcast as state events.

Expand All @@ -104,7 +77,7 @@ When both are present, the structured `org.matrix.msc4391.command` field takes p

## Security Considerations

- **Command authorization:** The bot SHOULD check room power levels before executing commands that modify room or session state. Commands like `reset`, `model`, and `elevated` affect all users in the room.
- **Command authorization:** The bot SHOULD check room power levels before executing commands that modify room or session state.
- **Argument validation:** The bot MUST validate structured arguments against the published schema before execution. Malformed arguments MUST be rejected with an error message.

## Unstable Prefix
Expand Down
2 changes: 1 addition & 1 deletion pkg/connector/agent_activity.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func (oc *AIClient) lastActivePortal(agentID string) *bridgev2.Portal {
portal := oc.portalByRoomID(context.Background(), id.RoomID(room))
// Guard against stale mappings when a room's agent assignment changes.
if portal != nil {
if meta := portalMeta(portal); meta != nil && normalizeAgentID(meta.AgentID) != normalizeAgentID(agentID) {
if meta := portalMeta(portal); meta != nil && normalizeAgentID(resolveAgentID(meta)) != normalizeAgentID(agentID) {
return nil
}
}
Expand Down
3 changes: 0 additions & 3 deletions pkg/connector/agent_display.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,6 @@ func (oc *AIClient) agentDefaultModel(agent *agents.AgentDefinition) string {
if agent == nil {
return oc.effectiveModel(nil)
}
if override := oc.agentModelOverride(agent.ID); override != "" {
return ResolveAlias(override)
}
if agent.Model.Primary != "" {
return ResolveAlias(agent.Model.Primary)
}
Expand Down
33 changes: 4 additions & 29 deletions pkg/connector/agentstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -522,13 +522,12 @@ func (b *BossStoreAdapter) CreateRoom(ctx context.Context, room tools.RoomData)
return "", fmt.Errorf("failed to get created portal: %w", err)
}

// Apply custom name and system prompt if provided
// Apply custom room name if provided.
pm := portalMeta(portal)
originalName := portal.Name
originalNameSet := portal.NameSet
originalTitle := pm.Title
originalTitleGenerated := pm.TitleGenerated
originalSystemPrompt := pm.SystemPrompt

if room.Name != "" {
pm.Title = room.Name
Expand All @@ -538,12 +537,6 @@ func (b *BossStoreAdapter) CreateRoom(ctx context.Context, room tools.RoomData)
resp.PortalInfo.Name = &room.Name
}
}
if room.SystemPrompt != "" {
pm.SystemPrompt = room.SystemPrompt
// Note: portal.Topic is NOT set to SystemPrompt - they are separate concepts
// Topic is for display only, SystemPrompt is for LLM context
}

// Create the Matrix room
if err := portal.CreateMatrixRoom(ctx, b.store.client.UserLogin, resp.PortalInfo); err != nil {
cleanupPortal(ctx, b.store.client, portal, "failed to create Matrix room")
Expand All @@ -562,13 +555,6 @@ func (b *BossStoreAdapter) CreateRoom(ctx context.Context, room tools.RoomData)
pm.TitleGenerated = originalTitleGenerated
}
}
if room.SystemPrompt != "" {
if err := b.store.client.setRoomSystemPromptNoSave(ctx, portal, room.SystemPrompt); err != nil {
b.store.client.log.Warn().Err(err).Msg("Failed to set room system prompt")
pm.SystemPrompt = originalSystemPrompt
}
}

if err := portal.Save(ctx); err != nil {
return "", fmt.Errorf("failed to save room overrides: %w", err)
}
Expand Down Expand Up @@ -597,29 +583,18 @@ func (b *BossStoreAdapter) ModifyRoom(ctx context.Context, roomID string, update
if err != nil {
return fmt.Errorf("agent '%s' not found: %w", updates.AgentID, err)
}
pm.AgentID = agent.ID
pm.Model = ""
modelID := b.store.client.effectiveModel(pm)
pm.Capabilities = getModelCapabilities(modelID, b.store.client.findModelInfo(modelID))
portal.OtherUserID = agentUserID(agent.ID)
pm.ResolvedTarget = resolveTargetFromGhostID(portal.OtherUserID)
modelID := b.store.client.effectiveModel(pm)
agentName := b.store.client.resolveAgentDisplayName(ctx, agent)
b.store.client.ensureAgentGhostDisplayName(ctx, agent.ID, modelID, agentName)
}
if updates.SystemPrompt != "" {
pm.SystemPrompt = updates.SystemPrompt
// Note: portal.Topic is NOT set to SystemPrompt - they are separate concepts
}

if updates.Name != "" && portal.MXID != "" {
if err := b.store.client.setRoomName(ctx, portal, updates.Name); err != nil {
b.store.client.log.Warn().Err(err).Msg("Failed to set Matrix room name")
}
}
if updates.SystemPrompt != "" && portal.MXID != "" {
if err := b.store.client.setRoomSystemPrompt(ctx, portal, updates.SystemPrompt); err != nil {
b.store.client.log.Warn().Err(err).Msg("Failed to set room system prompt")
}
}

return portal.Save(ctx)
}
Expand All @@ -645,7 +620,7 @@ func (b *BossStoreAdapter) ListRooms(ctx context.Context) ([]tools.RoomData, err
rooms = append(rooms, tools.RoomData{
ID: roomID,
Name: name,
AgentID: pm.AgentID,
AgentID: resolveAgentID(pm),
})
}

Expand Down
Loading