Skip to content

Add 蹬王之王 (expiry-guardian) · 只读版:本地到期账本与用卡决策(不碰宿主任何私有数据) - #47

Open
Hylouis233 wants to merge 3 commits into
MiniMax-AI:mainfrom
Hylouis233:feat/expiry-guardian-readonly
Open

Hylouis233 wants to merge 3 commits into
MiniMax-AI:mainfrom
Hylouis233:feat/expiry-guardian-readonly

Conversation

@Hylouis233

@Hylouis233 Hylouis233 commented Oct 20, 2026 •

Copy link
Copy Markdown

这是什么

社区插件版本的蹬王之王(Expiry Guardian):看你自己维护的本地账本,
算出「该用卡还是该留积分」,并把理由说清楚。

Mini App 看板 + Rust 后台守护进程(launchd 常驻,只读)。


为什么不碰宿主的三类私有数据

上游社区插件有三条硬红线(2026-10-20 管理员 review 明确列出)。这一版按红线裁剪:

红线 v1.x(已移除) v2.0(本 PR)
不能读 MiniMax Code 的登录凭据 读 lease socket / .cap 取 access token,再拿它调账号接口 不读。额度/积分/卡的信息改由本地账本提供
不能读写宿主内部数据库 sqlite3 直接 UPDATE/DELETE/INSERT 目标与执行队列,还读回合记录 不碰。只读插件自己的目录
不能读别的插件的数据目录 读 minimax-bot 的 state.json 拿 bot 与会话 id,再往那些会话发消息 不读。Bot 看门狗整体移除

⇒ 代价是明确的:v2.0 不再自动扣卡、不再自动暂停目标、不再自动叫醒 bot。
它只负责把结论算准、说清楚,动作由你自己做。

⇒ 好处也很明确:这是一份能进社区仓库的代码 ——
不碰宿主任何私有数据,行为全部可预测。


本地账本

路径:~/.config/mcode-reset-card/ledger.json(面板底部可直接编辑并保存)

{
  "schemaVersion": 1,
  "updatedAtMs": 1792480000000,
  "quota": {
    "fiveHour": { "usedPercent": 3, "totalPercent": 100, "unlimited": false, "resetTimeMs": 1792497600000 },
    "weekly":  { "usedPercent": 0, "totalPercent": 100, "unlimited": true,  "resetTimeMs": 1792944000000 }
  },
  "cards": [
    { "cardId": "card_1", "label": "Card A", "sourceType": "code",
      "outBizNo": "biz_1", "expireTimeMs": 1795000000000 }
  ],
  "credits": [
    { "label": "Granted", "source": "code", "granted": 400, "used": 0,
      "balance": 400, "expireTimeMs": 1795017600000 }
  ]
}
  • 一张卡要算「可用」,outBizNo 与 sourceType 两个都要有;已过期的也不算
  • 积分要 balance > 0 且未过期
  • ⚠️ 缺 credits 字段 ≠ 没有积分 ⇒ 判成「数据不完整」⇒ 结论停在「暂不处理」,
    绝不会因为「读不到」就反过来建议你烧掉一张真卡

决策逻辑:先过期先用

情况 结论
额度没到触发线 额度充足
到线 + 无积分 + 有卡 建议用卡
到线 + 无积分 + 无卡 暂不处理
到线 + 有积分 + 无卡 建议留积分
到线 + 都有,卡先过期 建议用卡
到线 + 都有,积分先过期 建议留积分
到线 + 距刷新不到 N 分钟 暂不处理(烧卡纯浪费)
数据不完整 暂不处理(宁可不给建议,也不给错建议)

三方一致性(决策逻辑一字未改)

Rust / Mini App / Python 三份同构实现,daemon/verify-parity.mjs 让三方读
同一份用例,逐字比对「动作 + 理由文本」:

$ node daemon/verify-parity.mjs
19/19 三方完全一致(动作 + 理由文本逐字)

裁剪只改了「数据从哪来」,决策引擎的输入结构与之前从账号接口拿到时完全一致。


红线守卫(19 项)

tests/server-lint.test.mjs 把上面三条红线变成可执行的门禁:

  • 不出现 lease socket / .cap / token 落盘
  • 不出现 runtime-state.sqlite / 宿主表的 UPDATE·DELETE·INSERT
  • 不出现 plugin-data / liveboards / 别的插件的 state.json
  • 不出现扣卡接口 / 目标暂停恢复

⇒ 谁不小心把旧代码加回来,CI 立刻红。
(v1.x 那些代码在仓库里躺了好几个版本才被发现 —— 人工审计靠不住。)


