Skip to content

feat(network): 添加只读网络监视器与按需地图库 - #10

Merged
ysicing merged 14 commits into
mainfrom
codex/feat-network-monitor
Oct 7, 2026
Merged

ysicing merged 14 commits into
mainfrom
codex/feat-network-monitor

Conversation

@ysicing

@ysicing ysicing commented Oct 6, 2026

Copy link
Copy Markdown
Owner

新增可选的网络监视器,查看应用连接及目标国家分布。功能默认关闭,仅在 macOS 15+ 开放;主应用的其它功能继续支持 macOS 14。

功能与边界

  • 使用 Network Extension 记录新连接元数据及结束状态,所有连接放行,不读取通信内容;本次不包含阻断或规则管理。
  • 默认按应用查看,支持进程、域名、国家汇总,提供原生分栏、连接详情、国家地图、搜索和暂停/继续。
  • 观察与下载按窗口可见性、暂停、锁屏及休眠状态启停。活动集合与界面记录有界,最多保留 512 条;仅覆盖观察期间建立的连接,不枚举启用前已有的连接。
  • IP 国家库首次使用时从 c-ip 下载,校验压缩与解压后的大小及 SHA-256,再原子切换本机缓存。失败时保留旧库,连接列表继续可用。30 天内复用缓存,不向服务商发送连接 IP。
  • 发布前从 DB-IP 官方获取最新月度数据,校验、转换和压缩后上传内容寻址对象;CDN 回读校验通过后更新索引,再发布应用制品。同步失败中止后续发布。
  • 补齐九种语言、隐私与数据许可说明,以及主应用/扩展签名配置。内部构建号推进至 133,避免 macOS 沿用同版本旧扩展;客户端也会明确拒绝缺少活动状态字段的旧协议回复。

验证

  • task test 通过:766 个 Swift 测试、12 个地图库发布流程测试及其它脚本/API 检查。
  • Developer ID 签名、公证、装订与 Gatekeeper 校验通过,本地预览已安装。
  • 实测活动连接出现及关闭后移除、首次下载、缓存校验与复用、国家定位和地图标记。
  • 走查中文、英文、德文、阿拉伯语的浅色/深色布局;修复 RTL 地图标记位置与列表选中文字对比度。
  • c-ip 地图库初始化及公开下载哈希回读通过。移除内置 IP 库后,本地 App 体积约从 45 MiB 降到 30 MiB。

正式构建需要包含 Network Extension 权限的主应用及扩展 provisioning profiles。地图位置为国家级示意,不代表远端设备精确位置。

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-07T03:36:28.862205Z be42c7c New commits
🔒 Security Review ✅ Completed 2026-10-06T02:23:28.980391Z 726e936 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 726e936eda

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

moduleEnabled = enabled
self.visible = visible
if !enabled && !records.isEmpty { records = [] }
if !enabled && (wantsRunning || status != .idle) { stop() }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Disable the persisted filter from an idle controller

When the filter was enabled in an earlier app session, it remains enabled in NEFilterManager after quitting, but a relaunched controller starts with status == .idle and wantsRunning == false. If the user then turns off the Network Monitor feature without starting it again, this condition skips stop(), so setFilterEnabled(false) is never called and the system extension continues receiving every socket flow despite the feature being disabled. Reconcile the persisted filter state on disable even when this controller has not entered the running state.

AGENTS.md reference: AGENTS.md:L20-L21

Useful? React with 👍 / 👎.

