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.
Đó vẫn là toàn bộ ý tưởng, không đổi. Cái đã đổi là mọi thứ bên dưới nó: dự án này khởi đầu với tám bản vá trên nền một dự án của người khác, và giờ đây con số đó là năm mươi — quản lý phiên, chuyển dự án, quay video bằng camera kèm cả một quy trình dựng video demo riêng, thống kê sử dụng xuất thẳng vào thư viện ảnh, và một giao diện tiếng Pháp. Bài viết này là nhật ký xây dựng đầy đủ, viết lại từ đầu để bao quát tất cả những điều đó.
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ỏ.
Đợt công việc đầu tiên tập trung vào độ tin cậy và bảo mật: 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. Phiên bản đó được đưa vào dùng hằng ngày, và chính việc dùng hằng ngày ấy đã sinh ra mọi thứ sau này. Từng bản vá trong bốn mươi hai bản vá tiếp theo đều xuất phát từ một thứ gì đó bị hỏng, gây khó chịu, hoặc còn thiếu khi thực sự dùng ngoài đường: chọn nói chuyện với dự án nào, tiếp tục một phiên sau khi đi vào nhà, xem giờ trước tốn bao nhiêu tiền, quay một đoạn demo cho khách hàng. Số bản vá giờ đã lên năm mươi, trải rộng trên khoảng 3.300 dòng Kotlin và 3.400 dòng TypeScript, được bảo vệ bởi 196 bài test phía relay và 181 bài test phía Android.
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 áp dụng một từ điển rút gọn nhỏ (nói "bao nhiêu tiền" sẽ thành /cost, chẳng hạn), 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 có thể chặn các lệnh gọi công cụ rủi ro — Bash, Write, Edit, MultiEdit, NotebookEdit — để 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. Bản thân giao thức cũng lớn dần theo các tính năng: ban đầu chỉ có vài loại thông điệp, giờ đã lên gần ba mươi loại, bao phủ phiên, dự án, thống kê và ghi hình, bên cạnh vòng lặp prompt/sự kiện ban đầu.


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.
Nhiều 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 giới hạn riêng của cấp API Android đã ảnh hưởng trực tiếp đến các tính năng ra đời sau này. WifiManager.setWifiEnabled() không còn tác dụng kể từ API 29 — đo được sau một lần khởi động lại khiến Wi-Fi tắt, ứng dụng vẫn chạy, nhưng không đoạn code nào có thể bật lại nó; việc duy nhất còn làm được là mở bảng điều khiển hệ thống và nhờ con người bấm. Và bảng nhớ tạm chỉ đọc được bởi ứng dụng đang có focus, không có gì được chia sẻ giữa kính và điện thoại cả — suy cho cùng đây là hai thiết bị riêng biệt, một sự thật rất dễ quên khi chúng chỉ cách nhau vài centimet.
Những 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, và một quy tắc phòng vệ mà các tính năng ra đời sau phải kế thừa.
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.
Chuyển dự án bằng giọng nói chỉ chấp nhận một dự án mà relay đã biết từ trước. Ngay khi "chuyển dự án" trở thành một lệnh thật, phiên bản ngây thơ hiển nhiên nhất sẽ đọc bất kỳ đường dẫn nào mà bản chuyển giọng nói đưa ra và mở một phiên Claude Code tại đó. Một câu bị nhận dạng sai khi đó sẽ được tự do trỏ relay — và do đó cả Claude Code — vào một thư mục bất kỳ trên máy Mac. Quy tắc được chọn thay vào đó: setProject kiểm tra theo một danh sách cố định các dự án đã biết, và quy tắc tương tự cũng áp dụng cho thư mục làm việc được khôi phục từ ổ đĩa sau khi relay khởi động lại, để một file trạng thái bị sửa tay cũng không thể mở lại bất cứ đâu.
Cả hai bản vá gốc đề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.
Năm mươi bản vá sau: những gì được xây thêm lên trên
Chính việc dùng phiên bản đầu tiên hằng ngày đã tạo ra phiên bản thứ hai. Bốn khả năng mới ra đời từ việc sử dụng đó, mỗi cái đều có một góc cạnh riêng.
Phiên làm việc và dự án, điều khiển hoàn toàn bằng giọng nói. Nói sessions sẽ liệt kê các phiên Claude Code có thể tiếp tục dưới dạng một menu đánh số, vuốt được; nói projects làm điều tương tự trên tất cả các thư mục làm việc mà relay biết, mới nhất trước. Việc tiếp tục một phiên sẽ đọc lại các tin nhắn cuối cùng của phiên đó trước khi giọng nói tiếp tục nó — cố tình không dùng chính từ resume, từng được thử rồi bỏ, vì từ đó vốn dành cho Claude ("tiếp tục từ chỗ đang dở"), và nếu dùng nó cho việc điều hướng thì người dùng sẽ mất đi một câu nói mà họ thực sự muốn dùng. Một lệnh thoại phát ra từ terminal trên Mac và một lệnh phát ra từ kính giờ đây luôn đồng bộ với nhau: việc chuyển phiên hoặc chuyển dự án giờ được phát tới mọi client đang kết nối, vì trước đây một lần chuyển chỉ cập nhật cho phía khởi tạo sẽ khiến kính âm thầm hiển thị một luồng hội thoại đã cũ.