后台守护进程(只读)

  • 每轮读一次本地账本 → 跑一遍上面那套决策 → 写日志与 daemon-status.json
  • 账本读不到 ⇒ 明确报「本轮没有任何结论」,连续失败逐级升级(5 轮 / 每 30 轮)
  • 不做任何写操作:不扣卡、不暂停目标、不改宿主任何数据

local MCP 端点

工具 作用
watchdog_status 查账本里的额度、重置卡、积分与当前结论
watchdog_decide 立即判定一次(只判定不扣卡)

⚠️ 刻意不暴露扣卡:扣卡是真实写操作,让别的插件随手就能扣太危险。


校验

套件 结果
上游 npm run validate 本插件 OK(0 错 0 警)、repository: OK
上游 npm test 69/69
Rust 25/25
Python 决策单测 20/20
Mini App 79/79
三方一致性 19/19

文档

  • plugins/hylouis233/expiry-guardian/README.md —— 英文
  • plugins/hylouis233/expiry-guardian/docs/README.zh-CN.md —— 中文
  • plugins/hylouis233/expiry-guardian/docs/BOT_DIAGNOSIS.md —— 排查手册

v1.x 的完整历史(自动扣卡、目标暂停、Bot 看门狗,以及那套「静默失效全部变成会说话」
的加固)保留在 git 里;那些加固在 v2.0 仍然生效,只是守护的对象换成了本地账本。


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

Copilot AI balanced review requested due to automatic review settings October 20, 2026 12:27

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.

@Hylouis233

Copy link
Copy Markdown
Author

这是「只读版」,与 #39 是二选一

两者的决策逻辑一字未改(三方 Rust / Mini App / Python 仍 19/19 逐字一致),
差别只在「数据从哪来」与「能不能自动执行」。

⇒ 管理员 review 的原话是「很欢迎你提的方案:把只读、能直接交付的部分……单独开一个 PR。
直接把这个 PR 裁到这个范围也可以,两种方式都行」。
本 PR 就是那个「单独开一个」的方案;#39 保持完整版继续讨论。

请先决定走哪条路;如果走只读版,#39 可以直接关掉。

社区插件版本的蹬王之王:**看你自己维护的本地账本,算出「该用卡还是该留积分」**。

## 为什么不碰宿主的三类私有数据

上游社区插件有三条硬红线(2026-10-20 管理员 review 明确列出):

| 红线 | v1.x(已移除) | v2.0 |
| --- | --- | --- |
| 不能读 MiniMax Code 的登录凭据 | 读 lease socket / .cap 取 token 调账号接口 | 不读。数据源是本地账本 |
| 不能读写宿主内部数据库 | UPDATE/DELETE/INSERT 目标与队列、读回合记录 | 不碰。只读插件自己的目录 |
| 不能读别的插件的数据目录 | 读 minimax-bot 的 state.json 拿 bot 与会话 id 去发消息 | 不读。Bot 看门狗整体移除 |

⇒ **不扣卡、不暂停目标、不叫醒 bot**:只把结论算准、说清楚,动作由人做。

## 亮点

- **决策逻辑一字未改**:Rust / Mini App / Python 三方同构,
  verify-parity.mjs 仍 **19/19 三方逐字一致**(动作 + 理由文本)。
  变的只是「数据从哪来」。
- **本地账本**:~/.config/mcode-reset-card/ledger.json,面板底部可直接编辑并保存。
- **诚实失败**:账本读不到 ⇒ 明确报「本轮没有任何结论」;
  **读不到 ≠ 没有** ⇒ 数据不完整时永远不给「用卡」这种花钱的建议。
- **红线守卫**(19 项):把上面三条红线变成**可执行的门禁** ——
  谁把旧代码加回来,CI 立刻红。v1.x 那些代码在仓库里躺了好几个版本才被发现。

## 校验

上游 npm run validate 对本插件 **OK**(0 错 0 警)、repository: OK、npm test 69/69。
本插件:Rust 25/25、Python 20/20、MiniApp 79/79、三方一致性 19/19。

## 文档

- plugins/hylouis233/expiry-guardian/README.md —— 英文
- plugins/hylouis233/expiry-guardian/docs/README.zh-CN.md —— 中文
- plugins/hylouis233/expiry-guardian/docs/BOT_DIAGNOSIS.md —— 排查手册
v2.0 已经不调任何账号接口、不再取 lease token,但文件头还在描述旧的
两套 API 与鉴权方式 ⇒ 会被误读成「这个插件还在调接口」。
改成如实描述本地账本这一唯一数据源,并写明为什么不再自己调接口。
@Hylouis233
Hylouis233 force-pushed the feat/expiry-guardian-readonly branch from 383a063 to 9a9cb7f Compare October 20, 2026 12:49

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

