Repository navigation
feat(web-shell): add PWA installability and Android development shell - #11722
Conversation
E2E verification reportI tested this PWA contribution on two Windows PCs and on my physical Pixel 7. The focused automated runs below completed successfully: 492 test/check executions, including repeat validation on the second PC and a final rerun after rebasing. This is a count of successful executions, not unique tests or a claim that the full repository suite passed.
The unit-test selections differed between the two PCs. The totals above exclude the full repository preflight, package smoke checks and physical-device acceptance. The PWA source files were verified byte-for-byte after transfer. Fresh dependency installation, full build, CLI bundle, workspace/integration typecheck and changed-file formatting/lint passed on the second PC. For the final rerun, I rebased onto current main and verified that all 21 PWA files remained identical. On submitted commit Physical Pixel 7 / Android 16 / Chrome 144I installed the Web Shell on my Pixel and verified the installed Chrome WebAPK through Android. I opened it from the launcher and confirmed standalone mode with no browser address bar. The installed context initially had no token, so I entered a synthetic test token through the Connect form and confirmed authenticated API access. I then stopped the test daemon and reloaded the installed app. The service worker displayed the connection-unavailable page and Retry button. The app stayed in standalone mode, preserved the current session token and left Cache Storage empty. After restarting the daemon, I tapped Retry on the phone: the Web Shell returned without another login, the stored token authorized API access with HTTP 200, and a request without a token still returned HTTP 401. This was physical-device acceptance with a real server interruption. Wi-Fi and wireless ADB remained enabled. I used a loopback origin through ADB reverse; this does not claim testing over cellular, a public HTTPS deployment, Wi-Fi loss or Android process death. Built-daemon HTTP and browser behavior
Independent npm archive smokeI extracted the same npm archive into a fresh directory outside the repository, with no Full-suite and platform limitsThe full Windows preflight failed with 152 assertions and five runner/unhandled errors. A separate full scripts run had six assertion failures. I reproduced the six scripts failures, four Web Shell locale failures and three CLI failures on unchanged baseline sources; I have not attributed every other failure. The baseline source reproductions shared dependencies/build outputs and were not a separately rebuilt baseline release. No failing assertion in the inspected logs targeted the new PWA behavior. I used Node 22.21.1, isolated profiles and synthetic credentials. No model calls were made. Safari/iOS, macOS, native Android WebView, background notifications, process-death persistence and the complete release matrix are outside this verification. Upstream CI is the next gate. 中文说明E2E 验证报告我在两台 Windows 电脑和自己的真实 Pixel 7 上测试了这项 PWA 改动。以下定向自动化检查全部成功:共 492 次测试/检查执行,包括第二台电脑上的重复验证和 rebase 后的最终重跑。这统计的是成功执行次数,并非不同测试的数量,也不表示整个仓库测试全部通过。
两台电脑选取的单元测试范围不同。上表不包含完整仓库 preflight、归档 smoke 检查或真实设备验收。转移后对 PWA 源文件进行了逐字节核对。第二台电脑上的全新依赖安装、完整构建、CLI bundle、workspace/集成测试类型检查以及改动文件的格式和 lint 检查均通过。 最终重跑前,我 rebase 到当前 main,并确认全部 21 个 PWA 文件保持一致。在已提交版本 真实 Pixel 7 / Android 16 / Chrome 144我在自己的 Pixel 上安装了 Web Shell,并通过 Android 确认已安装的 Chrome WebAPK。我从 launcher 打开应用,确认其为无浏览器地址栏的独立模式。安装上下文最初没有 token,因此我通过 Connect 表单输入合成测试 token,并确认了认证 API 访问。 随后我停止测试 daemon 并重新加载已安装应用。service worker 显示连接不可用页面和重试按钮。应用保持独立模式,保留当前会话 token,Cache Storage 为空。重启 daemon 后,我在手机上点击重试:Web Shell 恢复且无需再次登录,保存的 token 访问 API 返回 HTTP 200,无 token 请求仍返回 HTTP 401。 这是真实设备上的实际服务器中断验收。Wi-Fi 和无线 ADB 一直开启。我通过 ADB reverse 使用 loopback origin;这并不表示测试了蜂窝网络、公网 HTTPS 部署、Wi-Fi 断开或 Android 进程终止。 构建后 daemon 的 HTTP 和浏览器行为
独立 npm 归档 smoke我将同一个 npm 归档解压到仓库外的全新目录,其祖先路径中没有 完整测试和平台限制完整 Windows preflight 有 152 个断言失败和五个运行器/未处理错误。另一次完整 scripts 测试有六个断言失败。我在未修改基线源码上复现了这六个 scripts 失败、四个 Web Shell locale 失败和三个 CLI 失败;尚未归因其余所有失败。基线源码复现共享依赖/构建输出,并非单独重新构建的基线发行包。检查到的日志中,没有失败断言针对新增 PWA 行为。 我使用 Node 22.21.1、隔离配置和合成凭据,没有调用模型。Safari/iOS、macOS、原生 Android WebView、后台通知、进程终止后的持久化和完整发布矩阵不在此次验证范围内。上游 CI 是下一步检查。 |
|
@wenshao, I've marked the focused PWA first step from #11704 ready for review. My physical Pixel 7 checks passed, including installed launch, authentication, daemon interruption and Retry recovery. The detailed test report covers 492 successful focused executions, including reruns. Could you approve the pending workflow runs for this fork so upstream CI can run? The full Windows preflight limitations are documented in the PR. |
Additional physical-device acceptance: OnePlus 15I tested the same published commit
This adds a second physical Android device alongside the Pixel 7 / Chrome 144 acceptance already reported. The 492 automated executions remain unchanged; I am reporting these physical-device checks separately. I used an isolated loopback origin through ADB reverse, kept Wi-Fi enabled and made no model calls. This verifies a real server outage and recovery; it does not cover cellular/public HTTPS deployment, Wi-Fi loss, Android process death or background notifications. Product source and the submitted commit are unchanged. 中文说明补充真实设备验收:OnePlus 15我在自己的真实 OnePlus 15(CPH2745)上测试了同一个已发布提交
这是继已报告的 Pixel 7 / Chrome 144 验收之后,第二台真实 Android 设备。492 次自动化执行的数量保持不变;这些真实设备检查单独报告。 我通过 ADB reverse 使用隔离的 loopback origin,保持 Wi-Fi 开启,没有调用模型。这验证了真实服务器中断及恢复;不涵盖蜂窝网络/公网 HTTPS 部署、Wi-Fi 断开、Android 进程终止或后台通知。产品源码和已提交的 commit 均未改变。 |
|
I fixed item 1 from the code review in I reproduced the failure before changing the guard. All three new regression cases failed on the previous code and now pass: missing manifest, missing worker, and missing both. The full packaging suite passed 42 tests, with one existing skip. The original isolated reproduction also passed all four cases after the fix, including absent-output and complete-output controls. Full build, CLI bundle, workspace/integration typecheck, changed-file formatting and lint all passed on Windows / Node 22.21.1. For item 2, I viewed both PNGs and confirmed their dimensions are 192×192 and 512×512. They are byte-for-byte copies of the existing desktop-shell Android The earlier 492 automated executions and physical Pixel 7 / OnePlus 15 acceptance remain reported separately; I have not added this follow-up's checks to that total or claimed another device run. This commit changes only the packaging guard and its tests; the PWA runtime and assets are unchanged. Upstream CI and the remaining product decisions still need maintainer review. 中文说明我在 我先复现了问题再修改守卫条件。三个新增回归场景在旧代码上全部失败,修复后全部通过:缺少 manifest、缺少 worker,以及两者都缺少。完整打包测试套件 42 项通过,1 项为既有跳过。原始隔离复现的四个场景也全部通过,包括无构建产物和完整构建产物的对照。Windows / Node 22.21.1 上的完整构建、CLI bundle、workspace/集成测试类型检查,以及改动文件的格式和 lint 检查均通过。 关于第 2 项,我查看了两个 PNG,确认尺寸分别为 192×192 和 512×512。它们分别与现有 desktop-shell Android 此前的 492 次自动化执行和真实 Pixel 7 / OnePlus 15 验收仍单独报告;我没有把本次检查加入该总数,也没有声称重新进行了设备验收。本提交只修改打包守卫及其测试,PWA 运行时和资源保持不变。上游 CI 和其余产品决策仍需维护者评审。 |
Maintainer verification: local real-stack run on macOSI built this PR locally and drove it end to end against a real What was under test
1. Installability in a real browserChrome fires Chrome's own install dialog, reached from the page's Same daemon port, same URL, On 2. HTTP surface and the auth boundaryLive probe against the bundled daemon.
DNS-rebinding wall still applies to the new routes (this is not covered by the PR's tests, so I checked it explicitly with raw requests carrying a forged
The pre-auth widening exempts these two paths from bearer auth only; Host and Origin walls are untouched. 3. Worker behaviour on a live sessionWith the worker controlling the page (
4. Connection loss and recoveryI stopped the daemon for real (SIGKILL, then polled until the port refused connections) rather than emulating offline.
5. What happens to someone who rolls qwen backNot in the PR's test plan, and worth knowing before this ships, so I measured it: register the worker against the PR daemon, then serve the same origin and port from a pre-PR daemon.
Benign in every direction — the worker only substitutes on network failure — and the docs this PR adds already point users at browser site settings to remove it. Recording it here so the behaviour is not a surprise later. 6. Packaging, against the real build rather than fixtures
7. Test suites on macOS
The single first-run failure was 8. Mutation matrix — do the new tests bite?I flipped one thing at a time in the PR's own production code and re-ran the suite that owns it.
9. Non-blocking notesN1 — the wiring is the untested part. Deleting the N2 — two assertions that look like they pin the pre-auth anchor do not. N3 — N4 — cosmetic. The connection-loss page's own What I did not verifyAndroid/iOS, Safari, real HTTPS with a certificate (I used loopback, which is a secure context), Web Push, background execution, and the 中文说明维护者验证:macOS 本地真实环境实测我在本地构建了这个 PR,并用真实的 被测对象
1. 真实浏览器中的可安装性Chrome 首次加载即触发 同一端口、同一 URL, 2. HTTP 面与认证边界
DNS rebinding 防护对新路由仍然生效(PR 的测试没有覆盖这点,我用裸请求伪造 3. 真实会话下的 worker 行为
4. 断线与恢复我是真的杀掉 daemon(SIGKILL 后轮询到端口拒绝连接),不是模拟断网。
5. 回滚 qwen 的用户会遇到什么PR 的测试计划里没有这一项,但上线前值得知道,所以我实测了:先在 PR daemon 上注册 worker,再用同一 origin 同一端口跑改动前的 daemon。
各个方向都是良性的 —— worker 只在网络失败时才替换响应 —— 而且本 PR 新增的文档已经指引用户用浏览器站点设置移除它。记录在此只是为了避免以后成为意外。 6. 打包(对真实构建产物,不是 fixture)
7. macOS 上的测试套件
首轮唯一失败是 8. 变异矩阵 —— 新增测试有没有辨别力每次只改一处生产代码,再跑归属它的套件:
9. 不阻塞的说明N1 —— 没被测到的恰恰是「接线」。 删掉 N2 —— 两条看似钉住 pre-auth 锚点的断言其实没有。 把 N3 —— N4 —— 观感问题。 断线页自己的 未验证的部分Android/iOS、Safari、带证书的真实 HTTPS(我用的是 loopback,本身就是安全上下文)、Web Push、后台执行,以及 |
CI is red — but not because of anything in this diffOne lane is failing: Lint & Static (ubuntu-latest, Node 22.x). It died after 15 s at step 8, The step's own message: This gate exists because a branch carrying an old FixMerge Pre-flight: the merge should clear itI already had the prospective merged tree built locally for the verification report above —
The merge is clean and the new lint gate has nothing to say about this PR's files, so the lane should go green once the branch takes Everything else is already green
Pushing the merge will kick off a fresh 中文说明CI 红了,但不是这个 diff 的问题只有一个 lane 失败:Lint & Static (ubuntu-latest, Node 22.x)。它在第 8 步 该步骤自己的报错: 这道门存在的原因是:带着旧 修复方式把 预检:合并之后应该能过我为上面那份验证报告本来就在本地构建了「合并后」的树 ——
合并是干净的,新的 lint 门对本 PR 的文件也没有意见,所以分支取了 其余全绿
推送合并会触发一次新的 |
|
@qwen-code /review |
|
Qwen Code review request accepted. Review is running in workflow run. A command-triggered review is not listed under the checks of this PR; the result is posted here as a review when it finishes. |
Serves /manifest.webmanifest and /sw.js pre-auth, registers the worker deferred to load, and replaces the earlier service-worker.js draft with sw.js as a Vite entry (PNG icons included).
WebView shell around the daemon-served Web Shell (no second UI), profile tuples of URL/token/display name with #token= fragment delivery, native WebView version check at the Chrome 107 floor, and a foreground-service skeleton for SSE/notifications.
|
Thanks for the macOS verification and the CI notes — both were acted on. The branch now carries the agreed three steps from #11704: browser compatibility (browserslist + README matrix, On your four non-blocking notes, applied to the new code:
The earlier physical-device acceptance (Pixel 7, OnePlus 15) covered the old worker draft; the reworked 中文说明感谢 macOS 验证和 CI 说明,两点都已处理。 分支现在承载 #11704 中商定的三步:浏览器兼容性(browserslist + README 矩阵、全部 四条不阻塞说明在新代码上的落实情况:N1 接线未测——已为新的 worker 增加 此前的真机验收(Pixel 7、OnePlus 15)覆盖的是旧 worker 草稿;重构后的 |
|
Merged main into feat/web-shell-pwa-installability and pushed as requested! Thank you @wenshao for the thorough verification report. |
d67d07c to
384a244
Compare
|
Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration. 中文请勿对活跃的 PR 执行 rebase 或 force-push,因为这会使已有的评审评论失效。另外,供日后参考:作为集成流程的一部分,机器人始终会自动将所有改动压缩(squash)为单个提交。 |
|
Quick status: the branch now contains only the PWA/compat/shell changes from the agreed #11704 direction on top of current main — an unrelated upstream commit that had been pushed into the branch was removed, and it is not part of this PR anymore. Local checks on the clean head are green: ESLint/Prettier on all changed files, CLI pre-auth tests (5/5) and the new service-worker suite (17/17). The heavy CI lanes (Install, Test, Lint & Static, E2E) are still waiting for workflow approval for this fork on the new head, same as before — could you approve the runs when you get a moment? Happy to fix anything CI or review finds. 中文说明快速状态更新:分支现在只包含 #11704 商定方向下的 PWA/兼容性/壳改动,并位于当前 main 之上;之前被推入分支的一个无关 upstream 提交已被移除,不再属于本 PR。干净 head 上的本地检查全部通过:改动文件的 ESLint/Prettier、CLI pre-auth 测试(5/5)以及新的 service worker 套件(17/17)。 与新 head 相关的重型 CI 泳道(Install、Test、Lint & Static、E2E)仍在此 fork 的工作流批准页面等待批准,和之前一样——方便时能否批准这些运行?CI 或评审发现任何问题,我都会立即修复。 |
|
Addressed the complete round-4 review in
Validation: Web Shell/CLI/SDK builds pass; 77/77 focused Web Shell tests, 155/155 focused CLI tests, 90/90 script tests, and 1955/1955 SDK tests pass. The full Web Shell run completed with 8026 passing tests; five unrelated number-format assertions use this Windows host's |
|
Addressed the two outstanding Critical findings in
Validation: the workflow-size gate passes, its focused Vitest suite passes (188 passed, 23 fixture skips), and This PR has now reached five review rounds. In line with the repository contribution guidance, I am deferring the remaining R5 Suggestions (R5-2, R5-3, R5-4, R5-9 through R5-13) to focused follow-ups rather than expanding this already broad PWA/Android PR further. They cover import hygiene, extra artifact and route pins, workflow pinning/cache, and checksum documentation/normalization; none is marked Critical. The previously addressed R1/R4 Suggestions remain documented in their threads. The current fork workflows are still awaiting maintainer approval; once approved, I will address any actual CI failure. |
|
Resolved the branch divergence with the current Post-merge checks passed:
|
|
Addressed critical R6-1 and merged current upstream main in 0d91521. The Android README and both English/Chinese mobile-shell design records now state that adb reverse must never forward a token-less loopback daemon to a physical device. They require --require-auth with a bearer token stored as daemon_token, while keeping the loopback URL required by the existing cleartext policy. The merge also retains the PWA root worker entry and registration alongside upstream’s remote-daemon boot flow. Validation completed on this head:
|
Maintainer verification — round 2: local real-stack run on LinuxI rebuilt both arms from source, packed and installed the CLI the way npm would, and drove the result through a real My round 1 was against What was under test
Both arms also went through 1. Public PWA routes on a real, token-protected daemon30 probes per arm, then the same set again against the installed tarball (identical results).
2. Cache policy — measured A/B, not read from the diffBase serves every file under 3. Real Chromium against the real daemonPersistent profile, counting reverse proxy in front of the daemon so every request that actually left the browser is attributable.
4. Offline behaviour and recoveryKilling the daemon and navigating gives the worker's bilingual 5. Engine floor — 11/11 boundary cases in a real browserChrome 111/110, Edge 120, Firefox 128/127, Safari 16.4/16.3, iOS 17.2/15.6 and — the case the round-4 fix was about — Android System WebView 113 (which carries the 6. Add Workspace dialogAt a 1100×720 viewport I measure height 437.75 px, computed 7. Packaging
8. Suites and negative controls
Negative controls, each reverting exactly one production hunk at head:
9. Gradle wrapper provenanceThe only binary the PR adds is checked in CI against a 10. Android navigation policy, executed
Open bot suggestions that I found already closed at this head
Non-blocking observationsN1 — the wiring is still the untested part (same as round 1). Deleting N2 — daemon and worker disagree on what a content hash looks like. The daemon uses N3 — the "content-addressed" test has false positives. N4 — the retry page is the one unthemed surface. It renders in the browser's default serif with a blue link, while every other dead end this PR touches is themed. It is readable and bilingual, so purely cosmetic. N5 — the SDK barrel import costs more than the constant it reads (matches open R5-2). N6 — the worker outlives the feature on a loopback origin. After downgrading the same port to a build without N7 — Not verified hereNo Android SDK on this box, so the Gradle build, 中文版维护者验证 —— 第 2 轮:本地真实环境(Linux)我把两个分支都从源码重新构建,按 npm 的方式打包并安装,再用真实的 我的第 1 轮针对 验证对象
两臂还都执行了 1. 真实、带 token 保护的 daemon 上的公开 PWA 路由每臂 30 项探测,随后对安装后的 tarball 再跑一遍(结果一致)。
2. 缓存策略 —— 实测 A/B,而非读 diffbase 对 3. 真实 Chromium 对真实 daemon使用持久化 profile,并在 daemon 前放置计数反向代理,使每个真正离开浏览器的请求都可归因。
4. 离线行为与恢复杀掉 daemon 后再导航,得到 worker 的双语 5. 引擎下限 —— 真实浏览器中 11/11 边界用例Chrome 111/110、Edge 120、Firefox 128/127、Safari 16.4/16.3、iOS 17.2/15.6,以及第 4 轮修复所针对的那个用例:Android System WebView 113(UA 中带 6. Add Workspace 对话框在 1100×720 视口下我测得高度 437.75 px、计算后的 7. 打包
8. 测试套件与反向对照
反向对照,每次只在 head 上回退一处生产代码:
9. Gradle wrapper 来源核验本 PR 新增的唯一二进制文件,在 CI 中只与同一个 PR 里提交的 10. Android 导航策略:已实际执行
已在当前 head 关闭、但机器人线程仍开着的建议
非阻塞观察N1 —— 没被测到的仍然是「接线」(与第 1 轮相同)。 从 N2 —— daemon 与 worker 对「什么是内容哈希」判断不一致。 daemon 用 N3 —— 「内容寻址」判据存在误判。 N4 —— 重试页是唯一没有主题化的界面。 它以浏览器默认衬线字体和蓝色链接渲染,而本 PR 触及的其他终止界面都是主题化的。内容可读且双语,纯观感问题。 N5 —— SDK barrel 导入的代价超过它读取的常量(对应仍开着的 R5-2)。 N6 —— 在 loopback origin 上 worker 的生命周期长于功能本身。 把同一端口降级到没有 N7 —— 本轮未验证的部分本机没有 Android SDK,因此 Gradle 构建、 🤖 Generated with Claude Code — Claude Opus 5 (1M context) |
|
@qwen-code /triage |
|
@wenshao, thank you for the detailed round-2 verification and for approving this revision. Could you help clarify the remaining path to merge? At I also want to answer the maintenance question: I am willing to continue developing and maintaining For Phase 2, I propose small follow-up PRs, keeping the architecture agreed in #11704: a native shell around the daemon-served Web Shell, using the documented REST+SSE surface.
I will also track the round-2 N1–N7 observations as focused follow-ups: built-artifact wiring coverage, consistent asset-cache classification, offline-page styling, the narrow SDK import, worker retirement and mobile-package versioning. These remain proposals for subsequent work; I am not claiming Phase 2 is implemented or adding it to the scope of this PR. Does this sequence fit the team's priorities, and is there a maintainer who can coordinate the credential prerequisite and mobile-shell review? I can start with the storage/profile slice and provide a bilingual design, focused tests and screenshots in a separate PR. |
|
Phase 2 follow-ups are now implemented and submitted as focused draft PRs:
Each PR includes synchronized EN/zh-CN design documentation, its incremental commit link, reviewer steps and a separate report with actual device results. Builds and 18 JVM regressions pass; the native suites pass on API 26 (unsupported profile isolation skipped) and API 35. Actual HTML acceptance also covers file bytes/cancellation and microphone consent/audio processing/lifecycle. Remote workflows still need maintainer authorization; local success is not upstream CI success. The head of this original PR remains unchanged. The follow-ups declare their dependency on it and will need rebasing after the parent changes land. They do not complete the maintainer-owned device credential prerequisite or constitute an official mobile release. Native downloads need a coordinated H5 export design because existing exports use short-lived blob URLs; background events/notifications and broader accessibility acceptance remain separate work. I am ready to keep maintaining the mobile shell, address review findings and work through the next agreed slices. The earlier review-disposition question on this PR remains for maintainers; I will address any concrete blocking code or validation request. 中文进展Phase 2 后续实现已拆分提交为草稿 PR:#12121 提供 Keystore 加密凭据、可恢复迁移及隔离连接;#12126 提供受限 URI 授权的系统文件单选/多选及迟到结果处理;#12127 提供按 origin 限定的原生麦克风同意、Android 权限处理及进入后台关闭连接,提示明确说明未发送状态可能丢失的权衡。 每项均附同步中英文设计、增量提交链接、评审步骤和独立设备测试报告。构建、18 个 JVM 回归以及 API 26/35 原生测试通过(API 26 跳过不支持的 profile 隔离),真实 HTML 验收覆盖文件内容/取消及麦克风授权、音频处理、生命周期。远程工作流仍需维护者授权,本地通过不等于上游 CI 通过。 本原始 PR 的 head 未变,后续 PR 明确依赖它,合入后需变基。这些实现不替代维护者负责的设备凭据前置条件,也不代表正式移动发布。现有导出使用短生命周期 blob URL,原生下载需另行协调 H5 导出设计;后台事件/通知及更全面的无障碍验收仍为独立工作。我愿继续维护、处理评审意见并推进约定的后续切片。本 PR 先前的评审状态问题仍需维护者处理;任何明确的阻塞代码或验证要求都会继续跟进。 |
|
Two further Phase 2 slices are implemented and submitted:
I also found and fixed a profile-initialization regression during repeated acceptance, rather than relying on the earlier isolated passing run. On unchanged Current integrated E2E reports: file selection, microphone, downloads, accessibility. Each records the exact pushed commit/APK, full API 26 and API 36 suite results and feature-specific UI evidence. All five remote branch heads were verified after push. All five follow-ups remain drafts with their dependency on this unchanged PR explicit. Bilingual designs, reviewer instructions and actual local verification are attached to each; local results are not upstream CI results. The automated precheck requires maintainer inspection/authorization before review/workflows. Could a maintainer review the pending original verdict/remaining approval here and authorize the follow-up checks when appropriate? I will address concrete findings and continue maintaining the mobile shell. Full Phase 2 is not complete. Primary-listener per-device issuance/revocation remains a maintainer-owned prerequisite; current Local Control pairing is a separate contract. Background notifications, integration of all native capabilities together, real-device/TLS and complete TalkBack acceptance remain explicit follow-up work. These PRs are development-client increments, not an official signed mobile release. 中文进展又完成并提交两个 Phase 2 切片:#12129 改善原生配置无障碍,包括带上下文的操作名称、输入标签及 200% 字体下可用的恢复控件,附前后截图;#12130 通过 Android 文档选择器保存真实 Blob 导出字节,覆盖精确二进制、空文件、16MiB、取消及迟到结果,保持现有 CSP 不变。 反复验收还发现并修复了配置初始化回归,不能仅依赖早先单次通过结果:未修改的 最新集成验收分别见上方文件选择、麦克风、下载及无障碍报告链接,记录精确推送提交/APK、API 26/36 完整套件结果及功能界面证据。推送后已核对五个远程分支头。 五个后续 PR 均保留草稿状态,明确依赖本 PR,且本 PR 的代码未改变。每个 PR 附英中设计、审阅步骤和真实本地验证;本地结果不等于上游 CI。自动预检要求维护者检查并授权审查/工作流。请维护者在适当时处理此处待定的原有评审及剩余批准,并授权后续检查。我会处理具体发现并继续维护移动外壳。 完整 Phase 2 尚未完成。主监听器的每设备凭据签发/撤销仍是维护者负责的前置契约,现有 Local Control 配对不是同一契约。后台通知、全部原生能力共同集成、实体设备/TLS 及完整 TalkBack 验收仍需后续工作。这些 PR 是开发客户端增量,不是官方签名移动版本。 |
qqqys
left a comment
There was a problem hiding this comment.
Critical-only review pass at head 6b5ecad828d908f85ac252d32b934a30b0c3e42a. No merge-blocking defect found. The most recent automated round is bound to this exact commit and posted zero findings, all 20 Critical threads from earlier rounds are resolved, and none of the 38 still-open threads carries a Critical marker. I read the security-bearing surfaces in full; the scan found nothing provable.
Service worker: the cache cannot hold a credential or a session
packages/web-shell/client/sw.js, all 102 lines. The fetch handler declines to intercept at all when any of these hold: a non-GET method, a request carrying an Authorization header, an Accept that includes text/event-stream, or a URL whose origin differs from the worker's own. Authenticated API traffic and event streams therefore never reach the caching logic.
What is cached is narrow and content-addressed: url.pathname.startsWith('/assets/') plus /-[a-zA-Z0-9_-]{8,}\.[^/]+$/, so an unhashed file under /assets stays on the network by default. readAsset stores only response.status === 200 && response.type === 'basic', which excludes error responses and opaque cross-origin bodies, and its cache.put runs under waitUntil with a swallowing catch so a failed write cannot reject the response. A cache that is disabled or full falls through to fetch rather than failing the load.
HTML is never cached. A navigation is always fetch(request), and only its rejection produces the synthetic 503 retry page, sent with Cache-Control: no-store. That is the property the design claims - an unavailable daemon shows a retry page rather than a stale session - and the code does not have a path that puts a document into a cache. Activation deletes only keys starting with qwen-code-shell- that are not the current version, so obsolete shell caches are reclaimed without touching anything else on the origin.
Daemon side: the unauthenticated surface is exactly two public files
packages/sdk-typescript/src/daemon/pwa-assets.tsdefinesWEB_SHELL_PWA_ASSETSas a fixed two-entryas consttable -/manifest.webmanifestand/sw.js- with their content types and the worker'sService-Worker-Allowedflag. No session, workspace or authenticated route appears in it, and both the daemon and the browser client consume the same table so the contracts cannot drift.packages/cli/src/serve/web-shell-preauth.tswidensisPreAuthWebShellRequestby that table only, matching the exact route or its trailing-slash form, and the GET/HEAD restriction at the top of the function still applies.packages/cli/src/serve/web-shell-static.tsregisters each route with a path derived from the constant (route.slice(1)), not from request input, so there is no traversal surface. Responses set an explicitContent-Type,Cache-Control: no-cacheso installation metadata revalidates on every request, andX-Content-Type-Options: nosniff;Service-Worker-Allowed: /is set only on the worker route. The send callback distinguishes 404 from 500, logs only the 500, guards onres.headersSentbefore writing, and answerstext/plain- "Not found" or "Failed to load Web Shell asset" - so a missing installation resource can never return application HTML.- The
/assetsstatic mount tightened rather than widened: blanketimmutablewith a one-yearmaxAgebecame a per-file decision that grantspublic, max-age=31536000, immutableonly to content-addressed names andno-cacheto everything else.
Android development shell
OriginPolicy.kt fails closed throughout: parseHttp accepts only http and https, requires a non-blank host, rejects any URL carrying rawUserInfo, and validates the port range, returning null on a syntax error so every predicate answers false. isSameOrigin compares scheme, host case-insensitively and a port normalized to 443/80, so a differing port is not same-origin. isExternalLink admits only parseable http(s) and mailto, leaving javascript:, file: and custom schemes outside both the embed and the open-externally paths. network_security_config.xml blocks cleartext globally against system trust anchors and permits it only for localhost, 127.0.0.1 and ::1 with includeSubdomains="false", documenting that an operator must add any plain-HTTP LAN host explicitly.
Scope of this pass
Not audited to depth: MainActivity.kt, the roughly twenty CSS-module changes and the DialogShell reduction, the index.html boot inline scripts, main.tsx and pwa-registration.ts, scripts/check-desktop-isolation.js, the packaging scripts and the new mobile-shell.yml workflow. None is a credential, cache-scope or origin-policy surface, and the Android shell is a development scaffold excluded from the npm and pnpm workspace traversal. The 38 open threads are Suggestion-level and were not treated as gates.
CI
Every non-skipped check on this head concluded success except cancelled runs of route, which are not attributable to this change. No failure exposes a defect introduced here.








What this PR does
Adds installability metadata and a production service worker to the daemon-served Web Shell, including the published CLI package. Public installation resources revalidate on each request; content-addressed assets can be cached, while application HTML, authenticated traffic and event streams remain network-only. An unavailable daemon displays a retry page instead of a stale session.
Aligns the early browser guard and compatibility styles with Chrome/Edge/WebView 111+, Firefox 128+ and Safari/iOS 16.4+. Unsupported browsers use the existing themed startup-error surface. The Android development shell wraps the same Web Shell with validated origin navigation, native bootstrap and connection-error screens, and a pinned, buildable Android toolchain.
Why it's needed
The Web Shell should install from the actual released CLI, retain its authentication boundary during startup, and provide a readable failure when the browser or daemon cannot support a session. The Android portion establishes a small, testable host for the existing interface while the daemon's per-device credential design remains a prerequisite for a production mobile client.
Reviewer Test Plan
How to verify
Evidence (Before & After)
The baseline reproduced missing release assets, an undefined offline navigation response, stale cached installation metadata and an early guard that failed ES5 parsing. All five replay checks now pass. Independent testing of the actual bundled CLI passed 87 HTTP assertions, including explicitly instrumented cold/warm startup and deliberately missing-resource cases.
Local validation passed: root build, typecheck, bundle, package preparation and npm packing; 148 targeted unit tests with one Windows-only skip; lockfile parity and a fresh pinned-pnpm worktree installation. Both package-directory and root-directory Vite builds produced the required output paths. The shipped inline scripts parse as ES5 and the worker parses as a classic script. Android debug/release assembly, unit tests and lint passed; lint reports only four dependency-update warnings. A separate E2E comment records the commands, emulator scenarios and limits.
In the actual browser UI at a 720px viewport, Add Workspace rendered at 437.75px with a computed maximum height of 576px and the new viewport class attached.
Tested on
Environment
Windows, Node 22.21.1, npm 10.9.4, pinned pnpm 11.24.0, JDK 17, Android SDK 34 and Gradle 8.2.1. Tests used the bundled CLI and an isolated Android HTTP fixture; no request was sent to an external model.
Risk & Scope
The paired designs are synchronized: PWA English, PWA 中文, Android English, Android 中文.
Linked Issues
Related to #11704. This PR does not complete the production mobile-client requirements in that issue.
中文说明
此 PR 的改动
为 daemon 提供的 Web Shell 添加可安装元数据和生产环境 service worker,并将它们包含在发布的 CLI 包中。公开的安装资源每次请求都会重新验证;内容寻址的资源可以缓存,应用 HTML、认证流量和事件流则始终只通过网络获取。daemon 不可用时显示重试页面,不会显示过期会话。
将早期浏览器检查和兼容样式统一为 Chrome/Edge/WebView 111+、Firefox 128+ 和 Safari/iOS 16.4+。不受支持的浏览器使用现有的带主题启动错误界面。Android 开发外壳复用相同的 Web Shell,提供经过校验的同源导航、原生初始化和连接错误界面,以及固定版本、可构建的 Android 工具链。
为什么需要
Web Shell 应能够从实际发布的 CLI 安装,在启动期间保持认证边界,并在浏览器或 daemon 无法支持会话时提供可读的错误。Android 部分为现有界面建立一个小型、可测试的宿主;daemon 的设备级凭据设计仍是生产移动客户端的前置条件。
审阅者测试计划
如何验证
修复前后证据
基线复现了发布资源缺失、离线导航返回 undefined、安装元数据缓存过期,以及早期检查无法通过 ES5 解析的问题。五项回放检查现已全部通过。独立测试实际打包 CLI 的 87 项 HTTP 断言全部通过,包括明确记录时序的冷/热启动,以及故意移除资源的场景。
本地验证通过:根目录构建、类型检查、bundle、发布包准备和 npm 打包;148 项针对性单元测试,另有一项因 Windows 跳过;锁文件一致性,以及使用固定 pnpm 版本安装的全新 worktree。分别从包目录和根目录运行 Vite 都生成了所需路径。发布 HTML 的内联脚本可按 ES5 解析,worker 可按经典脚本解析。Android debug/release 构建、单元测试和 lint 通过;lint 仅有四项依赖版本更新提示。另附独立 E2E 评论记录命令、模拟器场景和验证边界。
实际浏览器界面中,720px 视口下的 Add Workspace 高度为 437.75px,计算得到的最大高度为 576px,且已应用新的视口样式类。
测试系统
环境
Windows、Node 22.21.1、npm 10.9.4、固定 pnpm 11.24.0、JDK 17、Android SDK 34 和 Gradle 8.2.1。测试使用实际打包 CLI 和独立 Android HTTP fixture,没有向外部模型发送请求。
风险与范围
双语设计文档保持同步:PWA 英文、PWA 中文、Android 英文、Android 中文。
关联 Issue
关联 #11704。本 PR 不代表该 issue 中的生产移动客户端要求已经完成。