Một menu thay vì một cú ngắt. Phiên bản đầu tiên coi bất kỳ cú chạm nào trong lúc một lượt đang chạy như một công tắc tắt toàn bộ — cách duy nhất để kiểm tra một phiên là giết nó. Bản vá mở ra một menu thay thế: ngắt, xếp hàng chờ lệnh mới cho đến khi lượt hiện tại xong, chuyển phiên, hoặc chuyển dự án, mà không làm mất lượt đang chạy dở. Làm cho việc quản lý trạng thái này đúng đắn đã cần thêm nhiều bản vá kế tiếp: phiên nào là "đang hoạt động" giờ được quyết định bởi chính hành động chuyển, chứ không phải bởi phiên nào tình cờ đang chạy khi sự kiện đầu tiên của một lượt chạy nền xuất hiện, và mỗi lượt được gắn cố định vào đúng phiên của nó để hai tiến trình claude chạy song song không còn ghi đè lên cùng một bản ghi transcript nữa.

Quay video bằng camera, và cả một quy trình để biến nó thành một đoạn demo. Kính có thể quay phim trong im lặng trong khi một tác vụ đang chạy, điều khiển bằng giọng nói (record / stop recording), với một máy trạng thái — chờ, bắt đầu, đang quay, đang dừng, thất bại — có thể kiểm thử đầy đủ mà không cần cắm phần cứng thật. Việc tạo ra một video demo thật từ đoạn phim đó lại trở thành cả một quy trình sản xuất nhỏ riêng: vì không thể chụp được màn hình thật, một trang presentation.html do relay phục vụ đóng vai trò thay cho HUD, được quay toàn màn hình bằng ffmpeg trên máy Mac trong khi kính quay lại cảnh đó, rồi một script ghép sẽ chồng hai đoạn phim lại với nhau. Hai đoạn phim không bao giờ bắt đầu cùng một thời điểm — relay báo "đã bắt đầu ghi hình" ngay khi nhận được lệnh, chứ không phải khi camera thực sự đã khởi động xong, và độ trễ đó được đo là từ 0,6 giây (camera đã "nóng" sẵn) đến 4,7 giây (lần quay đầu tiên của một phiên). Để canh đúng độ lệch, cần xác định đúng thời điểm chấm đỏ "● REC" thực sự sáng lên trên màn hình, rồi cộng thêm độ trễ camera đã đo được — chứ không dùng riêng một trong hai con số đó.
Thống kê sử dụng, vẽ ngay trên kính và lưu thẳng vào thư viện ảnh. Nói stats sẽ hiển thị số lượt yêu cầu, số phiên, số token, và số tiền đã chi trong ba khung thời gian trượt — bảy ngày, ba mươi ngày, và toàn bộ — cố tình chọn khung trượt thay vì theo tuần lịch, vì một tuần lịch sẽ luôn báo "gần như không có gì" vào mỗi sáng thứ Hai. Các con số tiền lấy từ chính sự kiện done của Claude Code, vì relay là phần duy nhất trong hệ thống lưu giữ chúng — bản thân các bản ghi transcript của Claude Code có lưu số token nhưng không lưu chi phí. Nói export sẽ ghi tấm thẻ thống kê đó vào cuộn phim máy ảnh thông qua API MediaStore của Android, đánh dấu file đang "chờ xử lý" cho đến khi ghi xong, để thư viện ảnh không bao giờ hiển thị một tấm ảnh vẽ dở — đây cũng là con đường duy nhất mà một ứng dụng chạy trong hộp cát có thể đi vào bộ nhớ dùng chung.

