Đến nội dung
codecrush®
Technology11 min read19 tháng 9, 2026

Điều khiển Claude Code từ kính AR Rokid: một trạm code hoàn toàn bằng giọng nói

Chúng tôi kết nối một chiếc kính AR Rokid với Claude Code chạy trên máy Mac để ở nhà, để có thể đọc lệnh từ ngoài đường và theo dõi tiến trình trên HUD màu xanh lá. Đây là kiến trúc hệ thống, hai lỗ hổng bảo mật có thật mà chúng tôi đã vá trong quá trình làm, và mọi kiểu lỗi âm thầm đã ngốn mất cả một buổi chiều.

Code Crush Team

Gamification Agency

Một lớp HUD màu xanh lá đơn sắc lơ lửng trên nền một con phố lúc hoàng hôn, tượng trưng cho giao diện kính AR điều khiển một agent lập trình từ xa

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.

Ý tưởng gói gọn trong một khung hình: lớp HUD đơn sắc lơ lửng trên đường phố trong khi agent lập trình làm việc trên một chiếc máy để ở nhà.
Ý tưởng gói gọn trong một khung hình: lớp HUD đơn sắc lơ lửng trên đường phố trong khi agent lập trình làm việc trên một chiếc máy để ở nhà.

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.

Thành phầnVai tròPhụ thuộc vào
Client trên kínhThu âm micro, hiển thị HUD, đọc cử chỉ, cấu hình Wi-Fi qua mã QRAPK sideload, Wi-Fi
RelayChuyển giọng nói thành văn bản, điều phối, giao thức JSON/WebSocketNode ≥ 18, whisper.cpp, mô hình ggml-small
Claude Code CLIThực hiện công việc thật, trên máy MacĐã cài đặt và xác thực
Tailscale FunnelĐường HTTPS công khai giữa mạng 5G điện thoại và máy MacMột tài khoản Tailscale

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.

Khả năng của adbTrạng thái trên thiết bị này
adb shell, monkey, svc, content, adb reverseHoạt động
adb install, adb pushBị từ chối: error: closed hoặc not allow package install!
pm, am, input, screencapBị từ chối: error: closed

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:

Ứng dụng không thể cài qua cáp. Nó phải cài qua ứng dụng đồng hành — Hi Rokid → Toolbox → Install a new app — một kênh cài đặt riêng (CXR-L) mà một chiếc điện thoại, kể cả iPhone, vẫn có thể điều khiển dù thiết bị đích là Android.
Không có cách nào để buộc dừng một ứng dụng. am force-stop, killpm đều bị chặn, và ứng dụng đồng hành cũng không có nút dừng cưỡng bức. Khởi động lại là cách duy nhất còn hoạt động.
Không thể chiếu màn hình. scrcpy cần adb push để cài thành phần phía Android của nó, và đường đó đã bị đóng; screencap cũng bị chặn. Cách thay thế duy nhất là một trang HTTP cục bộ do chính relay phục vụ, hiển thị bản sao trực tiếp của phiên làm việ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.

