feat(chrome): let the IDE agent use the user's Chrome and beta.42
Server CI and Windows package / verify (push) Successful in 3m33s
Server CI and Windows package / windows-package (push) Successful in 7m36s

The AITURK Chrome extension now registers the user's Chrome as the desktop
backend's browser controller, the way the ChatGPT extension lets its agent
work in the user's browser. The agent's existing browser tools (navigate,
snapshot, click, type, scroll, back, press, get_images, vision) run in its
own background tabs grouped as "AITURK", or a tab the user hands over.

Backend: a local desktop lane in the browser-control broker. One controller
registered from the loopback desktop transport is offered to every desktop
session as a lane template; each session still gets its own exact scope on
first use. The lane has its own switch (browser.extension_control.local_desktop,
on by default); with no extension registered, or after it disconnects, the
normal browser backend runs unchanged. browser_vision turns the extension's
capture into the usual native/auxiliary vision result.

Bridge: forwards controller commands only to a connection that registered,
refuses unknown or oversized commands, relays results.

Extension: service-worker controller with its own native connection,
trusted chrome.debugger input (press/release only: a hover move waits 5 s on
background tabs), refusal of password and payment-card fields and of
browser, extension, file and Web Store pages, panel switch, Stop, tab
hand-over, recent actions, Alt+Shift+A and right-click "send to AITURK".
Cancelling Chrome's debugging bar stops the agent. Existing panel flows and
privacy rules are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
yilsemandClaude Opus 5.5 committed 2026-10-15 08:38:52 +03:00
1 parent 5c76c24c9c
commit 86a6621936
32 files changed
+2755 -195

No files matched your search

