merge: fix macOS robot hotspot scanning
This commit is contained in:
@@ -4,6 +4,7 @@ This file is the integrated default-branch snapshot. Feature tasks record progre
|
||||
|
||||
## Integrated Through
|
||||
|
||||
- `55e61b7`: macOS Robot hotspot discovery performs one bounded worker-thread rescan after an empty or SSID-redacted CoreWLAN result; persistent SSID redaction maps to the existing permission error instead of a misleading empty-device state, while firmware and the open `Xiaozhi-*` contract remain unchanged.
|
||||
- `f5d47c8` / `b6148a5`: AI Programming voice capture is available after an Agent is selected but before the lazy first OpenCode session exists; transcription fills the composer draft without creating an empty session, while runtime, loading, transcribing, busy, and recording guards remain unchanged.
|
||||
- `4013edc` / `3b799af`: integrated per-user Code/Canvas/Learning/Robot entry policy from Works Square, projected by Electron Main as four booleans and enforced before disabled module routes initialize.
|
||||
- `01bee31`: enabled AI Learning course catalog/generation/download/playback, Main-owned cloud/runtime bridges, verified external OpenMAIC player-artifact packaging, account profile reuse, removal of the transient `game-engine` Skill, and project-root `planning-with-files` output from the authoritative remote main. The merge hardens this with strict DTO/error projection, account-isolated local state, bounded same-origin downloads/packages, a nonce-protected account-bound player HTTP session, and an exact-source/origin single-document iframe bridge.
|
||||
@@ -44,10 +45,11 @@ AI 编程首次发送在新建 OpenCode session 已知为空时不再等待冗
|
||||
|
||||
Updater 仍由 Electron Main 选择目标 feed、记录原始诊断并保持失败语义。正式稳定源缺少对应平台 manifest 时,设置页只显示一条简洁中文提示并允许重试,不把缺包误报为已是最新版,也不向普通界面暴露堆栈、URL、路径或错误码;签名产物发布和真实升级安装仍属于外部 Release Gate。
|
||||
|
||||
Robot 绑定设备默认先显示“引导配网 / 已有激活码”路径选择。引导路径在页面内扫描附近符合条件的开放 `Xiaozhi-*` 热点,要求用户明确选择后由 Main-owned Windows/macOS 原生适配器连接并核验当前 SSID;失败时保留系统 Wi-Fi 兜底。后续继续使用固定 `http://192.168.4.1/` 系统浏览器页面、恢复互联网和现有六位 Binding;精确环境值 `0` 可回滚为直接六位码。Makelore 不接收 Wi-Fi 密码、不修改固件,也不把热点发现或 Binding 成功等同于可信身份/在线。签名 macOS、Windows 真机、指定固件/发行契约和完整整链仍未完成验证。
|
||||
Robot 绑定设备默认先显示“引导配网 / 已有激活码”路径选择。引导路径在页面内扫描附近符合条件的开放 `Xiaozhi-*` 热点,要求用户明确选择后由 Main-owned Windows/macOS 原生适配器连接并核验当前 SSID;失败时保留系统 Wi-Fi 兜底。macOS 在首次 CoreWLAN 结果为空或 SSID 全不可见时只进行一次 250 ms worker-thread 延迟重扫;第二次仍有网络但没有可读 SSID 时显示定位权限错误,不再冒充“没有发现设备热点”。后续继续使用固定 `http://192.168.4.1/` 系统浏览器页面、恢复互联网和现有六位 Binding;精确环境值 `0` 可回滚为直接六位码。Makelore 不接收 Wi-Fi 密码、不修改固件,也不把热点发现或 Binding 成功等同于可信身份/在线。签名 macOS、Windows 真机、指定固件/发行契约和完整整链仍未完成验证。
|
||||
|
||||
## Recently Completed
|
||||
|
||||
- 2026-08-17: Corrected macOS Robot hotspot discovery after a system-visible `Xiaozhi-*` report. CoreWLAN now gets one bounded retry when its first result is empty or all SSIDs are unavailable; a persistent non-empty redacted result becomes the existing safe permission state. Open-only filtering, firmware, Host/Renderer contracts, exact-current-SSID verification, and the system-Wi-Fi fallback are unchanged; signed-package physical smoke remains pending.
|
||||
- 2026-08-17: Created merge commit `4013edc` for the reviewed per-user module-entry policy source tip `3b799af`. Main exposes only four booleans from `/api/auth/me`; missing fields remain enabled, `design` maps to `painting`, disabled root/deep/alias routes stop before module initialization, Code provider startup waits for policy hydration, terminal `401` clears both session layers, and global settings remains reachable.
|
||||
- 2026-08-17: Integrated remote `01bee31`: Learning is enabled with course browsing, strict bounded generation materials, verified atomic course installation, multi-module playback, Main-owned Agent/ASR/runtime bridges, and a manifest-verified external OpenMAIC player artifact. Merge review added account-isolated generation/library/player state, fixed-binding token/fetch/401 guards, passive-only course media with hardened responses, pre-existing active-registration checks before side-effect-free identity resolution, nonce-protected single-document player sessions, and a recoverable deep-link profile error gate. Publishing now reflects the actual Works contract: first create is coverless, existing draft/published are version-only, and races fail closed without cover/PATCH side effects. The transient `game-engine` Skill was removed and `planning-with-files` writes its files to the project root. Production Works/player-artifact/signed-package acceptance remains pending.
|
||||
- 2026-08-16: Integrated remote `26b52d7`: Canvas now has server-backed Prompt Museum navigation, editable server-repriced generation Quotes, task result details/downloads, guarded project deletion, cloud-default development, and Chinese-only UI. That tip briefly bundled `game-engine`; authoritative successor `01bee31` removed it. Client integration is verified separately from production Prompt Museum data/backend deployment.
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
## Scope
|
||||
|
||||
- On 2026-08-17, resume the existing Integration owner to merge reviewed macOS Robot hotspot scan source `55e61b7` into local `main`, preserve the firmware/open-SSID/Main-owned boundaries, retain signed-package physical smoke as a release gate, and keep remote push outside this resumption.
|
||||
- On 2026-08-17, resume the existing Integration owner at the user's request to merge reviewed AI Programming fresh-Agent voice-input source `b6148a5` into local `main`, preserve lazy first-session creation and existing voice guards, run merged-tree verification and independent review, and keep remote push outside this resumption.
|
||||
- On 2026-08-17, resume the existing Integration owner after the user's explicit takeover authorization to preserve the task's existing uncommitted record, merge reviewed Makelore module-access source tip `3b799af` into local `main`, promote its accepted per-user entry-policy facts, run merged-tree verification and independent review, and keep remote push outside this resumption unless separately authorized.
|
||||
- On 2026-08-16, resume the existing Integration owner after the user restored remote credentials: fetch authoritative `origin/main`, complete the already-started merge of remote tip `26b52d7`, preserve both remote Canvas/Prompt Museum work and local Robot hotspot work, run merged-tree verification and independent review, then perform a normal non-forced push and verify remote-tip equality.
|
||||
@@ -119,6 +120,14 @@ Gate result:
|
||||
- Canonical reconciliation, merged-tree checks and independent final review all passed. Git created normal no-ff merge commit `4013edcbbc982fcbef0a0ff6287c5307cd87bf8a` with first parent `eb16f73e4dece86a193dee0f797a58d77869470f` and reviewed source `3b799af17370dfa6c4c995c3fcbab8f2d84c3dda` as second parent.
|
||||
- Gate result: Passed for canonical promotion and merged-tree verification. Works migration/API deployment, a newly built client package, real-account four-module smoke and server-side API authorization validation remain release commitments.
|
||||
|
||||
### 2026-08-17 macOS Robot Hotspot Scan Integration Resume
|
||||
|
||||
- Reused the existing Integration owner because it exclusively owns clean local `main` at `7e8d9e38114158992c03e589de32535274796d04`; `task_context.py touch` refreshed the reservation and registry status confirms mode `integration`, branch `main`, and this worktree.
|
||||
- Formed reviewed source commit `55e61b7d8f9caccdad54dc9a42092fc7960fb394` from feature task `20260817-robot-hotspot-scan-missing-4c7e91`. The source task is `ready_for_integration`, its worktree is clean, and final Sol review returned Standards/Spec/overall `PASS` with no blocking findings.
|
||||
- Read the source outcome and follow-ups against ADR-003, the Main-owned Robot Hotspot Module, current state, evidence, and the existing signed-macOS/physical-device commitment. The repair changes no architecture, firmware, Host/Renderer DTO, candidate filter, credential, Portal, Binding, or rollback decision.
|
||||
- Started a normal `--no-ff --no-commit` merge of `55e61b7`; Git reported no textual conflict. The source task record remains on the source commit/branch and is excluded from the integrated `main` tree.
|
||||
- Gate result: Passed for a local no-ff merge, minimal current-state reconciliation, proportionate merged-tree verification, and independent final review. Remote push remains outside this resumption.
|
||||
|
||||
### 2026-08-16 Remote `26b52d7` Synchronization Resume
|
||||
|
||||
- Reused the existing Integration owner and refreshed its reservation. Local `main` entered this resumption at `9af6c526a9500a0dbfb88e39ba0dee1eb7e1d097`; the worktree already contained an unfinished merge whose `MERGE_HEAD` was `26b52d76e3dedd754ca1b1c428abaa074b7f98da`.
|
||||
@@ -196,6 +205,13 @@ Gate result:
|
||||
|
||||
## Plan
|
||||
|
||||
### 2026-08-17 macOS Robot Hotspot Scan Integration Plan
|
||||
|
||||
1. Merge reviewed source `55e61b7` into local `main` with a normal no-ff merge, preserving source history while excluding the source-owned task record from the final tree.
|
||||
2. Record the integrated macOS retry/permission behavior in current state without changing ADR-003, firmware, Host/Renderer, open `Xiaozhi-*`, exact-current-SSID, manual-fallback, or rollback contracts.
|
||||
3. Run the 14-test Robot hotspot module suite, full unit suite, typecheck, scoped lint, production build, task-aware document gates, whitespace/unmerged checks, source-record exclusion, and exact topology checks.
|
||||
4. Obtain an independent read-only Sol `PASS`/`FAIL` review, create the verified local merge commit, record its exact parents, and do not push remotely.
|
||||
|
||||
### 2026-08-17 Fresh-Agent Voice Input Integration Plan
|
||||
|
||||
1. Merge reviewed source `b6148a5` into local `main` with a normal no-ff merge, preserving source history while excluding the source-owned task record from the final tree.
|
||||
@@ -282,6 +298,10 @@ Gate result:
|
||||
|
||||
## Outcome
|
||||
|
||||
- On 2026-08-17, staged a normal no-ff merge of reviewed macOS Robot hotspot source `55e61b7` onto clean local `main` at `7e8d9e3`; Git reported no textual conflicts. The source task record remains reachable on its source commit/branch and is excluded from the integrated tree.
|
||||
- The staged tree performs exactly one 250 ms worker-thread rescan when the first macOS CoreWLAN result is empty or every returned SSID is unavailable. A recovered candidate continues through the unchanged open/printable `Xiaozhi-*` Module filter; two empty scans remain an empty result; a second non-empty fully redacted result becomes the existing fixed permission error.
|
||||
- No firmware, entitlements, Host API, Renderer, Portal, Binding, credential, Windows adapter, or remote ref changes are part of this resumption. Signed macOS x64/arm64 physical scan/connect smoke remains an explicit release gate.
|
||||
|
||||
- Created normal no-ff merge commit `f5d47c849e57db87051d59d4ccfb3fda7cab1271` with local `main` baseline `9fd9a7761d3872f25e853ac1b059e55251c6ae8f` as first parent and reviewed source `b6148a5cb45e70a75c055a57f8b269f24c619a6a` as second parent. This resumption is complete locally and did not push a remote ref.
|
||||
- Independent final staged-merge review returned `PASS` with no P0-P3 findings. It confirmed the exact no-ff topology, four-file scope, source-task-record exclusion, lazy-session behavior, preserved guards, regression strength, and document ownership boundaries.
|
||||
- The staged voice-input merge preserves the exact first-parent/second-parent topology `9fd9a77` + `b6148a5`, contains no unmerged entries or unstaged changes, and has passed merged-tree product, build, and project-document gates. It is ready for independent final review before the local merge commit is created.
|
||||
@@ -410,6 +430,12 @@ Gate result:
|
||||
|
||||
## Verification
|
||||
|
||||
- 2026-08-17 independent final macOS Robot hotspot staged-merge review — Standards `PASS`, Spec `PASS`, overall `PASS`, with no P0-P3 findings. It confirmed exact staged topology/scope, source-record exclusion, bounded worker-only retry, retained 8s abort/termination, unchanged filtering/connect contracts, and honest signed-macOS physical-smoke residual risk.
|
||||
- 2026-08-17 macOS Robot hotspot staged-merge regression — `tests/unit/robot-hotspot-module.test.ts` passed 14/14, covering first-scan recovery, persistent SSID redaction, bounded double-empty behavior, security filtering, RSSI/current-SSID projection, worker termination, candidate expiry, exclusivity, and exact-current-SSID verification.
|
||||
- 2026-08-17 macOS Robot hotspot staged-merge `pnpm test` — 175 files / 2052 tests passed.
|
||||
- 2026-08-17 staged-merge `pnpm run typecheck` and scoped ESLint on `macos.ts` plus its unit test — passed.
|
||||
- 2026-08-17 staged-merge `pnpm run build:vite` — Renderer, Electron Main, and Preload passed; the emitted macOS chunk retains the one 250 ms worker wait, bounded rescan, and fixed permission mapping. Only existing mixed-import and chunk-size warnings remain.
|
||||
- `check_project_docs.py`, integration task-aware `check_doc_drift.py`, task registry doctor, staged/unstaged whitespace checks, unmerged-entry check, source-task-record exclusion, and merge-base checks — passed before independent review.
|
||||
- Post-commit topology check confirmed merge `f5d47c8` has exact parents `9fd9a77` and `b6148a5`, the source tip is a `main` ancestor, the source task record is absent from the `main` tree, and the product worktree was clean before this evidence-only update.
|
||||
- 2026-08-17 independent final fresh-Agent voice integration review — `PASS`, no P0-P3 findings; exact staged topology/scope, source-record exclusion, lazy session creation, existing guards, test quality, and project-document boundaries were confirmed.
|
||||
- 2026-08-17 fresh-Agent/lazy-session/voice focused selection — 5/5 passed; the complete `opencode-chat-panel.test.tsx` file passed 85/85 on the staged merged tree.
|
||||
|
||||
@@ -10,6 +10,7 @@ const LOCATION_AUTHORIZED_ALWAYS = 3;
|
||||
const LOCATION_AUTHORIZED_WHEN_IN_USE = 4;
|
||||
const CORE_WLAN_PERMISSION_DENIED = -3930;
|
||||
const CORE_WLAN_UNSUPPORTED = -3903;
|
||||
const SCAN_RETRY_DELAY_MS = 250;
|
||||
|
||||
type StableReason = 'unsupported' | 'permission_denied' | 'scan_failed' | 'connect_failed';
|
||||
type ObjcId = unknown;
|
||||
@@ -21,6 +22,36 @@ interface WorkerCandidate {
|
||||
open: boolean;
|
||||
}
|
||||
|
||||
export interface MacosScanNetwork {
|
||||
ssid: string | null;
|
||||
rssi: number;
|
||||
open: boolean;
|
||||
}
|
||||
|
||||
export function scanMacosCandidatesWithRetry(
|
||||
readNetworks: () => MacosScanNetwork[],
|
||||
currentSsid: string | null,
|
||||
waitBeforeRetry: () => void,
|
||||
): WorkerCandidate[] {
|
||||
let networks = readNetworks();
|
||||
if (networks.length === 0 || networks.every((network) => !network.ssid)) {
|
||||
waitBeforeRetry();
|
||||
networks = readNetworks();
|
||||
}
|
||||
if (networks.length > 0 && networks.every((network) => !network.ssid)) {
|
||||
throw new RobotHotspotAdapterError('permission_denied');
|
||||
}
|
||||
return networks.flatMap((network): WorkerCandidate[] => {
|
||||
if (!network.open || !network.ssid) return [];
|
||||
return [{
|
||||
ssid: network.ssid,
|
||||
signalPercent: Math.max(0, Math.min(100, Math.round((network.rssi + 100) * 2))),
|
||||
connected: currentSsid === network.ssid,
|
||||
open: true,
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
type WorkerRequest = { id: number; operation: 'scan' } | { id: number; operation: 'connect'; ssid: string };
|
||||
type WorkerResponse = { id: number; ok: true; result: WorkerCandidate[] | { ssid: string } } | { id: number; ok: false; reason: StableReason };
|
||||
|
||||
@@ -219,13 +250,16 @@ async function runCoreWlanWorker(): Promise<void> {
|
||||
return withPool(() => {
|
||||
const wifi = wifiInterface('scan');
|
||||
const current = stringValue(sendId(wifi, selectors.ssid));
|
||||
return scanNetworks('scan').flatMap((network): WorkerCandidate[] => {
|
||||
if (!sendSupportsSecurity(network, selectors.supportsSecurity, 0)) return [];
|
||||
const ssid = stringValue(sendId(network, selectors.ssid));
|
||||
if (!ssid) return [];
|
||||
const rssi = Number(sendLong(network, selectors.rssiValue));
|
||||
return [{ ssid, signalPercent: Math.max(0, Math.min(100, Math.round((rssi + 100) * 2))), connected: current === ssid, open: true }];
|
||||
});
|
||||
const readNetworks = (): MacosScanNetwork[] => scanNetworks('scan').map((network) => ({
|
||||
ssid: stringValue(sendId(network, selectors.ssid)),
|
||||
rssi: Number(sendLong(network, selectors.rssiValue)),
|
||||
open: sendSupportsSecurity(network, selectors.supportsSecurity, 0),
|
||||
}));
|
||||
return scanMacosCandidatesWithRetry(
|
||||
readNetworks,
|
||||
current,
|
||||
() => { Atomics.wait(pollSleep, 0, 0, SCAN_RETRY_DELAY_MS); },
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import type { RobotHotspotAdapter, RobotHotspotAdapterCandidate } from '@electron/robot-hotspot/adapter';
|
||||
import { RobotHotspotError, createRobotHotspotModule } from '@electron/robot-hotspot';
|
||||
import { MacosWorkerController, requestMacosWorker } from '@electron/robot-hotspot/macos';
|
||||
import { MacosWorkerController, requestMacosWorker, scanMacosCandidatesWithRetry } from '@electron/robot-hotspot/macos';
|
||||
import type { MacosWorkerLike } from '@electron/robot-hotspot/macos';
|
||||
|
||||
function candidate(ssid: string, signalPercent: number, overrides: Partial<RobotHotspotAdapterCandidate> = {}): RobotHotspotAdapterCandidate {
|
||||
@@ -22,6 +22,62 @@ async function expectCode(promise: Promise<unknown>, code: string): Promise<void
|
||||
}
|
||||
|
||||
describe('Robot hotspot module', () => {
|
||||
it('retries once when the first macOS scan hides every SSID and returns the recovered open hotspot', () => {
|
||||
const readNetworks = vi.fn()
|
||||
.mockReturnValueOnce([
|
||||
{ ssid: null, rssi: -80, open: true },
|
||||
{ ssid: '', rssi: -60, open: true },
|
||||
])
|
||||
.mockReturnValueOnce([
|
||||
{ ssid: 'Xiaozhi-7A2B', rssi: -58, open: true },
|
||||
{ ssid: 'Private-Network', rssi: -42, open: false },
|
||||
]);
|
||||
const waitBeforeRetry = vi.fn();
|
||||
|
||||
expect(scanMacosCandidatesWithRetry(readNetworks, 'Xiaozhi-7A2B', waitBeforeRetry)).toEqual([
|
||||
{ ssid: 'Xiaozhi-7A2B', signalPercent: 84, connected: true, open: true },
|
||||
]);
|
||||
expect(readNetworks).toHaveBeenCalledTimes(2);
|
||||
expect(waitBeforeRetry).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('reports permission denied when both macOS scans find networks but expose no SSIDs', () => {
|
||||
const readNetworks = vi.fn()
|
||||
.mockReturnValueOnce([{ ssid: null, rssi: -70, open: true }])
|
||||
.mockReturnValueOnce([{ ssid: '', rssi: -65, open: true }]);
|
||||
|
||||
expect(() => scanMacosCandidatesWithRetry(readNetworks, null, vi.fn())).toThrowError(
|
||||
expect.objectContaining({ reason: 'permission_denied' }),
|
||||
);
|
||||
expect(readNetworks).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('bounds an empty macOS scan retry and keeps an empty result empty', () => {
|
||||
const readNetworks = vi.fn().mockReturnValue([]);
|
||||
const waitBeforeRetry = vi.fn();
|
||||
|
||||
expect(scanMacosCandidatesWithRetry(readNetworks, null, waitBeforeRetry)).toEqual([]);
|
||||
expect(readNetworks).toHaveBeenCalledTimes(2);
|
||||
expect(waitBeforeRetry).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('does not retry a readable macOS scan and filters secured networks while clamping RSSI', () => {
|
||||
const readNetworks = vi.fn().mockReturnValue([
|
||||
{ ssid: 'Xiaozhi-weak', rssi: -120, open: true },
|
||||
{ ssid: 'Xiaozhi-strong', rssi: -20, open: true },
|
||||
{ ssid: 'Xiaozhi-secured', rssi: -50, open: false },
|
||||
{ ssid: null, rssi: -40, open: true },
|
||||
]);
|
||||
const waitBeforeRetry = vi.fn();
|
||||
|
||||
expect(scanMacosCandidatesWithRetry(readNetworks, 'Xiaozhi-strong', waitBeforeRetry)).toEqual([
|
||||
{ ssid: 'Xiaozhi-weak', signalPercent: 0, connected: false, open: true },
|
||||
{ ssid: 'Xiaozhi-strong', signalPercent: 100, connected: true, open: true },
|
||||
]);
|
||||
expect(readNetworks).toHaveBeenCalledOnce();
|
||||
expect(waitBeforeRetry).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('terminates a hanging macOS native worker when the request is aborted', async () => {
|
||||
const worker = {
|
||||
postMessage: vi.fn(),
|
||||
|
||||
Reference in New Issue
Block a user