candidate.exportedInterface = NSXPCInterface(with: NetworkObservationService.self)
// 连接建立不等于认证通过。只有 macOS 校验签名后分发的首次读取才能替换观察者,
// 未认证客户端不能通过反复连接来关闭现有观察或夺走其租约。
candidate.exportedObject = ConnectionObservationSession { [weak self] candidate, firstRead, cursor, epoch in

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Break the XPC session retain cycle

Each accepted connection strongly owns its exportedObject, whose readBatch closure in turn captures candidate strongly, creating candidate → session → closure → candidate. Neither the invalidation handler nor the replacement path clears exportedObject, so every visibility or pause transition that invalidates and recreates the connection can leave another connection/session cycle resident in the long-lived system extension. Capture the candidate weakly or explicitly sever the exported-object reference during invalidation.

AGENTS.md reference: AGENTS.md:L26-L26

Useful? React with 👍 / 👎.

Comment on lines +82 to +89
for (file, data) in zip(manifest.files, pair) {
try data.write(to: directory.appendingPathComponent(file.name), options: .atomic)
}
let record = CacheRecord(manifest: manifest, directory: directoryName, checkedAt: Date())
// 两个文件均验证完成后才原子切换索引;更新失败不会把IPv4新库与IPv6旧库混用。
let mapped = try manifest.files.map { try read($0, directory: directory) }
try JSONEncoder().encode(record).write(to: cacheRoot.appendingPathComponent("current.json"), options: .atomic)
install(record, pair: mapped)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor cancellation while committing geography data

If the viewer is hidden after the final cancellation check but while this commit block is running, cancelling the task cannot interrupt the synchronous writes, rereads, hashes, index write, or install. With the allowed asset sizes this can continue tens of MiB of disk I/O after demand ends, and the cancelled generation can still become the fresh cache that a subsequent reopen reuses. Add cancellation checks between the file loop, verification, pointer write, and in-memory installation.

AGENTS.md reference: AGENTS.md:L24-L24

Useful? React with 👍 / 👎.

let known = Set(records.map(\.id))
records = Array((records + updates.values.filter { !known.contains($0.id) }.sorted { $0.sequence < $1.sequence }).suffix(ObservationLimits.displayCount))
}
if let activeIDs = batch.activeIDs { activeConnectionIDs = Set(activeIDs) }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid invalidating the whole page on unchanged active IDs

Every two-second read includes activeIDs, so this unconditional assignment emits an Observation mutation even when the set is unchanged. While connections are idle, that needlessly rebuilds the groups, table, and the world-map canvas with roughly ten thousand path points on every poll. Compare the new set with activeConnectionIDs before assigning so an unchanged snapshot does not cause continuous redraw work.

AGENTS.md reference: AGENTS.md:L25-L25

Useful? React with 👍 / 👎.

records = Array((records + updates.values.filter { !known.contains($0.id) }.sorted { $0.sequence < $1.sequence }).suffix(ObservationLimits.displayCount))
}
if let activeIDs = batch.activeIDs { activeConnectionIDs = Set(activeIDs) }
if discarded != batch.discarded { discarded = batch.discarded }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Surface journal eviction to the viewer

When more than 256 open/close events accumulate before the reader catches up, the extension ring overwrites events and increments discarded, but no view reads this controller property. The connection and aggregate views therefore silently present an incomplete observation as if it were complete; even the newly added localized eviction warning is never used. Display the discarded count or an explicit truncation warning whenever it increases.

Useful? React with 👍 / 👎.

Comment on lines +7 to +8
<key>CFBundleDisplayName</key>
<string>XStats Network Extension</string>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Localize the system extension display name

CFBundleDisplayName is user-visible in system extension approval and System Settings, but it is fixed to English here and none of the newly added localized InfoPlist.strings files overrides it. Consequently every non-English locale displays “XStats Network Extension” even though the feature's other text is localized. Add a translated CFBundleDisplayName entry for every supported language.

AGENTS.md reference: AGENTS.md:L30-L30

Useful? React with 👍 / 👎.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Filter cleanup, application-hide handling and cancellation-safe cache commits remain unresolved.

Review effort: Balanced
Findings: 3 Medium severity

Open (3)
What changed in this PR

Adds an optional, read-only Network Monitor for macOS 15+, while retaining macOS 14 support for existing features.

Changes:

  • Adds bounded connection observation, grouping, search and country maps.
  • Introduces verified, on-demand geography downloads and release-time data publishing.
  • Updates signing, localization, privacy documentation and regression coverage.
