feat(mobile): add Android development client and scoped IDE companion

This commit is contained in:
yilsem committed 2026-09-08 16:54:28 +03:00
1 parent 3332458708
commit 9f9f3226a1
84 files changed
+4371 -4

No files matched your search

+39
View File
@@ -0,0 +1,39 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import { taskText, taskState } from '../src/behavior.mjs'
import { VoiceSession, pcm16 } from '../src/voice.mjs'
test('research envelope preserves corrections and never broadens permission', () => {
const text = taskText({ intent: 'research', objective: 'Erişilebilirliği incele', scope: 'Yalnız Android',
context: 'Mavi iptal edildi; yeşil olsun.', constraints: 'Kod değişikliği yapma.', output: 'Rapor', completion: 'Kanıtları belirt.' })
assert.match(text, /Değişiklik yapma/); assert.match(text, /Mavi iptal edildi; yeşil olsun/)
assert.throws(() => taskText({ intent: 'execute', objective: 'test' }), /eksik/)
})
test('voice tool only drafts, cannot approve or submit jobs', async () => {
const replies = [], drafts = []
const session = new VoiceSession({ onDraft: (...args) => drafts.push(args) })
session.send = frame => replies.push(frame)
for (const name of ['approval.respond', 'task.submit', 'message.stop']) {
await session.receive({ data: JSON.stringify({ toolCall: { functionCalls: [{ id: name, name, args: { choice: 'once' } }] } }) })
}
assert.equal(drafts.length, 0); assert.equal(replies.length, 3)
assert.ok(replies.every(reply => reply.toolResponse.functionResponses[0].response.error))
})
test('discussions and absent terminal events cannot claim completion', () => {
assert.equal(taskState({ event: 'transcription', text: 'Tamamlandı' }), null)
assert.match(taskState({ event: 'disconnected' }), /bilinmiyor/)
assert.match(taskState({ event: 'approval.request' }), /bekliyor/)
assert.match(taskState({ event: 'message.complete', status: 'error' }), /error/)
})
test('PCM is little endian, clipped, and uses 16k sample output', () => {
const bytes = Buffer.from(pcm16(new Float32Array([-2, 0, 2]), 16000), 'base64')
assert.equal(bytes.readInt16LE(0), -32768); assert.equal(bytes.readInt16LE(4), 32767)
assert.equal(Buffer.from(pcm16(new Float32Array(480), 48000), 'base64').length, 320)
})
test('stopping voice frees every microphone and queued sound without IDE calls', () => {
let tracks = 0, sounds = 0, closed = 0
const voice = new VoiceSession({ onState: () => {} })
voice.stream = { getTracks: () => [{ stop() { tracks++ } }] }
voice.sources.add({ stop() { sounds++ } }); voice.socket = { close() { closed++ } }
voice.stop(); voice.stop()
assert.deepEqual([tracks, sounds, closed], [1, 1, 1])
})