Skip to content

Add 蹬王之王 / Expiry Sentinel: spend whichever credit expires first - #28

Closed
Hylouis233 wants to merge 2 commits into
MiniMax-AI:mainfrom
Hylouis233:feat/expiry-sentinel
Closed

Hylouis233 wants to merge 2 commits into
MiniMax-AI:mainfrom
Hylouis233:feat/expiry-sentinel

Conversation

@Hylouis233

@Hylouis233 Hylouis233 commented Oct 16, 2026 •

Copy link
Copy Markdown

中文说明

这个 Mini App 做什么

MiniMax Code 的积分和重置卡都会到期。放着不管,最快到期的那笔会悄悄作废,而你一直在用最耐放的那笔。

蹬王之王把每笔余额和到期日摊开给你看,然后「先过期先用」——不浪费,也不过期。

决策规则

5 小时或周限额耗尽时,请求开始扣积分——这就是该动手的时刻。应用会比两个日期:

情况 决策 原因
重置卡比最早到期的积分更早过期 用卡 重置额度,保住更耐放的积分
积分比重置卡更早过期 留卡 让积分先消耗掉,卡留到以后更有价值
额度未耗尽 不动 还没在烧积分

Node 服务和后台守护进程实现同一套规则,并有逐字比对的一致性校验,防止两边漂移。

三种使用方式

  • 只看不动(默认)——打开看板读数据,不消耗任何东西。
  • 页面内自动执行——页面开着时按设定间隔判定并执行。
  • 全天候后台执行——launchd 代理在页面关闭后继续运行。

三条路径共用同一套决策规则,服务端按幂等键去重——即使同时开着,卡也不会被扣两次。

截图

来自应用的实际运行:一张是看板全景,一张是重置卡列表与已用统计。

Review 说明

  • 应用界面是中文的;README 提供英文与简体中文两份。
  • 扣卡是真实写操作。 自动执行会真实消耗重置卡,默认是只读模式。
  • 后台守护进程在 daemon/ 下,以 Rust 源码形式提供,不提交构建产物;用 cargo build --release 构建。它暴露一个 --selftest 模式,喂给它与 Node 服务相同的一组用例,一致性检查比对的就是这两者。
  • 已在 macOS(Apple Silicon)上对真实账号验证,包括额度耗尽时触发的自动执行路径。未验证:跨平台表现与其他客户端版本。
  • 守护进程会读取桌面端的 OAuth lease socket:~/.minimax/run/mcode-auth-lease-v1.sock —— 这是内部路径,已在 ## Data & access 中说明。
  • 只访问两个网络地址,均为 MiniMax:www.minimaxi.com 与 agent.minimaxi.com。无遥测、无外部服务。
  • 页面不展示卡号,只展示序号与到期日。

npm run check 对本包报告 OK,无警告。


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

Hylouis233 added 2 commits October 17, 2026 03:43
A Mini App that tracks MiniMax Code credit and reset card expiry, then
automatically spends whichever lapses first so nothing expires unused.

- Dashboard shows expiry alerts, 5-hour quota, credit balance, next expiry
- Decision rule compares the earliest-expiring credit against the
  earliest-expiring reset card; a parity test keeps the Node service and
  the Rust daemon from drifting apart
- Optional Rust daemon keeps checking via launchd after the page is closed
- README in English and Simplified Chinese, with screenshots from a live run
Copilot AI balanced review requested due to automatic review settings October 16, 2026 19:48

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.

@MyPrototypeWhat MyPrototypeWhat left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

感谢贡献!「先过期先用」这个思路很实用,代码里也看得出对重复扣卡做了不少防护:先记账再请求、按 out_biz_no 幂等、token 只放在内存里。我在本地用 Node 22 跑了 npm run check,结果通过;daemon 用 stable Rust 跑 cargo test 9/9 通过。另外我用假的 lease socket 和 mock 接口把 Node 入口跑了一遍(没有访问真实接口),发现下面几个问题。

