Skip to content

feat(limits): 新增火山方舟 Ark Coding Plan 额度监控 - #450

Merged
xiufengsun merged 13 commits into
xiufengsun:mainfrom
Hu9956:feat/ark-coding-plan-limits
Aug 17, 2026
Merged

feat(limits): 新增火山方舟 Ark Coding Plan 额度监控#450
xiufengsun merged 13 commits into
xiufengsun:mainfrom
Hu9956:feat/ark-coding-plan-limits

Conversation

@Hu9956

@Hu9956 Hu9956 commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

需求

火山方舟 Ark Coding Planhttps://console.volcengine.com/ark/region:cn-beijing/subscription/coding-plan)增加额度监控。

Coding Plan 是订阅制套餐(Lite/Pro),额度按请求次数在三个周期自动刷新:5小时(session)/ 周 / 月。目前这些数据只能在火山控制台网页查看。

设计要点

  1. 只做额度监控,不做 token 消耗统计:Coding Plan 兼容的 Claude Code / Codex / OpenCode 等工具,其 token 消耗已由 TokenTracker 从本地文件统计(与请求走哪个网关无关)。新增 source 只展示套餐配额百分比,不会重复计数

  2. 数据源 = 用户本机的 Ark CLI:通过 arkcli usage plan --format json 读取三个周期的已用百分比与重置时间(arkcli plans get 补充套餐档位)。arkcli 是火山方舟官方 CLI(npm install -g @volcengine/ark-cli),本机安装并登录即可用。

  3. Feature-detect + 降级:未安装 / 未登录 arkcli 时该 provider 静默跳过(configured: false),不影响其他 provider;命令失败时回退磁盘缓存,无缓存才报错。

  4. 连接引导:未配置时 Limits 面板显示安装/登录引导(仿 OpenCode Go 模式),含官方文档链接与一键复制命令。

改动

  • 新增 src/lib/ark-coding-plan-limits.js:自包含的 fetch/解析/缓存(仿 qoder-limits.js,避免与 usage-limits 循环依赖)
  • src/lib/usage-limits.js:接入聚合抓取(Promise.all + withProviderTimeout + 降级)
  • dashboard:provider 注册、三窗口 spec(5h / Weekly / Monthly)、Ark CLI 连接引导、火山引擎官方品牌图标(方舟无独立 sub-brand,沿用火山三山 mark)、zh/zh-TW 文案
  • 测试:后端 9 个用例(成功/未装/未订阅/缓存回退/失败)+ dashboard 渲染与引导用例

验证

  • 后端 node --test:9/9 新增 + usage-limits 套件 106/106
  • dashboard:490/490 测试 + typecheck 干净
  • validate:copy / validate:locale(zh、zh-TW 100% 覆盖)通过
  • 实机验收:Limits 面板显示 Ark Coding Plan Lite,5h ≈ 39% / Weekly ≈ 16.5% / Monthly ≈ 8.6%,与控制台一致

Summary by CodeRabbit

  • New Features

    • Added Ark Coding Plan quota tracking with 5-hour, weekly, and monthly usage windows.
    • Added plan tier, subscription status, reset times, and cached usage information.
    • Added copyable Ark CLI installation and authentication guidance.
    • Added Volcano Ark branding throughout the limits dashboard.
  • Localization

    • Added Simplified and Traditional Chinese translations for provider labels and setup guidance.
  • Reliability

    • Preserved quota visibility when CLI data is temporarily unavailable by using cached results.

Add Ark Coding Plan (火山方舟) quota monitoring to the Limits panel.
The subscription refreshes on three windows (5-hour session / weekly /
monthly); percentages are read from the user's own arkcli binary
('arkcli usage plan'), feature-detected so machines without it simply
skip the provider. No token-consumption source is added — consumption
for the compatible CLIs is already counted from their local files, so
this only surfaces the quota (no double counting).

- src/lib/ark-coding-plan-limits.js: self-contained fetch + parse +
  disk-cache fallback (mirrors qoder-limits.js; no circular dep)
- usage-limits.js: wire codingPlan into the aggregate fetch
- dashboard: provider registration, 3-window spec, Ark CLI setup hint
  (mirrors OpenCode Go), Volcano engine brand mark, zh/zh-TW copy
- tests: 9 backend cases + panel rendering / setup-hint coverage
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 681a7cd1-5e7f-4324-a294-3ffb94bfeaef

📥 Commits

Reviewing files that changed from the base of the PR and between 997b0e2 and 781bfb5.

📒 Files selected for processing (5)
  • src/lib/ark-coding-plan-limits.js
  • src/lib/usage-limits.js
  • test/ark-coding-plan-limits.test.js
  • test/fixtures/noisy-command.js
  • test/usage-limits.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/ark-coding-plan-limits.test.js

📝 Walkthrough

Walkthrough

Adds Ark Coding Plan as a usage-limit provider. The change retrieves quotas through arkcli, supports cache fallback, aggregates results, and displays quota windows, setup guidance, branding, localized text, and tests.

Changes

Ark Coding Plan provider