File Description
web/​privacy.html Documents observation and download privacy.
ThirdPartyNotices.md Attributes map and IP datasets.
Taskfile.yml Adds extension profile and geography tests.
scripts/​sync_network_geography.py Prepares and publishes verified datasets.
scripts/​sign_sparkle.sh Updates nested and preview signing.
scripts/​release.sh Verifies extension architecture and signing.
scripts/​publish_release.sh Requires geography publication before app uploads.
scripts/​network_geography_test.py Tests conversion and publication boundaries.
scripts/​compress_network_geography.swift Compresses and verifies geography tables.
scripts/​build_network_geography.py Converts IP ranges and world outlines.
scripts/​arm64_release_test.sh Checks extension configuration and entitlements.
project.yml Configures extension target and build 133.
Packages/​XStatsKit/​Tests/​XStatsUITests/​NetworkMonitorTests.swift Tests monitor lifecycle and protocol handling.
Packages/​XStatsKit/​Tests/​XStatsUITests/​GeographyDownloadTests.swift Tests downloads, caching and cancellation.
Packages/​XStatsKit/​Tests/​NetworkObservationTests/​ConnectionJournalTests.swift Tests bounded journals and observation leases.
Packages/​XStatsKit/​Tests/​LocalizationTests/​LanguageSupportTests.swift Checks monitor translations.
Packages/​XStatsKit/​Sources/​XStatsUI/​State/​SettingsDocument.swift Backs up the feature preference.
Packages/​XStatsKit/​Sources/​XStatsUI/​State/​AppSettings.swift Adds preference and availability gates.
Packages/​XStatsKit/​Sources/​XStatsUI/​State/​AppModel.swift Owns monitor and geography controllers.
Packages/​XStatsKit/​Sources/​XStatsUI/​Snapshot/​SnapshotRenderer.swift Adds synthetic monitor previews.
Packages/​XStatsKit/​Sources/​XStatsUI/​Settings/​FeatureSettings.swift Adds the feature switch.
Packages/​XStatsKit/​Sources/​XStatsUI/​Resources/​NetworkGeography/​ATTRIBUTION.md Bundles dataset attribution.
Packages/​XStatsKit/​Sources/​XStatsUI/​Resources/​Legal/​Privacy.zh-Hans.md Updates Chinese privacy disclosures.
Packages/​XStatsKit/​Sources/​XStatsUI/​Resources/​Legal/​Privacy.en.md Updates English privacy disclosures.
Packages/​XStatsKit/​Sources/​XStatsUI/​Panel/​MainWindowView.swift Adds monitor page and sidebar entry.
Packages/​XStatsKit/​Sources/​XStatsUI/​NetworkObservation/​OfflineGeography.swift Manages cached tables and offline lookups.
Packages/​XStatsKit/​Sources/​XStatsUI/​NetworkObservation/​NetworkMonitorSupport.swift Gates support to macOS 15+.
Packages/​XStatsKit/​Sources/​XStatsUI/​NetworkObservation/​NetworkMonitorController.swift Coordinates observation lifecycle and records.
Packages/​XStatsKit/​Sources/​XStatsUI/​NetworkObservation/​NetworkMonitorBackend.swift Manages extension activation and authenticated communication.
Packages/​XStatsKit/​Sources/​XStatsUI/​NetworkObservation/​NetworkGeographyController.swift Coordinates demand-driven geography updates.
Packages/​XStatsKit/​Sources/​XStatsUI/​NetworkObservation/​NetworkConnectionsPage.swift Displays grouped connections and country maps.
Packages/​XStatsKit/​Sources/​XStatsUI/​NetworkObservation/​GeographyManifest.swift Validates manifests and compressed assets.
Packages/​XStatsKit/​Sources/​XStatsUI/​NetworkObservation/​GeographyDownload.swift Implements bounded, cancellable downloads.
Packages/​XStatsKit/​Sources/​XStatsUI/​AppKit/​AppDeepLink.swift Adds gated connections routing.
Packages/​XStatsKit/​Sources/​XStatsUI/​AppKit/​AppController.swift Connects observation demand to app lifecycle.
Packages/​XStatsKit/​Sources/​NetworkObservation/​ObservedConnection.swift Defines metadata and communication contracts.
Packages/​XStatsKit/​Sources/​NetworkObservation/​ObservationBuffer.swift Tracks leases and active connections.
Packages/​XStatsKit/​Sources/​NetworkObservation/​ConnectionJournal.swift Provides bounded incremental event storage.
Packages/​XStatsKit/​Sources/​Localization/​Translations.swift Adds English monitor translations.
Packages/​XStatsKit/​Sources/​Localization/​Resources/​zh-Hant.tsv Adds Traditional Chinese translations.
Packages/​XStatsKit/​Sources/​Localization/​Resources/​ko.tsv Adds Korean translations.
Packages/​XStatsKit/​Sources/​Localization/​Resources/​ja.tsv Adds Japanese translations.
Packages/​XStatsKit/​Sources/​Localization/​Resources/​fr.tsv Adds French translations.
Packages/​XStatsKit/​Sources/​Localization/​Resources/​es.tsv Adds Spanish translations.
Packages/​XStatsKit/​Sources/​Localization/​Resources/​de.tsv Adds German translations.
Packages/​XStatsKit/​Sources/​Localization/​Resources/​ar.tsv Adds Arabic translations.
Packages/​XStatsKit/​Package.swift Registers observation module, resources and tests.
NetworkExtension/​zh-Hant.lproj/​InfoPlist.strings Localizes extension permission text.
NetworkExtension/​zh-Hans.lproj/​InfoPlist.strings Supplies Chinese extension permission text.
NetworkExtension/​XStatsNetworkExtension.entitlements Declares sandbox and network permissions.
NetworkExtension/​main.swift Starts the system extension.
NetworkExtension/​ko.lproj/​InfoPlist.strings Localizes extension permission text.
NetworkExtension/​ja.lproj/​InfoPlist.strings Localizes extension permission text.
NetworkExtension/​Info.plist Declares provider and communication service.
NetworkExtension/​fr.lproj/​InfoPlist.strings Localizes extension permission text.
NetworkExtension/​es.lproj/​InfoPlist.strings Localizes extension permission text.
NetworkExtension/​en.lproj/​InfoPlist.strings Localizes extension permission text.
NetworkExtension/​de.lproj/​InfoPlist.strings Localizes extension permission text.
NetworkExtension/​ConnectionObservationSession.swift Exports session-scoped metadata reads.
NetworkExtension/​ConnectionFilterProvider.swift Observes connections while allowing traffic.
NetworkExtension/​ar.lproj/​InfoPlist.strings Localizes extension permission text.
LICENSING.md Clarifies configuration and dataset licensing.
DEVELOPMENT.md Documents setup, signing and publication.
ARCHITECTURE.md Describes observation and geography architecture.
App/​zh-Hant.lproj/​InfoPlist.strings Localizes app permission text.
App/​zh-Hans.lproj/​InfoPlist.strings Supplies Chinese app permission text.
App/​XStats.entitlements Adds extension and shared-group permissions.
App/​ko.lproj/​InfoPlist.strings Localizes app permission text.
App/​ja.lproj/​InfoPlist.strings Localizes app permission text.
App/​Info.plist Adds permission description and service name.
App/​fr.lproj/​InfoPlist.strings Localizes app permission text.
App/​es.lproj/​InfoPlist.strings Localizes app permission text.
App/​en.lproj/​InfoPlist.strings Localizes app permission text.
App/​de.lproj/​InfoPlist.strings Localizes app permission text.
App/​ar.lproj/​InfoPlist.strings Localizes app permission text.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +435 to +437
model.connectionMonitor.setDemand(enabled: model.settings.canViewNetworkConnections,
visible: model.isMainWindowVisible && model.settings.panelTab == .connections)
model.networkGeography.setDemand(enabled: model.connectionMonitor.isReading)
ysicing and others added 2 commits October 6, 2026 11:02
- 读取端暂停或切换时仅结束租约,保留活动连接与日志
- 批次读满时立即续读,并提示扩展缓冲区淘汰记录
- 重启后关闭模块仍写入停用,避免系统过滤器残留
- 应用隐藏(⌘H)时视为主窗口不可见,停止按需观察与下载
- 地图库提交前响应取消;缓存新鲜时回到页面不再闪更新提示
- 过滤器启动失败与停止共用收尾,listener 启停成对串行
- 裸可执行文件名保留点号;扩展显示名补齐九种语言
- DB-IP 当月文件未发布时回退上月;entitlements 许可记录于 LICENSING.md

