dsh-mobile-plugin 0.5.28 → 0.5.29

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.
Files changed (350) hide show
  1. package/extensions/catalog.json +10 -1
  2. package/extensions/magpie/agentcompact.js +104 -0
  3. package/extensions/magpie/app.css +4902 -0
  4. package/extensions/magpie/app.js +24474 -0
  5. package/extensions/magpie/autobackup.js +185 -0
  6. package/extensions/magpie/autoresetown.js +151 -0
  7. package/extensions/magpie/balfound.js +38 -0
  8. package/extensions/magpie/boot.js +23 -0
  9. package/extensions/magpie/cfgblocked.js +39 -0
  10. package/extensions/magpie/checkinnow.js +111 -0
  11. package/extensions/magpie/claudemode.js +92 -0
  12. package/extensions/magpie/compat.js +41 -0
  13. package/extensions/magpie/context.css +433 -0
  14. package/extensions/magpie/context.js +1576 -0
  15. package/extensions/magpie/ctxtext.js +223 -0
  16. package/extensions/magpie/fonts.js +42 -0
  17. package/extensions/magpie/i18n.js +18942 -0
  18. package/extensions/magpie/icons/ai21.svg +1 -0
  19. package/extensions/magpie/icons/ai302-color.svg +1 -0
  20. package/extensions/magpie/icons/ai360-color.svg +1 -0
  21. package/extensions/magpie/icons/aihubmix-color.svg +1 -0
  22. package/extensions/magpie/icons/aionui.svg +1 -0
  23. package/extensions/magpie/icons/air.svg +4 -0
  24. package/extensions/magpie/icons/alibaba-color.svg +1 -0
  25. package/extensions/magpie/icons/alibabacloud-color.svg +1 -0
  26. package/extensions/magpie/icons/alma.png +0 -0
  27. package/extensions/magpie/icons/anthropic.svg +1 -0
  28. package/extensions/magpie/icons/antigravity-color.svg +1 -0
  29. package/extensions/magpie/icons/anyscale-color.svg +1 -0
  30. package/extensions/magpie/icons/app-alma.png +0 -0
  31. package/extensions/magpie/icons/app-cc-switch.png +0 -0
  32. package/extensions/magpie/icons/apple-touch-icon.png +0 -0
  33. package/extensions/magpie/icons/aside.svg +28 -0
  34. package/extensions/magpie/icons/astrbot.svg +1 -0
  35. package/extensions/magpie/icons/atomcode.png +0 -0
  36. package/extensions/magpie/icons/aws.svg +1 -0
  37. package/extensions/magpie/icons/azure-color.svg +1 -0
  38. package/extensions/magpie/icons/azureai-color.svg +1 -0
  39. package/extensions/magpie/icons/baichuan-color.svg +1 -0
  40. package/extensions/magpie/icons/baidu-color.svg +1 -0
  41. package/extensions/magpie/icons/baiducloud-color.svg +1 -0
  42. package/extensions/magpie/icons/bailian-color.svg +1 -0
  43. package/extensions/magpie/icons/baseten.svg +1 -0
  44. package/extensions/magpie/icons/bedrock-color.svg +1 -0
  45. package/extensions/magpie/icons/burncloud-color.svg +1 -0
  46. package/extensions/magpie/icons/bytedance-color.svg +1 -0
  47. package/extensions/magpie/icons/cerebras.svg +1 -0
  48. package/extensions/magpie/icons/chatglm-color.svg +1 -0
  49. package/extensions/magpie/icons/cherryin-color.svg +1 -0
  50. package/extensions/magpie/icons/chutes.svg +1 -0
  51. package/extensions/magpie/icons/cindy.png +0 -0
  52. package/extensions/magpie/icons/claude-color.svg +1 -0
  53. package/extensions/magpie/icons/claudecode-color.svg +1 -0
  54. package/extensions/magpie/icons/cline.svg +1 -0
  55. package/extensions/magpie/icons/cloudflare-color.svg +1 -0
  56. package/extensions/magpie/icons/codebuddy-color.svg +1 -0
  57. package/extensions/magpie/icons/codex-color.svg +1 -0
  58. package/extensions/magpie/icons/cohere-color.svg +1 -0
  59. package/extensions/magpie/icons/cometapi-color.svg +1 -0
  60. package/extensions/magpie/icons/commandcode.svg +1 -0
  61. package/extensions/magpie/icons/copilot-color.svg +1 -0
  62. package/extensions/magpie/icons/coze.svg +1 -0
  63. package/extensions/magpie/icons/crush.png +0 -0
  64. package/extensions/magpie/icons/curl.svg +1 -0
  65. package/extensions/magpie/icons/cursor.svg +1 -0
  66. package/extensions/magpie/icons/deepinfra-color.svg +1 -0
  67. package/extensions/magpie/icons/deepmind-color.svg +1 -0
  68. package/extensions/magpie/icons/deepseek-color.svg +1 -0
  69. package/extensions/magpie/icons/deveco-color.svg +12 -0
  70. package/extensions/magpie/icons/devin.svg +1 -0
  71. package/extensions/magpie/icons/doubao-color.svg +1 -0
  72. package/extensions/magpie/icons/empryo-color.svg +1 -0
  73. package/extensions/magpie/icons/factory.svg +1 -0
  74. package/extensions/magpie/icons/favicon.svg +1 -0
  75. package/extensions/magpie/icons/featherless-color.svg +1 -0
  76. package/extensions/magpie/icons/fireworks-color.svg +1 -0
  77. package/extensions/magpie/icons/friendli.svg +1 -0
  78. package/extensions/magpie/icons/fx.svg +1 -0
  79. package/extensions/magpie/icons/gemini-color.svg +1 -0
  80. package/extensions/magpie/icons/geminicli-color.svg +1 -0
  81. package/extensions/magpie/icons/gemma-color.svg +1 -0
  82. package/extensions/magpie/icons/giteeai.svg +1 -0
  83. package/extensions/magpie/icons/github.svg +1 -0
  84. package/extensions/magpie/icons/githubcopilot.svg +1 -0
  85. package/extensions/magpie/icons/glama.svg +1 -0
  86. package/extensions/magpie/icons/gmicloud.svg +1 -0
  87. package/extensions/magpie/icons/google-color.svg +1 -0
  88. package/extensions/magpie/icons/googlecloud-color.svg +1 -0
  89. package/extensions/magpie/icons/goose.svg +1 -0
  90. package/extensions/magpie/icons/grok.svg +1 -0
  91. package/extensions/magpie/icons/groq.svg +1 -0
  92. package/extensions/magpie/icons/hanako.png +0 -0
  93. package/extensions/magpie/icons/hermes.svg +1 -0
  94. package/extensions/magpie/icons/huawei-color.svg +1 -0
  95. package/extensions/magpie/icons/huaweicloud-color.svg +1 -0
  96. package/extensions/magpie/icons/huggingface-color.svg +1 -0
  97. package/extensions/magpie/icons/hunyuan-color.svg +1 -0
  98. package/extensions/magpie/icons/hyperbolic-color.svg +1 -0
  99. package/extensions/magpie/icons/icon-512.png +0 -0
  100. package/extensions/magpie/icons/iflytekcloud-color.svg +1 -0
  101. package/extensions/magpie/icons/inference.svg +1 -0
  102. package/extensions/magpie/icons/infinigence-color.svg +1 -0
  103. package/extensions/magpie/icons/internlm-color.svg +1 -0
  104. package/extensions/magpie/icons/jcode.svg +1 -0
  105. package/extensions/magpie/icons/jina.svg +1 -0
  106. package/extensions/magpie/icons/kilo.svg +1 -0
  107. package/extensions/magpie/icons/kilocode.svg +1 -0
  108. package/extensions/magpie/icons/kimi-code-desktop.png +0 -0
  109. package/extensions/magpie/icons/kimi.svg +1 -0
  110. package/extensions/magpie/icons/kiro-color.svg +1 -0
  111. package/extensions/magpie/icons/kling-color.svg +1 -0
  112. package/extensions/magpie/icons/lambda.svg +1 -0
  113. package/extensions/magpie/icons/leptonai-color.svg +1 -0
  114. package/extensions/magpie/icons/liquid.svg +1 -0
  115. package/extensions/magpie/icons/liveagent-color.svg +1 -0
  116. package/extensions/magpie/icons/llmapi.svg +1 -0
  117. package/extensions/magpie/icons/lmstudio.svg +1 -0
  118. package/extensions/magpie/icons/lody.png +0 -0
  119. package/extensions/magpie/icons/longcat.svg +1 -0
  120. package/extensions/magpie/icons/magpie.svg +1 -0
  121. package/extensions/magpie/icons/meta-color.svg +1 -0
  122. package/extensions/magpie/icons/metaai-color.svg +1 -0
  123. package/extensions/magpie/icons/microsoft-color.svg +1 -0
  124. package/extensions/magpie/icons/mimocode.svg +1 -0
  125. package/extensions/magpie/icons/minimax-color.svg +1 -0
  126. package/extensions/magpie/icons/mistermorph-color.svg +1 -0
  127. package/extensions/magpie/icons/mistral-color.svg +1 -0
  128. package/extensions/magpie/icons/mlx-serve.png +0 -0
  129. package/extensions/magpie/icons/modelscope-color.svg +1 -0
  130. package/extensions/magpie/icons/monica-color.svg +1 -0
  131. package/extensions/magpie/icons/moonshot.svg +1 -0
  132. package/extensions/magpie/icons/morph-color.svg +1 -0
  133. package/extensions/magpie/icons/mothx.png +0 -0
  134. package/extensions/magpie/icons/nebius.svg +1 -0
  135. package/extensions/magpie/icons/newapi-color.svg +1 -0
  136. package/extensions/magpie/icons/nousresearch.svg +1 -0
  137. package/extensions/magpie/icons/nova-color.svg +1 -0
  138. package/extensions/magpie/icons/novita-color.svg +1 -0
  139. package/extensions/magpie/icons/nvidia-color.svg +1 -0
  140. package/extensions/magpie/icons/ollama.svg +1 -0
  141. package/extensions/magpie/icons/omlx.svg +1 -0
  142. package/extensions/magpie/icons/omo.svg +1 -0
  143. package/extensions/magpie/icons/omp.svg +1 -0
  144. package/extensions/magpie/icons/openai.svg +1 -0
  145. package/extensions/magpie/icons/openchamber.svg +11 -0
  146. package/extensions/magpie/icons/openclaw.svg +1 -0
  147. package/extensions/magpie/icons/opencode.svg +1 -0
  148. package/extensions/magpie/icons/openmausbot.svg +1 -0
  149. package/extensions/magpie/icons/openrouter.svg +1 -0
  150. package/extensions/magpie/icons/openwebui.svg +1 -0
  151. package/extensions/magpie/icons/parasail.svg +1 -0
  152. package/extensions/magpie/icons/penguin-color.svg +1 -0
  153. package/extensions/magpie/icons/perplexity-color.svg +1 -0
  154. package/extensions/magpie/icons/phala-color.svg +1 -0
  155. package/extensions/magpie/icons/pi.svg +1 -0
  156. package/extensions/magpie/icons/pipellm-color.svg +5 -0
  157. package/extensions/magpie/icons/poe.svg +1 -0
  158. package/extensions/magpie/icons/ppio-color.svg +1 -0
  159. package/extensions/magpie/icons/prime-agent.svg +1 -0
  160. package/extensions/magpie/icons/qiniu-color.svg +1 -0
  161. package/extensions/magpie/icons/qoder.svg +1 -0
  162. package/extensions/magpie/icons/qwen-color.svg +1 -0
  163. package/extensions/magpie/icons/qwenpaw.svg +1 -0
  164. package/extensions/magpie/icons/raycast.svg +1 -0
  165. package/extensions/magpie/icons/reasonix-color.svg +48 -0
  166. package/extensions/magpie/icons/reka.svg +1 -0
  167. package/extensions/magpie/icons/replicate.svg +1 -0
  168. package/extensions/magpie/icons/roocode.svg +1 -0
  169. package/extensions/magpie/icons/rtk.png +0 -0
  170. package/extensions/magpie/icons/sambanova-color.svg +1 -0
  171. package/extensions/magpie/icons/sensenova-color.svg +1 -0
  172. package/extensions/magpie/icons/siliconcloud-color.svg +1 -0
  173. package/extensions/magpie/icons/snow.png +0 -0
  174. package/extensions/magpie/icons/snowflake-color.svg +1 -0
  175. package/extensions/magpie/icons/sophnet-color.svg +1 -0
  176. package/extensions/magpie/icons/spark-color.svg +1 -0
  177. package/extensions/magpie/icons/stepfun-color.svg +1 -0
  178. package/extensions/magpie/icons/straico-color.svg +1 -0
  179. package/extensions/magpie/icons/submodel-color.svg +1 -0
  180. package/extensions/magpie/icons/t3code.svg +4 -0
  181. package/extensions/magpie/icons/targon-color.svg +1 -0
  182. package/extensions/magpie/icons/tencent-color.svg +1 -0
  183. package/extensions/magpie/icons/tencentcloud-color.svg +1 -0
  184. package/extensions/magpie/icons/tiangong-color.svg +1 -0
  185. package/extensions/magpie/icons/together-color.svg +1 -0
  186. package/extensions/magpie/icons/trae.svg +1 -0
  187. package/extensions/magpie/icons/typesafe.png +0 -0
  188. package/extensions/magpie/icons/upstage-color.svg +1 -0
  189. package/extensions/magpie/icons/venice-color.svg +1 -0
  190. package/extensions/magpie/icons/vercel.svg +1 -0
  191. package/extensions/magpie/icons/vertexai-color.svg +1 -0
  192. package/extensions/magpie/icons/vllm-color.svg +1 -0
  193. package/extensions/magpie/icons/volcengine-color.svg +1 -0
  194. package/extensions/magpie/icons/voyage-color.svg +1 -0
  195. package/extensions/magpie/icons/vscode.svg +1 -0
  196. package/extensions/magpie/icons/wenxin-color.svg +1 -0
  197. package/extensions/magpie/icons/windsurf.svg +1 -0
  198. package/extensions/magpie/icons/workbuddy-color.svg +1 -0
  199. package/extensions/magpie/icons/workersai-color.svg +1 -0
  200. package/extensions/magpie/icons/xai.svg +1 -0
  201. package/extensions/magpie/icons/xiaomimimo.svg +1 -0
  202. package/extensions/magpie/icons/xinference-color.svg +1 -0
  203. package/extensions/magpie/icons/yi.svg +1 -0
  204. package/extensions/magpie/icons/yuanbao-color.svg +1 -0
  205. package/extensions/magpie/icons/yylx.svg +5 -0
  206. package/extensions/magpie/icons/zai.svg +1 -0
  207. package/extensions/magpie/icons/zcode.png +0 -0
  208. package/extensions/magpie/icons/zed.svg +1 -0
  209. package/extensions/magpie/icons/zenmux.svg +1 -0
  210. package/extensions/magpie/icons/zeroone.svg +1 -0
  211. package/extensions/magpie/icons/zhipu-color.svg +1 -0
  212. package/extensions/magpie/icons/zoo-code.png +0 -0
  213. package/extensions/magpie/index.html +572 -0
  214. package/extensions/magpie/kernel.js +274 -0
  215. package/extensions/magpie/keyspend.js +63 -0
  216. package/extensions/magpie/layout.js +26 -0
  217. package/extensions/magpie/manifest.json +37 -0
  218. package/extensions/magpie/navlamp.js +47 -0
  219. package/extensions/magpie/omarchy.css +72 -0
  220. package/extensions/magpie/omarchy.js +46 -0
  221. package/extensions/magpie/passerrors.js +21 -0
  222. package/extensions/magpie/pluginpages.js +405 -0
  223. package/extensions/magpie/plugins/gateway/client.js +1194 -0
  224. package/extensions/magpie/plugins/gateway/keys.js +972 -0
  225. package/extensions/magpie/plugins/gateway/settings.js +459 -0
  226. package/extensions/magpie/plugins/library/client.js +6788 -0
  227. package/extensions/magpie/plugins/library/i18n.js +3228 -0
  228. package/extensions/magpie/plugins/library/library.css +848 -0
  229. package/extensions/magpie/plugins/otel/i18n.js +48 -0
  230. package/extensions/magpie/plugins/otel/page.js +189 -0
  231. package/extensions/magpie/plugins/redact/client.js +46 -0
  232. package/extensions/magpie/plugins/redact/demo.js +461 -0
  233. package/extensions/magpie/plugins/redact/i18n.js +432 -0
  234. package/extensions/magpie/plugins/redact/page.js +985 -0
  235. package/extensions/magpie/plugins/redact/redact.css +385 -0
  236. package/extensions/magpie/plugins/redact/settings.js +248 -0
  237. package/extensions/magpie/plugins/redact/xq_dark.jpg +0 -0
  238. package/extensions/magpie/plugins/redact/xq_dark.webm +0 -0
  239. package/extensions/magpie/plugins/redact/xq_light.jpg +0 -0
  240. package/extensions/magpie/plugins/redact/xq_light.webm +0 -0
  241. package/extensions/magpie/plugins/routing/client.js +8593 -0
  242. package/extensions/magpie/plugins/routing/i18n.js +2696 -0
  243. package/extensions/magpie/plugins/routing/routing.css +1111 -0
  244. package/extensions/magpie/plugins/search/settings.js +460 -0
  245. package/extensions/magpie/plugins/sessions/client.js +1622 -0
  246. package/extensions/magpie/plugins/sessions/i18n.js +360 -0
  247. package/extensions/magpie/plugins/sessions/sessions.css +181 -0
  248. package/extensions/magpie/plugins/sessions/settings.js +100 -0
  249. package/extensions/magpie/plugins/usage/client.js +150 -0
  250. package/extensions/magpie/plugins/usage/i18n.js +984 -0
  251. package/extensions/magpie/plugins/usage/page.js +4307 -0
  252. package/extensions/magpie/plugins.css +319 -0
  253. package/extensions/magpie/plugins.js +1926 -0
  254. package/extensions/magpie/profilerename.js +27 -0
  255. package/extensions/magpie/purposes.js +2 -0
  256. package/extensions/magpie/quitask.js +79 -0
  257. package/extensions/magpie/quotaall.js +142 -0
  258. package/extensions/magpie/ratewindows.js +123 -0
  259. package/extensions/magpie/registry.js +87 -0
  260. package/extensions/magpie/resetask.js +137 -0
  261. package/extensions/magpie/ruleedit.js +53 -0
  262. package/extensions/magpie/scrollends.js +91 -0
  263. package/extensions/magpie/settingshints.js +75 -0
  264. package/extensions/magpie/signsheets.js +86 -0
  265. package/extensions/magpie/start.js +26 -0
  266. package/extensions/magpie/syncfiles.js +216 -0
  267. package/extensions/magpie/theme.js +29 -0
  268. package/extensions/magpie/traywindows.js +41 -0
  269. package/extensions/magpie/webfonts.js +130 -0
  270. package/extensions/magpie/whatsnew.js +252 -0
  271. package/extensions/magpie.zip +0 -0
  272. package/knowledge/apps/baijiahao.md +30 -0
  273. package/knowledge/apps/bilibili.md +1 -0
  274. package/knowledge/apps/dedao.md +33 -0
  275. package/knowledge/apps/feishu.md +35 -0
  276. package/knowledge/apps/gmail.md +33 -0
  277. package/knowledge/apps/jike.md +31 -0
  278. package/knowledge/apps/kuaishou.md +34 -0
  279. package/knowledge/apps/linkedin.md +33 -0
  280. package/knowledge/apps/qqmail.md +30 -0
  281. package/knowledge/apps/telegram.md +27 -0
  282. package/knowledge/apps/toutiao.md +32 -0
  283. package/knowledge/apps/uber-eats.md +26 -0
  284. package/knowledge/apps/uber.md +27 -0
  285. package/knowledge/apps/whatsapp.md +27 -0
  286. package/knowledge/apps/x.md +32 -0
  287. package/knowledge/apps/xiaohongshu.md +1 -0
  288. package/knowledge/apps/zhihu.md +1 -0
  289. package/knowledge/sites/bilibili.com/SITE.md +50 -0
  290. package/knowledge/sites/bilibili.com/references/view-fields.md +17 -0
  291. package/knowledge/sites/github.com/SITE.md +51 -0
  292. package/knowledge/sites/github.com/references/repo-fields.md +13 -0
  293. package/knowledge/sites/x.com/SITE.md +47 -0
  294. package/knowledge/sites/xiaohongshu.com/SITE.md +48 -0
  295. package/knowledge/sites/xiaohongshu.com/references/note-fields.md +18 -0
  296. package/knowledge/sites/youtube.com/SITE.md +49 -0
  297. package/knowledge/sites/youtube.com/references/innertube-fields.md +14 -0
  298. package/knowledge/sites/zhihu.com/SITE.md +47 -0
  299. package/knowledge/sites/zhihu.com/references/api-fields.md +21 -0
  300. package/lib/mobile/extension-catalog.js +41 -1
  301. package/lib/mobile/extension-data.js +224 -0
  302. package/lib/mobile/extension-dev.js +3 -1
  303. package/lib/mobile/file-ops.js +252 -0
  304. package/lib/mobile/history-derive.js +80 -4
  305. package/lib/mobile/host-bridge/endpoints.js +48 -6
  306. package/lib/mobile/host-bridge/event-synthesis.js +33 -6
  307. package/lib/mobile/jobs-control.js +118 -0
  308. package/lib/mobile/memory.js +635 -0
  309. package/lib/mobile/model-badges.js +77 -0
  310. package/lib/mobile/pet-tools.js +50 -4
  311. package/lib/mobile/pets.js +214 -18
  312. package/lib/mobile/phone/access.js +85 -0
  313. package/lib/mobile/phone/evidence.js +228 -0
  314. package/lib/mobile/phone/gate.js +290 -0
  315. package/lib/mobile/phone/grounding.js +314 -0
  316. package/lib/mobile/phone/guard.js +465 -0
  317. package/lib/mobile/phone/irreversible.js +88 -8
  318. package/lib/mobile/phone/knowledge-tools.js +665 -0
  319. package/lib/mobile/phone/learn.js +329 -0
  320. package/lib/mobile/phone/redact.js +237 -0
  321. package/lib/mobile/phone/risk.js +95 -0
  322. package/lib/mobile/phone/routes.js +28 -1
  323. package/lib/mobile/phone/service.js +23 -0
  324. package/lib/mobile/phone/site-commands.js +271 -0
  325. package/lib/mobile/phone/step-summary.js +201 -0
  326. package/lib/mobile/phone/stop-summary.js +235 -0
  327. package/lib/mobile/phone/tool-groups.js +125 -0
  328. package/lib/mobile/phone/tools.js +589 -0
  329. package/lib/mobile/phone/untrusted.js +65 -0
  330. package/lib/mobile/phone-knowledge.js +20 -4
  331. package/lib/mobile/schedule-edit.js +500 -0
  332. package/lib/mobile/schedule.js +5 -0
  333. package/lib/mobile/send-continuation.js +52 -0
  334. package/lib/mobile/session-models.js +27 -31
  335. package/lib/mobile/session-send.js +12 -12
  336. package/lib/mobile/skill-import.js +338 -0
  337. package/lib/mobile/skill-store.js +524 -0
  338. package/lib/routes/mobile-api.js +42 -0
  339. package/package.json +7 -6
  340. package/skills/skill-authoring/SKILL.md +46 -0
  341. package/skills/skill-authoring/references/examples.md +82 -0
  342. package/skills/skill-authoring/references/frontmatter-and-fields.md +46 -0
  343. package/skills/skill-authoring/scripts/check_skill.py +167 -0
  344. package/skills/skill-authoring/scripts/manifest.json +3 -0
  345. package/knowledge/sites/bilibili.com.md +0 -27
  346. package/knowledge/sites/github.com.md +0 -27
  347. package/knowledge/sites/x.com.md +0 -28
  348. package/knowledge/sites/xiaohongshu.com.md +0 -27
  349. package/knowledge/sites/youtube.com.md +0 -28
  350. package/knowledge/sites/zhihu.com.md +0 -27