+83 -9
View File
@@ -112,9 +112,11 @@ function paintHistory(messages) {
async function openSession(id) {
const intent = ++generation
// A page sent from the context menu before any conversation was chosen
// waits for the first choice; switching conversations still clears it.
if (selected) { attachment = null; $('attachment').hidden = true }
selected = id; opening = Boolean(id); stream = null
bufferedEvents = []
attachment = null; $('attachment').hidden = true
$('message').value = ''; $('stop').hidden = true
notice(''); controls()
if (!id) { opening = false; $('transcript').replaceChildren(); activity('Bir konuşma seçin.'); return }
@@ -203,20 +205,92 @@ $('stop').addEventListener('click', async () => {
try { await request('message.stop', { sessionId: selected }); activity('Durdurma isteği IDE’ye iletildi.') }
catch (error) { notice(error.message) }
})
function setAttachment(context) {
if (!context?.text) throw new Error('Bu sayfadan okunabilir metin alınamadı.')
attachment = context
$('attachment-label').textContent = `${context.selection ? 'Seçim' : 'Sayfa'}: ${context.title} · ${context.text.length.toLocaleString('tr')} karakter`
$('attachment').hidden = false
notice(selected ? 'Sayfa içeriği mesajınızla birlikte IDE’ye ve seçili model sağlayıcısına gönderilecek. Hassas bilgi içermediğinden emin olun.'
: 'Ek hazır. Göndermek için yukarıdan bir IDE konuşması seçin.')
}
async function capture(tabId) {
const results = await chrome.scripting.executeScript({ target: { tabId }, func: capturePageContext })
return results[0]?.result
}
$('add-page').addEventListener('click', async () => {
const intent = generation
try {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true })
if (!tab?.id || !/^https?:\/\//.test(tab.url || '')) throw new Error('Yalnızca normal HTTP/HTTPS sayfaları eklenebilir. Önce AITURK araç çubuğu simgesine tıklayarak bu sekmeye erişim verin.')
const results = await chrome.scripting.executeScript({ target: { tabId: tab.id }, func: capturePageContext })
if (!tab?.id || !/^https?:\/\//.test(tab.url || '')) throw new Error('Yalnızca normal HTTP/HTTPS sayfaları eklenebilir.')
const context = await capture(tab.id)
if (intent !== generation) return
const context = results[0]?.result
if (!context?.text) throw new Error('Bu sayfadan okunabilir metin alınamadı.')
attachment = context
$('attachment-label').textContent = `${context.selection ? 'Seçim' : 'Sayfa'}: ${context.title} · ${context.text.length.toLocaleString('tr')} karakter`
$('attachment').hidden = false
notice('Sayfa içeriği mesajınızla birlikte IDE’ye ve seçili model sağlayıcısına gönderilecek. Hassas bilgi içermediğinden emin olun.')
setAttachment(context)
} catch (error) { notice(error.message) }
})
// Text or a page sent from the right-click menu while the panel opens.
async function takePendingContext() {
const saved = await chrome.storage.session.get(['pendingContext', 'pendingNotice'])
if (saved.pendingNotice) { notice(saved.pendingNotice); await chrome.storage.session.remove('pendingNotice') }
const pending = saved.pendingContext
if (!pending) return
await chrome.storage.session.remove('pendingContext')
if (Date.now() - pending.at > 120000) return
try {
if (pending.kind === 'selection' && pending.text) {
const tab = await chrome.tabs.get(pending.tabId).catch(() => null)
let address = ''
try { const url = new URL(tab?.url || ''); address = url.origin + url.pathname } catch {}
setAttachment({ title: (tab?.title || '').slice(0, 200), url: address, text: pending.text.slice(0, 24000), selection: true })
} else setAttachment(await capture(pending.tabId))
} catch (error) { notice(error.message) }
}
// ── The agent using this Chrome ───────────────────────────────────────
const STATUS_MARK = { ok: '✓', failed: '✕', cancelled: '■', running: '…' }
function renderControl(state) {
if (!state || typeof state !== 'object') return
$('control-enabled').checked = Boolean(state.enabled)
$('control-stop').hidden = !(state.enabled && state.running)
$('control-release').hidden = !(state.tabs?.length || state.shared)
let text
if (!state.enabled) text = 'Kapalı. Açarsan AITURK ajanı tarayıcı işlerini bu Chrome’da, kendi “AITURK” sekmelerinde yapar.'
else if (!state.registered) text = `IDE bekleniyor… AITURK IDE açık olmalı.${state.error ? ' (' + state.error + ')' : ''}`
else if (state.running) text = 'Ajan şu an bu Chrome’da çalışıyor. Durdurmak için “Durdur”a bas.'
else text = 'Hazır: ajan tarayıcı işlerini bu Chrome’da, “AITURK” sekme grubunda yapar. Şifre ve kart alanlarına yazmaz.'
if (state.shared) text += ` Paylaşılan sekme: “${state.shared.title || 'sekme'}”; ajan bir sonraki tarayıcı işinde bunu kullanır.`
if (state.tabs?.length) text += ` Ajanın sekmeleri: ${state.tabs.map(tab => tab.url || tab.title).filter(Boolean).join(', ')}.`
$('control-status').textContent = text
const items = (state.log || []).map(entry => {
const item = document.createElement('li')
item.className = entry.status
item.textContent = `${STATUS_MARK[entry.status] || ''} ${entry.text}${entry.note ? ' — ' + entry.note : ''}`
return item
})
$('control-log').replaceChildren(...items)
$('control-details').hidden = !items.length
}
async function control(type, extra = {}) {
try {
const reply = await chrome.runtime.sendMessage({ type, ...extra })
if (reply?.failure) notice(reply.failure)
else renderControl(reply)
} catch (error) { notice(`Eklenti arka planına ulaşılamadı: ${error.message}`) }
}
chrome.runtime.onMessage.addListener(message => { if (message?.type === 'control.state') renderControl(message.state) })
chrome.storage.onChanged.addListener((changes, area) => {
if (area === 'session' && (changes.pendingContext?.newValue || changes.pendingNotice?.newValue)) void takePendingContext()
})
$('control-enabled').addEventListener('change', () => void control('control.set', { enabled: $('control-enabled').checked }))
$('control-stop').addEventListener('click', () => void control('control.stop'))
$('control-share').addEventListener('click', () => void control('control.share', { sessionId: selected || null }))
$('control-release').addEventListener('click', () => void control('control.release'))
$('remove-attachment').addEventListener('click', () => { attachment = null; $('attachment').hidden = true; notice('') })
void connect()
void control('control.get')
void takePendingContext()