Layer / File(s) Summary
CLI quota retrieval and cache
src/lib/ark-coding-plan-limits.js, test/ark-coding-plan-limits.test.js, test/fixtures/noisy-command.js
Adds Ark CLI discovery, plan and quota normalization, cache persistence, fallback behavior, timeout handling, and comprehensive tests.
Usage aggregation
src/lib/usage-limits.js, test/usage-limits.test.js
Fetches Coding Plan limits with other providers and returns live or cached normalized data.
Dashboard provider contract and wiring
dashboard/src/hooks/use-usage-limits.ts, dashboard/src/lib/limits-providers.js, dashboard/src/ui/dashboard/components/usage-limits-provider-specs.js, dashboard/src/lib/pet-quips.js, dashboard/src/pages/LimitsPage.jsx, dashboard/src/ui/dashboard/components/ProviderIcon.jsx, TokenTrackerBar/TokenTrackerBar/Models/LimitsSettingsStore.swift, dashboard/src/hooks/*.test.*
Registers the provider, icon, labels, quota windows, usage data, page prop, native settings, summaries, and fixtures.
Dashboard rendering and setup guidance
dashboard/src/ui/dashboard/components/UsageLimitsPanel.jsx, dashboard/src/ui/dashboard/components/UsageLimitsPanel.test.jsx, dashboard/src/content/i18n/zh/core.json, dashboard/src/content/i18n/zh-TW/core.json, scripts/ops/ui-hardcode-baseline.json
Displays Coding Plan quota states and cached status. Adds Ark CLI installation, authentication, documentation, refresh, and copy guidance in Chinese and Traditional Chinese.

Estimated code review effort: 4 (Complex) | ~45 minutes

Mergeability Score: 🟡 Moderate · up to 781bf

This change adds Ark CLI-based quota monitoring and cache-backed reporting, but it is not yet merge-ready because unbounded CLI output could cause memory pressure and timeout handling may hide otherwise valid quota data.

Sequence Diagram(s)

sequenceDiagram
  participant Dashboard as LimitsPage
  participant Usage as usage-limits.js
  participant Fetcher as fetchArkCodingPlanLimits
  participant ArkCLI as arkcli
  participant Panel as UsageLimitsPanel
  Dashboard->>Usage: request usage limits
  Usage->>Fetcher: fetch Coding Plan quotas
  Fetcher->>ArkCLI: query plan and usage
  ArkCLI-->>Fetcher: return quota windows and plan tier
  Fetcher-->>Usage: return normalized or cached limits
  Usage-->>Dashboard: return codingPlan usage data
  Dashboard->>Panel: render codingPlan
Loading

Possibly related PRs

Suggested reviewers: xiufengsun

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 48.15% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题清晰概括了本次新增火山方舟 Ark Coding Plan 额度监控的主要变更,与代码和 PR 目标一致。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 4

🧹 Nitpick comments (3)
src/lib/usage-limits.js (1)

3229-3236: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Pass nowMs for consistency and testability.

fetchArkCodingPlanLimits accepts nowMs and defaults it to Date.now(). The neighbouring providers pass the shared clock value. Pass it here as well, so the cache timestamps use one clock per fetch cycle and tests can control time.

♻️ Proposed change
     withProviderTimeout(
       fetchArkCodingPlanLimits({
         commandRunner,
         home,
+        nowMs,
       }),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/usage-limits.js` around lines 3229 - 3236, Update the
fetchArkCodingPlanLimits call within the provider timeout flow to pass the
shared nowMs value alongside commandRunner and home. Preserve the existing
timeout and error handling while ensuring cache timestamps use the same clock as
neighbouring providers and remain controllable in tests.
test/ark-coding-plan-limits.test.js (1)

139-163: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for cache expiry.

No test passes nowMs, so the age-bound branches of readArkCodingPlanLimitsCache are never exercised. Add a test that writes the cache, then calls fetchArkCodingPlanLimits with a failing runner and an nowMs far past every reset_at. The test must assert that the stale snapshot is not served. This pairs with the cache-bound issue raised on src/lib/ark-coding-plan-limits.js Lines 124-148.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/ark-coding-plan-limits.test.js` around lines 139 - 163, Add a
cache-expiry test alongside the existing fetchArkCodingPlanLimits tests: write a
valid cache with a successful run, then use a failing commandRunner and pass
fetchArkCodingPlanLimits an nowMs well beyond every reset_at. Assert the expired
stale snapshot is not returned, while preserving the existing error behavior
when no usable data remains.
src/lib/ark-coding-plan-limits.js (1)

262-282: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Do not let the optional plans get call starve the quota call.

The three CLI calls run in sequence: which (2 s cap), plans get (10 s cap), then usage plan (10 s cap). The worst case is about 22 s. usage-limits.js wraps the whole call in withProviderTimeout(providerTimeoutMs). If providerTimeoutMs is lower than the serial worst case, a slow plans get consumes the budget and the provider reports an error, even though usage plan alone would have succeeded.

plans get only supplies a fallback plan label. Run both commands concurrently so the optional call cannot delay the essential one.

♻️ Proposed refactor to run both commands concurrently
-  let tier = null;
-  const plansResult = await runCommand(
-    commandRunner,
-    "arkcli",
-    ["plans", "get", "--format", "json"],
-    { timeout: ARK_USAGE_PLAN_TIMEOUT_MS },
-  );
+  const [plansResult, result] = await Promise.all([
+    runCommand(
+      commandRunner,
+      "arkcli",
+      ["plans", "get", "--format", "json"],
+      { timeout: ARK_USAGE_PLAN_TIMEOUT_MS },
+    ),
+    runCommand(
+      commandRunner,
+      "arkcli",
+      ["usage", "plan", "--format", "json"],
+      { timeout: ARK_USAGE_PLAN_TIMEOUT_MS },
+    ),
+  ]);
+
+  let tier = null;
   if (!plansResult?.error && plansResult?.status === 0) {
     try {
       tier = normalizeArkPlansResponse(JSON.parse(String(plansResult.stdout || "")));
     } catch (_error) {}
   }
-
-  const result = await runCommand(
-    commandRunner,
-    "arkcli",
-    ["usage", "plan", "--format", "json"],
-    { timeout: ARK_USAGE_PLAN_TIMEOUT_MS },
-  );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/ark-coding-plan-limits.js` around lines 262 - 282, Update the
quota-fetching flow around the plansResult and usage plan runCommand calls so
the optional “plans get” and essential “usage plan” commands execute
concurrently rather than sequentially. Preserve the existing non-fatal tier
parsing and ensure the usage-plan result remains available for quota processing
without waiting for the optional plan-label lookup beyond concurrent completion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@dashboard/src/lib/pet-quips.js`:
- Line 310: Localize the new Coding Plan labels in
dashboard/src/lib/pet-quips.js at lines 310 and 397: resolve the provider name
and all three period labels through the dashboard copy system, adding the
required entries to dashboard/src/content/copy.csv instead of hardcoding
user-facing text.

In `@src/lib/ark-coding-plan-limits.js`:
- Around line 124-148: Update readArkCodingPlanLimitsCache in
src/lib/ark-coding-plan-limits.js (lines 124-148) to retain only windows whose
reset_at is after nowMs, returning null when none survive; preserve the existing
cache validation and response shape for surviving windows. Add coverage in
test/ark-coding-plan-limits.test.js (lines 139-163) that writes a cache, invokes
fetchArkCodingPlanLimits with a failing runner and nowMs after every reset_at,
and asserts the stale snapshot is not returned.
- Around line 220-229: Update whichBinary and isBinaryAvailable to accept and
propagate a platform option through the arkcli lookup chain, including the
caller in fetchArkCodingPlanLimits. Select “where” when platform is “win32” and
“which” otherwise, and normalize command output so CRLF-separated Windows
results are parsed correctly.

In `@test/ark-coding-plan-limits.test.js`:
- Around line 15-20: Replace the real-looking user_id and profile values in the
USAGE_JSON viewer fixture with clearly synthetic placeholders. Keep the viewer
structure and auth_method unchanged, since normalizeArkCodingPlanResponse does
not depend on these fields.

---

Nitpick comments:
In `@src/lib/ark-coding-plan-limits.js`:
- Around line 262-282: Update the quota-fetching flow around the plansResult and
usage plan runCommand calls so the optional “plans get” and essential “usage
plan” commands execute concurrently rather than sequentially. Preserve the
existing non-fatal tier parsing and ensure the usage-plan result remains
available for quota processing without waiting for the optional plan-label
lookup beyond concurrent completion.

In `@src/lib/usage-limits.js`:
- Around line 3229-3236: Update the fetchArkCodingPlanLimits call within the
provider timeout flow to pass the shared nowMs value alongside commandRunner and
home. Preserve the existing timeout and error handling while ensuring cache
timestamps use the same clock as neighbouring providers and remain controllable
in tests.

In `@test/ark-coding-plan-limits.test.js`:
- Around line 139-163: Add a cache-expiry test alongside the existing
fetchArkCodingPlanLimits tests: write a valid cache with a successful run, then
use a failing commandRunner and pass fetchArkCodingPlanLimits an nowMs well
beyond every reset_at. Assert the expired stale snapshot is not returned, while
preserving the existing error behavior when no usable data remains.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a0b42198-3bfb-41bd-88c9-9ab7116cb3ef

📥 Commits

Reviewing files that changed from the base of the PR and between 2300e14 and d77dafd.

⛔ Files ignored due to path filters (2)
  • dashboard/public/brand-logos/volcano-ark.svg is excluded by !**/*.svg
  • dashboard/src/content/copy.csv is excluded by !**/*.csv
📒 Files selected for processing (15)
  • dashboard/src/content/i18n/zh-TW/core.json
  • dashboard/src/content/i18n/zh/core.json
  • dashboard/src/hooks/use-limits-display-prefs.test.js
  • dashboard/src/hooks/use-usage-limits.test.tsx
  • dashboard/src/hooks/use-usage-limits.ts
  • dashboard/src/lib/limits-providers.js
  • dashboard/src/lib/pet-quips.js
  • dashboard/src/pages/LimitsPage.jsx
  • dashboard/src/ui/dashboard/components/ProviderIcon.jsx
  • dashboard/src/ui/dashboard/components/UsageLimitsPanel.jsx
  • dashboard/src/ui/dashboard/components/UsageLimitsPanel.test.jsx
  • dashboard/src/ui/dashboard/components/usage-limits-provider-specs.js
  • src/lib/ark-coding-plan-limits.js
  • src/lib/usage-limits.js
  • test/ark-coding-plan-limits.test.js

zcode: "ZCode",
opencodeGo: "OpenCode Go",
qoder: "Qoder",
codingPlan: "Ark Coding Plan",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Localize the new pet-summary labels.

The new strings bypass the dashboard copy system. This causes untranslated provider and period names in pet summaries.

  • dashboard/src/lib/pet-quips.js#L310-L310: Resolve the Coding Plan provider name from localized copy.
  • dashboard/src/lib/pet-quips.js#L397-L397: Resolve the three Coding Plan period labels from localized copy.

As per coding guidelines: “Never hardcode user-facing text; add it to dashboard/src/content/copy.csv.” As per path instructions: “User-facing strings must come from dashboard/src/content/copy.csv.”

📍 Affects 1 file
  • dashboard/src/lib/pet-quips.js#L310-L310 (this comment)
  • dashboard/src/lib/pet-quips.js#L397-L397
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dashboard/src/lib/pet-quips.js` at line 310, Localize the new Coding Plan
labels in dashboard/src/lib/pet-quips.js at lines 310 and 397: resolve the
provider name and all three period labels through the dashboard copy system,
adding the required entries to dashboard/src/content/copy.csv instead of
hardcoding user-facing text.

Sources: Coding guidelines, Path instructions

Comment thread src/lib/ark-coding-plan-limits.js Outdated
Comment thread src/lib/ark-coding-plan-limits.js Outdated
Comment thread test/ark-coding-plan-limits.test.js
…providers

The macOS menu-bar app keeps its own canonical provider-id list; the
new codingPlan provider was added to the dashboard list but not to the
native store, breaking the parity guardrail. Add it to allProviders
(and a display name) and refresh the ui-hardcode baseline for the new
panel test assertions.
@github-actions github-actions Bot added the macos label Aug 11, 2026

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/ops/ui-hardcode-baseline.json`:
- Around line 416-421: Update the baseline generator that produces rawTextTokens
so it records only token counts, never source text fragments or
prompt/message/conversation content. Preserve count fields such as rawText,
remove raw token values from the generated structure, and regenerate
ui-hardcode-baseline.json using the corrected generator.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9727d16c-d12d-444a-9034-8c25a19b8af1

📥 Commits

Reviewing files that changed from the base of the PR and between d77dafd and c968632.

📒 Files selected for processing (2)
  • TokenTrackerBar/TokenTrackerBar/Models/LimitsSettingsStore.swift
  • scripts/ops/ui-hardcode-baseline.json

Comment on lines +416 to 421
"rawText": 4,
"rawTextTokens": [
", ); // Brand + tier, without repeating \"Coding Plan\" (title: \"Ark Coding Plan Lite\"). expect(screen.getByText(\"Ark Coding Plan Lite\")).toBeInTheDocument(); expect(screen.getByText(\"5h\")).toBeInTheDocument(); expect(screen.getByText(\"Weekly\")).toBeInTheDocument(); expect(screen.getByText(\"Monthly\")).toBeInTheDocument(); expect(screen.getByText(\"33%\")).toBeInTheDocument(); expect(screen.getByText(\"16%\")).toBeInTheDocument(); expect(screen.getByText(\"9%\")).toBeInTheDocument(); // Not-configured fallback shows the Ark CLI setup guide. rerender(",
", ); // Brand name only — no plan_label suffix to avoid \"OpenCode Go Go\". expect(screen.getByText(\"OpenCode Go\")).toBeInTheDocument(); expect(screen.getByText(\"5h\")).toBeInTheDocument(); expect(screen.getByText(\"Weekly\")).toBeInTheDocument(); expect(screen.getByText(\"Monthly\")).toBeInTheDocument(); expect(screen.getByText(\"12%\")).toBeInTheDocument(); expect(screen.getByText(\"30%\")).toBeInTheDocument(); expect(screen.getByText(\"60%\")).toBeInTheDocument(); // Not-configured fallback. rerender(",
", ); expect(screen.getByText(\"Codex\")).toBeInTheDocument(); const row = screen.getByText(\"Credits\").closest(\"div\"); expect(within(row).getByText(\"",
", ); expect(screen.getByText(\"Cursor\")).toBeInTheDocument(); rerender("

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Persist counts, not raw text tokens.

Lines 416-421 store source text fragments in rawTextTokens. Update the baseline generator and regenerate this file so entries retain counts such as rawText without retaining raw token values.

As per coding guidelines, **/*: Store token counts only; never collect prompts, messages, or conversation bodies.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/ops/ui-hardcode-baseline.json` around lines 416 - 421, Update the
baseline generator that produces rawTextTokens so it records only token counts,
never source text fragments or prompt/message/conversation content. Preserve
count fields such as rawText, remove raw token values from the generated
structure, and regenerate ui-hardcode-baseline.json using the corrected
generator.

Source: Coding guidelines

@xiufengsun xiufengsun left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

NO-MERGE — reviewed exact head c968632621538d76892209595ef11d0d5eff7e36 against current origin/main.

There is a cross-platform functional blocker in src/lib/ark-coding-plan-limits.js: whichBinary() unconditionally spawns the Unix which executable. Native Windows uses where.exe; when which is absent, the spawn returns ENOENT, isBinaryAvailable() returns false, and TokenTracker reports { configured: false } even when arkcli is correctly installed and logged in. This makes Ark Coding Plan unavailable in the supported Windows desktop app.

Please make binary discovery platform-aware (or use an existing cross-platform resolver), and add a regression test that exercises native Windows discovery rather than mocking which as universally available. The current-main integration itself is clean, and the targeted Ark/usage-limits tests pass once this blocker is fixed.

Address review feedback on xiufengsun#450:

- Binary discovery used the Unix 'which' unconditionally; native Windows
  ships 'where.exe' instead, so spawn('which') returned ENOENT and the
  provider always reported configured:false even with arkcli installed
  and signed in. Probe with 'where' on win32 (regression test added).
- The disk cache was served while any window had a future reset_at, even
  if a different window had already rolled over; drop windows whose
  reset_at has passed and refuse the snapshot when none survive
  (regression test added).
- Pass nowMs through to fetchArkCodingPlanLimits for a single clock per
  fetch cycle (consistency with neighbouring providers).
@Hu9956

Hu9956 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

已修复,新增 commit 70dd9931

1. 跨平台二进制发现(你提的 blocker)✅

whichBinary() 现在按平台选择探测命令:win32wherewhere.exe),其余平台用 which。Windows 上不再因 spawn which 返回 ENOENT 而误报 configured: false

新增回归测试 fetchArkCodingPlanLimits discovers arkcli via where.exe on Windows:断言 Windows 路径下只调用 where、绝不调用 which

2. 顺带修的(CodeRabbit 意见)

  • 缓存过期(CodeRabbit Major):readArkCodingPlanLimitsCache 之前只在所有窗口都无 reset_at 时才做 TTL 检查,导致 reset_at 已过的窗口仍被当作新鲜数据返回。现在会丢弃 reset_at <= now 的窗口,无窗口存活则拒绝返回快照。新增回归测试覆盖。
  • nowMs 一致性(CodeRabbit nitpick):fetchArkCodingPlanLimits 现在从调用方透传 nowMs,与周边 provider 共用同一个时钟。

3. 未改的两条 CodeRabbit 意见(说明理由)

  • ui-hardcode-baseline.jsonrawTextTokens:这是项目既有的 hardcode 基线机制(记录允许的代码片段 token,用于后续 diff 检测),UsageLimitsPanel.jsx 等历史条目同样如此,非本 PR 引入;修改生成器会影响全局基线,超出本 PR 范围。如需单独治理可以另开 PR。
  • pet-quips.js 的 provider 名/窗口 label 硬编码:该文件现有全部 provider(cursor/gemini/opencodeGo 等)都是同样的硬编码模式,非本 PR 新引入的写法;统一走 copy 系统属于全局重构,同样建议另开 PR 处理。

验证

  • test/ark-coding-plan-limits.test.js:11/11 通过(新增 Windows 发现 + 缓存过期 2 个)
  • usage-limits.test.js + qoder-limits.test.js:127/128(1 个既有 skip),0 失败

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/lib/usage-limits.js (1)

3225-3237: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Propagate cancellation to the Ark CLI command.

The 15-second provider timeout can expire while the sequential Ark commands are still running. Each command has its own 10-second timeout, which only sends SIGTERM; it does not kill the process group or use a SIGKILL fallback. Propagate cancellation through fetchArkCodingPlanLimits, or enforce a single provider deadline with guaranteed process termination.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/usage-limits.js` around lines 3225 - 3237, Update
fetchArkCodingPlanLimits and its commandRunner calls to accept and propagate
cancellation from withProviderTimeout, ensuring commands stop when the 15-second
provider deadline expires. Strengthen command termination so cancellation kills
the entire process group and uses a SIGKILL fallback when SIGTERM does not exit
promptly, while preserving the existing quota result and error handling.