需要修改

  1. 页面里的「使用建议的重置卡」和自动执行实际上不会扣卡。useCard() 用 snapshot.cards.items 判断目标卡是否还在列表里,但 items 已经去掉了 cardId(只剩 index 和 expireTimeMs),所以 stillListed 永远是 false。我用 mock 模拟「额度耗尽、卡比积分先过期」:决策是 use-card,但接口返回「这张卡已不在可用列表中,跳过」,自动执行首轮也是 SKIPPED_DUPLICATE,reset_card/use 一次都没有调用。
  2. 积分数据读取失败时,决策会变成用卡。Node 这边,credit/details 失败被 .catch(() => null) 吞掉以后,decide 会认为「没有可用积分」,给出 use-card。我用 mock 放了一笔比卡早到期的 Code 积分,正常情况下结论是留卡;让这个接口返回 502 后,结论变成「没有可用积分,使用重置卡重置额度」。daemon 的 earliest_credit() 读取失败时也只是记日志「忽略」,decide 随后返回 UseCard。扣卡是真实写操作,建议任何一路积分数据没拿到时都不扣卡,在页面上提示即可。第 1 条修好以后,这一条就会真的扣错卡。
  3. 后台服务目前没法从这个包装上。README 写开启「后台服务」会安装 launchd agent,plist「written only when you enable 后台服务」,但包里没有任何代码写这个 plist,controlDaemon 只检查它是否存在,然后执行 launchctl load/unload,也没有说明 plist 里运行的是哪个二进制。麻烦补上安装和卸载流程(写什么 plist、二进制放在哪里、卸载插件时怎么清理;Host 只会停掉入口进程),或者在 README 里把它标成需要手动配置的功能,并写清楚步骤。
  4. README 写截图来自真实账号,用的是真实数字。CONTRIBUTING.md 要求截图使用合成数据或充分脱敏的数据,麻烦换成合成数据。
  5. 「Tested environment」请补上 MiniMax Code 的具体版本号。3.1.2 前后,宿主对 Mini App 端口的处理不一样。

另外,这个插件通过桌面端内部的 OAuth lease socket 拿用户的 access token,再调用私有接口扣卡。仓库目前的文档里没有这类用法的说明,我们需要内部确认一下是否接受,确认后再回复你。

建议(可选)

  1. /api/use-card、/api/auto、/api/daemon 这几个写接口没有同源校验。3.1.2 以后宿主会挡住跨站请求,但老版本宿主不会,而这些接口会真实扣卡,建议加一道 Sec-Fetch-Site / Origin 检查。
  2. 「使用建议的重置卡」建议加一个确认步骤。
  3. README 写「No card IDs are shown anywhere in the UI」,但 /api/state 返回的 decision.target 带有 cardId 和 outBizNo,页面也会显示「目标卡 …」。
  4. restart 只做一次 load 或 unload,所以服务运行时点「重启」会把它停掉。
  5. daemon 默认会真实扣卡(只有 --dry-run 才不扣),README 里的「默认只读」指的是页面,建议写清楚。
  6. README 提到的 verify-parity.mjs 一致性校验不在 PR 里;~/.config/mcode-reset-card/token 包里只读不写,和 README 的描述不一致。
  7. Cargo.toml 建议加上 rust-version:我用 1.85 编译时 #[derive(Default)] 用在含裸指针字段的结构体上会报错,较新的 stable 可以编译。未使用的 BASE_GLOBAL 也可以删掉。
  8. 根 README 的新行建议放到表格末尾。