Co-Authored-By: Claude <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: debd250c64

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread NetworkExtension/Info.plist Outdated
Comment on lines +27 to +28
<key>NEMachServiceName</key>
<string>$(TeamIdentifierPrefix)work.12306.xstats.network-observation.ipc</string>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Version the extension Mach service across updates

When an already activated network system extension is replaced by a normal app update, this static service name can remain mapped to the old extension or yield immediately invalidated XPC connections even though the new extension activation succeeds. Apple is tracking this as FB17032197 and recommends including the app version in NEMachServiceName as the workaround (Apple Developer Forums); otherwise upgraded users can repeatedly hit the viewer's timeout/unavailable state until they manually recover the extension. Include CURRENT_PROJECT_VERSION in this value and in the matching NetworkObservationMachService value in the host app.

Useful? React with 👍 / 👎.

Comment on lines +111 to +114
private func install(_ record: CacheRecord, pair: [Data]) {
cache = record
v4 = pair[record.manifest.files.firstIndex { $0.name == "country-ipv4.bin" }!]
v6 = pair[record.manifest.files.firstIndex { $0.name == "country-ipv6.bin" }!]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Release geography tables after the feature is disabled

After the map is used once, these properties retain both complete IP tables for the lifetime of the menu-bar process, either as file mappings or heap-backed Data; each table permits 32 MiB, while NetworkGeographyController.setDemand(false) only cancels downloads and never unloads them. Consequently, turning the Network Monitor feature off cannot reclaim up to 64 MiB of cache memory even though no lookup can use it. Add an explicit bounded lifetime or unload path when the feature is disabled.

AGENTS.md reference: AGENTS.md:L20-L23

Useful? React with 👍 / 👎.

改用 SMAppService 按需控制服务,统一组件安装、更新和卸载入口,并按观察租约切换全部放行规则。

验证:完整 task test 824 项通过;组件签名、公证、c-ip 发布、在线首次安装及 Sparkle 检查更新通过。
验证边界:尚未执行组件跨版本 Sparkle 安装和可达 IPv6 目标的端到端通信测试。

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3133fda27e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

status = .preview
}