@@ -0,0 +1,228 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * 证据账本 + 回答来源挑选。
5
+ *
6
+ * 一轮跑下来,助手实际读到的材料(读屏、读到的网页、命令输出、读到的文件、工具结果里的事实)
7
+ * 记在这本账上:**脱敏、截短、去重、可回看**。交给核查、挑来源、落运行记录都读同一本账,
8
+ * 各看各的就没有「核对时看得见、回答时对不上」这种事。
9
+ *
10
+ * 回答下面的来源**由代码从账本里挑,不靠模型写**(照 Mika `core/verify/AnswerSources.kt`):
11
+ * 回答里的事实对得上的那几条观察,至多 6 处,指回具体步骤(哪一步、哪个 App/网址、标题、
12
+ * 画面编号)。回答里没有可对的事实时用最后一条观察——那是回答说的那个结果所在的地方。
13
+ *
14
+ * 与脱敏(M4)的关系:**共用同一份规则**。账本进账前一律先过 `phone/redact.js` 的
15
+ * `redact(text)`——运行记录、证据账本、网络数据用的是同一套口径,一处改了都跟着改。
16
+ * 万一那份还没就位(单独拷走这个模块调试),退回本地最小遮罩顶上,不会裸奔。
17
+ */
18
+
19
+ const grounding = require('./grounding.js');
20
+
21
+ /** 回答下面至多列这么多处来源。 */
22
+ const MAX_SOURCES = 6;
23
+
24
+ /** 账本总字数的安全上限:超了从最早的丢起(正常任务碰不到)。 */
25
+ const STORE_CHARS = 400_000;
26
+
27
+ /** 读屏 / 网页 / 别的 AI 的回答每条至多这么多字。 */
28
+ const READ_CHARS = 6_000;
29
+
30
+ /** 命令输出 / 文件内容 / 工具事实每条至多这么多字(按头尾截短)。 */
31
+ const OUTPUT_CHARS = 4_000;
32
+
33
+ const OUTPUT_KINDS = new Set(['output', 'fact', 'file']);
34
+
35
+ /**
36
+ * 找统一脱敏模块(M4 的 `phone/redact.js`)。存在就共用它的 `redact(text)`;
37
+ * 找不到(模块被单独拷走调试)返回 null,用下面的本地最小遮罩顶上。
38
+ */
39
+ function loadSharedRedactor() {
40
+ for (const candidate of ['./redact.js', '../../redact.js', '../../../redact.js']) {
41
+ try {
42
+ const mod = require(candidate);
43
+ if (mod && typeof mod.redact === 'function') return { fn: mod.redact };
44
+ if (mod && typeof mod.evidence === 'function') return { fn: mod.evidence };
45
+ if (mod && typeof mod.text === 'function') return { fn: mod.text };
46
+ } catch {
47
+ // 还没落地 / 路径不对,继续试下一个候选。
48
+ }
49
+ }
50
+ return null;
51
+ }
52
+
53
+ const sharedRedactor = loadSharedRedactor();
54
+
55
+ const SK_KEY = /\bsk-[A-Za-z0-9_-]{16,}/g;
56
+ const GOOGLE_KEY = /\bAIza[0-9A-Za-z_-]{30,}/g;
57
+ const GOOGLE_AUTH_KEY = /\bAQ\.[0-9A-Za-z_-]{20,}/g;
58
+ const GITHUB_TOKEN = /\bgh[pousr]_[A-Za-z0-9]{20,}/g;
59
+ const BEARER = /\bBearer\s+[A-Za-z0-9._~+/=-]{8,}/g;
60
+ const CODE_WORD = /验证|校验码|动态码|动态密码|确认码|安全码|短信|口令|\b(?:code|otp|passcode|pin|2fa)\b/i;
61
+ const CODE_DIGITS = /(?<![\d.¥$€£])\d(?:[ -]?\d){3,7}(?!\d)(?!\.\d)(?!,\d{3}(?!\d))/g;
62
+ const MASK = '***';
63
+ const CODE_WINDOW = 24;
64
+
65
+ /** 本地最小遮罩:Key、Bearer、以及「验证码/短信/code」附近 4–8 位数字。近似 Mika `Redactor.evidence`。 */
66
+ function localRedact(text) {
67
+ let out = String(text == null ? '' : text)
68
+ .replace(SK_KEY, MASK)
69
+ .replace(GOOGLE_KEY, MASK)
70
+ .replace(GOOGLE_AUTH_KEY, MASK)
71
+ .replace(GITHUB_TOKEN, MASK)
72
+ .replace(BEARER, `Bearer ${MASK}`);
73
+ // 一次性码:词与数字之间 24 字以内。
74
+ const words = [];
75
+ CODE_WORD.lastIndex = 0;
76
+ let w;
77
+ while ((w = CODE_WORD.exec(out)) !== null) words.push([w.index, w.index + w[0].length]);
78
+ if (words.length > 0) {
79
+ out = out.replace(CODE_DIGITS, (m, offset) => {
80
+ const near = words.some(([s, e]) => (offset - e >= 0 && offset - e <= CODE_WINDOW) || (s - (offset + m.length) >= 0 && s - (offset + m.length) <= CODE_WINDOW));
81
+ return near ? MASK : m;
82
+ });
83
+ }
84
+ return out;
85
+ }
86
+
87
+ /** 一条材料进账前的脱敏(账本、来源、给模型看都用这一份)。 */
88
+ function redactText(text) {
89
+ if (sharedRedactor) return sharedRedactor.fn(text);
90
+ return localRedact(text);
91
+ }
92
+
93
+ function clip(kind, text) {
94
+ const value = String(text == null ? '' : text);
95
+ if (OUTPUT_KINDS.has(kind)) {
96
+ if (value.length <= OUTPUT_CHARS) return value;
97
+ return `${value.slice(0, (OUTPUT_CHARS * 3) / 4 | 0)}\n…(中间省略)…\n${value.slice(-(OUTPUT_CHARS / 4 | 0))}`;
98
+ }
99
+ return value.length <= READ_CHARS ? value : value.slice(0, READ_CHARS);
100
+ }
101
+
102
+ /**
103
+ * 造一本证据账本。
104
+ *
105
+ * @param {{capChars?:number}} [opts]
106
+ */
107
+ function createLedger(opts) {
108
+ const capChars = opts && Number.isFinite(opts.capChars) && opts.capChars > 0 ? opts.capChars : STORE_CHARS;
109
+ const entries = [];
110
+ let total = 0;
111
+ let seq = 0;
112
+
113
+ /**
114
+ * 记一条材料。脱敏、截短后为空的返回 null(不占账)。
115
+ *
116
+ * @param {{kind?:string, text:string, place?:string, title?:string, step?:number, source?:string, mediaIds?:number[], atMs?:number}} item
117
+ * @returns {object|null} 记下的那一条(带 id / step / atMs)
118
+ */
119
+ function record(item) {
120
+ if (!item || typeof item !== 'object' || typeof item.text !== 'string') return null;
121
+ const kind = typeof item.kind === 'string' && item.kind ? item.kind : 'read';
122
+ let text = clip(kind, redactText(item.text));
123
+ if (text.trim() === '') return null;
124
+ const place = item.place != null && item.place !== '' ? redactText(String(item.place)) : '';
125
+ // 同一原文 + 同一处只留最近一次(来处随时间更新)。
126
+ const dup = entries.findIndex((e) => e.text === text && e.place === place);
127
+ if (dup >= 0) {
128
+ total -= entries[dup].text.length;
129
+ entries.splice(dup, 1);
130
+ }
131
+ const stored = {
132
+ id: ++seq,
133
+ kind,
134
+ text,
135
+ place,
136
+ title: item.title != null && item.title !== '' ? redactText(String(item.title)) : '',
137
+ step: Number.isInteger(item.step) ? item.step : 0,
138
+ source: typeof item.source === 'string' ? item.source : '',
139
+ mediaIds: Array.isArray(item.mediaIds) ? item.mediaIds.filter(Number.isInteger) : [],
140
+ atMs: Number.isFinite(item.atMs) ? item.atMs : 0,
141
+ };
142
+ entries.push(stored);
143
+ total += stored.text.length;
144
+ while (total > capChars && entries.length > 1) total -= entries.shift().text.length;
145
+ return stored;
146
+ }
147
+
148
+ /** 账本里此刻的全部条目(按读到先后)。 */
149
+ function all() {
150
+ return entries.slice();
151
+ }
152
+
153
+ /** 这段原文(脱敏、截短后)是否已经在账本里。 */
154
+ function has(kind, text) {
155
+ const k = typeof kind === 'string' && kind ? kind : 'read';
156
+ const t = clip(k, redactText(text));
157
+ return entries.some((e) => e.text === t);
158
+ }
159
+
160
+ /** 回答里某条事实对得上的条目(按读到先后)。回答里没有可核对的事实时为空。 */
161
+ function supporting(answer, goal) {
162
+ return entries.filter((e) => {
163
+ const r = grounding.check({ answer: String(answer == null ? '' : answer), evidence: [e.text], goal: typeof goal === 'string' ? goal : '' });
164
+ return r.matched > 0 && r.claims.length > 0;
165
+ });
166
+ }
167
+
168
+ /**
169
+ * 挑来源:对得上回答的条目;一条都对不上时退回最后一条观察(回答说的结果就在那儿)。
170
+ * 至多 MAX_SOURCES 处,取最近的几条,指回步骤与画面编号。**只给指针,不带正文**。
171
+ *
172
+ * @param {string} answer
173
+ * @param {{goal?:string}} [opts]
174
+ * @returns {Array<{obsId:number,kind:string,place:string|null,title:string|null,step:number,mediaIds:number[],source:string|null}>}
175
+ */
176
+ function selectSources(answer, opts) {
177
+ const goal = opts && typeof opts.goal === 'string' ? opts.goal : '';
178
+ let hits = supporting(answer, goal);
179
+ if (hits.length === 0 && entries.length > 0) hits = [entries[entries.length - 1]];
180
+ return hits.slice(-MAX_SOURCES).map((e) => ({
181
+ obsId: e.id,
182
+ kind: e.kind,
183
+ place: e.place || null,
184
+ title: e.title || null,
185
+ step: e.step,
186
+ mediaIds: e.mediaIds.slice(),
187
+ source: e.source || null,
188
+ }));
189
+ }
190
+
191
+ return {
192
+ record,
193
+ all,
194
+ has,
195
+ supporting,
196
+ selectSources,
197
+ get size() { return entries.length; },
198
+ get totalChars() { return total; },
199
+ capChars,
200
+ };
201
+ }
202
+
203
+ /** 直接作用于账本的 `record(item)`(等价于 `ledger.record(item)`)。 */
204
+ function record(ledger, item) {
205
+ if (!ledger || typeof ledger.record !== 'function') return null;
206
+ return ledger.record(item);
207
+ }
208
+
209
+ /**
210
+ * 直接作用于账本的 `selectSources(answer, ledger, opts)`(等价于 `ledger.selectSources`)。
211
+ * 返回 Mika `AnswerSource` 形状的来源数组,至多 6 处,不带正文。
212
+ */
213
+ function selectSources(answer, ledger, opts) {
214
+ if (!ledger || typeof ledger.selectSources !== 'function') return [];
215
+ return ledger.selectSources(answer, opts);
216
+ }
217
+
218
+ module.exports = {
219
+ createLedger,
220
+ record,
221
+ selectSources,
222
+ redactText,
223
+ clip,
224
+ MAX_SOURCES,
225
+ STORE_CHARS,
226
+ READ_CHARS,
227
+ OUTPUT_CHARS,
228
+ };
@@ -0,0 +1,290 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * 付款/不可逆动作的「申报 → 同意 → 单次放行」闸门:纯逻辑,无 IO,不读屏、不推卡。
5
+ *
6
+ * ## 为什么要有它
7
+ *
8
+ * 现在「用户同意一次」放行的是任意下一步:用户看着「给张三发一句你好」点了同意,模型下一步
9
+ * 换成「给李四转账」也照放 —— 这就是批里掉包。Mika 的 `ConfirmationGate.kt` 把同意**绑死在
10
+ * 申报内容**上:同一 App、同类动作、目标出现在屏上、要发的字逐字相等、应付金额在屏上,五条
11
+ * 同时成立才放行,且只放行紧接着的那一个匹配操作;中间做了别的写操作、或过了 3 步,同意作废。
12
+ * 这里把这套规则搬成纯 JS。
13
+ *
14
+ * ## 谁做什么
15
+ *
16
+ * 本模块只管逻辑:模型申报(`declare`)、用户同意(`approve` / `reject`)、拿一步来核(`check`)、
17
+ * 作废(`invalidate`)、看状态(`state`)。**审批卡推不推、走哪条审批桥、拿到什么结论,都由调用方决定** ——
18
+ * 我们复用宿主已有的 `approval/request`(见 `confirm.js`),不新开裁决路。调用方在拿到
19
+ * `allowed-once` 后调 `approve(stepIndex)` 把这份同意立起来,之后每一步写操作前调 `check(...)`。
20
+ *
21
+ * ## 一句话记住的规则
22
+ *
23
+ * - 同意只放行「紧接着的一个」匹配操作;`check` 一核就把这份同意用掉,不论最后是放行还是对不上。
24
+ * - 对不上、超 3 步、换了 App / 动作 / 目标 / 文字 / 金额,一律 `mismatch`,不执行。
25
+ * - **付款永远不放行**:`check` 碰到付款动作返回 `takeover`(交给人接管),不是 `allowed`。
26
+ *
27
+ * 与 Mika 的差别(我们只有扁平文字,没有元素树与 bounds):发送对象的「标题栏位置」这一条
28
+ * 退化成「目标文字出现在整屏文字里」;金额也从「可滚动区内外、离按钮最近」退化成「带币种的
29
+ * 应付金额出现在整屏文字里」。其余四条(同 App、同动作、逐字相等、单次/过期)原样保留。
30
+ */
31
+
32
+ const { matchLabel, payableAmounts, hasTotalLabel } = require('./irreversible.js');
33
+
34
+ /** 同意后过了这么多步还没用到就作废。 */
35
+ const MAX_STEPS = 3;
36
+
37
+ /** 目标文字比对只看前这么多字,避免长段落整段比。 */
38
+ const MATCH_CHARS = 20;
39
+
40
+ /** 模型/调用方用的语义动作 → 本模块的分类。 */
41
+ const ACTION_KIND = {
42
+ pay: 'pay', 支付: 'pay', 付款: 'pay',
43
+ order: 'order', buy: 'order', purchase: 'order', checkout: 'order', 下单: 'order', 购买: 'order', 結算: 'order', 结算: 'order',
44
+ send: 'send', 发送: 'send', 发布: 'send', 发表: 'send', post: 'send',
45
+ submit: 'submit', 提交: 'submit',
46
+ delete: 'delete', 删除: 'delete', 清空: 'delete', remove: 'delete',
47
+ change: 'change', 其他: 'other', other: 'other',
48
+ };
49
+
50
+ function norm(text) {
51
+ return typeof text === 'string' ? text.replace(/\s+/g, '').toLowerCase() : '';
52
+ }
53
+
54
+ /** 从 target 里判是哪一类动作(拿不到语义动作名时用)。 */
55
+ function kindFromTarget(target) {
56
+ const hit = target ? matchLabel(target) : null;
57
+ if (!hit) return 'other';
58
+ if (hit.kind === 'payment') {
59
+ // 支付/付款/买单/收银 是「付款」(永不自动放行),下单/购买/结算/订单 是「下单」(可按金额放行)。
60
+ return /支付|付款|买单|收银|转账/.test(target) ? 'pay' : 'order';
61
+ }
62
+ if (hit.kind === 'send') return 'send';
63
+ if (hit.kind === 'submit') return 'submit';
64
+ if (hit.kind === 'delete') return 'delete';
65
+ return 'other';
66
+ }
67
+
68
+ /** 语义动作 → 分类;语义名认不出就回落到按 target 判。 */
69
+ function declarationKind(action, target) {
70
+ const name = typeof action === 'string' ? action.trim().toLowerCase() : '';
71
+ // irreversible.js 把「付款」和「下单」都归成 payment;gate 需要分开(付款永不自动放行、
72
+ // 下单可按金额放行),所以拿到 payment 时按目标文字再分一次。
73
+ if (name === 'payment') return kindFromTarget(target);
74
+ if (ACTION_KIND[name]) return ACTION_KIND[name];
75
+ return kindFromTarget(target);
76
+ }
77
+
78
+ function numOf(text) {
79
+ const m = String(text == null ? '' : text).replace(/,/g, '').match(/\d+(?:\.\d+)?/);
80
+ return m ? Number(m[0]) : null;
81
+ }
82
+
83
+ function createGate() {
84
+ /** 等用户表态的申报单。 */
85
+ let pending = null;
86
+ /** 用户已同意、还没用掉的那一次:{ declaration, step }。 */
87
+ let grant = null;
88
+ /** 用户拒过的同类操作(同类动作 + 同一 App),不再问。 */
89
+ const declined = new Set();
90
+ let lastDecision = null;
91
+
92
+ function record(decision) {
93
+ lastDecision = decision;
94
+ return decision;
95
+ }
96
+
97
+ /** 把外部传入的申报整理成一份干净的申报单。 */
98
+ function snapshot(input) {
99
+ const src = input && typeof input === 'object' ? input : {};
100
+ const app = typeof src.app === 'string' ? src.app.trim() : '';
101
+ const target = typeof src.target === 'string' ? src.target.trim() : '';
102
+ const amountText = typeof src.amountText === 'string' ? src.amountText.trim() : '';
103
+ const textToSend = typeof src.textToSend === 'string' ? src.textToSend : null;
104
+ const rawAction = typeof src.action === 'string' ? src.action.trim() : '';
105
+ return {
106
+ app, target, amountText, textToSend, rawAction,
107
+ action: declarationKind(rawAction, target),
108
+ };
109
+ }
110
+
111
+ /**
112
+ * 模型主动申报一份要确认的操作。生成申报单并挂起,等用户表态(由调用方推卡、拿结论)。
113
+ * @returns {{status:'pending'|'declined'|'invalid', declaration?:object, reason?:string}}
114
+ */
115
+ function declare(input) {
116
+ const d = snapshot(input);
117
+ if (!d.app || (!d.rawAction && !d.target)) {
118
+ record({ decision: 'invalid', reason: '申报缺少 App 或动作/目标' });
119
+ return { status: 'invalid', reason: '申报缺少 App 或动作/目标' };
120
+ }
121
+ if (declined.has(`${d.action}|${d.app}`)) {
122
+ record({ decision: 'declined', reason: `用户已拒绝过 ${d.app} 上的同类操作` });
123
+ return { status: 'declined', declaration: d, reason: '这一 App 上的同类操作用户已拒绝过,不再问' };
124
+ }
125
+ pending = d;
126
+ record({ decision: 'pending' });
127
+ return { status: 'pending', declaration: d };
128
+ }
129
+
130
+ /**
131
+ * 用户同意:把挂起的申报单立成一次性同意(由调用方拿到 `allowed-once` 后调用)。
132
+ * @param {number} [stepIndex] 同意发生在第几步(过期按它算)
133
+ */
134
+ function approve(stepIndex) {
135
+ const step = Number.isFinite(stepIndex) ? stepIndex : 0;
136
+ if (!pending) return { status: 'no-pending' };
137
+ const declaration = pending;
138
+ pending = null;
139
+ grant = { declaration, step };
140
+ record({ decision: 'granted', declaration });
141
+ return { status: 'granted', declaration };
142
+ }
143
+
144
+ /** 用户拒绝:记下这一类,同类同 App 的申报以后直接 declined,不再打扰。 */
145
+ function reject() {
146
+ if (!pending) return { status: 'no-pending' };
147
+ const declaration = pending;
148
+ pending = null;
149
+ declined.add(`${declaration.action}|${declaration.app}`);
150
+ record({ decision: 'rejected', declaration });
151
+ return { status: 'rejected', declaration };
152
+ }
153
+
154
+ /**
155
+ * 拿即将执行的这一步来核。核完这份同意就用掉(不论结果),保证「一份同意只放行一步」。
156
+ *
157
+ * @param {object} input
158
+ * @param {string} input.app 当前前台包名
159
+ * @param {string} [input.action] 这一步的语义动作(tap/type/send/order/pay…);缺省时按 target 判
160
+ * @param {string} [input.target] 这一步作用的文字
161
+ * @param {string} [input.screenText] 当前整屏文字
162
+ * @param {number} [input.stepIndex] 当前步数
163
+ * @param {string} [input.textToSend] 这一步实际要发出/输入的文字(发送类必填,用来逐字比)
164
+ * @returns {{decision:'allowed'|'takeover'|'mismatch'|'no-grant', reason?:string}}
165
+ */
166
+ function check(input) {
167
+ const src = input && typeof input === 'object' ? input : {};
168
+ const app = typeof src.app === 'string' ? src.app.trim() : '';
169
+ const target = typeof src.target === 'string' ? src.target.trim() : '';
170
+ const screenText = typeof src.screenText === 'string' ? src.screenText : '';
171
+ const step = Number.isFinite(src.stepIndex) ? src.stepIndex : 0;
172
+ const textToSend = typeof src.textToSend === 'string' ? src.textToSend : null;
173
+ const currentKind = declarationKind(src.action, target);
174
+
175
+ // 付款永不自动放行:只走接管;顺手把可能挂着的同意作废掉,免得它被后一步误用。
176
+ if (currentKind === 'pay') {
177
+ grant = null;
178
+ return record({ decision: 'takeover', reason: '付款永不自动放行,交给人接管' });
179
+ }
180
+ if (!grant) {
181
+ return record({ decision: 'no-grant', reason: '没有可用的同意' });
182
+ }
183
+
184
+ // 有同意就只这一次:先取走,再判,无论对错都不留。
185
+ const used = grant;
186
+ grant = null;
187
+ const d = used.declaration;
188
+
189
+ // 申报本身是付款:同样只走接管。
190
+ if (d.action === 'pay') {
191
+ return record({ decision: 'takeover', reason: '申报的是付款,只走接管' });
192
+ }
193
+
194
+ const reason = firstMismatch(d, currentKind, app, target, screenText, textToSend, step, used.step);
195
+ if (reason) return record({ decision: 'mismatch', reason });
196
+ return record({ decision: 'allowed' });
197
+ }
198
+
199
+ /** 五条同时成立才放行;返回第一条不成立的理由,全成立返回 null。 */
200
+ function firstMismatch(d, currentKind, app, target, screenText, textToSend, step, grantStep) {
201
+ if (step - grantStep > MAX_STEPS) return `同意已过期(超过 ${MAX_STEPS} 步)`;
202
+ if (d.action !== currentKind) return `申报的是 ${d.action},这一步是 ${currentKind}`;
203
+ if (!app || app !== d.app) return `申报在 ${d.app || '(无)'},屏上却是 ${app || '(读不到)'}`;
204
+ if (!targetShown(d.target, screenText)) return `申报的目标「${d.target}」不在屏上`;
205
+ if (d.action === 'send' && !contentMatches(d.textToSend, textToSend)) {
206
+ return `要发的字与申报不逐字相等`;
207
+ }
208
+ if (amountBinds(d.action, screenText) && !amountShown(d.amountText, screenText)) {
209
+ return `申报的金额「${d.amountText || '(无)'}」没有在屏上对上`;
210
+ }
211
+ return null;
212
+ }
213
+
214
+ /** 申报的目标出现在整屏文字里(去空白、不区分大小写,至少两个字,比前 MATCH_CHARS 字)。 */
215
+ function targetShown(target, screenText) {
216
+ const t = norm(target).slice(0, MATCH_CHARS);
217
+ if (t.length < 2) return false;
218
+ return norm(screenText).includes(t);
219
+ }
220
+
221
+ /** 要发的字逐字相等;申报被截短(以 … 结尾)时比前缀。 */
222
+ function contentMatches(declaredText, actualText) {
223
+ if (typeof declaredText !== 'string' || typeof actualText !== 'string') return false;
224
+ const capped = declaredText.endsWith('…');
225
+ const declared = norm(declaredText.replace(/…$/, ''));
226
+ const actual = norm(actualText);
227
+ return capped ? actual.startsWith(declared) : actual === declared;
228
+ }
229
+
230
+ /** 金额是否参与这次核对:下单/付款始终要;提交/删除只在屏上有合计类字样时要;发送不要。 */
231
+ function amountBinds(kind, screenText) {
232
+ if (kind === 'order' || kind === 'pay') return true;
233
+ if (kind === 'submit' || kind === 'delete') return hasTotalLabel(screenText);
234
+ return false;
235
+ }
236
+
237
+ /** 屏上有一个带币种的应付金额等于申报金额(优惠金额已被 payableAmounts 排除)。 */
238
+ function amountShown(amountText, screenText) {
239
+ const want = numOf(amountText);
240
+ if (want === null) return false;
241
+ return payableAmounts(screenText).some((value) => value === want);
242
+ }
243
+
244
+ /**
245
+ * 显式把「刚放行的这一步已经执行了」记下来。因为 `check` 一核就自动用掉同意,正常流程里这
246
+ * 里通常已经没有可用的同意;留着它是给调用方一个明确的收尾口子(也方便审计)。
247
+ * @returns {{consumed:boolean, declaration?:object}}
248
+ */
249
+ function consume() {
250
+ if (!grant) return { consumed: false };
251
+ const declaration = grant.declaration;
252
+ grant = null;
253
+ return { consumed: true, declaration };
254
+ }
255
+
256
+ /**
257
+ * 作废:做了别的写操作(点了别处、滑了、返回、开了别的 App)就调它。只清掉已立起的同意,
258
+ * 不动还没表态的申报单(那份还没被同意,谈不上作废)。
259
+ */
260
+ function invalidate() {
261
+ const had = Boolean(grant);
262
+ grant = null;
263
+ return { invalidated: had };
264
+ }
265
+
266
+ /** 当前状态快照(拷贝,外部改不到内部)。 */
267
+ function state() {
268
+ return {
269
+ pending: pending ? { ...pending } : null,
270
+ grant: grant ? { declaration: { ...grant.declaration }, step: grant.step } : null,
271
+ declined: [...declined],
272
+ lastDecision,
273
+ };
274
+ }
275
+
276
+ return { declare, approve, reject, check, consume, invalidate, state };
277
+ }
278
+
279
+ module.exports = {
280
+ createGate,
281
+ MAX_STEPS,
282
+ MATCH_CHARS,
283
+ // 下面几个导出给守卫单独钉规则,不必经 createGate。
284
+ declarationKind,
285
+ amountBinds: (kind, screenText) => {
286
+ if (kind === 'order' || kind === 'pay') return true;
287
+ if (kind === 'submit' || kind === 'delete') return hasTotalLabel(screenText);
288
+ return false;
289
+ },
290
+ };