* 进程重启后 spent 清空也没关系,列表判定依然成立。
*/
const stillListed = (snapshot.cards.items ?? []).some(
(c) => c.cardId === target.cardId && !c.expired,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

这里的 snapshot.cards.items 已经映射成 { index, expireTimeMs },没有 cardId,所以这个判断永远是 false。我本地用 mock 测试,决策是 use-card 时也总是返回「这张卡已不在可用列表中」,reset_card/use 不会被调用。可以改用 buildSnapshot 内部的原始 cards 列表来判断。

cached('codeCredits', () =>
fetchCodeCredits(token)
.then((r) => r.payload)
.catch(() => null),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

这里失败时返回 null,decide 会把 Code 积分当作不存在。我用 mock 让 credit/details 返回 502,结论就从「留卡」变成了「没有可用积分,使用重置卡」。扣卡是真实写操作,建议这一路失败时不执行写操作。

}
}
}
Err(e) => log(&format!(" Code 积分读取失败(忽略): {e}")),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

和 Node 这边一样,这里读取失败只记日志,decide 收到 None 后会直接返回 UseCard。建议读取失败时这一轮跳过扣卡。

Hylouis233 added a commit to Hylouis233/MiniMax-Code-MiniApps that referenced this pull request Oct 19, 2026
## PR MiniMax-AI#28 review 的三条建议(都是真 bug,全部仍然存在)

1. **扣卡永远被跳过**
   `cards.items` 是给页面看的**脱敏视图**(只有序号与到期日,没有 card_id),
   却拿它判断「这张卡还在不在可用列表里」⇒ `c.cardId === target.cardId`
   恒为 false ⇒ **每次都跳过,`reset_card/use` 从未被调用**。
   ⇒ 新增 `cards.raw`(带 cardId)供写路径判定,页面仍不展示卡号。

2. **积分接口失败被当成「没有积分」⇒ 误扣真卡**
   `credit/details` 失败被 `.catch(() => null)` 吞掉 ⇒ codeCredits 为空
   ⇒ 结论从「留卡」翻转成「用卡」。而扣卡是**真实写操作**。
   ⇒ 新增 `creditDataComplete` 闸门:数据不完整 ⇒ 本轮**不做任何写操作**。

3. **守护进程侧同样的问题**
   积分读取失败只记日志(`best` 保持 None)⇒ 同样落到 use-card。
   ⇒ `earliest_credit()` 现在返回「数据是否完整」,不完整 ⇒ wait。

## 新功能:可重试错误无限重试

429(负载过高)、500/502/503/504(内部与网关错误)、连接重置/超时/DNS 失败
⇒ 一直重试到成功。退避指数增长 + 抖动,**封顶 60 秒**(不会把接口打爆)。
401/403 重取 token 一次;其余 4xx 与业务错误快速失败。
扣卡也走重试 —— 请求带 out_biz_no 幂等键,重复提交只回放不重复扣。
重试**可中断**:SIGINT/SIGTERM 让在飞的请求立刻放弃,守护进程不会被卡死。

## 顺带

- verify-parity.mjs 改为**插件内自包含**(原先硬编码 ~/Desktop/mmx,
  违反 CONTRIBUTING 的「插件应能独立复制使用」约定)
- 抽出 `decide_retry()` 纯函数,使重试决策可脱离网络单测

## 测试

Rust 41/41 · Python 20/20 · 三方 parity 19/19(新增 2 条「数据不完整」用例)
MiniApp 决策 58/58 · 重试真实行为 4/4(本地 mock server 实测:
500/429 会重试到成功、404 立即失败、持续 500 不提前放弃)
npm run validate OK · npm test 69/69

Copy link
Copy Markdown
Collaborator

#28 的内容已经全部包含在 #39 里,麻烦关闭这个 PR,后续统一在 #39 上继续,谢谢!

@Hylouis233

Copy link
Copy Markdown
Author

本 PR 的全部内容已被 #39 取代(同一插件,目录名从 expiry-sentinel 改为 expiry-guardian),按 review 意见在此关闭,后续只在 #39 上继续。

@Hylouis233 Hylouis233 closed this Oct 20, 2026
@Hylouis233
Hylouis233 deleted the feat/expiry-sentinel branch October 20, 2026 13:20
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.

3 participants