var terminationRequiresPreparation: Bool { isSupported && (wantsRunning || isBusy || isReading || terminationStopPending) }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reconcile the persisted filter before every normal quit

After a crash or forced quit leaves NEFilterManager.isEnabled persisted, a relaunch starts with wantsRunning == false, so quitting again before starting the viewer makes this property false and bypasses prepareForTermination; shutdown() only cancels local work and never saves isEnabled = false. The earlier disable-path fix does not cover this normal-quit case, leaving the system filter enabled after an ordinary exit; termination must reconcile the persisted configuration even when this controller is idle.

AGENTS.md reference: AGENTS.md:L20-L21

Useful? React with 👍 / 👎.

reply.finish(.failure(NetworkMonitorError.unavailable)); return
}
proxy.perform(action) { @Sendable data in reply.finish(.success(data)) }
DispatchQueue.global().asyncAfter(deadline: .now() + 65) { reply.finish(.failure(NetworkMonitorError.timeout)) }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep approval RPC alive until the system request resolves

When system-extension approval takes longer than 65 seconds, this timer fails the client reply while NetworkComponentServiceHost is still awaiting the submitted OSSystemExtensionRequest. reconcileConfiguration() then changes the viewer to .failed, and the eventual successful activation reply is discarded, so returning from System Settings does not enable the filter until the user manually starts again; approval-bearing actions need to remain pending or reconcile their eventual completion instead of using this fixed timeout.

Useful? React with 👍 / 👎.

