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,329 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * 从实际跑过的任务里沉淀 App / 网站知识草稿。
5
+ *
6
+ * 一次任务收尾后,把用过的动作序列(打开哪个 App / 网站、依次点了什么、在哪个框输入了什么)
7
+ * 归纳成一份「知识草稿」,**写进待审目录**,不直接改正式的 `knowledge/apps/*` 或
8
+ * `knowledge/sites/*`。人工看过、收下之后才并入正式知识——所以草稿与正式知识是两份东西。
9
+ *
10
+ * 知识只讲事实(入口在哪、界面怎么组织、走到哪一步会发生什么),不写行为规则;行为规则归
11
+ * 审批层。草稿里也会把这一次的具体值(输入过的文字、长数字串)换成 `<参数>`,避免把订单号、
12
+ * 手机号、人名固化进知识。
13
+ *
14
+ * 脱敏优先用仓库里已有的 `redact.js`(存在就用),不存在时走这里一个保守的兜底并在代码里
15
+ * 留了清楚的 TODO。见 `desensitize()`。
16
+ *
17
+ * 待审目录:默认插件根下的 `.knowledge-drafts/`,可用 `DSH_PHONE_KNOWLEDGE_DRAFT_DIR` 覆盖。
18
+ * 它**不在** `knowledge/` 里,也不在发布声明的打包路径里,所以草稿不会被当成正式知识发出去。
19
+ */
20
+
21
+ const fs = require('node:fs');
22
+ const path = require('node:path');
23
+
24
+ /** 待审目录:插件根下的 `.knowledge-drafts/`。 */
25
+ function draftsDir() {
26
+ const override = process.env.DSH_PHONE_KNOWLEDGE_DRAFT_DIR;
27
+ if (typeof override === 'string' && override.trim()) return override.trim();
28
+ // learn.js 在 lib/mobile/phone/ 下,往上三层是插件根。
29
+ return path.join(__dirname, '..', '..', '..', '.knowledge-drafts');
30
+ }
31
+
32
+ /* ── 脱敏 ──────────────────────────────────────────────────────────────── */
33
+
34
+ /**
35
+ * 找现成的脱敏模块。同目录的 `phone/redact.js` 就是统一的那一份(运行记录、证据账本、
36
+ * 网络数据共用同一个实现);路径对不上时再往下试旧位置,最后才退保守兜底。
37
+ */
38
+ function loadRedactor() {
39
+ const candidates = ['./redact.js', '../redact.js', '../../redact.js'];
40
+ for (const candidate of candidates) {
41
+ try {
42
+ const mod = require(candidate);
43
+ if (typeof mod === 'function') return { fn: mod, source: candidate };
44
+ if (mod && typeof mod.redact === 'function') return { fn: (t) => mod.redact(t), source: candidate };
45
+ } catch {
46
+ // 没装 / 路径不对,试下一个。
47
+ }
48
+ }
49
+ return null;
50
+ }
51
+
52
+ const REDACTOR = loadRedactor();
53
+
54
+ /** 兜底脱敏:把这次输入过的原文与较长的数字串换成 `<参数>`。 */
55
+ function fallbackRedact(text, sensitive) {
56
+ let out = String(text || '');
57
+ for (const value of sensitive) {
58
+ if (typeof value !== 'string') continue;
59
+ const needle = value.trim();
60
+ // 太短的词(如「好」「1」)换掉会把正文弄烂,跳过。
61
+ if (needle.length < 2) continue;
62
+ out = out.split(needle).join('<参数>');
63
+ }
64
+ // 6 位以上的连续数字(订单号、手机号、卡号一类)打码;版本号、年份一般不到 6 位。
65
+ out = out.replace(/\d{6,}/g, '<参数>');
66
+ return out;
67
+ }
68
+
69
+ /**
70
+ * 对一段文本做脱敏。
71
+ *
72
+ * TODO(脱敏):仓库里还没有 `lib/mobile/redact.js`。它应有的规则(对齐 Mika 的 SkillRedactor)是:
73
+ * 把「引号内的内容、2 位以上数字、金额、这次输入的文字」换成 `<参数>`(先 NFKC、中文数字与
74
+ * 分隔数字归一,长的先换、纯数字只换整段),保留 App 名字里自带的数字。补上后本模块自动改用。
75
+ * 现在先走 `fallbackRedact`,只换本次输入原文与 6 位以上数字串。
76
+ *
77
+ * @param {string} text 待脱敏文本
78
+ * @param {string[]} sensitive 这次任务里输入过的原文
79
+ */
80
+ function desensitize(text, sensitive = []) {
81
+ // 先换掉本次任务里确实输入过的原文(人名、地址这类 redactor 的模式不一定认识),
82
+ // 再交给统一脱敏模块跑一遍凭据/卡号/验证码那套;没有 redactor 时退回纯数字兜底。
83
+ let out = String(text || '');
84
+ for (const value of sensitive) {
85
+ if (typeof value !== 'string') continue;
86
+ const needle = value.trim();
87
+ if (needle.length < 2) continue;
88
+ out = out.split(needle).join('<参数>');
89
+ }
90
+ if (REDACTOR) return REDACTOR.fn(out);
91
+ return out.replace(/\d{6,}/g, '<参数>');
92
+ }
93
+
94
+ /* ── 动作序列 → 事实 ────────────────────────────────────────────────────── */
95
+
96
+ /** 从一段地址里抽主机名(小写);认不出就按 `/`、`:` 切第一段。 */
97
+ function hostOf(raw) {
98
+ const text = typeof raw === 'string' ? raw.trim() : '';
99
+ if (!text) return '';
100
+ try {
101
+ return new URL(text.includes('://') ? text : `https://${text}`).hostname.toLowerCase();
102
+ } catch {
103
+ return text.toLowerCase().split('/')[0].split(':')[0];
104
+ }
105
+ }
106
+
107
+ /** 一步动作里最能说明「动了哪儿」的标签。 */
108
+ function stepLabel(step) {
109
+ const args = (step && step.args) || {};
110
+ const raw = args.target || args.text || args.label || step.target || step.label || args.ref || step.ref || '';
111
+ return String(raw || '').trim();
112
+ }
113
+
114
+ /** 一步的动作名;兼容 `{action}` 与 `{name}` 两种写法。 */
115
+ function stepAction(step) {
116
+ return String((step && (step.action || step.name)) || '').trim().toLowerCase();
117
+ }
118
+
119
+ /**
120
+ * 把动作序列归成「经过的界面 / 做过的操作 / 需注意的现象」三组事实。
121
+ * 只做归纳,不评价对错,也不写行为规则。
122
+ */
123
+ function summarizeSteps(steps) {
124
+ const opened = [];
125
+ const taps = [];
126
+ const typed = [];
127
+ const scrolled = new Set();
128
+ const keys = new Set();
129
+ const waits = [];
130
+ const sensitive = [];
131
+
132
+ for (const step of Array.isArray(steps) ? steps : []) {
133
+ const action = stepAction(step);
134
+ const args = (step && step.args) || {};
135
+ switch (action) {
136
+ case 'app.open':
137
+ opened.push(stepLabel(step) || String(args.packageName || args.package || '').trim());
138
+ break;
139
+ case 'app.link':
140
+ opened.push(stepLabel(step) || String(args.uri || args.url || '').trim());
141
+ break;
142
+ case 'tap':
143
+ taps.push(stepLabel(step));
144
+ break;
145
+ case 'type': {
146
+ const where = stepLabel(step);
147
+ const text = String(args.text || step.text || '').trim();
148
+ if (text) {
149
+ typed.push({ where, text });
150
+ sensitive.push(text);
151
+ }
152
+ break;
153
+ }
154
+ case 'swipe':
155
+ scrolled.add(args.direction === 'up' ? '注意:向上滑是看下面的内容' : `向${args.direction || '下'}滑动`);
156
+ break;
157
+ case 'key':
158
+ if (args.key) keys.add(String(args.key));
159
+ break;
160
+ case 'wait':
161
+ case 'screen.wait':
162
+ if (args.appears) waits.push(String(args.appears));
163
+ break;
164
+ default:
165
+ break;
166
+ }
167
+ }
168
+
169
+ return { opened, taps, typed, scrolled, keys, waits, sensitive };
170
+ }
171
+
172
+ /* ── 由任务提炼草稿 ────────────────────────────────────────────────────── */
173
+
174
+ /** 把一段名字压成可做文件名的 slug。 */
175
+ function slugify(value) {
176
+ return String(value || '')
177
+ .toLowerCase()
178
+ .trim()
179
+ .replace(/[^a-z0-9.-]+/g, '-')
180
+ .replace(/^-+|-+$/g, '')
181
+ .slice(0, 60) || 'unknown';
182
+ }
183
+
184
+ /** 现在的时间,ISO 到分钟。 */
185
+ function nowIso() {
186
+ return new Date().toISOString().replace(/\.\d+Z$/, 'Z');
187
+ }
188
+
189
+ /**
190
+ * 从一次任务的轨迹提炼一份知识草稿(不落盘)。
191
+ *
192
+ * @param {object} trace 任务轨迹
193
+ * @param {string} [trace.kind] 'app' | 'site',不给时按有没有 url / 包名推断
194
+ * @param {string} [trace.packageName] App 包名
195
+ * @param {string} [trace.appName] App 中文名
196
+ * @param {string} [trace.url] 网站地址
197
+ * @param {string} [trace.host] 网站主机名(与 url 二选一)
198
+ * @param {string} [trace.siteName] 网站名
199
+ * @param {string} [trace.outcome] done / partial / not_possible / unknown
200
+ * @param {string} [trace.sessionId] 来源会话
201
+ * @param {Array} [trace.steps] 动作序列,每步 `{ action, args }`
202
+ * @param {string[]} [trace.inputs] 额外需要脱敏的原文
203
+ * @returns {{kind, name, slug, display, status, markdown, outcome}}
204
+ */
205
+ function draftFromTask(trace) {
206
+ const t = trace && typeof trace === 'object' ? trace : {};
207
+ const url = String(t.url || t.host || '').trim();
208
+ const host = hostOf(url);
209
+ const pkg = String(t.packageName || t.package || '').trim();
210
+ const kind = t.kind === 'app' || t.kind === 'site'
211
+ ? t.kind
212
+ : (host && !pkg ? 'site' : 'app');
213
+
214
+ const summary = summarizeSteps(t.steps);
215
+ const extraSensitive = Array.isArray(t.inputs) ? t.inputs.map(String) : [];
216
+ const sensitive = [...summary.sensitive, ...extraSensitive];
217
+
218
+ const name = kind === 'site' ? (host || 'site') : (pkg || slugify(t.appName));
219
+ const display = kind === 'site'
220
+ ? String(t.siteName || host || name)
221
+ : String(t.appName || name);
222
+
223
+ const lines = [];
224
+ const fm = [];
225
+ fm.push('---');
226
+ fm.push(`name: ${name}`);
227
+ fm.push(`display: ${display}`);
228
+ fm.push(`kind: ${kind}`);
229
+ fm.push('status: draft');
230
+ fm.push('review: pending');
231
+ fm.push(`learned_from: ${String(t.sessionId || 'task').slice(0, 80)}`);
232
+ fm.push(`outcome: ${String(t.outcome || 'unknown').slice(0, 40)}`);
233
+ fm.push(`created: ${nowIso()}`);
234
+ fm.push('---');
235
+ lines.push(fm.join('\n'));
236
+ lines.push('');
237
+ lines.push('# 知识草稿(待审)');
238
+ lines.push('');
239
+ lines.push('> 这是从一次实际任务归纳出的草稿,收下并人工核对后才并入正式知识。只记录事实(入口、界面、走到哪一步会发生什么),不含行为规则。');
240
+ lines.push('');
241
+ lines.push('## 是什么');
242
+ if (kind === 'site') {
243
+ lines.push(`${display}(${host || name})。以下按一次实际任务的操作顺序记录。`);
244
+ } else {
245
+ lines.push(`${display}(${pkg || name})。以下按一次实际任务的操作顺序记录。`);
246
+ }
247
+ lines.push('');
248
+ lines.push('## 界面');
249
+ const iface = [];
250
+ if (summary.opened.length > 0) iface.push(`- 入口:${summary.opened.filter(Boolean).join('、') || display}`);
251
+ for (const tap of summary.taps) {
252
+ if (tap) iface.push(`- 点「${tap}」`);
253
+ }
254
+ if (iface.length === 0) iface.push('- 这次任务没有记录到具体的界面入口。');
255
+ lines.push(...iface);
256
+ lines.push('');
257
+ lines.push('## 功能');
258
+ const func = [];
259
+ for (const item of summary.typed) {
260
+ func.push(`- 在「${item.where || '输入框'}」里输入:${item.text}`);
261
+ }
262
+ for (const note of summary.scrolled) func.push(`- ${note}`);
263
+ if (func.length === 0) func.push('- 这次任务没有记录到具体操作。');
264
+ lines.push(...func);
265
+ lines.push('');
266
+ lines.push('## 已知现象');
267
+ const signs = [];
268
+ if (summary.waits.length > 0) signs.push(`- 某些页面需要等待加载:${summary.waits.join('、')}`);
269
+ if (summary.keys.has('back')) signs.push('- 过程中用返回键退出过当前页');
270
+ if (signs.length === 0) signs.push('- 这次任务没有遇到需要特别记下的现象。');
271
+ lines.push(...signs);
272
+
273
+ const body = desensitize(lines.join('\n'), sensitive);
274
+
275
+ return {
276
+ kind,
277
+ name,
278
+ slug: slugify(name),
279
+ display,
280
+ status: 'draft',
281
+ outcome: String(t.outcome || 'unknown'),
282
+ markdown: body,
283
+ };
284
+ }
285
+
286
+ /**
287
+ * 把草稿写进待审目录。文件名带时间戳,避免同一条知识的多份草稿互相覆盖。
288
+ *
289
+ * @returns {{path, file, dir, kind, name, status}}
290
+ */
291
+ function writeDraft(draft, options = {}) {
292
+ if (!draft || typeof draft.markdown !== 'string') throw new Error('没有可写的草稿。');
293
+ const slug = String(draft.slug || '');
294
+ if (!/^[a-z0-9.-]+$/.test(slug)) throw new Error('草稿 slug 不合法,可能会逃出待审目录。');
295
+ const kind = draft.kind === 'site' ? 'site' : 'app';
296
+ const dir = options.dir || draftsDir();
297
+ fs.mkdirSync(dir, { recursive: true });
298
+ const stamp = new Date().toISOString().replace(/[-:]/g, '').replace(/\..*$/, '').replace('T', '-');
299
+ const file = `${kind}-${slug}-${stamp}.md`;
300
+ const target = path.resolve(dir, file);
301
+ const root = path.resolve(dir);
302
+ if (target !== root && !target.startsWith(root + path.sep)) {
303
+ throw new Error('草稿文件名逃出了待审目录。');
304
+ }
305
+ fs.writeFileSync(target, draft.markdown, 'utf8');
306
+ return { path: target, file, dir, kind: draft.kind, name: draft.name, status: 'draft' };
307
+ }
308
+
309
+ /** 便利入口:从轨迹直接提炼并落盘,返回写入位置与草稿概要。 */
310
+ function learnFromTask(trace, options = {}) {
311
+ const draft = draftFromTask(trace);
312
+ return writeDraft(draft, options);
313
+ }
314
+
315
+ module.exports = {
316
+ draftsDir,
317
+ draftFromTask,
318
+ writeDraft,
319
+ learnFromTask,
320
+ desensitize,
321
+ // 供测试
322
+ _internal: {
323
+ summarizeSteps,
324
+ slugify,
325
+ hostOf,
326
+ fallbackRedact,
327
+ redactorSource: REDACTOR ? REDACTOR.source : null,
328
+ },
329
+ };
@@ -0,0 +1,237 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * 统一脱敏:运行记录、证据账本、网络数据三处共用同一套规则,一处改了另两处跟着改。
5
+ * 纯逻辑、无 IO、不改入参(redactObject 返回新对象)。
6
+ *
7
+ * 覆盖 Mika Redactor.kt 的全部口径,并补上卡号:
8
+ * - 密码框里打进去的字(字段名像凭据的,整段打码);
9
+ * - sk-* / AIza* / AQ.* / ghp_* 等已知格式的 Key、Bearer 令牌;
10
+ * - JWT(eyJ… 三段);
11
+ * - 又长又杂的串(≥32 位,且同时有大写、小写、数字);
12
+ * - 16 位 / 19 位卡号类数字串(含分组空格 / 连字符);
13
+ * - 名字像凭据的 query / 表单 / JSON / 头字段的值(token、secret、password、cookie、
14
+ * authorization…;author、keyword 这类不算);
15
+ * - OAuth 回跳里的 code、state;
16
+ * - 前后 24 字内有「验证码 / 短信 / code / otp」一类词的 4–8 位数字。
17
+ *
18
+ * 三个导出的分工:
19
+ * redact(text) 对一段自由文本跑全部文本级规则(Key/JWT/长串/卡号/验证码)。
20
+ * redactObject(obj) 递归处理对象/数组:凭据名整段打码,其余字符串跑 redact。
21
+ * redactNetwork(body) 网络数据(URL / 表单 / JSON 正文 / 头表):凭据名与 OAuth 一并打码。
22
+ */
23
+
24
+ const MASK = '***';
25
+
26
+ /* ── 已知格式的凭据 ───────────────────────────────────────────────────── */
27
+
28
+ const SK_KEY = /\bsk-[A-Za-z0-9_-]{16,}/g;
29
+ const GOOGLE_KEY = /\bAIza[0-9A-Za-z_-]{30,}/g;
30
+ /** Google 新格式认证 Key:AQ. 开头一长串 base64url。 */
31
+ const GOOGLE_AUTH_KEY = /\bAQ\.[0-9A-Za-z_-]{20,}/g;
32
+ const GITHUB_TOKEN = /\bgh[pousr]_[A-Za-z0-9]{20,}/g;
33
+ const BEARER = /\bBearer\s+[A-Za-z0-9._~+/=-]{8,}/g;
34
+
35
+ /* ── JWT 与又长又杂的串 ───────────────────────────────────────────────── */
36
+
37
+ const JWT = /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g;
38
+ const LONG = /[A-Za-z0-9_\-+=]{32,}/g;
39
+ const LONG_SLASH = /[A-Za-z0-9_\-+/=]{32,}/g;
40
+
41
+ /* ── 16 / 19 位卡号类 ─────────────────────────────────────────────────── */
42
+
43
+ /** 13–19 位数字,允许单个空格或连字符分组;是否真按卡号遮由位数(16 或 19)决定。 */
44
+ const CARD_LIKE = /(?<![\d.,])(?:\d[ -]?){15,18}\d(?![\d.,])/g;
45
+
46
+ /* ── 验证码一类的 4–8 位数字 ──────────────────────────────────────────── */
47
+
48
+ const CODE_WORD = /验证|校验码|动态码|动态密码|确认码|安全码|短信|口令|\b(?:code|otp|passcode|pin|2fa)\b/gi;
49
+ /** 4–8 位数字,中间可隔单个空格或连字符;后面跟单位 / 年份 / 货币的是量,不是码。 */
50
+ const CODE_DIGITS =
51
+ /(?<![\d.¥$€£])(?<!\d,)\d(?:[ -]?\d){3,7}(?!\d)(?!\.\d)(?!,\d{3}(?!\d))(?!\s*(?:年|元|块|角|分钟|小时|秒|天|月|日|号|次|个|人|件|条|米|公里|千米|克|公斤|千克|斤|吨|度|℃|°|%|‰|[¥$€£]|km|kg|mb|gb|kb|ms|cm|mm|mah|rmb|usd|cny))/gi;
52
+ /** 码和词之间至多隔几个字(哪一边都算)。 */
53
+ const CODE_WINDOW = 24;
54
+
55
+ /* ── 名字像凭据 ──────────────────────────────────────────────────────── */
56
+
57
+ const SECRET_WORDS = new Set([
58
+ 'token', 'tokens', 'secret', 'password', 'passwd', 'pwd', 'passcode', 'session', 'sessionid', 'sessid', 'sid',
59
+ 'auth', 'authorization', 'csrf', 'xsrf', 'signature', 'sig', 'otp', 'cookie', 'credential', 'credentials',
60
+ 'apikey', 'jwt', 'bearer', 'pin', 'privatekey',
61
+ ]);
62
+ const SECRET_COMPOUNDS = new Set([
63
+ 'apikey', 'accesskey', 'secretkey', 'privatekey', 'clientsecret', 'sessionkey', 'authkey',
64
+ ]);
65
+ const CASE_BOUNDARY = /(?<=[a-z0-9])(?=[A-Z])/g;
66
+ const WORD_SPLIT = /[^A-Za-z0-9]+/g;
67
+
68
+ /** 字段名按驼峰 / 下划线 / 连字符拆成词,有像凭据的词就是凭据字段。 */
69
+ function secretName(name) {
70
+ const words = String(name == null ? '' : name)
71
+ .split(WORD_SPLIT)
72
+ .flatMap((part) => part.split(CASE_BOUNDARY))
73
+ .map((w) => w.toLowerCase())
74
+ .filter(Boolean);
75
+ return words.some((w) => SECRET_WORDS.has(w)) || SECRET_COMPOUNDS.has(words.join(''));
76
+ }
77
+
78
+ const OAUTH_MARKERS = new Set(['state', 'redirect_uri', 'client_id', 'code_verifier']);
79
+ const OAUTH_RESULTS = new Set(['code', 'state']);
80
+
81
+ /* ── 文本级规则 ──────────────────────────────────────────────────────── */
82
+
83
+ function maskLong(s, slash) {
84
+ const re = slash ? LONG_SLASH : LONG;
85
+ return s.replace(re, (m) => {
86
+ if (/[A-Z]/.test(m) && /[a-z]/.test(m) && /\d/.test(m)) return MASK;
87
+ return m;
88
+ });
89
+ }
90
+
91
+ function maskCards(s) {
92
+ return s.replace(CARD_LIKE, (m) => {
93
+ const digits = m.replace(/\D/g, '').length;
94
+ return digits === 16 || digits === 19 ? MASK : m;
95
+ });
96
+ }
97
+
98
+ function maskCodes(s) {
99
+ const words = [];
100
+ for (const m of s.matchAll(CODE_WORD)) {
101
+ words.push({ start: m.index, last: m.index + m[0].length - 1 });
102
+ }
103
+ if (words.length === 0) return s;
104
+ return s.replace(CODE_DIGITS, (match, offset) => {
105
+ const first = offset;
106
+ const last = offset + match.length - 1;
107
+ const near = words.some((w) => {
108
+ const afterWord = first - w.last;
109
+ const beforeWord = w.start - last;
110
+ return (afterWord >= 1 && afterWord <= CODE_WINDOW + 1) || (beforeWord >= 1 && beforeWord <= CODE_WINDOW + 1);
111
+ });
112
+ return near ? MASK : match;
113
+ });
114
+ }
115
+
116
+ /**
117
+ * 自由文本脱敏:Key / Bearer / JWT / 长杂串 / 卡号 / 验证码。
118
+ * @param {string} input
119
+ * @returns {string}
120
+ */
121
+ function redact(input) {
122
+ if (input === null || input === undefined) return input;
123
+ let s = typeof input === 'string' ? input : String(input);
124
+ s = s.replace(SK_KEY, MASK);
125
+ s = s.replace(GOOGLE_KEY, MASK);
126
+ s = s.replace(GOOGLE_AUTH_KEY, MASK);
127
+ s = s.replace(GITHUB_TOKEN, MASK);
128
+ s = s.replace(BEARER, 'Bearer ' + MASK);
129
+ s = s.replace(JWT, MASK);
130
+ s = maskLong(s, true);
131
+ s = maskCards(s);
132
+ s = maskCodes(s);
133
+ return s;
134
+ }
135
+
136
+ /* ── 结构化数据:query / 表单 / JSON / 头 ────────────────────────────── */
137
+
138
+ /**
139
+ * 查询串或表单里 a=b&c=d:凭据名的值遮住;像 OAuth 回跳的(有 state 等,且带 code 或凭据)把 code、state 也遮住。
140
+ * [bareKey] 为真时,孤零零一个叫 key 的参数也算凭据。
141
+ */
142
+ function maskParams(q, bareKey) {
143
+ const parts = q.split('&');
144
+ const names = parts.map((p) => p.split('=')[0].toLowerCase());
145
+ const oauth = names.some((n) => OAUTH_MARKERS.has(n)) && (names.includes('code') || names.some((n) => secretName(n)));
146
+ return parts
147
+ .map((p) => {
148
+ const eq = p.indexOf('=');
149
+ if (eq <= 0) return p;
150
+ const name = p.slice(0, eq);
151
+ const n = name.toLowerCase();
152
+ const hide = secretName(name) || (bareKey && n === 'key') || (oauth && OAUTH_RESULTS.has(n));
153
+ return hide ? name + '=' + MASK : p;
154
+ })
155
+ .join('&');
156
+ }
157
+
158
+ function maskUrl(u) {
159
+ const hash = u.indexOf('#');
160
+ const beforeHash = hash >= 0 ? u.slice(0, hash) : u;
161
+ const fragment = hash >= 0 ? u.slice(hash + 1) : null;
162
+ const q = beforeHash.indexOf('?');
163
+ const base = q >= 0 ? beforeHash.slice(0, q + 1) + maskParams(beforeHash.slice(q + 1), true) : beforeHash;
164
+ return fragment === null ? base : base + '#' + (fragment.includes('=') ? maskParams(fragment, true) : fragment);
165
+ }
166
+
167
+ const JSON_PAIR = /"((?:[^"\\]|\\.){1,100})"(\s*:\s*)("(?:[^"\\]|\\.)*"|-?\d[\d.eE+-]*|true|false|null)/g;
168
+ const FORM = /^[^\s{}[\]"<>]+=[^\s]*$/;
169
+
170
+ function maskBody(b) {
171
+ if (FORM.test(b)) return maskParams(b, false);
172
+ return b.replace(JSON_PAIR, (m, key, sep) => (secretName(key) ? `"${key}"${sep}"${MASK}"` : m));
173
+ }
174
+
175
+ /**
176
+ * 网络数据里的一个字符串(URL / 表单 / JSON 正文)脱敏。先按结构遮凭据名与 OAuth,
177
+ * 再跑一遍文本级规则兜住值里的 Key / JWT / 长串 / 卡号。
178
+ */
179
+ function maskNetworkString(s) {
180
+ let out;
181
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(s) || (/^[^\s]*\?/.test(s) && s.includes('='))) {
182
+ out = maskUrl(s);
183
+ } else if (FORM.test(s)) {
184
+ out = maskParams(s, false);
185
+ } else {
186
+ out = maskBody(s);
187
+ }
188
+ return redact(out);
189
+ }
190
+
191
+ /**
192
+ * 递归脱敏对象 / 数组。名字像凭据的键整段打码;其余字符串跑 redact;数字布尔原样。
193
+ * @param {*} value
194
+ * @returns {*}
195
+ */
196
+ function redactObject(value) {
197
+ if (value === null || value === undefined) return value;
198
+ if (Array.isArray(value)) return value.map((v) => redactObject(v));
199
+ if (typeof value === 'string') return redact(value);
200
+ if (typeof value === 'object') {
201
+ const out = {};
202
+ for (const k of Object.keys(value)) {
203
+ if (secretName(k)) out[k] = MASK;
204
+ else out[k] = redactObject(value[k]);
205
+ }
206
+ return out;
207
+ }
208
+ return value;
209
+ }
210
+
211
+ /**
212
+ * 网络数据脱敏:body 可以是字符串(URL / 表单 / JSON 正文),也可以是头表这类对象。
213
+ * @param {*} body
214
+ * @returns {*}
215
+ */
216
+ function redactNetwork(body) {
217
+ if (body === null || body === undefined) return body;
218
+ if (typeof body === 'string') return maskNetworkString(body);
219
+ if (Array.isArray(body)) return body.map((b) => redactNetwork(b));
220
+ if (typeof body === 'object') return redactObject(body);
221
+ return body;
222
+ }
223
+
224
+ module.exports = {
225
+ redact,
226
+ redactObject,
227
+ redactNetwork,
228
+ secretName,
229
+ maskParams,
230
+ maskUrl,
231
+ // 给「证据账本 / 运行记录」这类只要纯文本遮罩的调用方留的别名,语义等价于 redact:
232
+ // phone/evidence.js 的 loadSharedRedactor 找的就是 evidence / text 这两个名字之一。
233
+ evidence: redact,
234
+ text: redact,
235
+ MASK,
236
+ CODE_WINDOW,
237
+ };
@@ -0,0 +1,95 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * 动作风险五级分级:纯函数,只回答「这一步大概会造成多大影响」,不做任何动作、不读屏。
5
+ *
6
+ * 从 Mika(`core/action/Approval.kt` 的 `ActionRisk`)借五级口径:
7
+ *
8
+ * Read 只看,或只在界面间移动(读屏、等、开/关 App、返回)
9
+ * Change 在界面上改东西(点、打字、滑、手势、按键),改的是界面不是世界
10
+ * External 把东西交给别人(发送、发布、提交),会影响屏外的世界
11
+ * Irreversible 做了收不回(付款、下单、删除)
12
+ * Unknown 看不清点的是什么,按拿不准处理
13
+ *
14
+ * `outward`(影响屏幕之外的世界,或拿不准会不会)对 External / Irreversible / Unknown 为真,
15
+ * 用来决定「这一步要不要记账、要不要人工确认」。这份分级只给调用方做参考:真正的放行/接管
16
+ * 判定在 gate.js 里,risk 不碰任何状态。
17
+ *
18
+ * 我们对 Mika 的取舍:Mika 分级要读整屏快照,能按资源 ID、组合键、弹框、拖拽等信号判;
19
+ * 我们的输入只有「动作名 + 目标文字 + 是否对外」这一份结构化信息,所以分级只用到这三样,
20
+ * 判不准的一律落到 Unknown,不猜。
21
+ */
22
+
23
+ const { matchLabel } = require('./irreversible.js');
24
+
25
+ /** 只看、只在界面间移动,不改屏外的世界。 */
26
+ const READ_ACTIONS = new Set([
27
+ 'screen.read', 'read', 'screenshot', 'list', 'find', 'wait',
28
+ 'scroll', 'app.open', 'app.close', 'back', 'home', 'recents', 'tab',
29
+ ]);
30
+
31
+ /** 在界面上改东西。 */
32
+ const CHANGE_ACTIONS = new Set(['tap', 'type', 'swipe', 'key', 'gesture', 'clear', 'clear_text', 'long_press']);
33
+
34
+ /** 点按类:读不到目标文字时判不了点的是什么。 */
35
+ const TAP_LIKE = new Set(['tap', 'long_press']);
36
+
37
+ function result(level, outward, why) {
38
+ return { level, outward, why };
39
+ }
40
+
41
+ /**
42
+ * classify({ action, target, outward }) → { level, outward, why }
43
+ *
44
+ * @param {object} input
45
+ * @param {string} input.action 动作名(tap / type / swipe / key / app.open / screen.read …)
46
+ * @param {string} [input.target] 这一下作用的文字(按钮名、发送对象、输入框标签…)
47
+ * @param {boolean} [input.outward] 调用方已经知道这一步对外(如带回车的打字、提交式点击)时置 true
48
+ * @returns {{level:string, outward:boolean, why:string}}
49
+ */
50
+ function classify(input) {
51
+ const src = input && typeof input === 'object' ? input : {};
52
+ const action = typeof src.action === 'string' ? src.action.trim().toLowerCase() : '';
53
+ const target = typeof src.target === 'string' ? src.target.trim() : '';
54
+ const outwardFlag = src.outward === true;
55
+
56
+ if (!action) return result('Unknown', true, '没有动作名,判不了这一步做什么');
57
+
58
+ if (READ_ACTIONS.has(action)) {
59
+ return result('Read', false, `${action} 只看或只在界面间移动,不改屏外的东西`);
60
+ }
61
+
62
+ if (!CHANGE_ACTIONS.has(action)) {
63
+ return result('Unknown', true, `不认识的动作「${action}」,按拿不准处理`);
64
+ }
65
+
66
+ // 目标文字能命中不可逆词表:按命中的类别定级。
67
+ const hit = target ? matchLabel(target) : null;
68
+ if (hit) {
69
+ if (hit.kind === 'payment') {
70
+ return result('Irreversible', true, `「${target}」是付款/下单一类,做了收不回`);
71
+ }
72
+ if (hit.kind === 'delete') {
73
+ return result('Irreversible', true, `「${target}」是删除一类,做了收不回`);
74
+ }
75
+ return result('External', true, `「${target}」会把东西交给别人(${hit.kind})`);
76
+ }
77
+
78
+ // 调用方已经声明这一步对外(例如带回车的打字)。
79
+ if (outwardFlag) {
80
+ return result('External', true, `${action} 被声明为对外动作,会影响屏外的世界`);
81
+ }
82
+
83
+ // 点按类却读不到目标文字:看不清点的是什么。
84
+ if (TAP_LIKE.has(action) && !target) {
85
+ return result('Unknown', true, '点按但读不到目标文字,看不清点的是什么');
86
+ }
87
+
88
+ return result('Change', false, `${action} 只在界面上改动,不涉及屏外的世界`);
89
+ }
90
+
91
+ module.exports = {
92
+ classify,
93
+ READ_ACTIONS,
94
+ CHANGE_ACTIONS,
95
+ };