COMMENT(head 9a9cb7f)

感谢单独开出这个只读版。v2.0 已经去掉了读取凭据、访问宿主数据库和读取其他插件数据这几部分,符合社区插件的红线。

不过裁掉这些之后,数据只能由用户手动填写,插件目前的实际用处比较有限。这部分先暂停,等有合规的取数方式再继续。是否关闭由你决定。

如果继续保留这个 PR,请顺手处理这几处:

  • .minimax-plugin/plugin.json 的 description 还在写已经移除的功能(自动暂停目标、自动用卡、Bot 守护),版本号也还是 1.6.0,请改成和现在的行为一致。
  • server.mjs 里请删除已移除功能的残留注释。
  • daemon/rust/src/decision.rs:342:含 *const u8 字段的结构体上 #[derive(Default)] 会报 E0277,cargo build --release 编译不过。
  • Cargo.toml 里的 ureq 依赖已经没用了,可以删掉。
  • 面板文案里还在写「暂停目标 / 动用重置卡」一类说法,请改成和只读提醒一致的表述。

"name": "expiry-guardian",
"displayName": "蹬王之王",
"version": "1.6.0",
"description": "实时追踪积分与重置卡的到期时间,额度打满时自动暂停所有进行中的目标、额度回落后自动恢复并叫醒;无人值守后台守护进程配合 Mini App 看板,按「先过期先用」自动使用重置卡,遇到 500/429 等可重试错误会一直重试。v1.6 修掉一个让 Bot 守护完全静默的真 bug(minimax-bot 状态文件里一个被截断的代理对转义导致严格 JSON 解析失败),并新增唤醒链路自检:宿主重启后运行时没起来时如实告警、投递空转时也能发现,绝不再「看起来一切正常、实际一个 bot 都没叫醒」。",

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.

这段描述还在写已经移除的功能(自动暂停/恢复目标、叫醒、自动用卡、Bot 守护),版本也还是 1.6.0。请改成现在的实际行为:基于本地账本的到期提醒和用卡建议,只读,不自动扣卡;版本号请一并更新。

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

已改。描述改成只读版的实际行为(基于本地账本的到期提醒与用卡建议、只读、不自动扣卡),版本号 1.6.0 → 2.0.0。

schemaVersion: 1,
cardTriggerPercent,
cardSkipBeforeResetMinutes,
// ⚠️ `resumeCronId` 已彻底删除(2026-10-20):本插件不再武装/解除武装任何 cron。

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.

请删除已移除功能的残留注释(此处 resumeCronId / Bot 投递相关,以及文件中其他已移除能力的注释块)。

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

已删。server.mjs 里所有已移除能力的残留注释块都清掉了(resumeCronId / Bot 投递相关,以及整段「Bot 唤醒循环」注释)。

context.signal.addEventListener('abort', onAbort, { once: true });
if (context.signal.aborted) await dispose();

// ── Bot 唤醒循环 ──

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.

请删除已移除功能的残留注释(「Bot 唤醒循环」整段)。

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

已删 —— 整段「── Bot 唤醒循环 ──」注释块(那段描述的是早已移除的功能)。

/// 桌面端与 Python 版都用本地时区展示;这里手算避免引入 chrono 依赖。
/// libc 的 `struct tm`,只需本地时区换算用到的字段。
#[repr(C)]
#[derive(Default)]

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.

*const u8 没有实现 Default,这里会导致 cargo build/test --release 报 E0277,编译失败。可以去掉 derive,改用 unsafe { std::mem::zeroed() } 来初始化。

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

已改。#[derive(Default)] 去掉,改成手写 impl(全零 tm + tm_zone: std::ptr::null())—— 语义与 derive 一致,但不再依赖 impl Default for *const T 在各版本工具链里是否可用。

感谢指出:我本地 rustc 1.98 能编过,所以没发现;老工具链会直接 E0277 ⇒ 整个包装不上。手写实现是唯一稳妥的解法。


[dependencies]
# HTTP 客户端:手写 TLS 容易出错且难维护,用成熟实现更可靠
ureq = { version = "3", default-features = false, features = ["rustls", "json", "gzip"] }

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.

可选:守护进程已经不再联网,ureq 可以删掉。

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

已删。守护进程不再联网,ureq 连同 Cargo.lock 里的记录都清掉了。