Một file cấu hình được ghi bởi adb thuộc về sai người dùng. Ghi config.json của client qua adb shell khiến file đó thuộc quyền sở hữu của shell; ứng dụng không thể đọc một file nó không sở hữu, âm thầm nuốt lỗi ngoại lệ, và quay về giá trị mặc định — giao diện tiếng Trung, token trống, kết nối bị từ chối. Chạy chmod 644 trên file đó là đủ để sửa; nhưng ứng dụng không bao giờ nói cho bạn biết đó là nguyên nhân.
Trạng thái Wi-Fi không giữ được sau khi khởi động lại. wifi_on luôn về 0 mỗi lần kính khởi động lại, khiến mạng đã lưu trở nên vô dụng. cmd wifi bị chặn trên thiết bị này; svc wifi enable thì không.
TMPDIR tạm thời làm vô hiệu hóa âm thầm hook cấp quyền. Relay ghi file --settings chứa hook PreToolUse vào TMPDIR. Nếu thư mục đó bị một công cụ nào đó dọn dẹp giữa phiên làm việc, Claude Code vẫn tiếp tục chạy — nhưng không bao giờ hỏi xác nhận nữa, và không có dòng log nào thông báo điều đó. Cách khắc phục nằm ở vận hành chứ không phải mã nguồn: khởi chạy relay từ một TMPDIR tồn tại suốt phiên làm việc, và để relay tự thông báo tư thế cấp quyền của nó ở mỗi lần khởi động để nó không bao giờ là ngầm định.
Ngôn ngữ mặc định là sai, một cách âm thầm. Relay mặc định chạy tiếng Trung và áp đặt ngôn ngữ đó cho whisper.cpp. Giọng nói tiếng Anh được đọc dưới cấu hình này ra thành những câu kết thúc video kiểu tiếng Trung, và được chuyển thẳng cho Claude đúng như vậy. Đặt ROKID_LANG=en sẽ khắc phục điều này, đồng thời khóa ngôn ngữ lại trước một client có cấu hình đã lỗi thời.
File APK của bản phát hành chính thức thiếu quyền mà chính mã nguồn của nó khai báo. So sánh manifest trong cây mã nguồn với nội dung thực tế của APK v0.1.0 cho thấy nó thiếu CAMERA, ACCESS_WIFI_STATECHANGE_WIFI_STATE. Triệu chứng nhìn thấy được là máy quét mã QR tự đóng trong im lặng và việc cấu hình Wi-Fi hoàn toàn không hoạt động — khiến việc sử dụng khi ra ngoài là bất khả thi cho tới khi chúng tôi biên dịch lại từ mã nguồn.

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.

NóiHiệu ứng
new session hoặc resetBắt đầu lại từ đầu
sessions hoặc list sessionsDanh sách các phiên có thể tiếp tục, được đánh số — vuốt để xem, chạm để tiếp tục
wifi hoặc networkMở bảng cấu hình Wi-Fi
switch languageChuyển ngôn ngữ giao diện
exit, quit hoặc closeĐóng ứng dụng

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.

Đọc lệnh bằng tiếng Việt hay tiếng Pháp chưa hoạt động ổn định. Việc mồi từ vựng giúp sửa đúng các từ ngắn, thông dụng — "commit" và "push" được nhận dạng chính xác khi có prompt mồi thuật ngữ chuyên ngành — nhưng những câu dài hơn vẫn có thể bị nhận sai thành một từ hoàn toàn khác nhưng vẫn hợp ngữ pháp, một kiểu lỗi mà không có cơ chế kiểm tra nào ở phía sau có thể tự phát hiện.
Thời lượng pin trong một phiên sử dụng thực tế dài chưa được đo đạc. Dịch vụ pin của thiết bị (dumpsys battery) không hoạt động trên chiếc kính này; cách đọc duy nhất là qua sysfs, và phải đọc ngay lập tức sau khi rút cáp, vì việc sạc làm sai lệch con số chỉ sau vài giây.
Mọi phép đo cho tới nay đều dùng giọng nói tổng hợp, đây là trường hợp lý tưởng nhất. Chưa ai kiểm tra điều này với một giọng nói thật ngoài đường phố ồn ào.

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ẻ

Claude CodeRokidAR glassesvoice codingTailscalewhisper.cppdeveloper tools
Bản tin miễn phí

Nhận thông tin chuyên sâu về gamification gửi hằng tuần

Tham gia cùng các quản lý thương hiệu và marketer để nhận những nội dung hay nhất của chúng tôi về game thương hiệu, chiến lược tương tác và tiếp thị tương tác — không spam, hủy đăng ký bất cứ lúc nào.

Không spam. Hủy đăng ký bất cứ lúc nào. Chúng tôi tôn trọng hộp thư của bạn.

Hãy cùng tạo ra điều mà khán giả của bạn sẽ yêu thích

Từ game thương hiệu đến trải nghiệm 3D nhập vai — chúng tôi thổi hồn vào sự tương tác.