Source: Linters/SAST tools

🧹 Nitpick comments (1)
test/ark-coding-plan-limits.test.js (1)

217-241: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Add a mixed-expiry cache test.

This test expires all three windows. It covers only the no-valid-window path. Add a case with one expired window and one future reset_at, then assert that only valid windows are returned. The PR objective requires expired windows to be discarded individually.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/ark-coding-plan-limits.test.js` around lines 217 - 241, Add a
mixed-expiry test alongside fetchArkCodingPlanLimits, with at least one window
reset before nowMs and another reset in the future. Mock an unsuccessful
refresh, then assert the result preserves only the future-reset window and
discards the expired window individually, including any expected stale/source
metadata.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/ark-coding-plan-limits.test.js`:
- Around line 81-85: Update mockRunner to record the command arguments as well
as command names, then assert that the Windows where probe receives the target
arkcli. Apply the same argument capture and assertion to the additional
mockRunner usage identified in the discovery tests, while preserving the
existing success and failure behavior.

---

Outside diff comments:
In `@src/lib/usage-limits.js`:
- Around line 3225-3237: Update fetchArkCodingPlanLimits and its commandRunner
calls to accept and propagate cancellation from withProviderTimeout, ensuring
commands stop when the 15-second provider deadline expires. Strengthen command
termination so cancellation kills the entire process group and uses a SIGKILL
fallback when SIGTERM does not exit promptly, while preserving the existing
quota result and error handling.

