Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 0 additions & 133 deletions docs/plans/tab-emojis-design.md

This file was deleted.

14 changes: 7 additions & 7 deletions shell/chat/claude-activity-backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
* ClaudeActivityBackend — Polls GET /chat for Claude MCP activity
* Implements ChatBackend interface (see src/chat/interfaces.ts)
*
* Creates a chat loop: Robin (browser) <-> Claude (Cowork) via MCP.
* Creates a chat loop: User (browser) <-> Claude (Cowork) via MCP.
* Claude writes via tandem_send_message MCP tool -> POST /chat from:"claude"
* Robin writes via this backend -> POST /chat from:"robin"
* User writes via this backend -> POST /chat from:"user"
* Claude reads via tandem_get_chat_history MCP tool -> GET /chat
*/
class ClaudeActivityBackend {
Expand Down Expand Up @@ -57,7 +57,7 @@ class ClaudeActivityBackend {
const res = await fetch(`${this._apiBase}/chat`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text, from: 'robin' })
body: JSON.stringify({ text, from: 'user' })
});
if (res.ok) {
const data = await res.json();
Expand All @@ -66,7 +66,7 @@ class ClaudeActivityBackend {
id: data.message?.id?.toString() || crypto.randomUUID(),
role: 'user',
text,
source: 'robin',
source: 'user',
timestamp: Date.now()
});
}
Expand Down Expand Up @@ -111,7 +111,7 @@ class ClaudeActivityBackend {
for (const m of messages) {
if (m.id > this._lastSeenId) {
this._lastSeenId = m.id;
// Only emit Claude messages (not our own robin messages) during polling
// Only emit Claude messages (not our own user messages) during polling
if (m.from === 'claude' || m.from === 'wingman') {
this._emit('message', {
id: m.id.toString(),
Expand Down Expand Up @@ -140,9 +140,9 @@ class ClaudeActivityBackend {
if (m.id > this._lastSeenId) this._lastSeenId = m.id;
parsed.push({
id: m.id.toString(),
role: m.from === 'robin' ? 'user' : 'assistant',
role: m.from === 'user' ? 'user' : 'assistant',
text: m.text,
source: m.from === 'robin' ? 'robin' : 'claude',
source: m.from === 'user' ? 'user' : 'claude',
timestamp: m.timestamp || Date.now()
});
}
Expand Down
2 changes: 1 addition & 1 deletion shell/chat/openclaw-backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class OpenClawBackend {
id: m.id || crypto.randomUUID(),
role: m.role,
text,
source: m.role === 'user' ? 'robin' : 'openclaw',
source: m.role === 'user' ? 'user' : 'openclaw',
timestamp: m.timestamp || m.createdAt || Date.now()
});
}
Expand Down
4 changes: 2 additions & 2 deletions shell/css/wingman.css
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@
font-size: 10px;
}

.activity-item .a-source.robin {
.activity-item .a-source.user {
color: var(--success);
}

Expand Down Expand Up @@ -259,7 +259,7 @@
min-width: 0;
}

.chat-msg.robin {
.chat-msg.user {
background: rgba(78, 204, 163, 0.2);
border: 1px solid rgba(78, 204, 163, 0.3);
align-self: flex-end;
Expand Down
52 changes: 26 additions & 26 deletions shell/js/wingman.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,8 @@
else if (event.data.selector) text = `${event.type}: ${event.data.selector}`;
else if (event.data.title) text = `${event.type}: ${event.data.title}`;

const rawSource = event.data.source || 'robin';
const source = ['wingman', 'robin'].includes(rawSource) ? rawSource : 'robin';
const rawSource = event.data.source || 'user';
const source = ['wingman', 'user'].includes(rawSource) ? rawSource : 'user';
const sourceEmoji = source === 'wingman' ? '🤖' : '👤';
const item = document.createElement('div');
item.className = 'activity-item';
Expand Down Expand Up @@ -227,19 +227,19 @@
}
});

// Robin claims an AI tab by focusing it (click on tab header)
// User claims an AI tab by focusing it (click on tab header)
// The click handler already calls focusTab, we hook into it to also claim
const origTabClickHandler = (tabId) => {
// Check if this is an AI tab
const entry = getTabs().get(tabId);
if (entry) {
const sourceEl = entry.tabEl.querySelector('.tab-source');
if (sourceEl && sourceEl.textContent === '🤖') {
// Claim the tab for Robin
// Claim the tab for User
fetch('http://localhost:8765/tabs/source', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ tabId, source: 'robin' })
body: JSON.stringify({ tabId, source: 'user' })
}).catch(() => { });
}
}
Expand Down Expand Up @@ -329,7 +329,7 @@
const sourceClass = source || 'openclaw';
let cls, name;
if (role === 'user') {
cls = 'robin';
cls = 'user';
name = 'You';
} else if (sourceClass === 'claude') {
cls = 'claude';
Expand Down Expand Up @@ -442,15 +442,15 @@
}