1. **plugin.json 的描述与版本号还停在 1.6.0 的老功能** —— 改成只读版的实际功能,
   版本 → 2.0.0。
2. **server.mjs 里有残留的旧注释** —— 删掉整段「── Bot 唤醒循环 ──」
   (功能早已移除,注释还在描述它),并把「已暂停自动扣卡」改成「不建议用卡」。
3. **decision.rs:342 的 `#[derive(Default)]` 会让 cargo 编译报 E0277** ——
   `*const u8` 的 `Default` 实现并非所有 Rust 工具链都有。
   ⇒ 改成**手写 impl**(全零 tm,语义与 derive 一致,不依赖工具链版本)。
4. **Cargo.toml 里有用不上的 `ureq` 依赖** —— 本插件不再调任何 HTTP 接口 ⇒ 去掉
   (Cargo.lock 同步)。
5. **面板上「暂停目标 / 动用重置卡」这类说法要改** —— 「自动执行」统一改成
   「自动刷新」,并把所有「不会真的扣卡」补成「本插件只读」的口径。

校验:上游 `npm run validate` → 本插件 **OK**(0 错 0 警)、`repository: OK`;
Rust 25/25、Python 20/20、MiniApp 79/79、三方一致性 19/19。
@Hylouis233

Copy link
Copy Markdown
Author

五处都改了

意见 处理
plugin.json 描述与版本号停在 1.6.0 的老功能 描述改成只读版的实际行为(基于本地账本的到期提醒与用卡建议、只读、不自动扣卡),版本 → 2.0.0
server.mjs 残留旧注释 删掉所有已移除能力的残留注释块(含 resumeCronId / Bot 投递相关)
decision.rs:342 的 #[derive(Default)] 报 E0277 去掉 derive,改成手写 impl(全零 tm + tm_zone: std::ptr::null())—— 语义与 derive 一致,但不再依赖 impl Default for *const T 在各版本工具链里是否可用
Cargo.toml 里用不上的 ureq 删掉(Cargo.lock 同步)—— 守护进程不再联网
面板上「暂停目标 / 动用重置卡」的说法 「自动执行」统一改成「自动刷新」;所有「不会真的扣卡」补成「本插件只读」的口径

关于 E0277 那条:我本地 rustc 1.98 能编过所以没发现,老工具链会直接装不上。手写实现是唯一稳妥的解法,谢谢指出。

校验:上游 npm run validate → 本插件 OK(0 错 0 警)、repository: OK;
Rust 25/25、Python 20/20、MiniApp 79/79、三方一致性 19/19。

关于「只靠手填数据用处有限」

这点我同意 —— 目前账本确实要手填,插件的决策逻辑再准,喂进去的数据不对也没用。

所以想请你定个方向:

  • 保留这个 PR:当作「社区插件能做到什么」的基线 —— 决策逻辑、三方一致性、以及那套红线守卫(tests/server-lint.test.mjs 19 项,把「不读凭据 / 不碰内部库 / 不读别的插件数据」变成可执行的门禁)都能进社区仓库。
  • 先关掉:等有更靠谱的数据来源(比如宿主提供官方的额度/账本接口)再重新提。

你说先放着,那我就先开着,不动它;你哪天觉得没意义,说一句我就关掉。

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

感谢跟进!上次提到的几处都看过了,改得很到位 🙏

  • plugin.json:description 已经和只读行为一致,版本号也改成了 2.0.0。✅
  • server.mjs:已移除功能的大段残留注释清掉了。✅
  • decision.rs:改成手写 impl Default for Tm,cargo check / cargo test 本地都通过(25 个测试全过)。✅
  • Cargo.toml:ureq 去掉了,Cargo.lock 也跟着瘦身了。✅
  • 面板文案:改成「自动刷新 / 只给建议 / 不会真的扣卡」,读起来很清楚。✅

npm run check 在这个 head 上也通过了(expiry-guardian: OK)。

另外还有几处很小的尾巴,完全不影响结论,有空的话顺手清一下就好:

  • miniapp/client/index.html 约 485、809 行,miniapp/node/server.mjs 约 1133 行,还有一些「目标自动暂停」相关的旧注释。
  • index.html 里 CARD_ERROR / CARD_FAILED 的判断(约 1285、1318 行)在 v2.0 里应该不会再出现了,可以考虑去掉。
  • daemon/reset_card_autouse.py 和 daemon/verify-parity.mjs 里还留着 goalPause 的默认配置。

这个 PR 可以先留着,取数这块我们先缓一缓,等有合规的取数方式再继续推进;要不要关掉你来定就好。再次感谢你的耐心跟进!

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