---

Nitpick comments:
In `@test/ark-coding-plan-limits.test.js`:
- Around line 217-241: Add a mixed-expiry test alongside
fetchArkCodingPlanLimits, with at least one window reset before nowMs and
another reset in the future. Mock an unsuccessful refresh, then assert the
result preserves only the future-reset window and discards the expired window
individually, including any expected stale/source metadata.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 37111f58-3836-44ac-ad8b-8127ee375f63

📥 Commits

Reviewing files that changed from the base of the PR and between c968632 and 70dd993.

📒 Files selected for processing (3)
  • src/lib/ark-coding-plan-limits.js
  • src/lib/usage-limits.js
  • test/ark-coding-plan-limits.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/ark-coding-plan-limits.js

Comment thread test/ark-coding-plan-limits.test.js

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/lib/ark-coding-plan-limits.js (1)

183-185: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Enforce the output limit for spawned commands.

child_process.spawn() does not enforce maxBuffer for these piped streams. Lines 267-268 append all output without a byte limit, which can exhaust memory if arkcli is verbose. Track combined stdout and stderr bytes, terminate the child when the limit is exceeded, and return an error so the cache fallback runs. Add a regression test with output larger than maxBuffer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/ark-coding-plan-limits.js` around lines 183 - 185, Update the
spawned-command handling around the stdout/stderr accumulation to enforce
maxBuffer manually, tracking combined UTF-8 output bytes from both streams.
Terminate the child and propagate an error when the limit is exceeded so the
cache fallback executes, and add a regression test covering output larger than
maxBuffer.
src/lib/usage-limits.js (1)

3247-3249: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Keep the disk-cache fallback on provider timeout.

When withProviderTimeout reaches its deadline, it rejects before fetchArkCodingPlanLimits can return its internal failWithCache result. This catch returns only { configured: true, error }, so a slow or aborted Ark CLI request hides a valid cached snapshot. Read the Ark cache in this outer timeout path before returning the error.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/usage-limits.js` around lines 3247 - 3249, Update the outer timeout
catch around fetchArkCodingPlanLimits to read and return the Ark disk-cache
snapshot before falling back to the timeout error object. Preserve the existing
configured/error response when no cached snapshot is available, and reuse the
existing Ark cache access and result shape.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/usage-limits.js`:
- Around line 3241-3246: Update the fetchArkCodingPlanLimits call within
fetchUsageLimitsUncached to pass through the existing platform value alongside
commandRunner, home, nowMs, and signal, so Ark uses the requested platform
rather than process.platform.

---

Outside diff comments:
In `@src/lib/ark-coding-plan-limits.js`:
- Around line 183-185: Update the spawned-command handling around the
stdout/stderr accumulation to enforce maxBuffer manually, tracking combined
UTF-8 output bytes from both streams. Terminate the child and propagate an error
when the limit is exceeded so the cache fallback executes, and add a regression
test covering output larger than maxBuffer.

In `@src/lib/usage-limits.js`:
- Around line 3247-3249: Update the outer timeout catch around
fetchArkCodingPlanLimits to read and return the Ark disk-cache snapshot before
falling back to the timeout error object. Preserve the existing configured/error
response when no cached snapshot is available, and reuse the existing Ark cache
access and result shape.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 32e2063d-0cf6-4f30-ae21-677bf7155789

📥 Commits

Reviewing files that changed from the base of the PR and between 70dd993 and 997b0e2.

📒 Files selected for processing (3)
  • src/lib/ark-coding-plan-limits.js
  • src/lib/usage-limits.js
  • test/ark-coding-plan-limits.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/ark-coding-plan-limits.test.js

Comment thread src/lib/usage-limits.js
@Hu9956

Hu9956 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

先不合并,正在修复新发现的问题

@Hu9956

Hu9956 commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

已修复

@xiufengsun xiufengsun left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

整体设计是这批 provider PR 里最扎实的:错误隔离与 qoderCn 写法一致、abort 是把 signal 传进 spawn 生命周期的真取消、maxBuffer 补了 cp.spawn 的真实缺口、缓存的时钟回拨守卫和跨 profile 拒绝都很细,macOS 侧 decoding/view/reset detector/settings store 覆盖也比之前的 provider 完整。「只做额度监控不重复计数」经核实成立。四个问题修完即可合:

  1. ServerManager.swift 的 PATH 改动越界且未达目的。 这是全 PR 唯一改变所有 macOS 用户内嵌 server 行为的代码:standardPaths 被前置到继承 PATH 之前,从终端启动 .app 的用户,asdf/nvm shim 会被 Homebrew 路径抢先,内嵌 server 按名解析的 git(项目归因)等命令解析结果被静默改变。而它想解决的问题也没解决——arkcli 是 npm install -g 安装,落点在 npm global prefix,nvm/fnm/volta 用户照样发现不了。改为追加 + 探测 npm global prefix,或者把 PATH 发现从本 PR 拆出去单独做完整。

  2. ~140 行 runCommand/whichBinary/isBinaryAvailable 从 usage-limits.js 复制到了 ark-coding-plan-limits.js。 副本里的三处改进(abort signal、win32 用 where、maxBuffer 上限)原版都没有,等于修复只活在副本里,两份实现从合并第一天就分叉。避免循环依赖的理由成立,但正解是抽成 src/lib/command-runner.js 两边共用。

  3. Windows 上 shell:true + 裸名 arkcli 有 CWD 劫持面。 cmd.exe 先搜当前目录再搜 PATH,server 进程 cwd 下的 arkcli.bat 会被优先执行。whichBinary() 已经拿到了绝对路径却只留了个布尔值——把绝对路径传下去用于后续 spawn 即可。

  4. 没装 arkcli 的用户每轮轮询白付一次 spawn。 任一 provider 临近 reset 时整轮 fetch 会退到 5 秒间隔(CACHE_MIN_TTL_MS),此时无条件 spawn which 的成本被放大。参考 Gemini 的写法:先做便宜的文件检查(arkcli 配置/凭据目录是否存在),文件不在就不 spawn。plans get 和 profile show 也建议按需化——前者只在 usage 响应缺 tier 时才需要,后者只服务缓存守卫,适合长缓存。

两个需要你实机确认的外部事实(代码无法自证):(a) period.percent 是「已用」还是「剩余」,请贴一下与控制台数值的比对;(b) arkcli profile show --format json 是否真实存在——若不存在,跨账号缓存守卫会因 profileIdentity 恒为 null 永远静默失效。

另外:test/ark-coding-plan-limits.test.js 里 usageJsonFor() 硬编码的 user_id "2126262990" 看起来是真实火山账号,换成合成值(同文件 PROFILE_JSON 用的 test-user-001 就很好);macOS 三处(popover/设置列表/重置通知)目前无 Ark 图标,dashboard 已有 volcano-ark.svg,请补 asset catalog 条目。

修完 ping 我,这个功能我是想要的。

…n-limits

# Conflicts:
#	TokenTrackerBar/TokenTrackerBar/Models/UsageLimits.swift
#	TokenTrackerBar/TokenTrackerBar/Views/UsageLimitsView.swift
Review follow-up for the Ark Coding Plan provider:

- Extract src/lib/command-runner.js so ark-coding-plan-limits.js and
  usage-limits.js no longer fork ~140 lines of runCommand/whichBinary.
  The three hardening fixes (abort signal wired into the spawn
  lifecycle, where.exe discovery on native Windows, byte-capped
  maxBuffer for piped output) now live once and apply to both.
- Spawn the resolved absolute arkcli path, never the bare name, so
  cmd.exe's current-directory-first search cannot hijack the spawn.
- Skip every spawn when ~/.arkcli is absent (spawn-free install
  evidence, mirroring Gemini's credential check); run `plans get`
  only when the usage payload lacks a tier, and `profile show` only
  on the cache-guard path.
- Revert ServerManager.swift to the fully inherited environment —
  the provider now covers minimal-PATH launches itself through a
  spawn-free statSync probe of the common global bin directories.
- Tests: exercise native Windows where.exe discovery, keep the
  runner isolated from the real filesystem via globalBinDirs, and
  replace the real account id in fixtures/comments with a synthetic
  one.
All three macOS surfaces (limits popover, settings provider list,
reset toast catalog) now render the codingPlan provider from the
dashboard's bundled volcano-ark.svg — the same EmbeddedServer
brand-logos path already used by Kimi/Kiro/Zcode/Qoder, so no asset
catalog entry is needed.
@Hu9956

Hu9956 commented Aug 15, 2026

Copy link
Copy Markdown
Contributor Author

四个问题已全部处理完毕(50dd52ef + 3b6dbf34),两个外部事实也已实机验证:

1. ServerManager.swift — 已完整回退为纯继承环境。Finder 冷启动发现不到全局 bin 的问题改由 provider 自己兜底:resolveBinaryPathwhich 失败后用 statSync 免 spawn 探测常见全局目录(/opt/homebrew/bin/usr/local/bin~/.npm-global/bin~/.volta/bin、Windows %APPDATA%\npm),拿到绝对路径后再 spawn。零 PATH 改动、零 spawn 开销,也不影响其他命令的解析。

2. 命令执行副本 — 已抽取 src/lib/command-runner.jsrunCommand/whichBinary/isBinaryAvailable/resolveBinaryPath),ark 与 usage-limits 共用同一实现。三处改进(abort signal 接入 spawn 生命周期、win32 用 where.exe、piped 输出的 maxBuffer 字节上限)随之带回了 usage-limits 原版。

3. CWD 劫持fetchArkCodingPlanLimits 现在全程 spawn resolveBinaryPath 解析出的绝对路径,不再用裸名。

4. 免 spawn 预检 + 按需化 — 无 ~/.arkcli 目录时直接返回 configured:false(零 spawn,有测试断言 calls.length === 0);happy path 只跑 usage planplans get 仅在 usage 响应缺 tier 时执行;profile show 仅在缓存守卫路径执行。

外部事实 (a) percent = 已用:2026-08-15 实机比对,CLI weekly: 68.08% / monthly: 34.38%,与控制台开通管理页显示的周/月"已使用"百分比一致(周 ≈68%)。佐证:session 窗口刚滚动时 CLI 报 0,若语义为"剩余"则意味着 5h 窗口耗尽、正在被限流,与实际不符;官方 ArkClaw 文档对同源指标的描述也是"套餐用量百分比"。

外部事实 (b) profile show --format json 存在,实机输出(打码):

{
  "name": "coding-plan_cn-beijing_personal",
  "display_name": "Coding Plan Lite",
  "owner_trn": "trn:iam::***:root",
  "identity_key": "volc-***",
  "expires_at": "2026-09-09T23:59:59+08:00"
}

owner_trn / identity_key 两条提取路径均可命中,且与 usage plan viewer 的 user_id 比较相等,跨账号缓存守卫闭环成立。

小项:test fixture 与注释中的真实账号 ID 已换为合成值(test-user-001);macOS 三处(popover / 设置列表 / 重置通知 toast)图标已补,均从 dashboard 既有的 volcano-ark.svg 经 EmbeddedServer brand-logos 路径渲染——与 Kimi/Kiro/Zcode/Qoder 同一模式,无需新建 asset catalog 条目。

相关测试全绿(ark 24/24、usage-limits 109/109),validate 全套 + dashboard build 通过;本机无 xcodegen,macOS xcodebuild 交 CI。修完了,麻烦再看下。

@Hu9956

Hu9956 commented Aug 15, 2026

Copy link
Copy Markdown
Contributor Author

@xiufengsun 四个评审问题与两个外部事实验证都已处理完毕,详见上一条评论(含实机比对证据与打码后的 profile show 输出)。CI 已全绿(macOS unit tests / Windows build / test+validate+build 等均通过),麻烦有空复审。

@xiufengsun xiufengsun left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

复审完成。四项都是真修,而且质量高于要求:command-runner 的抽取不是挪代码,是真正的超集合并(保留了 Kiro 在用的 completeWhen 钩子,win32 where 修复也回流给了 Gemini 探测);resolveBinaryPath 的免 spawn 目录探测比原 PATH 方案作用域更干净还覆盖了 volta/npm-global;CWD 劫持消除有专门测试;~/.arkcli 前置门的零 spawn 断言正好锁在成本点上。两个外部事实的证据也都成立——session 窗口刚滚动时报 0 是「percent=已用」的证伪性观测,方向无法两解;profile show 缺 user_id 字段你主动补的 owner_trn/identity_key 回退避免了缓存守卫全拒,这个坑发现得好。

但抽取过程引入了一个新的阻塞回归:

command-runner.js 的 shell: platform === "win32" 无条件套用到了 usage-limits 的所有既有调用方,其中 detectAntigravityProcess 在 Windows 上会直接失效。 main 上的原实现从来没有 shell 选项;现在 detectAntigravityProcess 传给 powershell.exe 的 -Command 脚本里含字面量 |,Node 在 shell:true 下对 args 不转义只拼接(DEP0190),cmd.exe 会把命令从管道符处劈开,Get-CimInstance 之后的部分变成不存在的 cmd 命令。CI 全绿是因为所有测试都注入 commandRunner stub,真 spawn 分支从未被执行;生产的 local-api 调 getUsageLimits 不传 commandRunner,Windows 托盘上必中。修法:加一个 useShell 选项默认 false,只有 ark 的调用点开启(它需要 shell 是因为 npm 全局装的是 .cmd shim)——ark 的参数全是常量无元字符,开着安全;whichBinary 的 where 探测也不需要 shell。

两个非阻塞但建议顺手处理:

  1. arkcli 挂死时磁盘缓存读不到了。 profile show 挪进 failWithCache 后是串行执行:which 2s + usage plan 10s + 1s 宽限 + profile show 10s + 1s ≈ 24s,超过外层 15s 的 provider race——挂死场景下 .catch 直接报错,缓存来不及端出去,而「挂了还能看上次数据」正是缓存存在的意义。给 failWithCache 里的 profile show 一个 2-3s 的短超时即可。
  2. ~/.arkcli 是硬前置门,如果 arkcli 在 Linux/Windows 用别的配置目录(~/.config/arkcli、%APPDATA%\arkcli),那些平台装了也永远 configured:false 且无任何可排查的提示。建议多接受几个候选路径,或让 resolveBinaryPath 命中也算 evidence。

fixture 和图标已确认无需再动。就差 shell 这一处收敛,改完即合。

Review follow-up: the shared command runner had applied
shell-on-Windows unconditionally, which broke detectAntigravityProcess
— its powershell -Command script contains a literal `|` that cmd.exe
would split at under shell execution.

- runCommand now takes an explicit `useShell` flag, default false.
  Only the ark call sites opt in (npm's Windows .cmd shims need a
  shell and every ark argument is a metacharacter-free constant);
  `which`/`where` probes and every pre-existing usage-limits spawn
  stay direct. A regression test pins useShell=false on the
  powershell enumeration call.
- `profile show` on the cache-guard path now runs with a 2.5s timeout
  instead of the full 10s: it executes after `usage plan` already
  failed, and a hung arkcli there would push the total past the outer
  15s provider race, starving the disk-cache read the fallback exists
  to serve.
- Install evidence now also accepts ~/.config/arkcli and
  %APPDATA%\arkcli, and a spawn-free statSync hit in a global bin
  directory counts as evidence on its own — machines without the CLI
  still pay zero spawns per poll (asserted).
@Hu9956

Hu9956 commented Aug 16, 2026

Copy link
Copy Markdown
Contributor Author

@xiufengsun 三处都已处理(751203f5):

shell 收敛(阻塞项)runCommand 改为显式 useShell 选项,默认 false——所有既有调用方恢复直接 spawn,detectAntigravityProcess 的 powershell 脚本不再被 cmd.exe 从 | 劈开。只有 ark 的三个调用点显式开启(win32 下 npm 的 .cmd shim 必须 shell 执行,参数全为无元字符常量);whichBinary 的 where 探测保持直接 spawn。加了回归测试把 powershell 枚举调用的 useShell === false 锁死。

profile show 短超时:cache-guard 路径上的 profile show 从 10s 收紧到 2.5s(usage plan 保持 10s)。挂死场景总预算约 which 2s + usage 10s + 1s 宽限 + profile 2.5s + 1s ≈ 16.5s 的最坏串行段不再出现,15s race 内缓存能正常端出。测试断言了两处 timeout 值。

evidence 放宽:候选目录扩为 ~/.arkcli~/.config/arkcli、win32 %APPDATA%\arkcli;无目录 evidence 时先做免 spawn 的 statSync 全局 bin 探测(statBinaryInDirs,从 resolveBinaryPath 抽出共用),命中即视为已安装并直接使用该路径。未装机器仍是零 spawn(测试断言 calls.length === 0 保持)。

相关测试全绿(ark 27/27、usage-limits 109/109),validate 全套 + architecture-guardrails 通过。

…nm discovery

Post-review hardening round:

- whichBinary: `where` on Windows emits CRLF and may list several
  matches; the first line previously kept its trailing `\r`, which
  produced a polluted path that fails at spawn. Split on /\r?\n/ and
  trim each line, with a multi-match CRLF test.
- Unsubscribe now drops the disk cache. The authoritative live
  response saying `subscribed: false` used to leave the cache from
  the subscribed era in place, so a later transient CLI failure would
  resurrect the retired plan's numbers through failWithCache. The
  not-subscribed branch unlinks the cache; a test walks the full
  sequence (subscribed cache -> unsubscribe -> CLI hang -> no
  resurrection).
- commonGlobalBinDirectories now expands nvm (~/.nvm/versions/node/
  <ver>/bin) and fnm (node-versions/<ver>/installation/bin, incl. the
  macOS Application Support root), newest version first, so an
  npm-global arkcli stays discoverable under a Finder-launched minimal
  PATH.
- Documented the deliberate fail-open of the cross-profile cache guard
  (guard applies only when profile show could establish identity).
@Hu9956

Hu9956 commented Aug 16, 2026

Copy link
Copy Markdown
Contributor Author

@xiufengsun 又补了一轮加固(0771d157):

where CRLF 修复:多结果时首行残留的 \r 会污染路径导致 spawn 失败。whichBinary 改为 split(/\r?\n/) + 逐行 trim 取首个非空行,补了 CRLF 双结果的测试。

退订清缓存:权威 live 响应说 subscribed: false 时现在会 unlink 磁盘缓存,堵住"已退订 → 消失 → CLI 临时失败 → 旧套餐复活"的状态机漏洞。测试覆盖完整序列(订阅期缓存 → 退订 → CLI 挂 → 不复活)。

nvm/fnm 发现commonGlobalBinDirectories 现在展开 ~/.nvm/versions/node/<ver>/bin 和 fnm 的 node-versions/<ver>/installation/bin(含 macOS 的 Application Support 根),新版本优先——Finder 冷启动 + npm global 安装的场景也能找到了。

另外把跨账号缓存守卫的 fail-open 语义用注释显式化了:profile show 也失败时宁给 stale 数据不报错,可用性优先于严格性。

相关测试全绿(ark 30/30、usage-limits 109/109),validate 全套 + architecture-guardrails 通过。

@xiufengsun

Copy link
Copy Markdown
Owner

复审完两轮提交(751203f5 + 0771d157)。上一轮的 Windows 回归确认已真正修复:useShell 默认 false,全部既有调用方回到直接 spawn,唯一开启点是 ark 的 win32 分支,argv 逐个核对过全是常量。CRLF 修复还顺带修掉了 gemini / kiro-cli / lsof 在 Windows 上同样吃 \r 污染的既有隐患,好改动。CI 8/8 绿。

合并前还有三条要修:

1. 「锁死回归」的测试锁不住回归。 runCommandtypeof commandRunner === "function" 时提前 return(command-runner.js:25-27),真实 cp.spawn 那行永远不执行——所有 usage-limits 测试都注入 mock runner。我把 shell: useShell 改回 shell: useShell || platform === "win32"(精确复现上一轮回归)实测,两个断言仍然全绿。测试断言的是传给 mock 的 options,只能抓「调用方显式传了 useShell:true」,抓不到「runner 内部把 shell 强开」——后者才是上一轮的 bug。要真正锁死得触达 spawn:stub cp.spawn 断言 shell === false,或真实 spawn 传含 | 的参数断言未被劈开。这个回归已经出现过一次,现有测试挡不住它,所以放在合并前而不是 follow-up。

2. 退订 unlink 触发条件过宽。 normalizeArkCodingPlanResponseif (!item || item.subscribed !== true) return null 把「明确退订」和「响应里根本没有 coding-plan 条目」揉进同一个 null。arkcli 返回 {}{"items":[]}(未登录、后端降级、product key 改名)时能通过 typeof body !== "object" 检查、不会走 failWithCache,却会被当成确认退订删掉磁盘缓存——瞬态信号驱动了持久化破坏。让 normalize 区分这两种 null(或调用方自查 item && item.subscribed === false),只在明确的 subscribed: false 上 unlink。

3. useShell 引号只覆盖空格。 加引号条件是 /\s/.test(command),但 cmd.exe 的元字符还有 & ^ (),而 Windows 本地账户名允许这些字符,npm 全局目录就在用户目录下——C:\Users\a&b\AppData\Roaming\npm\arkcli.cmd 会在 & 处被劈成两条命令。实测未加引号。不是注入面(路径来自 where 输出和固定拼接),是功能性 bug:useShell 时无条件加引号即可。

三条 advisory,不阻塞:

  • 最坏串行链 whichBinary 2s + usage plan 10s + profile show 2.5s = 14.5s,对 15s provider 预算余量只剩 0.5s;算上 stopChild SIGTERM 后 1s hardTimer,悲观场景为负——超预算时 Promise.race 直接判负,cache fallback 送不出去,用户看到的是错误而不是 stale 条。usage-limits.js 里 codex 的 codexResetCreditListTimeoutMs(remainingProviderBudgetMs) 是现成的按剩余预算收敛范式,plans get 的 10s 同理。
  • searchDirs 在有 evidence 时也无条件计算了一遍,且 resolveBinaryPath 内部 which 落空时会再算一遍;挪进 else 分支。
  • versionedBinDirs 没过滤非目录项(readdirSyncwithFileTypes 可清掉);nvm-windows 的 %APPDATA%\nvm\<ver> 布局未覆盖,不过 Windows 上 where.exe 通常够用,优先级低。

三条 should-fix 改完即可合并。

…conditional quoting

Third review round:

- The previous "regression guard" only asserted options handed to a
  mock commandRunner, which never reaches the real cp.spawn branch —
  reintroducing `shell: useShell || platform === "win32"` kept every
  test green. New test/command-runner.test.js stubs cp.spawn itself
  and asserts the spawn options: direct spawn (shell === false) by
  default even on win32, and unconditional quoting under useShell.
  Verified the guard catches the exact regression when reapplied.
- Cache deletion on the not-subscribed path is now driven only by an
  explicit coding-plan entry with `subscribed: false`. Payloads with
  no coding-plan entry at all (`{}`, `{"items":[]}`, renamed product
  key) are ambiguous transients (signed out, backend degraded) and no
  longer destroy the disk cache; a test walks all three shapes.
- useShell quoting is no longer gated on whitespace: cmd.exe
  metacharacters include `&` `^` `()` and Windows account names (and
  therefore npm global paths) may contain them without any space.
- Advisory: searchDirs computation is now lazy (evidence branch never
  builds the list), and versionedBinDirs filters non-directories via
  readdirSync({ withFileTypes: true }).
@Hu9956

Hu9956 commented Aug 16, 2026

Copy link
Copy Markdown
Contributor Author

@xiufengsun 三条 should-fix 已处理(a44ca8e7):

spawn 级回归测试:新建 test/command-runner.test.js,直接 stub cp.spawn 断言真实 spawn 选项——默认路径 win32 下 shell === false、args 数组原样传递;useShellshell === true 且命令无条件加引号。按你的方式把 shell: useShell || platform === "win32" 改回去实测,两个测试立即红(复现即被抓),还原后全绿。这个回归现在真的锁死了。

unlink 收窄:cache 删除现在只由显式的 coding-plan 条目 subscribed: false 驱动(调用方自查 entry && entry.subscribed === false)。{}{"items":[]}、product key 改名这类无条目 payload 是歧义瞬态(未登录/后端降级),一律不碰缓存——瞬态信号不再驱动持久化破坏。三种形状各测了一遍缓存仍在。

无条件引号useShell 时不再用 /\s/ 做门槛,直接引用整个命令——C:\Users\a&b\...\npm\arkcli.cmd 这类无空格含 & 的路径不会再被 cmd 劈开。stub spawn 测试断言了带 & 无空格路径的引用结果。

Advisory 两条顺手带了searchDirs 改为惰性计算(evidence 分支不再构建目录列表);versionedBinDirsreaddirSync({ withFileTypes: true }) 过滤非目录项并补了测试。预算收敛(codexResetCreditListTimeoutMs 范式)按 advisory 留作 follow-up。

相关测试全绿(command-runner 3/3、ark 31/31、usage-limits 109/109),validate 全套 + architecture-guardrails 通过。

The serial arkcli chain (discovery -> usage plan -> plans get /
profile show -> cache read) could outrun the outer 15s provider race:
worst case a hung CLI burned the full budget before the disk-cache
fallback could run, so users saw an error instead of last-known data.

Mirrors codexResetCreditListTimeoutMs: each CLI call's timeout is
clamped to what is left of providerTimeoutMs minus a kill guard, and
calls that no longer fit are skipped. `plans get` skipping only drops
the tier label; `profile show` skipping falls back to the existing
fail-open cache read; an exhausted budget before `usage plan` reports
a timeout error without serving the unverified cache (matching the
outer race semantics asserted by the existing timeout-fallback test).
With the default 15s budget every timeout is unchanged — the clamp
only bites once the chain actually runs long.

Also runs command-runner tests in the Windows CI job and adds a
.cmd-with-metacharacters spawn test (path contains `&`, no spaces):
under useShell the whole path stays quoted, and the default path
keeps shell === false with args passed verbatim.
@github-actions github-actions Bot added the ci label Aug 16, 2026
@Hu9956

Hu9956 commented Aug 16, 2026

Copy link
Copy Markdown
Contributor Author

@xiufengsun 上轮 advisory 的预算收敛已处理(3ee314ad),另补了 Windows 真实 spawn 覆盖:

预算收敛:按 codexResetCreditListTimeoutMs 范式实现——fetchArkCodingPlanLimits 新增 providerTimeoutMs 参数(生产调用点从 usage-limits 传入),入口记时后每个 CLI 调用(usage plan / plans get / profile show)的 timeout 都钳制到 剩余预算 - 1.5s kill guard,不再简单沿用固定值。预算耗尽的调用直接跳过:plans get 跳过只丢 tier label;profile show 跳过则走既有的 fail-open cache 读取;usage plan 前置耗尽报含 "timed out" 的 error 而不读未验证 cache——这与既有 getUsageLimits Ark timeout fallback 测试锁定的外层 race 语义一致。默认 15s 预算下各 timeout 值与原来完全一致(既有 exact 断言测试未动仍绿),钳制只在链路真正跑长时生效。挂死场景端到端有测试:usage 挂到收缩后超时 → profile 预算不足被跳过 → disk cache 在 15s race 内返回 stale 数据而非 error。

Windows spawn 覆盖:windows-build job 补跑 test/command-runner.test.js(ci.yml 一行);新增 .cmd 路径含 & 元字符(无空格)的测试——useShell 时整路径被引号包裹、& 留在引号内,默认模式 shell === false 且 args 数组逐字传递。未恢复 shell: useShell || platform === "win32"

pet-quips 的 codingPlan label 确认与 kimi/kiro/zcode/qoder/opencodeGo 等所有 provider 同为硬编码模式,整体迁移 copy 系统超出本 PR 范围,未动。

相关测试全绿(command-runner 4/4、ark 33/33、usage-limits 109/109),validate 全套 + architecture-guardrails 通过,Windows CI 已实跑新测试。

@xiufengsun
xiufengsun merged commit 95b236f into xiufengsun:main Aug 17, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants