@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.
Files changed (169) hide show
  1. package/LICENSE +62 -0
  2. package/README.md +279 -0
  3. package/bin/cli.cjs +631 -0
  4. package/crosschat.md +196 -0
  5. package/dashboard/dist/assets/index-Blgmbgo_.css +1 -0
  6. package/dashboard/dist/assets/index-DzcjxzDR.js +49 -0
  7. package/dashboard/dist/index.html +13 -0
  8. package/dist/dashboard/dashboard-listener.d.ts +22 -0
  9. package/dist/dashboard/dashboard-listener.d.ts.map +1 -0
  10. package/dist/dashboard/dashboard-listener.js +124 -0
  11. package/dist/dashboard/dashboard-listener.js.map +1 -0
  12. package/dist/dashboard/http-server.d.ts +25 -0
  13. package/dist/dashboard/http-server.d.ts.map +1 -0
  14. package/dist/dashboard/http-server.js +281 -0
  15. package/dist/dashboard/http-server.js.map +1 -0
  16. package/dist/dashboard/message-bridge.d.ts +19 -0
  17. package/dist/dashboard/message-bridge.d.ts.map +1 -0
  18. package/dist/dashboard/message-bridge.js +48 -0
  19. package/dist/dashboard/message-bridge.js.map +1 -0
  20. package/dist/hub/agent-connection.d.ts +101 -0
  21. package/dist/hub/agent-connection.d.ts.map +1 -0
  22. package/dist/hub/agent-connection.js +383 -0
  23. package/dist/hub/agent-connection.js.map +1 -0
  24. package/dist/hub/hub-main.d.ts +2 -0
  25. package/dist/hub/hub-main.d.ts.map +1 -0
  26. package/dist/hub/hub-main.js +16 -0
  27. package/dist/hub/hub-main.js.map +1 -0
  28. package/dist/hub/hub-server.d.ts +8 -0
  29. package/dist/hub/hub-server.d.ts.map +1 -0
  30. package/dist/hub/hub-server.js +1500 -0
  31. package/dist/hub/hub-server.js.map +1 -0
  32. package/dist/hub/protocol.d.ts +221 -0
  33. package/dist/hub/protocol.d.ts.map +1 -0
  34. package/dist/hub/protocol.js +20 -0
  35. package/dist/hub/protocol.js.map +1 -0
  36. package/dist/hub/task-manager.d.ts +68 -0
  37. package/dist/hub/task-manager.d.ts.map +1 -0
  38. package/dist/hub/task-manager.js +250 -0
  39. package/dist/hub/task-manager.js.map +1 -0
  40. package/dist/index.d.ts +2 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +7 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/lifecycle.d.ts +2 -0
  45. package/dist/lifecycle.d.ts.map +1 -0
  46. package/dist/lifecycle.js +234 -0
  47. package/dist/lifecycle.js.map +1 -0
  48. package/dist/prompts.d.ts +3 -0
  49. package/dist/prompts.d.ts.map +1 -0
  50. package/dist/prompts.js +48 -0
  51. package/dist/prompts.js.map +1 -0
  52. package/dist/registry/cleanup.d.ts +2 -0
  53. package/dist/registry/cleanup.d.ts.map +1 -0
  54. package/dist/registry/cleanup.js +60 -0
  55. package/dist/registry/cleanup.js.map +1 -0
  56. package/dist/registry/registry.d.ts +11 -0
  57. package/dist/registry/registry.d.ts.map +1 -0
  58. package/dist/registry/registry.js +82 -0
  59. package/dist/registry/registry.js.map +1 -0
  60. package/dist/server.d.ts +9 -0
  61. package/dist/server.d.ts.map +1 -0
  62. package/dist/server.js +91 -0
  63. package/dist/server.js.map +1 -0
  64. package/dist/stores/message-store.d.ts +21 -0
  65. package/dist/stores/message-store.d.ts.map +1 -0
  66. package/dist/stores/message-store.js +83 -0
  67. package/dist/stores/message-store.js.map +1 -0
  68. package/dist/stores/task-store.d.ts +15 -0
  69. package/dist/stores/task-store.d.ts.map +1 -0
  70. package/dist/stores/task-store.js +57 -0
  71. package/dist/stores/task-store.js.map +1 -0
  72. package/dist/tools/accept-claim.d.ts +4 -0
  73. package/dist/tools/accept-claim.d.ts.map +1 -0
  74. package/dist/tools/accept-claim.js +27 -0
  75. package/dist/tools/accept-claim.js.map +1 -0
  76. package/dist/tools/chat-send.d.ts +4 -0
  77. package/dist/tools/chat-send.d.ts.map +1 -0
  78. package/dist/tools/chat-send.js +19 -0
  79. package/dist/tools/chat-send.js.map +1 -0
  80. package/dist/tools/claim-task.d.ts +4 -0
  81. package/dist/tools/claim-task.d.ts.map +1 -0
  82. package/dist/tools/claim-task.js +26 -0
  83. package/dist/tools/claim-task.js.map +1 -0
  84. package/dist/tools/clear-session.d.ts +5 -0
  85. package/dist/tools/clear-session.d.ts.map +1 -0
  86. package/dist/tools/clear-session.js +41 -0
  87. package/dist/tools/clear-session.js.map +1 -0
  88. package/dist/tools/complete-task.d.ts +4 -0
  89. package/dist/tools/complete-task.d.ts.map +1 -0
  90. package/dist/tools/complete-task.js +29 -0
  91. package/dist/tools/complete-task.js.map +1 -0
  92. package/dist/tools/create-room.d.ts +4 -0
  93. package/dist/tools/create-room.d.ts.map +1 -0
  94. package/dist/tools/create-room.js +28 -0
  95. package/dist/tools/create-room.js.map +1 -0
  96. package/dist/tools/delegate-task.d.ts +4 -0
  97. package/dist/tools/delegate-task.d.ts.map +1 -0
  98. package/dist/tools/delegate-task.js +32 -0
  99. package/dist/tools/delegate-task.js.map +1 -0
  100. package/dist/tools/get-messages.d.ts +4 -0
  101. package/dist/tools/get-messages.d.ts.map +1 -0
  102. package/dist/tools/get-messages.js +35 -0
  103. package/dist/tools/get-messages.js.map +1 -0
  104. package/dist/tools/get-room-digest.d.ts +4 -0
  105. package/dist/tools/get-room-digest.d.ts.map +1 -0
  106. package/dist/tools/get-room-digest.js +63 -0
  107. package/dist/tools/get-room-digest.js.map +1 -0
  108. package/dist/tools/get-task-status.d.ts +4 -0
  109. package/dist/tools/get-task-status.d.ts.map +1 -0
  110. package/dist/tools/get-task-status.js +26 -0
  111. package/dist/tools/get-task-status.js.map +1 -0
  112. package/dist/tools/join-room.d.ts +4 -0
  113. package/dist/tools/join-room.d.ts.map +1 -0
  114. package/dist/tools/join-room.js +26 -0
  115. package/dist/tools/join-room.js.map +1 -0
  116. package/dist/tools/list-peers.d.ts +4 -0
  117. package/dist/tools/list-peers.d.ts.map +1 -0
  118. package/dist/tools/list-peers.js +26 -0
  119. package/dist/tools/list-peers.js.map +1 -0
  120. package/dist/tools/list-tasks.d.ts +4 -0
  121. package/dist/tools/list-tasks.d.ts.map +1 -0
  122. package/dist/tools/list-tasks.js +28 -0
  123. package/dist/tools/list-tasks.js.map +1 -0
  124. package/dist/tools/send-message.d.ts +4 -0
  125. package/dist/tools/send-message.d.ts.map +1 -0
  126. package/dist/tools/send-message.js +28 -0
  127. package/dist/tools/send-message.js.map +1 -0
  128. package/dist/tools/set-status.d.ts +4 -0
  129. package/dist/tools/set-status.d.ts.map +1 -0
  130. package/dist/tools/set-status.js +28 -0
  131. package/dist/tools/set-status.js.map +1 -0
  132. package/dist/tools/update-task.d.ts +4 -0
  133. package/dist/tools/update-task.d.ts.map +1 -0
  134. package/dist/tools/update-task.js +28 -0
  135. package/dist/tools/update-task.js.map +1 -0
  136. package/dist/tools/wait-for-messages.d.ts +4 -0
  137. package/dist/tools/wait-for-messages.d.ts.map +1 -0
  138. package/dist/tools/wait-for-messages.js +84 -0
  139. package/dist/tools/wait-for-messages.js.map +1 -0
  140. package/dist/transport/peer-protocol.d.ts +7 -0
  141. package/dist/transport/peer-protocol.d.ts.map +1 -0
  142. package/dist/transport/peer-protocol.js +30 -0
  143. package/dist/transport/peer-protocol.js.map +1 -0
  144. package/dist/transport/uds-client.d.ts +3 -0
  145. package/dist/transport/uds-client.d.ts.map +1 -0
  146. package/dist/transport/uds-client.js +57 -0
  147. package/dist/transport/uds-client.js.map +1 -0
  148. package/dist/transport/uds-server.d.ts +12 -0
  149. package/dist/transport/uds-server.d.ts.map +1 -0
  150. package/dist/transport/uds-server.js +87 -0
  151. package/dist/transport/uds-server.js.map +1 -0
  152. package/dist/types.d.ts +18 -0
  153. package/dist/types.d.ts.map +1 -0
  154. package/dist/types.js +3 -0
  155. package/dist/types.js.map +1 -0
  156. package/dist/util/id.d.ts +2 -0
  157. package/dist/util/id.d.ts.map +1 -0
  158. package/dist/util/id.js +5 -0
  159. package/dist/util/id.js.map +1 -0
  160. package/dist/util/logger.d.ts +3 -0
  161. package/dist/util/logger.d.ts.map +1 -0
  162. package/dist/util/logger.js +13 -0
  163. package/dist/util/logger.js.map +1 -0
  164. package/dist/util/pid.d.ts +2 -0
  165. package/dist/util/pid.d.ts.map +1 -0
  166. package/dist/util/pid.js +10 -0
  167. package/dist/util/pid.js.map +1 -0
  168. package/hooks/permission-hook.sh +116 -0
  169. 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}