function switchBackend(id) {
// Store any locally typed Robin messages before clearing
// Store any locally typed user messages before clearing
const localRobinMessages = [];
for (const child of messagesEl.children) {
if (child.classList.contains('robin') && child.dataset.localMessage === 'true') {
if (child.classList.contains('user') && child.dataset.localMessage === 'true') {
localRobinMessages.push({
role: 'user',
text: child.querySelector('.msg-text').textContent,
timestamp: child.querySelector('.msg-time').textContent,
source: 'robin'
source: 'user'
});
}
}
Expand All @@ -470,7 +470,7 @@
const el = appendMessage(m.role, m.text, m.timestamp, m.source, m.image);
el.dataset.fromHistory = 'true';
}
// Re-add local Robin messages
// Re-add local user messages
for (const localMsg of localRobinMessages) {
const el = appendMessage(localMsg.role, localMsg.text, localMsg.timestamp, localMsg.source, localMsg.image);
el.dataset.localMessage = 'true';
Expand All @@ -485,7 +485,7 @@
const el = appendMessage(m.role, m.text, m.timestamp, m.source, m.image);
el.dataset.fromHistory = 'true';
}
// Re-add local Robin messages
// Re-add local user messages
for (const localMsg of localRobinMessages) {
const el = appendMessage(localMsg.role, localMsg.text, localMsg.timestamp, localMsg.source, localMsg.image);
el.dataset.localMessage = 'true';
Expand Down Expand Up @@ -538,15 +538,15 @@
if (currentMode === 'both') return; // handled by dualMode

if (type === 'historyReload') {
// Store any locally typed Robin messages before processing history
// Store any locally typed user messages before processing history
const localRobinMessages = [];
for (const child of messagesEl.children) {
if (child.classList.contains('robin') && child.dataset.localMessage === 'true') {
if (child.classList.contains('user') && child.dataset.localMessage === 'true') {
localRobinMessages.push({
role: 'user',
text: child.querySelector('.msg-text').textContent,
timestamp: child.querySelector('.msg-time').textContent,
source: 'robin'
source: 'user'
});
}
}
Expand All @@ -571,7 +571,7 @@
for (const localMsg of localRobinMessages) {
let alreadyExists = false;
for (const child of messagesEl.children) {
if (child.classList.contains('robin') &&
if (child.classList.contains('user') &&
child.querySelector('.msg-text').textContent === localMsg.text &&
child.dataset.fromHistory === 'true') {
alreadyExists = true;
Expand Down Expand Up @@ -608,7 +608,7 @@
// Update existing streaming element content
streamData.element.querySelector('.msg-text').innerHTML = escapeHtml(msg.text);

// Ensure streaming element stays at the end (after any Robin messages sent during streaming)
// Ensure streaming element stays at the end (after any user messages sent during streaming)
const currentIndex = Array.from(messagesEl.children).indexOf(streamData.element);
const lastIndex = messagesEl.children.length - 1;
if (currentIndex !== lastIndex) {
Expand Down Expand Up @@ -650,15 +650,15 @@
let dualStreamingConversations = {}; // per-backend conversation tracking
dualMode.onMessage((msg, type, backendId) => {
if (type === 'historyReload') {
// Store any locally typed Robin messages before clearing
// Store any locally typed user messages before clearing
const localRobinMessages = [];
for (const child of messagesEl.children) {
if (child.classList.contains('robin') && child.dataset.localMessage === 'true') {
if (child.classList.contains('user') && child.dataset.localMessage === 'true') {
localRobinMessages.push({
role: 'user',
text: child.querySelector('.msg-text').textContent,
timestamp: child.querySelector('.msg-time').textContent,
source: 'robin'
source: 'user'
});
}
}
Expand All @@ -674,7 +674,7 @@
el.dataset.fromHistory = 'true';
}

// Re-add local Robin messages that aren't in history
// Re-add local user messages that aren't in history
for (const localMsg of localRobinMessages) {
const el = appendMessage(localMsg.role, localMsg.text, localMsg.timestamp, localMsg.source, localMsg.image);
el.dataset.localMessage = 'true';
Expand Down Expand Up @@ -838,7 +838,7 @@
inputEl.style.height = '';

// Show local preview immediately
const robinMsg = appendMessage('user', text || '', Date.now(), 'robin');
const robinMsg = appendMessage('user', text || '', Date.now(), 'user');
robinMsg.dataset.localMessage = 'true';
const msgText = robinMsg.querySelector('.msg-text');
const img = document.createElement('img');
Expand Down Expand Up @@ -870,7 +870,7 @@
if (target === 'both' && !openclawBackend.isConnected() && !claudeBackend.isConnected()) return;

// Show user message (display original text with @-mention for clarity)
const robinMsg = appendMessage('user', text, Date.now(), 'robin');
const robinMsg = appendMessage('user', text, Date.now(), 'user');
robinMsg.dataset.localMessage = 'true';

dualMode.sendMessage(text);
Expand All @@ -881,11 +881,11 @@

// OpenClaw: send through the official gateway chat path.
if (activeId === 'openclaw') {
const robinMsg = appendMessage('user', text, Date.now(), 'robin');
const robinMsg = appendMessage('user', text, Date.now(), 'user');
robinMsg.dataset.localMessage = 'true';
const sentViaGateway = await router.sendMessage(text);
if (sentViaGateway) {
void persistChatMessage('robin', text);
void persistChatMessage('user', text);
} else {
appendMessage('assistant', '⚠️ Wingman could not reach OpenClaw.', Date.now(), 'wingman');
}
Expand Down Expand Up @@ -1017,8 +1017,8 @@
if (window.tandem && window.tandem.onChatMessage) {
window.tandem.onChatMessage((msg) => {
// msg: {id, from, text, timestamp, image}
// Skip robin messages — already shown optimistically in the UI
if (msg.from === 'robin') return;
// Skip user messages — already shown optimistically in the UI
if (msg.from === 'user') return;
const source = msg.from; // 'wingman' or 'claude'
appendMessage('assistant', msg.text, msg.timestamp, source, msg.image);
if (messagesEl) messagesEl.scrollTop = messagesEl.scrollHeight;
Expand Down
Loading
Loading