Một giao diện tiếng Pháp, dựa trên số đo thật thay vì phỏng đoán. HUD, các menu, và các lệnh thoại giờ đây tồn tại bằng tiếng Trung, tiếng Anh và tiếng Pháp, và bộ từ vựng tiếng Pháp không được viết ra từ một cuốn sổ tay hội thoại — nó đến từ việc thực sự thu âm một giọng nói tiếng Pháp thật vào cùng mô hình whisper.cpp (ggml-small, -l fr) rồi sửa những chỗ bị hỏng. Việc so khớp lệnh đưa mỗi câu nói qua một dạng chuẩn hóa: chuyển chữ thường, bỏ dấu câu cuối câu, bỏ dấu (accent), và đưa số nhiều về số ít, vì đầu ra của whisper trên phần cứng này không đủ ổn định để so sánh chính xác từng ký tự: "réglages" (cài đặt) đôi khi ra thành "Réglage", "enregistrer" (ghi âm) ra thành "enregistré", và một tiếng "aide" (trợ giúp) đơn giản đôi khi lại ra thành từ tiếng Anh "head" vì whisper chuyển sang tiếng Anh khi gặp câu quá ngắn. Cả ba trường hợp đều được xử lý — hai trường hợp đầu nhờ dạng chuẩn hóa, trường hợp thứ ba bằng cách thêm chính từ "head" vào tập các câu mở trợ giúp, vì từ đó không mở bất kỳ cánh cửa nào khác trong ứng dụng này. Giới hạn thẳng thắn vẫn giữ nguyên như trước: cách này chỉ sửa được các từ lệnh ngắn, được định nghĩa rõ ràng, chứ không phải chuyện đọc lệnh tự do. Một câu hoàn chỉnh nói bằng tiếng Pháp để mô tả một công việc lập trình thật vẫn có khả năng cao rơi vào một từ sai nhưng hợp lệ, mà không cơ chế kiểm tra nào ở phía sau có thể phát hiện đó là lỗi nhận dạng chứ không phải một lệnh thật.


Thông báo có thể sống sót qua màn hình tắt. Trước đây, một phiên chạy ở nơi khác vừa hoàn tất, hoặc một yêu cầu cấp quyền đang chờ, sẽ hiển thị lên một màn hình đã tắt từ trước — thông báo vẫn tồn tại, nhưng nằm dưới đúng lớp phủ đen che khuất mọi thứ khác, và một yêu cầu cấp quyền sẽ âm thầm hết hạn mà không ai kịp đọc. Bản vá cho phép đánh thức màn hình để hiện một thông báo nổi, được đặt vị trí sao cho sống sót qua đặc điểm kỳ lạ duy nhất của tấm kính ghép quang học: màu đen thuần trên đó lại trong suốt, nên một nền tối phủ kín màn hình sẽ khiến mọi thứ khác trong phòng — một thanh menu, một tab đang mở, thanh Dock — nổi lềnh bềnh lên trên HUD như thể chúng thuộc về nó. Hộp thông báo giờ nổi trực tiếp lên trên phiên đang chạy, thay vì nằm gọn trong khung nền đen riêng của nó.

