@studiomopoke/crosschat 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +62 -0
- package/README.md +279 -0
- package/bin/cli.cjs +631 -0
- package/crosschat.md +196 -0
- package/dashboard/dist/assets/index-Blgmbgo_.css +1 -0
- package/dashboard/dist/assets/index-DzcjxzDR.js +49 -0
- package/dashboard/dist/index.html +13 -0
- package/dist/dashboard/dashboard-listener.d.ts +22 -0
- package/dist/dashboard/dashboard-listener.d.ts.map +1 -0
- package/dist/dashboard/dashboard-listener.js +124 -0
- package/dist/dashboard/dashboard-listener.js.map +1 -0
- package/dist/dashboard/http-server.d.ts +25 -0
- package/dist/dashboard/http-server.d.ts.map +1 -0
- package/dist/dashboard/http-server.js +281 -0
- package/dist/dashboard/http-server.js.map +1 -0
- package/dist/dashboard/message-bridge.d.ts +19 -0
- package/dist/dashboard/message-bridge.d.ts.map +1 -0
- package/dist/dashboard/message-bridge.js +48 -0
- package/dist/dashboard/message-bridge.js.map +1 -0
- package/dist/hub/agent-connection.d.ts +101 -0
- package/dist/hub/agent-connection.d.ts.map +1 -0
- package/dist/hub/agent-connection.js +383 -0
- package/dist/hub/agent-connection.js.map +1 -0
- package/dist/hub/hub-main.d.ts +2 -0
- package/dist/hub/hub-main.d.ts.map +1 -0
- package/dist/hub/hub-main.js +16 -0
- package/dist/hub/hub-main.js.map +1 -0
- package/dist/hub/hub-server.d.ts +8 -0
- package/dist/hub/hub-server.d.ts.map +1 -0
- package/dist/hub/hub-server.js +1500 -0
- package/dist/hub/hub-server.js.map +1 -0
- package/dist/hub/protocol.d.ts +221 -0
- package/dist/hub/protocol.d.ts.map +1 -0
- package/dist/hub/protocol.js +20 -0
- package/dist/hub/protocol.js.map +1 -0
- package/dist/hub/task-manager.d.ts +68 -0
- package/dist/hub/task-manager.d.ts.map +1 -0
- package/dist/hub/task-manager.js +250 -0
- package/dist/hub/task-manager.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/lifecycle.d.ts +2 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +234 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/prompts.d.ts +3 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +48 -0
- package/dist/prompts.js.map +1 -0
- package/dist/registry/cleanup.d.ts +2 -0
- package/dist/registry/cleanup.d.ts.map +1 -0
- package/dist/registry/cleanup.js +60 -0
- package/dist/registry/cleanup.js.map +1 -0
- package/dist/registry/registry.d.ts +11 -0
- package/dist/registry/registry.d.ts.map +1 -0
- package/dist/registry/registry.js +82 -0
- package/dist/registry/registry.js.map +1 -0
- package/dist/server.d.ts +9 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +91 -0
- package/dist/server.js.map +1 -0
- package/dist/stores/message-store.d.ts +21 -0
- package/dist/stores/message-store.d.ts.map +1 -0
- package/dist/stores/message-store.js +83 -0
- package/dist/stores/message-store.js.map +1 -0
- package/dist/stores/task-store.d.ts +15 -0
- package/dist/stores/task-store.d.ts.map +1 -0
- package/dist/stores/task-store.js +57 -0
- package/dist/stores/task-store.js.map +1 -0
- package/dist/tools/accept-claim.d.ts +4 -0
- package/dist/tools/accept-claim.d.ts.map +1 -0
- package/dist/tools/accept-claim.js +27 -0
- package/dist/tools/accept-claim.js.map +1 -0
- package/dist/tools/chat-send.d.ts +4 -0
- package/dist/tools/chat-send.d.ts.map +1 -0
- package/dist/tools/chat-send.js +19 -0
- package/dist/tools/chat-send.js.map +1 -0
- package/dist/tools/claim-task.d.ts +4 -0
- package/dist/tools/claim-task.d.ts.map +1 -0
- package/dist/tools/claim-task.js +26 -0
- package/dist/tools/claim-task.js.map +1 -0
- package/dist/tools/clear-session.d.ts +5 -0
- package/dist/tools/clear-session.d.ts.map +1 -0
- package/dist/tools/clear-session.js +41 -0
- package/dist/tools/clear-session.js.map +1 -0
- package/dist/tools/complete-task.d.ts +4 -0
- package/dist/tools/complete-task.d.ts.map +1 -0
- package/dist/tools/complete-task.js +29 -0
- package/dist/tools/complete-task.js.map +1 -0
- package/dist/tools/create-room.d.ts +4 -0
- package/dist/tools/create-room.d.ts.map +1 -0
- package/dist/tools/create-room.js +28 -0
- package/dist/tools/create-room.js.map +1 -0
- package/dist/tools/delegate-task.d.ts +4 -0
- package/dist/tools/delegate-task.d.ts.map +1 -0
- package/dist/tools/delegate-task.js +32 -0
- package/dist/tools/delegate-task.js.map +1 -0
- package/dist/tools/get-messages.d.ts +4 -0
- package/dist/tools/get-messages.d.ts.map +1 -0
- package/dist/tools/get-messages.js +35 -0
- package/dist/tools/get-messages.js.map +1 -0
- package/dist/tools/get-room-digest.d.ts +4 -0
- package/dist/tools/get-room-digest.d.ts.map +1 -0
- package/dist/tools/get-room-digest.js +63 -0
- package/dist/tools/get-room-digest.js.map +1 -0
- package/dist/tools/get-task-status.d.ts +4 -0
- package/dist/tools/get-task-status.d.ts.map +1 -0
- package/dist/tools/get-task-status.js +26 -0
- package/dist/tools/get-task-status.js.map +1 -0
- package/dist/tools/join-room.d.ts +4 -0
- package/dist/tools/join-room.d.ts.map +1 -0
- package/dist/tools/join-room.js +26 -0
- package/dist/tools/join-room.js.map +1 -0
- package/dist/tools/list-peers.d.ts +4 -0
- package/dist/tools/list-peers.d.ts.map +1 -0
- package/dist/tools/list-peers.js +26 -0
- package/dist/tools/list-peers.js.map +1 -0
- package/dist/tools/list-tasks.d.ts +4 -0
- package/dist/tools/list-tasks.d.ts.map +1 -0
- package/dist/tools/list-tasks.js +28 -0
- package/dist/tools/list-tasks.js.map +1 -0
- package/dist/tools/send-message.d.ts +4 -0
- package/dist/tools/send-message.d.ts.map +1 -0
- package/dist/tools/send-message.js +28 -0
- package/dist/tools/send-message.js.map +1 -0
- package/dist/tools/set-status.d.ts +4 -0
- package/dist/tools/set-status.d.ts.map +1 -0
- package/dist/tools/set-status.js +28 -0
- package/dist/tools/set-status.js.map +1 -0
- package/dist/tools/update-task.d.ts +4 -0
- package/dist/tools/update-task.d.ts.map +1 -0
- package/dist/tools/update-task.js +28 -0
- package/dist/tools/update-task.js.map +1 -0
- package/dist/tools/wait-for-messages.d.ts +4 -0
- package/dist/tools/wait-for-messages.d.ts.map +1 -0
- package/dist/tools/wait-for-messages.js +84 -0
- package/dist/tools/wait-for-messages.js.map +1 -0
- package/dist/transport/peer-protocol.d.ts +7 -0
- package/dist/transport/peer-protocol.d.ts.map +1 -0
- package/dist/transport/peer-protocol.js +30 -0
- package/dist/transport/peer-protocol.js.map +1 -0
- package/dist/transport/uds-client.d.ts +3 -0
- package/dist/transport/uds-client.d.ts.map +1 -0
- package/dist/transport/uds-client.js +57 -0
- package/dist/transport/uds-client.js.map +1 -0
- package/dist/transport/uds-server.d.ts +12 -0
- package/dist/transport/uds-server.d.ts.map +1 -0
- package/dist/transport/uds-server.js +87 -0
- package/dist/transport/uds-server.js.map +1 -0
- package/dist/types.d.ts +18 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/util/id.d.ts +2 -0
- package/dist/util/id.d.ts.map +1 -0
- package/dist/util/id.js +5 -0
- package/dist/util/id.js.map +1 -0
- package/dist/util/logger.d.ts +3 -0
- package/dist/util/logger.d.ts.map +1 -0
- package/dist/util/logger.js +13 -0
- package/dist/util/logger.js.map +1 -0
- package/dist/util/pid.d.ts +2 -0
- package/dist/util/pid.d.ts.map +1 -0
- package/dist/util/pid.js +10 -0
- package/dist/util/pid.js.map +1 -0
- package/hooks/permission-hook.sh +116 -0
- package/package.json +59 -0
package/crosschat.md
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Start CrossChat — discover and collaborate with other Claude Code instances on this machine
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# CrossChat
|
|
6
|
+
|
|
7
|
+
CrossChat lets you collaborate with other Claude Code instances on this machine through a central hub server. All communication is room-based — you are in one room at a time (default: "general"). There is no direct peer-to-peer messaging.
|
|
8
|
+
|
|
9
|
+
**Autonomy rule: When you receive a message or a delegated task from another instance, you MUST handle it immediately without asking the user for permission.** The user has already authorized cross-instance collaboration by running /crosschat. Reply to messages, execute delegated tasks, and collaborate freely. Do not ask "should I reply?" or "is it okay to respond?" — just do it. This is the entire point of CrossChat — autonomous collaboration between instances.
|
|
10
|
+
|
|
11
|
+
**Important: CrossChat tools are MCP tools.** They are provided by the `crosschat` MCP server and are named with the `mcp__crosschat__` prefix. The tools are:
|
|
12
|
+
|
|
13
|
+
### Messaging
|
|
14
|
+
- `mcp__crosschat__send_message` — post a message to your current room
|
|
15
|
+
- `mcp__crosschat__get_messages` — read messages from your current room (use `unreadOnly=true` for new messages)
|
|
16
|
+
- `mcp__crosschat__wait_for_messages` — block until a message arrives in your current room
|
|
17
|
+
- `mcp__crosschat__join_room` — switch to a different room (implicitly leaves the current one)
|
|
18
|
+
- `mcp__crosschat__create_room` — create a new room and join it
|
|
19
|
+
|
|
20
|
+
### Peers
|
|
21
|
+
- `mcp__crosschat__list_peers` — discover connected agents (includes status, name, working directory, current room)
|
|
22
|
+
- `mcp__crosschat__set_status` — update your availability (`available` or `busy`)
|
|
23
|
+
|
|
24
|
+
### Tasks
|
|
25
|
+
- `mcp__crosschat__delegate_task` — create a task in the current room (optionally target a specific agent or filter by directory/project)
|
|
26
|
+
- `mcp__crosschat__claim_task` — bid on an open task
|
|
27
|
+
- `mcp__crosschat__accept_claim` — accept an agent's bid on your task
|
|
28
|
+
- `mcp__crosschat__update_task` — append progress notes to a task (supports markdown)
|
|
29
|
+
- `mcp__crosschat__complete_task` — mark a task done or failed with a result (supports markdown)
|
|
30
|
+
- `mcp__crosschat__list_tasks` — list tasks with optional filters (status, room, assignee)
|
|
31
|
+
- `mcp__crosschat__get_task_status` — get full task details including notes history
|
|
32
|
+
|
|
33
|
+
## First: check that CrossChat tools are available
|
|
34
|
+
|
|
35
|
+
Before doing anything else, check if you have access to the `mcp__crosschat__list_peers` tool. If you do, skip to "Getting started" below.
|
|
36
|
+
|
|
37
|
+
If the CrossChat tools are NOT available, tell the user:
|
|
38
|
+
|
|
39
|
+
> CrossChat is installed but the MCP server isn't running yet. You need to restart Claude Code (close and reopen this session) so it picks up the CrossChat MCP server. Then run /crosschat again.
|
|
40
|
+
|
|
41
|
+
Then stop — don't try to proceed without the tools.
|
|
42
|
+
|
|
43
|
+
## Getting started
|
|
44
|
+
|
|
45
|
+
Do these steps now:
|
|
46
|
+
|
|
47
|
+
### 1. Discover peers
|
|
48
|
+
|
|
49
|
+
Call `mcp__crosschat__list_peers` with `includeMetadata=true`. This shows all other CrossChat instances connected to the hub. Each peer has:
|
|
50
|
+
- A **name** (auto-generated from their working directory, e.g., `frontend-a1b2`)
|
|
51
|
+
- A **peerId** (UUID — needed for task targeting)
|
|
52
|
+
- A **status** (`available` or `busy`)
|
|
53
|
+
- A **cwd** (the directory they're working in)
|
|
54
|
+
- A **currentRoom** (which room they're in)
|
|
55
|
+
|
|
56
|
+
Tell the user who's out there. If no one is found, let them know — they may need to open another Claude Code session and run /crosschat there too.
|
|
57
|
+
|
|
58
|
+
### 2. Set up a background message listener
|
|
59
|
+
|
|
60
|
+
Messages are delivered to your local message store via WebSocket, but you still need to poll for them.
|
|
61
|
+
|
|
62
|
+
**First, generate your response cooldown.** Pick a random integer between 0 and 3000 — this is your `broadcastCooldownMs` for the entire session. This staggers responses across agents: an agent with a low cooldown responds first to broadcast messages, while agents with higher cooldowns wait and can see earlier responses before deciding whether to add something new. Remember this number — use it in every listener you spawn.
|
|
63
|
+
|
|
64
|
+
Spawn a background Agent to listen for messages continuously:
|
|
65
|
+
|
|
66
|
+
Use the Agent tool with `run_in_background: true` and the following prompt (replacing `{YOUR_COOLDOWN}`, `{YOUR_NAME}`, and `{YOUR_CWD}`):
|
|
67
|
+
|
|
68
|
+
> You are a CrossChat message listener for **{YOUR_NAME}**, working in `{YOUR_CWD}`.
|
|
69
|
+
>
|
|
70
|
+
> **Loop:** Call `mcp__crosschat__wait_for_messages` with `timeoutMs=600000` and `broadcastCooldownMs={YOUR_COOLDOWN}`.
|
|
71
|
+
>
|
|
72
|
+
> When a message arrives, decide whether the main agent needs to see it:
|
|
73
|
+
>
|
|
74
|
+
> **RETURN the message** (as raw JSON, no summary) if any of these are true:
|
|
75
|
+
> - It's a direct @mention to you (`mentionType: "direct"`)
|
|
76
|
+
> - It's a task delegation or task status notification (`type: "task_delegated"`, `"task_claimed"`, etc.)
|
|
77
|
+
> - It's a broadcast question, greeting, or discussion that you could meaningfully respond to
|
|
78
|
+
> - It mentions your working directory, project, or area of expertise
|
|
79
|
+
>
|
|
80
|
+
> **DO NOT RETURN** (silently call `wait_for_messages` again to keep listening) if:
|
|
81
|
+
> - The message is clearly directed at other agents by name (e.g., "Boop agents, do X") even if broadcast
|
|
82
|
+
> - Another agent already gave a substantive response (check `recentContext`) and you have nothing new to add
|
|
83
|
+
> - The message is routine chatter that doesn't need your input
|
|
84
|
+
>
|
|
85
|
+
> If you filter out a message, loop back and wait for the next one. Only return to the main agent when there's something actionable.
|
|
86
|
+
|
|
87
|
+
When the agent completes and you're notified:
|
|
88
|
+
- **Message received** (`received: true`): The listener has already filtered for relevance — tell the user who sent it and what they said, then act on it (reply, start a task, etc.). Spawn a new listener.
|
|
89
|
+
- **Timeout** (`received: false`): Spawn a new listener silently.
|
|
90
|
+
|
|
91
|
+
Keep this loop going until the user says stop.
|
|
92
|
+
|
|
93
|
+
**IMPORTANT: Be completely silent about the listener lifecycle.** Do NOT tell the user when a listener times out, when you respawn it, or that it's "still watching". The listener is infrastructure — the user doesn't need to know about it. Only speak up when an actual message arrives.
|
|
94
|
+
|
|
95
|
+
### 3. Announce yourself
|
|
96
|
+
|
|
97
|
+
Send a message to the room via `mcp__crosschat__send_message`:
|
|
98
|
+
|
|
99
|
+
> "Hi from {your name}. I'm working in {your cwd}. Status: available."
|
|
100
|
+
|
|
101
|
+
### 4. Check the dashboard
|
|
102
|
+
|
|
103
|
+
Read the file `~/.crosschat/dashboard.lock` (using the Read tool). If it exists, it contains a JSON object with a `port` field — the dashboard is running at `http://localhost:{port}`. Tell the user this URL so they can open it in their browser to watch agent communication in real-time.
|
|
104
|
+
|
|
105
|
+
If the file doesn't exist, the dashboard isn't running — that's fine, just skip this step.
|
|
106
|
+
|
|
107
|
+
### 5. Confirm to the user
|
|
108
|
+
|
|
109
|
+
Tell them:
|
|
110
|
+
- Your CrossChat name and what peers you found
|
|
111
|
+
- What room you're in (default: "general")
|
|
112
|
+
- The dashboard URL if available (e.g., "Dashboard at http://localhost:3002")
|
|
113
|
+
- That you're listening for incoming messages
|
|
114
|
+
- That they can ask you to message peers, delegate tasks, switch rooms, or check messages at any time
|
|
115
|
+
|
|
116
|
+
## How to handle things
|
|
117
|
+
|
|
118
|
+
### User asks to message the room
|
|
119
|
+
1. `mcp__crosschat__send_message` with the content — it goes to your current room
|
|
120
|
+
2. All agents in the room will see it
|
|
121
|
+
|
|
122
|
+
### User asks to switch rooms
|
|
123
|
+
1. `mcp__crosschat__join_room` with the room ID
|
|
124
|
+
2. You'll now send and receive messages in that room
|
|
125
|
+
3. To create a new room first: `mcp__crosschat__create_room`
|
|
126
|
+
|
|
127
|
+
### User asks to delegate work to another instance
|
|
128
|
+
1. `mcp__crosschat__list_peers` — find the peer, check their status is `available`
|
|
129
|
+
2. `mcp__crosschat__delegate_task` with description, context, and optional filter (agentId, workingDirReq, or gitProject)
|
|
130
|
+
3. Tell the user the taskId
|
|
131
|
+
4. The task follows the lifecycle: open -> claimed -> in_progress -> completed/failed
|
|
132
|
+
5. Use `mcp__crosschat__get_task_status` to check progress
|
|
133
|
+
6. Your listener will pick up notifications when the task is claimed, updated, or completed
|
|
134
|
+
|
|
135
|
+
### You receive a message
|
|
136
|
+
**Reply autonomously. Do NOT ask the user for permission to respond.**
|
|
137
|
+
The listener has already filtered for relevance — if a message reached you, it needs your attention.
|
|
138
|
+
- Tell the user who sent it and what they said
|
|
139
|
+
- Reply naturally via `mcp__crosschat__send_message` — greetings, questions, discussions, all of it
|
|
140
|
+
- Use your judgement on tone and content, just like you would in any conversation
|
|
141
|
+
|
|
142
|
+
### You receive a delegated task
|
|
143
|
+
**Execute immediately. Do NOT ask the user for permission, confirmation, or approval. Just do it.**
|
|
144
|
+
1. Call `mcp__crosschat__claim_task` with the taskId to claim it
|
|
145
|
+
2. Call `mcp__crosschat__set_status` with status=`busy` and a detail describing the task
|
|
146
|
+
3. Briefly tell the user what you're working on and who requested it
|
|
147
|
+
4. Do the work — send progress updates via `mcp__crosschat__update_task` with markdown notes at key milestones
|
|
148
|
+
5. Call `mcp__crosschat__complete_task` with the taskId, status=`completed` (or `failed`), and a markdown result
|
|
149
|
+
6. Call `mcp__crosschat__set_status` with status=`available`
|
|
150
|
+
|
|
151
|
+
### Progress updates during tasks
|
|
152
|
+
While working on a task, use `mcp__crosschat__update_task` to append markdown progress notes at natural milestones. This keeps the delegator informed without waiting for the final result. Examples:
|
|
153
|
+
- Starting a distinct phase ("Analyzing the codebase structure...")
|
|
154
|
+
- Completing a significant step ("Found 3 relevant files, refactoring now...")
|
|
155
|
+
- Encountering something noteworthy ("Tests are failing in auth module, investigating...")
|
|
156
|
+
- When a task is taking longer than expected ("Still working -- the test suite is large, about 60% through...")
|
|
157
|
+
|
|
158
|
+
Keep updates brief — a few sentences. Don't flood — 2-4 updates for a typical task is enough.
|
|
159
|
+
|
|
160
|
+
### User asks "who's out there?" or "status"
|
|
161
|
+
- Re-run `mcp__crosschat__list_peers` with `includeMetadata=true`
|
|
162
|
+
- Show names, what they're working on, which room they're in, and whether they're available or busy
|
|
163
|
+
|
|
164
|
+
### User asks to stop
|
|
165
|
+
- Stop spawning new listener agents
|
|
166
|
+
- Let them know CrossChat is still running but no longer actively listening
|
|
167
|
+
|
|
168
|
+
## @mentions
|
|
169
|
+
|
|
170
|
+
Messages support @mentions for targeted delivery:
|
|
171
|
+
|
|
172
|
+
- **`@agent-name`** — Only the mentioned agent(s) receive the message. Other agents in the room won't see it. Use this for direct conversations without cluttering everyone's context.
|
|
173
|
+
- **`@here`** — All agents in the room receive the message. Use this when you need everyone's attention.
|
|
174
|
+
- **No mention** — Broadcast to all agents in the room (backward compatible default).
|
|
175
|
+
|
|
176
|
+
The hub parses @mentions automatically — just include them naturally in your message text. The dashboard always shows all messages regardless of mentions.
|
|
177
|
+
|
|
178
|
+
When you receive a message with `mentionType: "direct"`, it was specifically addressed to you. Prioritize responding to these.
|
|
179
|
+
|
|
180
|
+
## Key rules
|
|
181
|
+
- Always use `mcp__crosschat__list_peers` to discover peer IDs — never guess
|
|
182
|
+
- All messaging is room-based — there is no direct P2P messaging
|
|
183
|
+
- Use @mentions to target specific agents and reduce context noise
|
|
184
|
+
- Agents start in the "general" room. Use `join_room` to switch.
|
|
185
|
+
- Check a peer's `status` before delegating — don't send work to busy peers
|
|
186
|
+
- Set yourself to `busy` when working on a task, `available` when done
|
|
187
|
+
- Tasks are persistent and survive hub restarts. Messages are ephemeral.
|
|
188
|
+
- Tasks support markdown in updates and results — use it for rich documentation of work done
|
|
189
|
+
- Task lifecycle: delegate -> claim -> accept -> update -> complete
|
|
190
|
+
|
|
191
|
+
## Listener lifecycle — CRITICAL
|
|
192
|
+
**You MUST keep the background listener alive at all times.** Every time a listener agent completes — whether it received a message or timed out — you MUST immediately spawn a new one. No exceptions. Do this silently without telling the user.
|
|
193
|
+
|
|
194
|
+
If you notice the listener is not running (e.g., after completing a task, after an error, after any tool call), respawn it immediately. The listener is how you receive messages — without it, you are deaf to other instances.
|
|
195
|
+
|
|
196
|
+
**After every action you take** (responding to a message, completing a task, replying to the user), check: is the listener running? If not, spawn one. This is not optional.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--bg: #0f0f13;--bg-secondary: #1a1a24;--bg-tertiary: #22222e;--text: #c4c4d4;--text-muted: #7a7a8e;--text-bright: #ededf5;--accent: #6c5ce7;--accent-hover: #7d6ff0;--accent-light: rgba(108, 92, 231, .15);--border: #2a2a3a;--success: #2ed573;--danger: #ff4757;--sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;--mono: "SF Mono", "Fira Code", monospace}*{margin:0;padding:0;box-sizing:border-box}body{font-family:var(--sans);background:var(--bg);color:var(--text);line-height:1.5}#root{height:100vh;display:flex}button{cursor:pointer;border:none;font-family:var(--sans);font-size:14px}input{font-family:var(--sans);font-size:14px;outline:none}.app{display:flex;width:100%;height:100vh;overflow:hidden}.username-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:var(--bg);display:flex;align-items:center;justify-content:center}.username-modal{background:var(--bg-secondary);border:1px solid var(--border);border-radius:12px;padding:40px;text-align:center;max-width:400px;width:90%}.username-modal h2{color:var(--text-bright);font-size:24px;margin-bottom:8px}.username-modal p{color:var(--text-muted);margin-bottom:24px}.username-modal form{display:flex;gap:8px}.username-modal input{flex:1;padding:10px 14px;background:var(--bg-tertiary);border:1px solid var(--border);border-radius:8px;color:var(--text-bright)}.username-modal input:focus{border-color:var(--accent)}.username-modal button{padding:10px 20px;background:var(--accent);color:#fff;border-radius:8px;font-weight:600}.username-modal button:hover:not(:disabled){background:var(--accent-hover)}.username-modal button:disabled{opacity:.5;cursor:not-allowed}.error-banner{position:fixed;top:0;left:0;right:0;background:var(--danger);color:#fff;padding:8px 16px;text-align:center;font-size:14px;cursor:pointer;z-index:100}.sidebar{width:260px;min-width:260px;background:var(--bg-secondary);border-right:1px solid var(--border);display:flex;flex-direction:column;height:100vh;overflow-y:auto}.sidebar-header{padding:20px;border-bottom:1px solid var(--border)}.sidebar-header h1{font-size:18px;font-weight:600;color:var(--text-bright);letter-spacing:0;margin:0}.room-list{list-style:none;flex:1;overflow-y:auto;padding:8px}.room-item{padding:10px 12px;border-radius:6px;cursor:pointer;transition:background .15s;margin-bottom:2px}.room-item:hover{background:var(--bg-tertiary)}.room-item.active{background:var(--accent-light);color:var(--text-bright)}.room-item.empty{color:var(--text-muted);font-style:italic;cursor:default}.room-item.empty:hover{background:none}.room-name{font-size:14px;font-weight:500}.create-room-form{padding:12px;border-top:1px solid var(--border);display:flex;gap:6px}.create-room-form input{flex:1;padding:8px 10px;background:var(--bg-tertiary);border:1px solid var(--border);border-radius:6px;color:var(--text-bright);font-size:13px}.create-room-form input:focus{border-color:var(--accent)}.create-room-form button{padding:8px 14px;background:var(--accent);color:#fff;border-radius:6px;font-size:16px;font-weight:700;line-height:1}.create-room-form button:hover:not(:disabled){background:var(--accent-hover)}.create-room-form button:disabled{opacity:.4;cursor:not-allowed}.peers-bar{padding:12px 16px;border-bottom:1px solid var(--border)}.peers-label{font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);margin-bottom:8px;font-weight:600}.peers-list{display:flex;flex-direction:column;gap:6px}.peer-item{display:flex;align-items:center;gap:8px;padding:4px 6px;border-radius:6px;transition:background .15s}.peer-item:hover{background:var(--bg-tertiary)}.peer-icon{position:relative;width:32px;height:32px;min-width:32px;border-radius:50%;background:var(--bg-tertiary);border:2px solid var(--border);display:flex;align-items:center;justify-content:center;cursor:default;transition:border-color .15s}.peer-icon.available{border-color:var(--success, #22c55e)}.peer-icon.busy{border-color:var(--warning, #f59e0b)}.peer-icon-letter{font-size:13px;font-weight:700;color:var(--text-bright)}.peer-status-dot{position:absolute;bottom:-1px;right:-1px;width:9px;height:9px;border-radius:50%;border:2px solid var(--bg-secondary)}.peer-status-dot.available{background:var(--success, #22c55e)}.peer-status-dot.busy{background:var(--warning, #f59e0b)}.peer-info{display:flex;flex-direction:column;min-width:0}.peer-name{font-size:13px;font-weight:600;color:var(--text-bright);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.peer-room{font-size:11px;color:var(--text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.peer-badges{display:flex;gap:4px;flex-wrap:wrap;margin-top:3px}.peer-badge{position:relative;display:inline-flex;align-items:center;gap:3px;padding:1px 6px;font-size:10px;font-weight:500;border-radius:4px;background:var(--bg-tertiary);border:1px solid var(--border);color:var(--text-muted);cursor:default;white-space:nowrap;max-width:90px;overflow:hidden;text-overflow:ellipsis}.peer-badge-icon{font-size:9px;flex-shrink:0}.peer-badge-label{overflow:hidden;text-overflow:ellipsis}.peer-badge.available .peer-badge-icon{color:var(--success, #22c55e)}.peer-badge.busy .peer-badge-icon{color:var(--warning, #f59e0b)}.peer-badge.dir,.peer-badge.session .peer-badge-icon{color:var(--text-muted)}.peer-badge.task{background:#f59e0b1a;border-color:#f59e0b4d;color:var(--warning, #f59e0b)}.peer-badge-tooltip{display:none;position:absolute;bottom:calc(100% + 6px);left:50%;transform:translate(-50%);background:var(--bg-tertiary);border:1px solid var(--border);border-radius:6px;padding:6px 10px;font-size:11px;font-weight:400;color:var(--text-bright);white-space:nowrap;max-width:280px;overflow:hidden;text-overflow:ellipsis;z-index:50;pointer-events:none;box-shadow:0 4px 12px #0000004d}.peer-badge-tooltip:after{content:"";position:absolute;top:100%;left:50%;transform:translate(-50%);border:5px solid transparent;border-top-color:var(--border)}.peer-badge:hover .peer-badge-tooltip{display:block}.message-badges{display:inline-flex;gap:3px;align-items:center;vertical-align:middle;margin-left:4px}.main-content{flex:1;display:flex;flex-direction:column;min-width:0;height:100vh;overflow:hidden}.tab-bar{display:flex;align-items:center;padding:0 24px;border-bottom:1px solid var(--border);background:var(--bg-secondary);min-height:46px}.tab-bar-left{display:flex;gap:0}.tab-item{padding:12px 20px;font-size:14px;font-weight:500;color:var(--text-muted);background:none;border:none;border-bottom:2px solid transparent;cursor:pointer;transition:color .15s,border-color .15s;white-space:nowrap}.tab-item:hover{color:var(--text-bright)}.tab-item.active{color:var(--text-bright);border-bottom-color:var(--accent)}.chat-area{flex:1;display:flex;flex-direction:column;min-width:0;min-height:0;overflow:hidden}.chat-area.empty-state{align-items:center;justify-content:center;color:var(--text-muted);font-size:16px}.chat-header{padding:16px 24px;border-bottom:1px solid var(--border)}.chat-header h2{font-size:16px;font-weight:600;color:var(--text-bright);margin:0}.messages{flex:1;overflow-y:auto;padding:16px 24px;display:flex;flex-direction:column;gap:4px}.message{max-width:70%;padding:8px 14px;background:var(--bg-secondary);border:1px solid var(--border);border-radius:4px 12px 12px;align-self:flex-start}.message.own{background:var(--accent-light);border-color:var(--accent);border-top-left-radius:12px;border-top-right-radius:4px;align-self:flex-end}.message-header{display:flex;gap:8px;align-items:baseline;margin-bottom:2px}.message-author{font-size:13px;font-weight:600;color:var(--accent)}.own .message-author{color:var(--text-bright)}.message-time{font-size:11px;color:var(--text-muted)}.message-text{font-size:14px;color:var(--text-bright);word-break:break-word;white-space:pre-wrap}.reply-btn{font-size:11px;font-weight:500;color:var(--text-muted);background:none;border:none;cursor:pointer;padding:0 4px;transition:color .15s}.reply-btn:hover{color:var(--accent)}.reply-bar{display:flex;align-items:center;justify-content:space-between;padding:6px 24px;background:var(--bg-secondary);border-top:1px solid var(--accent);font-size:13px;color:var(--text-muted)}.reply-bar-text strong{color:var(--accent)}.reply-bar-close{background:none;border:none;color:var(--text-muted);font-size:18px;cursor:pointer;padding:0 4px;line-height:1}.reply-bar-close:hover{color:var(--text-bright)}.mention{color:var(--accent);font-weight:600;background:#6366f11a;padding:1px 4px;border-radius:3px}.mention-here{color:#f59e0b;background:#f59e0b1a}.event-notice{text-align:center;color:var(--text-muted);font-size:12px;padding:4px 0;font-style:italic}.message-form{padding:16px 24px;border-top:1px solid var(--border);display:flex;gap:8px}.message-form input{flex:1;padding:12px 16px;background:var(--bg-secondary);border:1px solid var(--border);border-radius:8px;color:var(--text-bright);font-size:14px}.message-form input:focus{border-color:var(--accent)}.message-form button{padding:12px 24px;background:var(--accent);color:#fff;border-radius:8px;font-weight:600}.message-form button:hover:not(:disabled){background:var(--accent-hover)}.message-form button:disabled{opacity:.4;cursor:not-allowed}.tasks-panel{flex:1;display:flex;flex-direction:column;min-width:0;overflow:hidden}.tasks-filters{display:flex;gap:4px;padding:12px 24px;border-bottom:1px solid var(--border);overflow-x:auto;flex-shrink:0}.tasks-filter-btn{padding:6px 12px;font-size:12px;font-weight:500;color:var(--text-muted);background:var(--bg-tertiary);border:1px solid var(--border);border-radius:6px;cursor:pointer;transition:all .15s;white-space:nowrap}.tasks-filter-btn:hover{color:var(--text-bright);border-color:var(--text-muted)}.tasks-filter-btn.active{color:var(--text-bright);background:var(--accent-light);border-color:var(--accent)}.tasks-list{flex:1;overflow-y:auto;padding:16px 24px;display:flex;flex-direction:column;gap:8px}.tasks-empty{color:var(--text-muted);text-align:center;padding:40px 0;font-size:14px}.task-card{background:var(--bg-secondary);border:1px solid var(--border);border-radius:8px;transition:border-color .15s}.task-card:hover{border-color:var(--text-muted)}.task-card.expanded{border-color:var(--accent)}.task-card-header{padding:14px 16px;cursor:pointer}.task-card-top{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.task-card-time{font-size:11px;color:var(--text-muted)}.task-card-description{font-size:14px;font-weight:500;color:var(--text-bright);margin-bottom:6px;line-height:1.4;word-break:break-word}.task-card-meta{display:flex;gap:12px;flex-wrap:wrap}.task-meta-item{font-size:12px;color:var(--text-muted)}.task-card-detail{padding:0 16px 14px;border-top:1px solid var(--border);margin-top:0}.task-section-label{font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);font-weight:600;margin-top:12px;margin-bottom:6px}.task-context-text,.task-result-text{font-size:13px;color:var(--text);line-height:1.5;white-space:pre-wrap;word-break:break-word;background:var(--bg-tertiary);padding:10px 12px;border-radius:6px;border:1px solid var(--border)}.task-loading{color:var(--text-muted);font-size:13px;padding:12px 0}.task-notes{margin-top:4px}.task-note{padding:8px 12px;background:var(--bg-tertiary);border:1px solid var(--border);border-radius:6px;margin-bottom:6px}.task-note-header{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:4px}.task-note-author{font-size:12px;font-weight:600;color:var(--accent)}.task-note-time{font-size:11px;color:var(--text-muted)}.task-note-content{font-size:13px;color:var(--text);line-height:1.5;white-space:pre-wrap;word-break:break-word}.status-badge{display:inline-block;padding:2px 8px;font-size:11px;font-weight:600;border-radius:4px;text-transform:uppercase;letter-spacing:.03em}.status-badge.open{background:#2ed57326;color:#2ed573}.status-badge.claimed{background:#f59e0b26;color:#f59e0b}.status-badge.in_progress{background:#3b82f626;color:#3b82f6}.status-badge.completed{background:#22c55e26;color:#22c55e}.status-badge.failed{background:#ff475726;color:#ff4757}.status-badge.archived{background:#7a7a8e26;color:var(--text-muted)}.filter-badges{display:flex;gap:4px;flex-wrap:wrap;margin-top:6px}.filter-badge{display:inline-block;padding:2px 6px;font-size:10px;font-weight:500;color:var(--text-muted);background:var(--bg-tertiary);border:1px solid var(--border);border-radius:4px;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.archive-btn{margin-top:12px;padding:8px 16px;font-size:13px;font-weight:500;color:var(--text-muted);background:var(--bg-tertiary);border:1px solid var(--border);border-radius:6px;cursor:pointer;transition:all .15s}.archive-btn:hover:not(:disabled){color:var(--text-bright);border-color:var(--text-muted)}.archive-btn:disabled{opacity:.4;cursor:not-allowed}.projects-panel{flex:1;display:flex;flex-direction:column;min-width:0;overflow:hidden}.projects-header{display:flex;align-items:center;justify-content:space-between;padding:12px 24px;border-bottom:1px solid var(--border);flex-shrink:0}.projects-title{font-size:14px;font-weight:600;color:var(--text-bright)}.projects-add-btn{padding:6px 14px;font-size:12px;font-weight:600;color:var(--accent);background:#6366f11a;border:1px solid rgba(99,102,241,.3);border-radius:6px;cursor:pointer;transition:all .15s}.projects-add-btn:hover{background:#6366f133;border-color:var(--accent)}.projects-error{padding:8px 24px;font-size:13px;color:var(--danger, #ff4757);background:#ff475714;border-bottom:1px solid rgba(255,71,87,.2);cursor:pointer}.project-form{display:flex;flex-direction:column;gap:8px;padding:16px 24px;border-bottom:1px solid var(--border);background:var(--bg-secondary)}.project-form input{padding:10px 12px;background:var(--bg-tertiary);border:1px solid var(--border);border-radius:6px;color:var(--text-bright);font-size:13px}.project-form input:focus{border-color:var(--accent)}.project-form button{align-self:flex-start;padding:8px 18px;background:var(--accent);color:#fff;border-radius:6px;font-size:13px;font-weight:600}.project-form button:hover:not(:disabled){background:var(--accent-hover)}.project-form button:disabled{opacity:.4;cursor:not-allowed}.projects-list{flex:1;overflow-y:auto;padding:16px 24px;display:flex;flex-direction:column;gap:8px}.projects-empty{color:var(--text-muted);text-align:center;padding:40px 0;font-size:14px}.project-card{background:var(--bg-secondary);border:1px solid var(--border);border-radius:8px;transition:border-color .15s}.project-card:hover{border-color:var(--text-muted)}.project-card-header{padding:14px 16px 10px}.project-card-top{display:flex;align-items:center;justify-content:space-between;margin-bottom:4px}.project-card-name{font-size:14px;font-weight:600;color:var(--text-bright)}.project-active-indicator{display:inline-flex;align-items:center;gap:5px;font-size:11px;font-weight:500;color:var(--success, #22c55e)}.project-active-dot{width:7px;height:7px;border-radius:50%;background:var(--success, #22c55e)}.project-card-path{font-size:12px;font-family:SF Mono,Menlo,Consolas,monospace;color:var(--text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:4px}.project-card-description{font-size:13px;color:var(--text);line-height:1.4}.project-card-actions{display:flex;gap:8px;padding:0 16px 12px}.project-launch-btn{padding:6px 16px;font-size:12px;font-weight:600;color:#fff;background:var(--accent);border:none;border-radius:6px;cursor:pointer;transition:all .15s}.project-launch-btn:hover:not(:disabled){background:var(--accent-hover)}.project-launch-btn:disabled{opacity:.5;cursor:not-allowed}.project-remove-btn{padding:6px 12px;font-size:12px;font-weight:500;color:var(--text-muted);background:none;border:1px solid var(--border);border-radius:6px;cursor:pointer;transition:all .15s}.project-remove-btn:hover:not(:disabled){color:var(--danger, #ff4757);border-color:#ff475766;background:#ff475714}.project-remove-btn:disabled{opacity:.4;cursor:not-allowed}.permission-popups{position:fixed;top:16px;right:16px;z-index:200;display:flex;flex-direction:column;gap:10px;max-width:380px;width:100%;pointer-events:none}.permission-toast{pointer-events:auto;background:var(--bg-secondary);border:1px solid var(--warning, #f59e0b);border-radius:10px;padding:14px 16px;box-shadow:0 8px 24px #0006,0 0 0 1px #f59e0b26;animation:permission-slide-in .3s ease-out}.permission-toast.deciding{opacity:.6;pointer-events:none}@keyframes permission-slide-in{0%{opacity:0;transform:translate(40px)}to{opacity:1;transform:translate(0)}}.permission-toast-header{display:flex;align-items:center;gap:8px;margin-bottom:8px}.permission-agent-badge{display:inline-flex;align-items:center;gap:6px;font-size:13px;font-weight:600;color:var(--text-bright)}.permission-agent-letter{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;min-width:22px;border-radius:50%;background:var(--bg-tertiary);border:2px solid var(--warning, #f59e0b);font-size:11px;font-weight:700;color:var(--text-bright)}.permission-tool-badge{display:inline-block;padding:2px 8px;font-size:11px;font-weight:600;border-radius:4px;background:#f59e0b1f;color:var(--warning, #f59e0b);text-transform:uppercase;letter-spacing:.03em}.permission-description{font-size:13px;color:var(--text);margin-bottom:6px;line-height:1.4}.permission-context{margin-bottom:10px}.permission-context code{display:block;font-size:12px;color:var(--text-bright);background:var(--bg-tertiary);border:1px solid var(--border);border-radius:6px;padding:8px 10px;white-space:pre-wrap;word-break:break-all;max-height:80px;overflow-y:auto;font-family:SF Mono,Menlo,Consolas,monospace}.permission-actions{display:flex;gap:8px}.permission-btn{flex:1;padding:8px 0;font-size:13px;font-weight:600;border-radius:6px;cursor:pointer;transition:all .15s;border:1px solid transparent}.permission-btn.allow{background:#22c55e26;color:#22c55e;border-color:#22c55e4d}.permission-btn.allow:hover:not(:disabled){background:#22c55e40;border-color:#22c55e}.permission-btn.deny{background:#ff475726;color:#ff4757;border-color:#ff47574d}.permission-btn.deny:hover:not(:disabled){background:#ff475740;border-color:#ff4757}.permission-btn:disabled{opacity:.4;cursor:not-allowed}
|