diff --git a/frontend/src/components/TorchButton.vue b/frontend/src/components/TorchButton.vue index d91afb0..c3e6b4e 100644 --- a/frontend/src/components/TorchButton.vue +++ b/frontend/src/components/TorchButton.vue @@ -2,7 +2,7 @@ import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue' import { useTorchStore } from '../stores/torch' import { useCanvasStore } from '../stores/canvas' -import { requestTorch, releaseTorch, transferTorch, updateName } from '../services/torch' +import { requestTorch, releaseTorch, transferTorch, updateName, setAutoRequest } from '../services/torch' const torchStore = useTorchStore() const canvasStore = useCanvasStore() @@ -86,7 +86,7 @@ async function handleTransfer(targetId: string) { } function toggleAutoRequest() { - torchStore.setAutoRequest(!torchStore.autoRequest) + setAutoRequest(!torchStore.autoRequest) } onMounted(() => { diff --git a/frontend/src/services/torch.ts b/frontend/src/services/torch.ts index 2ff5b9d..269163b 100644 --- a/frontend/src/services/torch.ts +++ b/frontend/src/services/torch.ts @@ -94,6 +94,12 @@ async function handleMessage(data: any) { await connectToMCP() } + // Sync autoRequest from server (server is source of truth) + const myClient = data.clients.find((c: any) => c.id === clientId) + if (myClient && myClient.autoRequest !== torchStore.autoRequest) { + torchStore.setAutoRequest(myClient.autoRequest) + } + // Auto-request: if no one holds the torch and we have autoRequest enabled if (!hasTorchNow && data.holderId === null && torchStore.autoRequest) { console.log('[Torch] Auto-requesting torch (no holder)') @@ -163,6 +169,15 @@ export async function transferTorch(targetId: string): Promise { return true } +/** + * Toggle auto-request on server (exclusive — only one client at a time) + */ +export function setAutoRequest(value: boolean): void { + if (torchWs?.readyState === WebSocket.OPEN) { + torchWs.send(JSON.stringify({ type: 'set-auto-request', value })) + } +} + /** * Update client name on server */ diff --git a/frontend/src/stores/torch.ts b/frontend/src/stores/torch.ts index 84cc529..24f5707 100644 --- a/frontend/src/stores/torch.ts +++ b/frontend/src/stores/torch.ts @@ -4,6 +4,7 @@ import { ref, computed } from 'vue' export interface TorchClient { id: string name: string + autoRequest: boolean userAgent: string hostname: string url: string diff --git a/server/services/handlers/torch-handler.ts b/server/services/handlers/torch-handler.ts index 5d7d804..37a1c5f 100644 --- a/server/services/handlers/torch-handler.ts +++ b/server/services/handlers/torch-handler.ts @@ -9,6 +9,7 @@ interface TorchClient { ws: any id: string name: string + autoRequest: boolean userAgent: string hostname: string connectedAt: Date @@ -29,6 +30,7 @@ function broadcastTorchState(broadcast: (message: string, filter?: (ws: any) => const clientList = Array.from(torchClients.values()).map(c => ({ id: c.id, name: c.name, + autoRequest: c.autoRequest, userAgent: c.userAgent, hostname: c.hostname, connectedAt: c.connectedAt.toISOString(), @@ -53,6 +55,7 @@ export function handleTorchConnect(ws: any, broadcast: (message: string, filter? ws, id, name: 'Anonymous', + autoRequest: false, userAgent: 'Unknown', hostname: 'Unknown', connectedAt: new Date() @@ -73,8 +76,16 @@ export function handleTorchMessage(ws: any, data: any, broadcast: (message: stri client.hostname = data.hostname || 'Unknown' client.name = data.name || 'Anonymous' + // Claim autoRequest exclusively (only one client at a time) + if (data.autoRequest) { + for (const [, c] of torchClients) { + if (c.id !== client.id) c.autoRequest = false + } + client.autoRequest = true + } + // Auto-grant torch if requested and no one holds it - if (data.autoRequest && torchHolderId === null) { + if (client.autoRequest && torchHolderId === null) { torchHolderId = client.id console.log(`[Torch] Auto-granted torch to ${client.name} (${client.id})`) } @@ -87,7 +98,21 @@ export function handleTorchMessage(ws: any, data: any, broadcast: (message: stri hasTorch })) - console.log(`[Torch] Registered: ${client.name} (${client.id}) (torch: ${hasTorch})`) + console.log(`[Torch] Registered: ${client.name} (${client.id}) (torch: ${hasTorch}, autoRequest: ${client.autoRequest})`) + broadcastTorchState(broadcast) + break + } + + case 'set-auto-request': { + const value = !!data.value + if (value) { + // Exclusive: clear autoRequest from all other clients + for (const [, c] of torchClients) { + if (c.id !== client.id) c.autoRequest = false + } + } + client.autoRequest = value + console.log(`[Torch] Auto-request ${value ? 'claimed by' : 'released by'}: ${client.name} (${client.id})`) broadcastTorchState(broadcast) break } diff --git a/server/services/sync-server.ts b/server/services/sync-server.ts index 5080106..8a1fd4d 100644 --- a/server/services/sync-server.ts +++ b/server/services/sync-server.ts @@ -78,7 +78,7 @@ export function startSyncServer() { const data = JSON.parse(message.toString()) // Route to appropriate handler based on message type - if (data.type?.startsWith('torch-') || ['register', 'request', 'release', 'transfer', 'update-name'].includes(data.type)) { + if (data.type?.startsWith('torch-') || ['register', 'request', 'release', 'transfer', 'update-name', 'set-auto-request'].includes(data.type)) { handleTorchMessage(ws, data, broadcast) } // Git doesn't expect messages from client