Một phát hiện âm thầm hơn dọc đường: chính tư thế cấp quyền của relay cũng ẩn chứa một cái bẫy. ROKID_PERMISSION_MODE có thể là default (hành vi ban đầu — mỗi lệnh gọi Bash, Write, Edit đều chặn lại chờ một cử chỉ trên kính), auto (một bộ phân loại tự quyết định, không cần xác nhận theo từng công cụ — đây là chế độ mà dịch vụ chạy nền thường trực hiện đang dùng), hoặc bypassPermissions (không còn rào chắn nào phía công cụ nữa). Hook PreToolUse và tùy chọn này hoàn toàn độc lập với nhau, và việc bật auto mà không gỡ bỏ hook sẽ khiến mọi lệnh gọi công cụ đứng chờ một xác nhận mà chẳng ai định gửi cả, rồi âm thầm bị từ chối sau sáu mươi giây. Relay giờ không còn cài hook ngoài chế độ default nữa, và luôn thông báo tư thế cấp quyền của nó ngay trong dòng khởi động, để nó không bao giờ còn là điều phải đoán.
Thay đổi cuối cùng này cũng là một sự chuyển dịch thật sự trong mô hình bảo mật, chứ không chỉ là một bản sửa lỗi: cấu hình dùng hằng ngày giờ chạy ở chế độ auto, nghĩa là từng lệnh gọi công cụ riêng lẻ không còn phải chờ một cử chỉ trên kính nữa. Chốt chặn thật sự đã dời lên sớm hơn — về confirmPrompts, bước hiển thị câu đã nhận dạng và chờ Send hoặc Discard trước khi bất cứ thứ gì được gửi cho Claude. Đây không phải một cách lách luật; đó chính là kết luận mà hai lỗ hổng ban đầu đã chỉ ra từ trước, giờ được đưa thẳng vào cấu hình mặc định thay vì chỉ được lập luận ở cuối một bài blog.
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. Toàn bộ chuỗi này giờ tự khởi động lại: một dịch vụ chạy nền sẽ dựng lại relay và đường hầm của nó sau mỗi lần kết nối lại và mỗi lần Mac khởi động lại, thay vì đòi hỏi phải khởi động thủ công trước lần "ra ngoài đường" đầu tiên trong ngày. Bản đặc tả muốn một giải pháp tốt hơn Funnel; phần cứng không cho phép điều đó — nhưng ít nhất, những gì đang có bây giờ tự đứng vững mà không cần ai nhắc.
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; mỗi cụm từ này cũng tồn tại bằng tiếng Trung và tiếng Pháp.


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, liệt kê và tiếp tục các phiên theo số thứ tự, 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 — 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ỉ. Việc xem lại transcript đó tính theo lượt trao đổi, không phải theo số tin nhắn thô: trên dự án này, cứ mỗi tin nhắn người dùng viết thì trợ lý viết khoảng bảy tin nhắn gọi công cụ, nên nếu chỉ đếm số tin nhắn thì các chỉ dẫn thật sự sẽ bị chôn vùi dưới hàng loạt thao tác công cụ của agent.
Mọi lệnh gọi công cụ có rủi ro ở chế độ default 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 — nhưng như phần thảo luận về các chế độ cấp quyền ở trên đã nêu, điểm kiểm soát thực sự quan trọng hằng ngày nằm ở câu lệnh đã được phê duyệt trước khi nó được gửi đi, 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 ban đầu 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. Mọi thứ được xây thêm từ đó tới nay — phiên làm việc, dự án, ghi hình, thống kê, tiếng Pháp — đều chạy trên đúng cùng một hệ thống thường trực đó, chứ không phải trên một bộ demo riêng biệt.
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ó bốn đ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ều đó đúng từ khi dự án này mới có hai bản vá, và vẫn đúng khi đã có năm mươi bản vá: đ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, và cấu hình đang triển khai hiện nay phản ánh điều đó một cách trực tiếp — xác nhận theo từng công cụ đã bị tắt, và việc kiểm tra ở cấp độ câu lệnh là thứ duy nhất vẫn được giữ nguyên, không thể thương lượng.
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 ngay từ đầu.
So khớp chuỗi chính xác không sống sót nổi khi va chạm với nhận dạng giọng nói thật. Mỗi lệnh thoại mới được thêm vào sau phiên bản đầu tiên đều cần đúng cơ chế bỏ dấu, chuyển số nhiều về số ít, và cơ chế dự phòng đa ngôn ngữ mà các lệnh ban đầu chưa có — được đo trên một giọng nói thật, chứ không suy đoán từ một bản đặc tả — vì đầu ra của whisper trên phần cứng này đơn giản là không đủ ổn định để so sánh theo kiểu chính xác từng ký tự.
Và một nền tảng không thể kiểm thử không phải là lý do để viết code không có test. Điểm khởi đầu của client Android hoàn toàn không thể chạy dưới bất kỳ bộ khung kiểm thử JVM nào, nên quy tắc ra đời từ chính sự bắt buộc đó — bất cứ thứ gì có thể diễn đạt thành một hàm thuần túy đều phải tách ra khỏi file không thể kiểm thử ấy và có bài test riêng, được xác minh bằng kỹ thuật mutation testing để một bài test vẫn pass ngay cả khi logic nó bảo vệ đã bị phá vỡ sẽ không được tính là hợp lệ — chính là điều đã giúp năm mươi bản vá có thể chạy được trên một thiết bị mà, theo chính lời thừa nhận của nó, không thể chụp màn hình, không thể chiếu ra ngoài, và không thể gỡ lỗi theo cách thông thường.
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. The first wave added a design spec, a task-by-task implementation plan, and eight patches to the upstream code — two of which closed real security vulnerabilities — plus a recompiled APK. Fifty patches in, it now also has voice-driven session and project switching, camera recording with its own demo-video pipeline, usage statistics exported to the photo gallery, and a French interface.
- How is the connection secured, given the relay can execute code on a Mac?
- 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, the tunnel to the public internet is TLS end-to-end, and voice-driven project switching only ever resolves to a project the relay already knows about. Per-tool confirmation on the glasses is now off by default in favor of a sentence-level check — you approve the transcribed instruction before it's ever sent to Claude, rather than approving each tool call it makes to carry it out. The honest caveat is that the public tunnel is discoverable by URL, and only the token and that sentence-level check 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, and it now restarts itself automatically after a reconnect or a Mac reboot.
- Does dictation work in languages other than English?
- The interface and its fixed voice commands now work in Chinese, English and French, with the French vocabulary tuned against a real recorded voice rather than guessed — accent-folding and singularization catch most of whisper's quirks on short command words. Free-form dictation of an actual task is a different problem: a full French sentence can still transcribe into a completely different, grammatically valid word — 'commits' becomes 'commis,' for instance — a failure mode nothing downstream can automatically detect.
- What can the glasses actually do beyond sending a single instruction?
- List and resume Claude Code sessions by voice, switch between known projects, queue or interrupt a running turn instead of only being able to kill it, film a silent demo of a session, pull up usage statistics over rolling 7-day/30-day/all-time windows and export that card straight to the photo gallery, and get a screen-waking notification when a task started elsewhere finishes.
Thẻ


