deepseek-harness-wallet 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,23 @@
2
2
 
3
3
  All notable changes to this project are documented in this file.
4
4
 
5
+ ## 0.2.5 - 2026-08-23
6
+
7
+ - 修复浮动峰谷卡片在窗口缩放、卡片放大或历史坐标过期后可能跑出视口的问题;加载、缩放、排版切换和 resize 都会重新钳制位置。 / Fixed floating peak cards escaping the viewport after resize, scale changes, or stale saved coordinates; load, scale, layout changes, and resize now re-clamp the position.
8
+ - 恢复 Portal 挂载,确保自由浮动卡片和控制面板脱离侧边栏溢出裁剪。 / Restored Portal mounting so floating cards and the control panel escape sidebar overflow clipping.
9
+ - 完成主题回退清理,移除峰谷控制面板、浮动卡片和开关中的硬编码浅色背景/文字回退。 / Completed theme fallback cleanup for the peak panel, floating card, and switches.
10
+ - 保留账户安全、存储权限、刷新异常和偏好同步加固,并避免原始凭证错误文本进入日志或浏览器。 / Kept account-safety, storage-permission, refresh-failure, and preference-sync hardening without exposing raw credential errors to logs or the browser.
11
+ - 新增浮动坐标边界、Portal 和完整主题回退回归测试;当前测试基线为 78/78。 / Added regression coverage for floating-coordinate bounds, Portal mounting, and complete theme fallbacks; the test baseline is now 78/78.
12
+
13
+ ## 0.2.4 - 2026-08-22
14
+
15
+ - 彻底修复暗色模式(Dark Mode)主题适配(Issue #26):移除所有未主题化的浅色硬编码回退(`--dsw-alias-bg-elevated,#fff`、`--dsw-alias-bg-overlay,#fff` 等),全站统一使用 DSH 原生主题变量(`--dsw-alias-bg-layer-*` 与 `--dsw-alias-label-dimmed`),在深色模式下完美融入背景。 / Fully fixed Dark Mode theme compliance (Issue #26): replaced unthemed hardcoded light fallbacks with DSH native theme variables (`--dsw-alias-bg-layer-*`), rendering dark themes seamlessly without blinding white boxes.
16
+ - 新增峰谷时钟专属控制面板(Dedicated Control Panel):点击时钟卡片即可就地弹出专属浮动控制面板,支持实时查看峰谷优惠说明、账户余额明细与快速充值,并提供排版切换、缩放调节与归位管理。 / Added a dedicated peak clock control popover: clicking the clock card opens an interactive panel for live rate info, quick recharge, orientation toggles, and layout controls.
17
+ - 峰谷时钟支持自由拖拽与持久化记忆:卡片支持在屏幕上任意拖拽移动,脱离侧边栏限制(Portal 渲染),拖拽释放自动持久化坐标,并在浮动卡片右上角和控制面板提供一键「↩ 归位」回到侧边栏底部。 / Added free-floating drag & drop for the peak clock: smoothly movable across the viewport with boundary clamping, position persistence in localStorage, and one-click dock reset.
18
+ - 峰谷时钟横向与纵向排版自由切换:支持标准的横向卡片与 ~140px 纤细纵向胶囊卡片(侧边栏内居中、浮动时轻巧紧凑)。 / Added customizable layout: seamlessly toggle between horizontal card layout and slim ~140px vertical capsule layout.
19
+ - 充值按钮内嵌排版优化:将充值按钮移至第三行倒计时右侧,彻底消除竖排按钮对第二行金额造成的水平挤压与 `¥...` 截断问题,金额与本场消耗 100% 完整显示。 / Relocated recharge button inline next to the countdown row, completely eliminating text truncation on the balance and session cost line.
20
+ - 新增时钟缩放滑条(100% ~ 120%)与充值按钮独立开关:支持按需放大时钟或收缩整体卡片尺寸。 / Added a dedicated scale slider (100% ~ 120%) and recharge button visibility toggle in both the popup panel and settings page.
21
+
5
22
  ## 0.2.3 - 2026-08-21
6
23
 
7
24
  - 修复 Provider 分桶实际未参与计费的问题;已勾选的包装路由现在真正进入官方 token/花费桶,并记录新观察到的 Provider。 / Fixed provider aliases not reaching the billing path: opted-in wrapper routes now enter the official token/cost bucket, and newly observed providers are recorded.
package/index.js CHANGED
@@ -78,7 +78,9 @@ export const PRICE_POLICIES = [
78
78
  export function ratesFor(model, atMs) {
79
79
  let entry
80
80
  for (const policy of PRICE_POLICIES) {
81
- if (atMs >= policy.since && policy.models[model] !== undefined) entry = policy
81
+ // Own-property check: a model named `__proto__`/`toString` would otherwise
82
+ // resolve to an Object.prototype member and produce NaN costs.
83
+ if (atMs >= policy.since && Object.hasOwn(policy.models, model)) entry = policy
82
84
  }
83
85
  if (entry === undefined) return null
84
86
  const rates = entry.models[model]
@@ -258,9 +260,10 @@ function persistStore(logger) {
258
260
  saveTimer = null
259
261
  }
260
262
  try {
261
- mkdirSync(dirname(STORE_PATH), { recursive: true })
263
+ mkdirSync(dirname(STORE_PATH), { recursive: true, mode: 0o700 })
262
264
  const tmp = STORE_PATH + '.tmp'
263
- writeFileSync(tmp, JSON.stringify(store))
265
+ // Owner-only: the store carries usage accounting next to credential files.
266
+ writeFileSync(tmp, JSON.stringify(store), { mode: 0o600 })
264
267
  renameSync(tmp, STORE_PATH)
265
268
  } catch (error) {
266
269
  if (logger && typeof logger.warn === 'function') {
@@ -325,9 +328,10 @@ function persistAccounts(logger) {
325
328
  accountsSaveTimer = null
326
329
  }
327
330
  try {
328
- mkdirSync(dirname(ACCOUNTS_PATH), { recursive: true })
331
+ mkdirSync(dirname(ACCOUNTS_PATH), { recursive: true, mode: 0o700 })
329
332
  const tmp = ACCOUNTS_PATH + '.tmp'
330
- writeFileSync(tmp, JSON.stringify(accounts))
333
+ // This file holds plaintext API keys: owner-only, never group/world readable.
334
+ writeFileSync(tmp, JSON.stringify(accounts), { mode: 0o600 })
331
335
  renameSync(tmp, ACCOUNTS_PATH)
332
336
  } catch (error) {
333
337
  if (logger && typeof logger.warn === 'function') {
@@ -436,15 +440,20 @@ async function resolveBalanceKey(ctx) {
436
440
  */
437
441
  export async function activateAccount(ctx, id) {
438
442
  const account = findAccount(id)
439
- if (account === null) return { ok: false, error: 'account not found' }
443
+ if (account === null) return { ok: false, error: 'account-not-found' }
440
444
  const credentials = ctx.get('credentials')
441
- if (credentials === undefined) return { ok: false, error: 'credentials service unavailable' }
445
+ if (credentials === undefined) return { ok: false, error: 'credentials-unavailable' }
442
446
  try {
443
447
  await credentials.set(CREDENTIAL_REF, account.apiKey)
444
448
  } catch (error) {
445
449
  // e.g. the launching environment supplies DEEPSEEK_API_KEY read-only, so
446
- // the write is shadowed and refused by the credentials provider.
447
- return { ok: false, error: error instanceof Error ? error.message : String(error) }
450
+ // the write is shadowed and refused by the credentials provider. Keep the
451
+ // browser error bounded and do not log provider text that might contain
452
+ // paths, environment details, or key fragments.
453
+ if (ctx.logger && typeof ctx.logger.warn === 'function') {
454
+ ctx.logger.warn('dsh-wallet: credential write refused')
455
+ }
456
+ return { ok: false, error: 'credential-write-refused' }
448
457
  }
449
458
  accounts.activeId = account.id
450
459
  scheduleAccountsSave(ctx.logger)
@@ -509,12 +518,12 @@ let balance = { fetchedAt: 0, available: false, balances: [], error: null }
509
518
  let balanceRefresh = null
510
519
 
511
520
  async function performBalanceRefresh(ctx) {
512
- const credentials = ctx.get('credentials')
513
- if (credentials === undefined) {
514
- balance = { fetchedAt: Date.now(), available: false, balances: [], error: 'no-credentials' }
515
- return
516
- }
517
521
  try {
522
+ const credentials = ctx.get('credentials')
523
+ if (credentials === undefined) {
524
+ balance = { fetchedAt: Date.now(), available: false, balances: [], error: 'no-credentials' }
525
+ return
526
+ }
518
527
  const key = await resolveBalanceKey(ctx)
519
528
  if (key === undefined || key === '') {
520
529
  balance = { fetchedAt: Date.now(), available: false, balances: [], error: 'no-api-key' }
@@ -564,9 +573,24 @@ async function performBalanceRefresh(ctx) {
564
573
 
565
574
  function refreshBalance(ctx) {
566
575
  if (balanceRefresh !== null) return balanceRefresh
567
- balanceRefresh = performBalanceRefresh(ctx).finally(() => {
568
- balanceRefresh = null
569
- })
576
+ // Never let a refresh escape as an unhandled rejection: every caller uses
577
+ // `void refreshBalance(ctx)`, so a throw here would take the host process
578
+ // down (Node exits on unhandled rejections). Absorb it into the error state.
579
+ balanceRefresh = performBalanceRefresh(ctx)
580
+ .catch((error) => {
581
+ balance = {
582
+ fetchedAt: Date.now(),
583
+ available: false,
584
+ balances: [],
585
+ error: 'balance-unavailable',
586
+ }
587
+ if (ctx && ctx.logger && typeof ctx.logger.warn === 'function') {
588
+ ctx.logger.warn('dsh-wallet: balance refresh failed')
589
+ }
590
+ })
591
+ .finally(() => {
592
+ balanceRefresh = null
593
+ })
570
594
  return balanceRefresh
571
595
  }
572
596
 
@@ -609,8 +633,18 @@ function balanceTotal() {
609
633
 
610
634
  function sessionView(sessionId) {
611
635
  if (sessionId === undefined || sessionId === '') return { official: null, third: null }
636
+ // Own-property lookup only: session ids like `__proto__`, `constructor` or
637
+ // `toString` otherwise resolve to an Object.prototype member, slip past the
638
+ // undefined guard, and throw inside bucketTotals.
639
+ if (!Object.prototype.hasOwnProperty.call(store.sessions, sessionId)) {
640
+ return { official: null, third: null }
641
+ }
612
642
  const session = store.sessions[sessionId]
613
- if (session === undefined) return { official: null, third: null }
643
+ if (session === null || typeof session !== 'object'
644
+ || session.official === null || typeof session.official !== 'object'
645
+ || session.third === null || typeof session.third !== 'object') {
646
+ return { official: null, third: null }
647
+ }
614
648
  const official = bucketTotals(session.official)
615
649
  const third = bucketTotals(session.third)
616
650
  return {