Comment on lines +155 to +158
info["CFBundleIdentifier"] as? String == bundleIdentifier,
info["CFBundlePackageType"] as? String == "APPL",
let team = dependencies.teamIdentifier(hostApp), !team.isEmpty,
dependencies.teamIdentifier(app) == team else { throw NetworkComponentInstallError.untrustedComponent }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject incompatible component protocols before installation

When an older XStats performs a first-time install after the fixed download alias has advanced to a component with a newer protocol, this validation accepts and commits that component because it checks only the bundle identity, type, and signing team. prepareComponentImpl() then rejects the installed metadata with protocolMismatch, while subsequent install attempts only validate the now-existing bundle and never replace it, leaving the viewer unusable until the user manually deletes the component or upgrades XStats. Validate the protocol and expected control service before the exclusive rename.

Useful? React with 👍 / 👎.

<key>NSHumanReadableCopyright</key>
<string>© 2026 XStats</string>
<key>NSSystemExtensionUsageDescription</key>
<string>只在本机查看应用的网络连接,不阻断连接或读取通信内容。</string>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add localized InfoPlist strings for the component host

On a non-Chinese system, the system-extension approval request is submitted by XStats Network Monitor.app, but this target has no localized InfoPlist.strings; despite declaring nine localizations, its NSSystemExtensionUsageDescription therefore remains the hard-coded Simplified Chinese fallback in every locale. Add the translated usage description to this host target, not only to the embedded extension.

AGENTS.md reference: AGENTS.md:L30-L30

Useful? React with 👍 / 👎.

未安装组件时弹出安装面板,安装成功后继续启用;关闭、离页或失败时不执行待启用操作。关闭按钮补充 Escape 支持和本地化文案。

独立 Monitor 应用复用 XStats 图标集,补齐资源、资产目录和 Info.plist 配置。

验证:完整测试 827 项通过,安装流程定向测试与打包配置检查通过;主程序和组件签名、公证、本地安装及登录项图标验收通过。

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The companion lacks runtime language initialization and localized authorization resources, leaving its application-defined text in Simplified Chinese.

Review effort: Balanced
Findings: 3 Medium severity

Open (3)
Resolved since last review (2)

