Chiếc Mac của bạn vẫn ở nhà, vẫn bật, vẫn đăng nhập. Bạn đang ở ngoài đường với một chiếc điện thoại và một chiếc kính AR. Bạn chạm vào gọng kính, nói một câu, và một agent lập trình bắt đầu làm việc trên chiếc Mac đó — đọc file, chạy lệnh, mở pull request — trong khi một dòng chữ xanh lá đơn giản cho bạn biết nó đang làm gì. Không cần mở laptop, không cần mở khóa điện thoại, không có bàn phím nào trong vòng lặp này.
Đó là toàn bộ ý tưởng. Bài viết này là nhật ký xây dựng: chúng tôi đã dùng gì, cái gì đã hỏng, và hai lỗ hổng bảo mật có thật mà chúng tôi phải vá trong quá trình làm.

Chúng tôi kế thừa, không viết lại từ đầu
Một dự án mã nguồn mở tên Rokid_Claude, của người dùng GitHub williamlzz, đã hiện thực gần như chính xác vòng lặp này: một client Android cho kính thông minh Rokid, thu âm giọng nói và hiển thị HUD, giao tiếp qua WebSocket với một relay Node/TypeScript nhỏ gọn; relay này chuyển giọng nói thành văn bản tại chỗ bằng whisper.cpp rồi khởi chạy claude -p --output-format stream-json, truyền từng sự kiện ngược lại kính. Chúng tôi fork dự án này thay vì tự viết lại, vì khoảng cách giữa những gì nó đã làm được và những gì chúng tôi cần là rất nhỏ, còn chi phí viết lại thì không hề nhỏ.
Những gì chúng tôi bổ sung: một bản đặc tả thiết kế, một kế hoạch triển khai thực hiện từng tác vụ một với lệnh kiểm chứng cụ thể thay vì phỏng đoán, tám bản vá lên mã nguồn gốc, và một file APK được biên dịch lại. Hai trong số các bản vá đó đóng những lỗ hổng bảo mật có thật. Số còn lại là những sửa lỗi chỉ tồn tại vì có người thực sự chạy thử trên phần cứng thật thay vì chỉ đọc README.
Kiến trúc hệ thống
Vòng lặp này có bốn thành phần, và chỉ một trong số đó nằm ngoài mạng riêng của bạn.
Luồng hoạt động: chạm vào gọng kính để thu âm PCM qua WebSocket, relay chuyển giọng nói thành văn bản tại chỗ bằng whisper.cpp rồi tạo prompt, relay khởi chạy claude -p ở chế độ JSON streaming và chuyển tiếp từng sự kiện lên HUD, và một hook PreToolUse chặn các lệnh gọi công cụ rủi ro — Bash, Write, Edit — để gửi yêu cầu xác nhận mà một cử chỉ trên kính sẽ trả lời. Khi tác vụ hoàn tất, HUD hiển thị kết quả, số token đã dùng và chi phí.
Vì relay giao tiếp bằng một giao thức JSON đơn giản qua WebSocket thuần, client trên kính thực ra không phải là thứ bắt buộc. Một trình duyệt hay một điện thoại cũng có thể kết nối cùng relay đó để gỡ lỗi — kính chỉ là thiết bị đầu vào mà chúng tôi chọn.
Phần cứng chống trả trước khi phần mềm kịp
Bản đặc tả thiết kế giả định một thiết bị Android tương đối mở. Chiếc kính thực tế lại là một bản build sản xuất bị khóa — ro.build.type=user, ro.debuggable=0 — và Rokid vô hiệu hóa phần lớn bề mặt adb mà một lập trình viên Android thường dựa vào.
Ba hệ quả rút ra từ bảng này, và không cái nào được ghi lại ở bất cứ đâu trong dự án gốc:
Không điều nào trong số này hiện ra dưới dạng thông báo lỗi. Chúng hiện ra như một sự cố có vẻ hợp lý: máy quét mã QR tự đóng trong im lặng, ứng dụng quay về giao diện tiếng Trung với token trống, Wi-Fi từ chối kết nối lại sau khi khởi động lại. Mỗi trường hợp đều có nguyên nhân thật, và không cái nào tự thông báo nguyên nhân đó.
Hai lỗ hổng bảo mật có thật, tìm ra khi đọc mã nguồn
Relay thực thi Claude Code trên máy Mac của bạn. Token của nó về bản chất tương đương với khả năng thực thi mã từ xa. Việc đọc mã nguồn trước khi tin tưởng nó đã phát hiện hai lỗi không hề mang tính hình thức.
Token bị rò rỉ vào một trang được phục vụ công khai. Relay phục vụ một bản sao phiên làm việc cục bộ và tự động chèn token xác thực vào đó, nhưng chỉ với những yêu cầu mà nó cho là cục bộ — được xác định chỉ dựa trên địa chỉ nguồn của socket. Tailscale Funnel, đường hầm đưa relay ra internet công khai, kết nối tới chính relay đó từ 127.0.0.1 trong khi vẫn chuyển tiếp lưu lượng từ bất kỳ đâu. Cơ chế kiểm tra cũ nhìn thấy một địa chỉ loopback và chèn token vào HTML phục vụ cho toàn bộ internet. Bản vá kiểm tra thêm các header chuyển tiếp — x-forwarded-for, x-forwarded-proto, x-forwarded-host, forwarded — và từ chối coi một yêu cầu là cục bộ nếu có bất kỳ header nào trong số đó, kể cả khi đến từ 127.0.0.1.
Các yêu cầu xác nhận hết hạn thành chấp thuận âm thầm — hoặc từ chối âm thầm, tùy vào bạn đứng ở phía nào. Một yêu cầu cấp quyền chỉ được phát tới các client đang kết nối đúng một lần. Nếu kính bị ngắt rồi kết nối lại giữa chừng — điều xảy ra liên tục trên kết nối di động — kết nối mới sẽ không bao giờ thấy yêu cầu đang chờ đó, và nó hết hạn sau sáu mươi giây thành một lựa chọn mặc định mà không ai kịp trả lời. Bản vá giữ lại nội dung của mỗi yêu cầu còn đang mở và phát lại cho bất kỳ client nào kết nối trong lúc nó vẫn còn hiệu lực.
Cả hai bản vá đều đi kèm test hồi quy: một test xác nhận yêu cầu thực sự cục bộ vẫn nhận được token, một yêu cầu được chuyển tiếp từ cùng địa chỉ thì không, và một client kết nối sau khi yêu cầu cấp quyền đã được phát đi vẫn nhận được nó và có thể trả lời.
Những kiểu lỗi âm thầm, mỗi cái đều ngốn thời gian thật
Mọi sự cố trong dự án này đều trông giống một thứ khác. Không cái nào ném ra ngoại lệ. Đây là danh sách mà chúng tôi ước gì đã có trước khi bắt đầu.
Tailscale, rồi một sự thỏa hiệp với chính Tailscale
Thiết kế ban đầu dự định để kính gia nhập trực tiếp một mạng Tailscale riêng tư, sao cho không tồn tại điểm truy cập công khai nào — đúng loại rủi ro mà tùy chọn ngrok của dự án tạo ra, và bị loại bỏ hoàn toàn. Điều đó không trụ được khi va chạm với phần cứng thật. Cài client Tailscale cho Android vẫn phải đi qua đường cài đặt của ứng dụng đồng hành, việc này vẫn ổn, nhưng kết nối client đó với một tài khoản lại đòi hỏi luồng OAuth trong trình duyệt — mà HUD thì không có trình duyệt, và scrcpy cũng không thể thay thế vì nó cần chính adb push mà thiết bị đã từ chối.
Phương án dự phòng là Tailscale Funnel: máy Mac đưa relay ra một URL HTTPS ổn định, truy cập được qua mạng di động của điện thoại. Đây là cùng một loại rủi ro với đường ngrok mà thiết kế ban đầu muốn tránh, với ba điểm khác biệt — một tên miền ổn định, một chứng chỉ được quản lý, và không cần thêm tài khoản bên thứ ba nào. Sự thật cần nói thẳng: URL này có thể bị phát hiện công khai, và chỉ có token cùng cử chỉ xác nhận đứng giữa URL đó và khả năng thực thi mã trên máy Mac. Bản đặc tả muốn một giải pháp tốt hơn; phần cứng không cho phép điều đó.
Trải nghiệm sử dụng hằng ngày trông như thế nào
Từ phía kính, một bộ từ vựng cố định gồm các cụm từ chính xác điều khiển mọi thứ — được so khớp theo toàn bộ câu chứ không phải chuỗi con, để một lệnh vô tình chứa từ "sessions" không kích hoạt nhầm danh sách phiên. Nói help sẽ đọc lại danh sách hiện tại lấy trực tiếp từ mã nguồn định nghĩa nó, nên không bao giờ bị lệch pha.
Từ phía máy Mac, một CLI nhỏ bọc quanh cùng một relay: gửi một lệnh đơn lẻ, gửi lệnh và theo dõi kết quả ngay tại đó, quan sát phiên làm việc trực tiếp, xem lại toàn bộ luồng hội thoại qua mọi lượt (kể cả từ kính, vì một câu nói với kính không bao giờ xuất hiện trong bất kỳ cửa sổ Claude Code nào đang mở sẵn, dù mọi tiến trình đều đọc cùng một file transcript), liệt kê các phiên có thể tiếp tục, tiếp tục một phiên theo số thứ tự, và cấp hoặc từ chối một yêu cầu cấp quyền đang chờ bằng bàn phím thay vì bằng cử chỉ.
Mọi lệnh gọi công cụ có rủi ro vẫn luôn chờ một cử chỉ từ con người trước khi chạy, bất kể cử chỉ đó đến từ kính hay từ lệnh allow/deny của CLI — điểm kiểm soát nằm ở câu lệnh đã được phê duyệt, chứ không phải từng công cụ riêng lẻ mà agent gọi ra sau đó để thực thi nó.
Bằng chứng
Tiêu chí thành công của bản thiết kế rất cụ thể: từ một mạng bên ngoài nhà, nói ra một tác vụ, xem tiến trình hiển thị trên HUD, phê duyệt một hành động rủi ro bằng cử chỉ, và thấy nó hoàn tất trên máy Mac mà không chạm vào Mac lẫn điện thoại.
Chúng tôi chạy bài kiểm tra này với dây cáp USB vẫn cắm về mặt vật lý nhưng cố tình gỡ bỏ chuyển tiếp cổng adb reverse, để không thể "thành công" qua đường cáp một cách vô tình, và để kính dùng phát Wi-Fi từ điện thoại thay vì Wi-Fi ở nhà. Chuyến đi-về hoàn tất với kết nối đến từ tiến trình tailscale chứ không phải adb, xác nhận đúng đường mạng cần kiểm chứng đã được sử dụng. Toàn bộ trao đổi, từ kính tới Mac và ngược lại: một phản hồi trả về đúng như những gì đã yêu cầu, đúng thư mục làm việc đã được nạp sẵn làm phiên đang hoạt động, và tác vụ hoàn tất thành công với chi phí chỉ vài xu.
Những gì vẫn còn hỏng
Danh sách hạn chế thẳng thắn, vì một nhật ký xây dựng chỉ toàn kể chiến thắng thì không phải là nhật ký thật.
Những bài học rút ra
Nếu bạn đang gắn giọng nói vào bất kỳ agent nào có khả năng thực thi mã, có ba điều từ dự án này có thể áp dụng rộng hơn ngoài phạm vi kính AR. Việc xác nhận phải nhắm vào câu lệnh, không phải vào công cụ — điểm thất bại thực sự ở đây chưa bao giờ là một lệnh gọi công cụ có hành vi sai lệch, mà là một câu lệnh bị nhận dạng sai, nên đó chính là lớp cần đặt chốt chặn của con người. Một đường hầm công khai không tự động là phương án dự phòng an toàn như vẻ ngoài của nó — Tailscale Funnel chuyển tiếp mọi yêu cầu qua 127.0.0.1, âm thầm vô hiệu hóa mọi kiểm tra "đây có phải cục bộ không?" chỉ dựa trên địa chỉ socket, và chính lỗ hổng đó đã làm rò rỉ token của chúng tôi. Và những ràng buộc phần cứng không được ghi lại tốn nhiều thời gian hơn cả mã nguồn ứng dụng — gần như mọi giờ làm việc trong dự án này đều dồn vào một bề mặt adb bị khóa, một file cấu hình sai chủ sở hữu, hoặc một TMPDIR âm thầm vô hiệu hóa một cơ chế an toàn, chứ không phải vào relay, HUD, hay chính Claude Code.
Câu hỏi thường gặp
- Is this built on an open-source project, or from scratch?
- It forks an existing open-source project, Rokid_Claude by GitHub user williamlzz, which already implemented the glasses-to-relay-to-Claude-Code loop. We added a design spec, a task-by-task implementation plan, eight patches to the upstream code — two of which close real security vulnerabilities — and a recompiled APK with the permissions the published release was missing.
- How is the connection secured, given the relay can execute code on a Mac?
- Four layers: the relay listens only on 127.0.0.1 unless explicitly told otherwise, every connection needs a 48-character token or it's refused with a 1008 close code, every risky tool call (Bash, Write, Edit) requires a human gesture on the glasses before it runs, and the tunnel to the public internet is TLS end-to-end. The honest caveat is that the public tunnel is discoverable by URL, and only the token and the gesture confirmation protect it from there.
- Why Tailscale Funnel instead of keeping the glasses on a fully private network?
- That was the original plan, but the glasses' HUD has no browser, and joining a private Tailscale network requires an OAuth login in one. Screen mirroring tools that could work around that need adb push, which this hardware refuses outright. Tailscale Funnel was the fallback that kept a stable domain and a managed certificate without adding a third-party tunneling account.
- Does dictation work in languages other than English?
- Not reliably yet. The relay currently locks to English because the project defaults to Chinese for its whisper.cpp transcription. French was tested with vocabulary priming and improved short jargon like 'commit' and 'push', but longer phrases can still transcribe into a completely different, grammatically valid word — a failure mode nothing downstream can automatically detect.
Thẻ