Comment thread NetworkMonitorApp/main.swift Outdated
Comment on lines +161 to +162
let arguments = CommandLine.arguments
if arguments.contains("--register-service") || arguments.contains("--refresh-service") || arguments.contains("--unregister-service") {
Comment thread project.yml
SUVerifyUpdateBeforeExtraction: true
LSApplicationCategoryType: public.app-category.utilities
NSHumanReadableCopyright: © 2026 XStats
NSSystemExtensionUsageDescription: 只在本机查看应用的网络连接,不阻断连接或读取通信内容。
- 退出时停用失败只拦截一次,再次退出直接放行
- 等待系统扩展授权超时保留授权提示,并提供重试入口
- 旧过滤器停止等待在截止时间内重试并丢弃失效连接
- 状态刷新与变更命令串行,避免系统扩展请求冲突
- agent 空闲退出仅重建连接,失效时才重新注册
- 连接页分组每次渲染只计算一次
- 扩展复用 NetworkCodeIdentity 获取团队标识

Co-Authored-By: Claude <noreply@anthropic.com>
@ysicing
ysicing requested a balanced review from Copilot October 6, 2026 14:41

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@ysicing

ysicing commented Oct 6, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Another round soon, please!

Reviewed commit: 2eba631e40

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

有效续租推动失败的观察设置重试,持续失败通过读取错误进入主程序现有重试与停止流程;使用代次和请求序号隔离旧完成。

组件 GUI 初始化主程序语言偏好,服务和注册 CLI 返回中文源文案键,由接收界面翻译;系统过滤配置使用显式语言翻译。组件及扩展构建号推进至 139。

验证:完整 task test 833 项通过,包含真实匿名 XPC 与模拟 apply 失败联动测试;组件及扩展 Release 签名构建通过。未在实际系统扩展中注入 apply 故障,未安装或发布本次构建。

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9c769f319c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread NetworkMonitorApp/main.swift Outdated
Comment on lines +73 to +75
nonisolated func componentStatusChanged(_ data: Data) {
guard data.count <= 256 * 1024, let value = try? JSONDecoder().decode(NetworkComponentStatus.self, from: data) else { return }
Task { @MainActor in status = value }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Refresh the standalone status message after pushed updates

When system-extension approval takes longer than the standalone client's 120-second RPC timeout, the request reports a timeout while the service continues processing it; the eventual componentStatusChanged push updates only status, leaving message stuck on the timeout or approval text even when filterEnabled is now true. Recompute the displayed message from the pushed status so the standalone component UI reflects the eventual successful activation.

Useful? React with 👍 / 👎.

Comment on lines +84 to +89
return Dictionary(grouping: matching) { record in
grouping == .domain ? (record.hostname ?? record.address ?? tr("目标未知"))
: (record.address.flatMap { countries[$0] } ?? "??")
}.map { key, records in
NetworkConnectionGroup(id: key, name: grouping == .country ? networkRegionName(key) : key,
applicationPath: nil, records: records)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Sort domain and country groups by connection time

When a domain or country contains connections from multiple applications, matching is flattened from application groups that are sorted by application name, and the grouped records array is stored without re-sorting. Selecting that domain or country therefore shows connections in application-group order rather than newest-first, unlike the app and “all connections” views. Sort each cross-application group's records by timestamp before constructing it.

Useful? React with 👍 / 👎.

固定下载地址可能已指向更新协议的组件;在排他 rename 前校验协议版本与控制服务名,
避免装上后无法使用、也无法通过组件服务卸载。

Co-Authored-By: Claude <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5269691292

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

installationCancelled = false
preparedForHandoff = false
lifetime.setSDKActive(true)
driver?.check(userInitiated: true, action: .install(release))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Gate self-updates on protocol compatibility

When the component feed eventually advances to a newer NetworkObservationProtocolVersion while a user keeps this v2 main app, this path installs the release after validating only version/build and extension metadata; the signed component manifest does not carry the protocol or control-service contract. After replacement, NativeNetworkMonitorBackend.prepareComponentImpl() rejects every status, enable, and uninstall command with protocolMismatch, leaving the viewer unusable until the main app is upgraded or the component is manually removed. Add signed compatibility metadata and reject an incompatible target before invoking Sparkle's .install action.

Useful? React with 👍 / 👎.

// 在目标文件系统暂存,最后使用排他 rename;目标即使在检查后出现也不会被覆盖。
let staged = destination.deletingLastPathComponent().appendingPathComponent(".xstats-network-\(UUID().uuidString).app")
defer { try? manager.removeItem(at: staged) }
try manager.copyItem(at: candidate, to: staged)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Route the /Applications install through elevation

When XStats runs under a standard macOS account, this copy targets a staging path directly under /Applications, where the process normally has no write permission, and no authorization or privileged-helper path is used. The first-time component install therefore downloads and validates successfully but fails at the final copy, making the feature unavailable to non-admin users; perform the destination write through the repository's privileged helper or an explicit authenticated installation flow.

AGENTS.md reference: AGENTS.md:L7-L7

Useful? React with 👍 / 👎.

ysicing and others added 2 commits October 7, 2026 11:05
- 组件独立窗口空闲时按推送状态刷新提示,授权超时后最终启用不再停留在超时文案
- 域名与国家或地域分组先整体按时间倒序,避免按应用名分段

Co-Authored-By: Claude <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 72b887886c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Label(tr("网络监视器"), systemImage: "network").font(.title2.bold())
Text(model.message).foregroundStyle(.secondary).textSelection(.enabled)
HStack {
Button(tr("启用连接查看")) { Task { await model.run("enable") } }.disabled(model.busy)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Route standalone enable to an observation reader

When a user launches XStats Network Monitor.app directly and clicks this button, enable only turns on the NEFilterManager configuration; this GUI never calls readEvents to create an observation lease. The provider therefore remains in its no-demand, allow-only mode and records no connections, while statusMessage reports “正在监视” solely because filterEnabled is true. Open the main viewer or establish a reader before presenting this action as active monitoring.

AGENTS.md reference: AGENTS.md:L21-L21

Useful? React with 👍 / 👎.

} catch { self.error = error.localizedDescription }
}
}
var terminationRequiresPreparation: Bool { operation != nil || handoff != nil }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include component downloads in quit preparation

When the component updater is downloading or verifying, the installUpdate RPC has already returned, so both operation and handoff are nil and this property reports that no termination preparation is needed. Quitting XStats therefore neither sends cancelUpdate nor waits for cleanup; the on-demand component service remains alive through sdkActive and can finish replacing the component after the controlling app has exited. This is especially unsafe during a simultaneous main-app update, because the late component replacement can disable the filter after the relaunched viewer has restored it, with no controller left tracking the handoff. Treat all active component-update phases as requiring preparation and explicitly cancel or await the service-side cycle.

AGENTS.md reference: AGENTS.md:L24-L24

Useful? React with 👍 / 👎.

func uninstall() async throws -> Bool {
guard ProcessInfo.processInfo.operatingSystemVersion.majorVersion >= 15 else { throw NetworkMonitorError.unsupportedVersion }
stopReading()
try await setFilterEnabled(false)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow removal when another filter owns the configuration

When XStats's system extension is installed but the user has since enabled another product's content filter, setFilterEnabled(false) throws wrongConfiguration for that foreign provider. Calling it unconditionally here aborts before the XStats deactivation request and before component cleanup, so the user cannot uninstall XStats without first disabling the unrelated filter. Only disable and remove preferences when the provider identifier belongs to XStats; otherwise leave the foreign configuration untouched and continue deactivating the XStats extension.

Useful? React with 👍 / 👎.

- /Applications 无写权限时返回明确的本地化错误,不引入提权写入
- 开发文档记录协议版本升级前必须补齐的兼容与重装路径

Co-Authored-By: Claude <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: be42c7c1f7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

func filterEnabled() async throws -> Bool {
let manager = NEFilterManager.shared()
try await manager.loadFromPreferences()
return manager.isEnabled

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Verify the provider before reporting the filter enabled

When another security product owns the shared NEFilterManager configuration, this returns true solely because that foreign filter is enabled. The resulting NetworkComponentStatus makes the component-management sheet report XStats's filter as enabled and makes the standalone app display “正在监视,” even though XStats is not receiving flows. Return true only when the configured data-provider identifier belongs to XStats and its socket-filter settings are valid.

Useful? React with 👍 / 👎.

Comment thread LICENSING.md
`ReleaseNotes.json` 中自行编写的更新摘要译文采用 AGPL-3.0-or-later。该 JSON 文件不支持注释,许可在本文件中记录。
`ReleaseNotes.json` 和 `NetworkComponentReleaseNotes.json` 中自行编写的更新摘要译文采用 AGPL-3.0-or-later。这些 JSON 文件不支持注释,许可在本文件中记录。

`App/XStats.entitlements`、`NetworkExtension/Info.plist` 与 `NetworkExtension/XStatsNetworkExtension.entitlements` 为 XcodeGen 生成的项目配置,重新生成时不保留注释文件头,采用 AGPL-3.0-or-later,许可在本文记录。网络连接模块为 XStats 自行编写;FreeSnitch 仅用于架构调研,未复制其源码或素材。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Record the component plist files in the license exception

The commit adds NetworkMonitorApp/Info.plist and NetworkMonitorApp/XStatsNetworkMonitor.entitlements without SPDX headers, but this exception records only the equivalent main-app and extension files. Since these component files are also regenerated by XcodeGen and therefore intentionally omit comment headers, include both in this AGPL declaration (or add preserved SPDX headers); otherwise the new repository-owned configuration files have no recorded license designation.

AGENTS.md reference: AGENTS.md:L37-L38

Useful? React with 👍 / 👎.

@ysicing
ysicing merged commit 1d75230 into main Oct 7, 2026
1 check passed
@ysicing
ysicing deleted the codex/feat-network-monitor branch October 7, 2026 03:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants