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,524 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * 自建技能:把办成的一件事沉淀成技能草稿 → 用户确认 → 落进技能目录;并提供「技能正文 /
5
+ * references / scripts」的读取通道。
6
+ *
7
+ * ## 为什么要有这一层
8
+ *
9
+ * 宿主里的技能是工作区 `.dsh/skills/<name>/SKILL.md` 这类 Markdown,手机端此前只能拿到
10
+ * `name` + `description`(见 `skills.js`),正文与附件读不到、更写不进去。这一层补两块:
11
+ * 1. **写入闭环**:一段办成的任务 → 五节结构的 `SKILL.md` 草稿(脱敏后)→ 待用户确认 →
12
+ * 按 skill-authoring 约定落盘(name kebab、目录名 == name、长资料进 `references/`、
13
+ * 能跑的脚本进 `scripts/` 并登记 `manifest.json`)。
14
+ * 2. **读取通道**:`getSkillContent` 读全文,`listResources` 列附件,`readResource` 按相对
15
+ * 路径读一个附件(resolve 后必须在技能目录内、只读、限大小)。
16
+ *
17
+ * ## 目录与状态
18
+ *
19
+ * · 技能根:`<rootDir>`(缺省 `$DSH_HOME/storages/mobile-plugin/skills`,可用 `DSH_MOBILE_SKILLS`
20
+ * 覆盖)。装配层可以把 rootDir 指向某工作区的 `.dsh/skills`,让宿主 `skills.list` 直接收进去;
21
+ * 本模块只认 rootDir,不猜工作区。
22
+ * · 待确认草稿:`<dshHome>/storages/mobile-plugin/skill-drafts.json`(`DSH_MOBILE_SKILL_DRAFTS`
23
+ * 覆盖),跨重启不丢。
24
+ *
25
+ * 草稿一律 `pending`,用户 `approveDraft` 才真正写盘;同名已有技能默认拒(`name_taken`),
26
+ * 只有明确是它的「更新草稿」才覆盖。
27
+ */
28
+
29
+ const crypto = require('node:crypto');
30
+ const fs = require('node:fs');
31
+ const path = require('node:path');
32
+
33
+ const { dshHome } = require('../shared/paths.js');
34
+ const { sensitiveHit } = require('./memory.js');
35
+ const {
36
+ NAME_RE,
37
+ SECTIONS,
38
+ parseFrontMatter,
39
+ withField,
40
+ validateSkill,
41
+ isSafeResourcePath,
42
+ } = require('./skill-import.js');
43
+
44
+ /** 单份技能正文读取上限。 */
45
+ const MAX_SKILL_BYTES = 256 * 1024;
46
+ /** 单个附件读取上限:references / scripts 都是小文本,别让一次读取把内存顶爆。 */
47
+ const MAX_RESOURCE_BYTES = 512 * 1024;
48
+ /** 草稿带附件的文件数 / 单文件 / 整包上限。 */
49
+ const MAX_DRAFT_FILES = 100;
50
+ const MAX_DRAFT_FILE_BYTES = 1024 * 1024;
51
+ const MAX_DRAFT_TOTAL_BYTES = 5 * 1024 * 1024;
52
+ /** 脱敏占位。 */
53
+ const PLACEHOLDER = '<参数>';
54
+ const DIGITS = /\d{2,}/g;
55
+ const AMOUNT = /[¥$]\s*\d+(?:\.\d+)?|\d+(?:\.\d+)?\s*(?:元|块钱|块|万元|万|美元)/g;
56
+
57
+ /** 技能根。`DSH_MOBILE_SKILLS` 只给测试用 / 装配层覆盖。 */
58
+ function skillsRoot() {
59
+ return process.env.DSH_MOBILE_SKILLS || path.join(dshHome(), 'storages', 'mobile-plugin', 'skills');
60
+ }
61
+
62
+ /** 待确认草稿的状态文件。 */
63
+ function draftsStateFile() {
64
+ return process.env.DSH_MOBILE_SKILL_DRAFTS || path.join(dshHome(), 'storages', 'mobile-plugin', 'skill-drafts.json');
65
+ }
66
+
67
+ function escapeRegExp(text) {
68
+ return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
69
+ }
70
+
71
+ /** 把这次的具体值(引号里的人名、金额、2 位以上数字)换成 `<参数>`;纯数字只换整段。 */
72
+ function redact(text, literals) {
73
+ const source = String(text == null ? '' : text).normalize('NFKC');
74
+ const list = [...new Set(
75
+ (Array.isArray(literals) ? literals : [])
76
+ .map((s) => String(s == null ? '' : s).trim())
77
+ .filter((s) => s.length >= 2 || /^\d+$/.test(s)),
78
+ )].sort((a, b) => b.length - a.length);
79
+ let out = source;
80
+ for (const lit of list) {
81
+ if (/^\d+$/.test(lit)) out = out.replace(new RegExp(`(?<!\\d)${escapeRegExp(lit)}(?!\\d)`, 'g'), PLACEHOLDER);
82
+ else out = out.split(lit).join(PLACEHOLDER);
83
+ }
84
+ return out;
85
+ }
86
+
87
+ /** 正文里剩下的一次性数字(单号、电话、价格)一律换掉;frontmatter 与 keepDigits 里的豁免。 */
88
+ function scrubNumbers(markdown, keepDigits) {
89
+ const lines = String(markdown == null ? '' : markdown).split('\n');
90
+ const frontEnd = lines[0] && lines[0].trimEnd() === '---'
91
+ ? lines.findIndex((line, i) => i > 0 && line.trimEnd() === '---')
92
+ : -1;
93
+ const keep = keepDigits instanceof Set ? keepDigits : new Set(Array.isArray(keepDigits) ? keepDigits : []);
94
+ return lines.map((line, i) => {
95
+ if (frontEnd >= 0 && i <= frontEnd) return line;
96
+ return line
97
+ .replace(AMOUNT, (m) => (keep.has(m.replace(/[^0-9.]/g, '')) ? m : PLACEHOLDER))
98
+ .replace(DIGITS, (m) => (keep.has(m) ? m : PLACEHOLDER));
99
+ }).join('\n');
100
+ }
101
+
102
+ /** 从结构化分节拼出一份 `SKILL.md`(五节都必须给)。拼不全就抛 `missing_section`。 */
103
+ function composeMarkdown(input = {}) {
104
+ const name = typeof input.name === 'string' ? input.name.trim() : '';
105
+ const description = typeof input.description === 'string' ? input.description.trim() : '';
106
+ if (!NAME_RE.test(name)) throw new Error('invalid_name');
107
+ if (!description) throw new Error('missing_description');
108
+ const title = typeof input.title === 'string' && input.title.trim() ? input.title.trim() : name;
109
+ const sections = input.sections && typeof input.sections === 'object' ? input.sections : {};
110
+ const chunks = ['---', `name: ${name}`, `description: ${description}`, '---', '', `# ${title}`, ''];
111
+ for (const section of SECTIONS) {
112
+ const body = typeof sections[section] === 'string' ? sections[section].trim() : '';
113
+ if (!body) throw new Error('missing_section');
114
+ chunks.push(`## ${section}`, '', body, '');
115
+ }
116
+ return `${chunks.join('\n').trim()}\n`;
117
+ }
118
+
119
+ /** 把一串动作整理成「步骤」节(编号列表)。 */
120
+ function stepsSection(steps) {
121
+ const list = (Array.isArray(steps) ? steps : [])
122
+ .map((step) => (typeof step === 'string' ? step.trim() : (step && typeof step.text === 'string' ? step.text.trim() : '')))
123
+ .filter(Boolean);
124
+ if (list.length === 0) return '按用户这次说的目标一步步操作,每步做完确认画面确有变化。';
125
+ return list.map((line, i) => `${i + 1}. ${line}`).join('\n');
126
+ }
127
+
128
+ /** 把一串要点整理成节(项目符号),空时给一句兜底。 */
129
+ function bullets(values, fallback) {
130
+ const list = (Array.isArray(values) ? values : []).map((v) => String(v == null ? '' : v).trim()).filter(Boolean);
131
+ if (list.length === 0) return fallback;
132
+ return list.map((line) => `- ${line}`).join('\n');
133
+ }
134
+
135
+ /** 从一段办成的任务(结构化)起草五节正文:供装配层把模型给出的步骤/坑/验收喂进来。 */
136
+ function draftFromTask(task = {}, opts = {}) {
137
+ const sections = {
138
+ 入口: typeof task.entry === 'string' && task.entry.trim() ? task.entry.trim() : '从用户指定的 App / 网站开始;不确定用哪个就先问用户。',
139
+ 步骤: stepsSection(task.steps),
140
+ 容易出错的地方: bullets(task.pitfalls, '界面改版、弹窗、登录墙会让入口对不上;找不到就如实说,不要硬猜。'),
141
+ 怎样算办成: bullets(task.done, '用户看到预期的结果,且关键信息(名称、金额、对象)与确认时一致。'),
142
+ 什么时候停下交给用户: bullets(task.stop, '涉及付款、发送、删除、授权这些不可逆动作前停下,让用户确认。'),
143
+ };
144
+ return composeMarkdown({
145
+ name: task.name,
146
+ description: task.description,
147
+ title: task.title,
148
+ sections,
149
+ });
150
+ }
151
+
152
+ function isRecord(value) {
153
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
154
+ }
155
+
156
+ /**
157
+ * 建一个技能库。
158
+ *
159
+ * @param {object} [options]
160
+ * @param {string} [options.rootDir] 技能根,缺省 {@link skillsRoot}。
161
+ * @param {string} [options.draftsFile] 草稿状态文件,缺省 {@link draftsStateFile}。
162
+ * @param {() => number} [options.clock] 时钟。
163
+ * @param {() => string} [options.ids] id 生成器。
164
+ */
165
+ function createSkillStore(options = {}) {
166
+ const root = options.rootDir || skillsRoot();
167
+ const draftsFile = options.draftsFile || draftsStateFile();
168
+ const clock = typeof options.clock === 'function' ? options.clock : () => Date.now();
169
+ const makeId = typeof options.ids === 'function' ? options.ids : () => crypto.randomUUID();
170
+
171
+ function skillDir(name) {
172
+ return path.join(root, name);
173
+ }
174
+
175
+ // ── 草稿状态 ──────────────────────────────────────────────────────────────
176
+
177
+ function readDrafts() {
178
+ let raw;
179
+ try {
180
+ raw = fs.readFileSync(draftsFile, 'utf8');
181
+ } catch (err) {
182
+ if (err && err.code === 'ENOENT') return { version: 1, drafts: {} };
183
+ throw new Error('drafts_unavailable');
184
+ }
185
+ let parsed;
186
+ try {
187
+ parsed = JSON.parse(raw);
188
+ } catch {
189
+ throw new Error('drafts_unavailable');
190
+ }
191
+ if (!parsed || typeof parsed !== 'object' || !isRecord(parsed.drafts)) throw new Error('drafts_unavailable');
192
+ return parsed;
193
+ }
194
+
195
+ function writeDrafts(state) {
196
+ fs.mkdirSync(path.dirname(draftsFile), { recursive: true });
197
+ const payload = `${JSON.stringify({ version: 1, drafts: state.drafts }, null, 2)}\n`;
198
+ const tmp = `${draftsFile}.${process.pid}.${clock()}.tmp`;
199
+ fs.writeFileSync(tmp, payload);
200
+ fs.renameSync(tmp, draftsFile);
201
+ }
202
+
203
+ function summarize(draft) {
204
+ return {
205
+ id: draft.id,
206
+ name: draft.name,
207
+ status: draft.status,
208
+ source: draft.source,
209
+ untrusted: draft.untrusted === true,
210
+ isUpdateOf: draft.isUpdateOf || null,
211
+ createdAt: draft.createdAt,
212
+ fileCount: Array.isArray(draft.files) ? draft.files.length : 0,
213
+ };
214
+ }
215
+
216
+ // ── 附件校验与落盘 ────────────────────────────────────────────────────────
217
+
218
+ function normalizeFiles(files) {
219
+ if (files == null) return { ok: true, files: [] };
220
+ if (!Array.isArray(files)) return { ok: false, problem: 'bad_files' };
221
+ if (files.length > MAX_DRAFT_FILES) return { ok: false, problem: 'too_many_files' };
222
+ const out = [];
223
+ const seen = new Set();
224
+ let total = 0;
225
+ for (const file of files) {
226
+ if (!isRecord(file) || !isSafeResourcePath(file.path)) return { ok: false, problem: 'bad_path' };
227
+ const rel = file.path.trim();
228
+ if (seen.has(rel)) return { ok: false, problem: 'duplicate_path' };
229
+ seen.add(rel);
230
+ const bytes = typeof file.content === 'string'
231
+ ? Buffer.byteLength(file.content, 'utf8')
232
+ : (typeof file.base64 === 'string' ? Math.floor((file.base64.length * 3) / 4) : 0);
233
+ if (bytes > MAX_DRAFT_FILE_BYTES) return { ok: false, problem: 'file_too_large' };
234
+ total += bytes;
235
+ if (total > MAX_DRAFT_TOTAL_BYTES) return { ok: false, problem: 'package_too_large' };
236
+ const entry = { path: rel, size: bytes, runnable: file.runnable === true, untrusted: file.untrusted === true };
237
+ if (typeof file.content === 'string') entry.content = file.content;
238
+ else if (typeof file.base64 === 'string') entry.base64 = file.base64;
239
+ out.push(entry);
240
+ }
241
+ return { ok: true, files: out };
242
+ }
243
+
244
+ /** resolve 后必须仍在技能目录内;只写 references/ 或 scripts/ 下的相对路径。 */
245
+ function resolveInside(dir, relPath) {
246
+ const target = path.resolve(dir, relPath);
247
+ const prefix = dir.endsWith(path.sep) ? dir : `${dir}${path.sep}`;
248
+ if (!target.startsWith(prefix)) return null;
249
+ return target;
250
+ }
251
+
252
+ function writeSkillDir(dir, markdown, files) {
253
+ fs.mkdirSync(dir, { recursive: true });
254
+ fs.writeFileSync(path.join(dir, 'SKILL.md'), markdown, 'utf8');
255
+ for (const file of files || []) {
256
+ const target = resolveInside(dir, file.path);
257
+ if (!target) throw new Error('bad_path');
258
+ fs.mkdirSync(path.dirname(target), { recursive: true });
259
+ if (typeof file.content === 'string') fs.writeFileSync(target, file.content, 'utf8');
260
+ else fs.writeFileSync(target, Buffer.from(file.base64 || '', 'base64'));
261
+ }
262
+ }
263
+
264
+ // ── 草稿写入 ──────────────────────────────────────────────────────────────
265
+
266
+ /**
267
+ * 生成 / 收下一份待确认草稿。
268
+ * input: `{ markdown }` 或 `{ name, description, title, sections }`;可选 `files`、`literals`、
269
+ * `keepDigits`、`source`(`'imported'` 时来自外部)、`untrusted`、`isUpdateOf`、`toolNames`、
270
+ * `scriptTool`、`runnableScripts`、`enforceRunnable`。
271
+ * 返回 `{ ok:true, draft:<摘要> }` 或 `{ ok:false, problem }`。
272
+ */
273
+ function prepareDraft(input = {}) {
274
+ let markdown;
275
+ try {
276
+ markdown = typeof input.markdown === 'string' ? input.markdown : composeMarkdown(input);
277
+ } catch (err) {
278
+ return { ok: false, problem: err.message || 'bad_draft' };
279
+ }
280
+ if (Array.isArray(input.literals) && input.literals.length > 0) markdown = redact(markdown, input.literals);
281
+ if (input.keepDigits !== undefined) markdown = scrubNumbers(markdown, input.keepDigits);
282
+
283
+ const problem = validateSkill(markdown, {
284
+ toolNames: input.toolNames,
285
+ scriptTool: input.scriptTool,
286
+ runnableScripts: input.runnableScripts,
287
+ enforceRunnable: input.enforceRunnable === true,
288
+ });
289
+ if (problem) return { ok: false, problem };
290
+
291
+ const files = normalizeFiles(input.files);
292
+ if (!files.ok) return { ok: false, problem: files.problem };
293
+
294
+ const name = String(parseFrontMatter(markdown).meta.name || '').trim();
295
+ const id = makeId();
296
+ const draft = {
297
+ id,
298
+ name,
299
+ markdown,
300
+ files: files.files,
301
+ status: 'pending',
302
+ source: input.source === 'imported' ? 'imported' : 'drafted',
303
+ untrusted: input.untrusted === true,
304
+ isUpdateOf: typeof input.isUpdateOf === 'string' && input.isUpdateOf ? input.isUpdateOf : null,
305
+ createdAt: clock(),
306
+ };
307
+ const state = readDrafts();
308
+ state.drafts[id] = draft;
309
+ writeDrafts(state);
310
+ return { ok: true, draft: summarize(draft) };
311
+ }
312
+
313
+ function listDrafts() {
314
+ const state = readDrafts();
315
+ return Object.values(state.drafts).map(summarize).sort((a, b) => (a.createdAt || 0) - (b.createdAt || 0));
316
+ }
317
+
318
+ function getDraft(id) {
319
+ const state = readDrafts();
320
+ const draft = state.drafts[String(id)];
321
+ return draft ? { ...draft, files: Array.isArray(draft.files) ? draft.files.map((f) => ({ ...f })) : [] } : null;
322
+ }
323
+
324
+ function rejectDraft(id) {
325
+ const state = readDrafts();
326
+ const key = String(id);
327
+ if (!state.drafts[key]) return false;
328
+ delete state.drafts[key];
329
+ writeDrafts(state);
330
+ return true;
331
+ }
332
+
333
+ /**
334
+ * 用户确认:把草稿落成真正的技能。
335
+ * opts: `{ editedMarkdown?, name?, replace? }`。默认同名已有技能拒 `name_taken`,除非是它的
336
+ * 更新草稿或显式 replace。
337
+ * 返回 `{ ok:true, name, dir, files }` 或 `{ ok:false, problem }`。
338
+ */
339
+ function approveDraft(id, opts = {}) {
340
+ const state = readDrafts();
341
+ const draft = state.drafts[String(id)];
342
+ if (!draft) return { ok: false, problem: 'not_found' };
343
+
344
+ let markdown = typeof opts.editedMarkdown === 'string' ? opts.editedMarkdown : draft.markdown;
345
+ const problem = validateSkill(markdown, { enforceRunnable: false });
346
+ if (problem) return { ok: false, problem };
347
+ if (sensitiveHit(markdown)) return { ok: false, problem: 'sensitive' };
348
+
349
+ const meta = parseFrontMatter(markdown).meta;
350
+ const name = String(opts.name != null && String(opts.name).trim() ? opts.name : meta.name).trim();
351
+ if (!NAME_RE.test(name)) return { ok: false, problem: 'invalid_name' };
352
+ markdown = withField(markdown, 'name', name);
353
+
354
+ const dir = skillDir(name);
355
+ const exists = fs.existsSync(dir);
356
+ const isUpdate = draft.isUpdateOf === name || opts.replace === true;
357
+ if (exists && !isUpdate) return { ok: false, problem: 'name_taken' };
358
+
359
+ try {
360
+ writeSkillDir(dir, markdown, draft.files);
361
+ } catch (err) {
362
+ return { ok: false, problem: err && err.message ? err.message : 'write_failed' };
363
+ }
364
+ delete state.drafts[String(id)];
365
+ writeDrafts(state);
366
+ return { ok: true, name, dir, files: (draft.files || []).map((f) => f.path) };
367
+ }
368
+
369
+ // ── 读取通道 ──────────────────────────────────────────────────────────────
370
+
371
+ function listSkills() {
372
+ let entries;
373
+ try {
374
+ entries = fs.readdirSync(root, { withFileTypes: true });
375
+ } catch {
376
+ return [];
377
+ }
378
+ const out = [];
379
+ for (const entry of entries) {
380
+ if (!entry.isDirectory()) continue;
381
+ const file = path.join(root, entry.name, 'SKILL.md');
382
+ let stat;
383
+ try {
384
+ stat = fs.statSync(file);
385
+ } catch {
386
+ continue;
387
+ }
388
+ if (!stat.isFile()) continue;
389
+ let meta = {};
390
+ try {
391
+ meta = parseFrontMatter(fs.readFileSync(file, 'utf8')).meta;
392
+ } catch {
393
+ // 读坏了就只报目录名。
394
+ }
395
+ out.push({
396
+ name: typeof meta.name === 'string' && meta.name ? meta.name : entry.name,
397
+ description: typeof meta.description === 'string' ? meta.description : '',
398
+ dir: entry.name,
399
+ nameMatchesDir: meta.name === entry.name,
400
+ });
401
+ }
402
+ out.sort((a, b) => a.name.localeCompare(b.name));
403
+ return out;
404
+ }
405
+
406
+ function getSkillContent(name) {
407
+ const skill = String(name == null ? '' : name).trim();
408
+ if (!NAME_RE.test(skill)) return { ok: false, problem: 'invalid_name' };
409
+ const file = path.join(skillDir(skill), 'SKILL.md');
410
+ let stat;
411
+ try {
412
+ stat = fs.statSync(file);
413
+ } catch {
414
+ return { ok: false, problem: 'not_found' };
415
+ }
416
+ if (!stat.isFile()) return { ok: false, problem: 'not_found' };
417
+ if (stat.size > MAX_SKILL_BYTES) return { ok: false, problem: 'too_large' };
418
+ try {
419
+ return { ok: true, name: skill, content: fs.readFileSync(file, 'utf8') };
420
+ } catch {
421
+ return { ok: false, problem: 'unreadable' };
422
+ }
423
+ }
424
+
425
+ function walkResources(dir, prefix) {
426
+ let entries;
427
+ try {
428
+ entries = fs.readdirSync(dir, { withFileTypes: true });
429
+ } catch {
430
+ return [];
431
+ }
432
+ const out = [];
433
+ for (const entry of entries) {
434
+ if (entry.isSymbolicLink()) continue;
435
+ const abs = path.join(dir, entry.name);
436
+ const rel = `${prefix}/${entry.name}`;
437
+ if (entry.isDirectory()) {
438
+ out.push(...walkResources(abs, rel));
439
+ } else if (entry.isFile()) {
440
+ let stat;
441
+ try {
442
+ stat = fs.statSync(abs);
443
+ } catch {
444
+ continue;
445
+ }
446
+ out.push({ path: rel, bytes: stat.size });
447
+ }
448
+ }
449
+ return out;
450
+ }
451
+
452
+ function listResources(name) {
453
+ const skill = String(name == null ? '' : name).trim();
454
+ if (!NAME_RE.test(skill)) return { ok: false, problem: 'invalid_name' };
455
+ const dir = skillDir(skill);
456
+ try {
457
+ if (!fs.statSync(path.join(dir, 'SKILL.md')).isFile()) return { ok: false, problem: 'not_found' };
458
+ } catch {
459
+ return { ok: false, problem: 'not_found' };
460
+ }
461
+ const resources = [
462
+ ...walkResources(path.join(dir, 'references'), 'references'),
463
+ ...walkResources(path.join(dir, 'scripts'), 'scripts'),
464
+ ].sort((a, b) => a.path.localeCompare(b.path));
465
+ return { ok: true, name: skill, resources };
466
+ }
467
+
468
+ function readResource(name, relPath) {
469
+ const skill = String(name == null ? '' : name).trim();
470
+ if (!NAME_RE.test(skill)) return { ok: false, problem: 'invalid_name' };
471
+ if (!isSafeResourcePath(relPath)) return { ok: false, problem: 'bad_path' };
472
+ const dir = skillDir(skill);
473
+ const target = resolveInside(dir, relPath);
474
+ if (!target) return { ok: false, problem: 'bad_path' };
475
+ let stat;
476
+ try {
477
+ stat = fs.lstatSync(target);
478
+ } catch {
479
+ return { ok: false, problem: 'not_found' };
480
+ }
481
+ if (stat.isSymbolicLink() || !stat.isFile()) return { ok: false, problem: 'not_found' };
482
+ if (stat.size > MAX_RESOURCE_BYTES) return { ok: false, problem: 'too_large' };
483
+ try {
484
+ return { ok: true, path: relPath.trim(), bytes: stat.size, text: fs.readFileSync(target, 'utf8') };
485
+ } catch {
486
+ return { ok: false, problem: 'unreadable' };
487
+ }
488
+ }
489
+
490
+ return {
491
+ root,
492
+ draftsFile,
493
+ skillDir,
494
+ composeMarkdown,
495
+ draftFromTask,
496
+ redact,
497
+ scrubNumbers,
498
+ prepareDraft,
499
+ listDrafts,
500
+ getDraft,
501
+ rejectDraft,
502
+ approveDraft,
503
+ listSkills,
504
+ getSkillContent,
505
+ listResources,
506
+ readResource,
507
+ };
508
+ }
509
+
510
+ module.exports = {
511
+ MAX_SKILL_BYTES,
512
+ MAX_RESOURCE_BYTES,
513
+ MAX_DRAFT_FILES,
514
+ MAX_DRAFT_FILE_BYTES,
515
+ MAX_DRAFT_TOTAL_BYTES,
516
+ PLACEHOLDER,
517
+ skillsRoot,
518
+ draftsStateFile,
519
+ composeMarkdown,
520
+ draftFromTask,
521
+ redact,
522
+ scrubNumbers,
523
+ createSkillStore,
524
+ };
@@ -44,6 +44,16 @@ const subagents = require('../mobile/subagents.js');
44
44
  const monitor = require('../mobile/monitor.js');
45
45
  const references = require('../mobile/references.js');
46
46
  const schedule = require('../mobile/schedule.js');
47
+ // 定时任务的新建 / 编辑 / 暂停 / 恢复 / 立即执行(见 mobile/schedule-edit.js):宿主
48
+ // ScheduleService 本来就有 create/edit/run,只是移动端一直没接 —— 手机上只能看和删。
49
+ // 暂停/恢复不是独立动词,就是 edit 的 patch.active=false/true。
50
+ const scheduleEdit = require('../mobile/schedule-edit.js');
51
+ // 文件的新建 / 删除 / 重命名(见 mobile/file-ops.js):仍然只在 allowed-zone 里动,
52
+ // 符号链接一律拒(不顺着链接删目标)。
53
+ const fileOps = require('../mobile/file-ops.js');
54
+ // 后台任务的取消(见 mobile/jobs-control.js):只做 cancel。启动走宿主内部 seam,
55
+ // 放开等于移动端能跑任意命令,撞「不暴露 shell」,所以那条恒 501。
56
+ const jobsControl = require('../mobile/jobs-control.js');
47
57
  const pushNotifyRoute = require('../mobile/push-notify-route.js');const plugins = require('../mobile/plugins.js');
48
58
  const pluginUpdate = require('../mobile/plugin-update.js');
49
59
  const pluginSelf = require('../mobile/plugin-self.js');
@@ -64,6 +74,7 @@ const { createSyncInboxStore } = require('../mobile/sync-inbox-store.js');
64
74
  const { credentialsEntry } = require('../shared/credentials-entry.js');
65
75
  const { handleCredentialUpgrade } = require('../mobile/credential-upgrade.js');
66
76
  const extensions = require('../mobile/extensions.js');
77
+ const extensionData = require('../mobile/extension-data.js');
67
78
  const extensionDev = require('../mobile/extension-dev.js');
68
79
  const extensionCatalog = require('../mobile/extension-catalog.js');
69
80
  const sessionDelivery = require('../mobile/session-delivery.js');
@@ -562,6 +573,25 @@ function registerMobileRoutes(deps) {
562
573
  route('/api/mobile/v1/schedule/list', schedule.handleScheduleList)
563
574
  route('/api/mobile/v1/schedule/history', schedule.handleScheduleHistory)
564
575
  route('/api/mobile/v1/schedule/delete', schedule.handleScheduleDelete)
576
+ // 写侧五条:新建、编辑、暂停、恢复、立即执行(见 mobile/schedule-edit.js)。
577
+ // 宿主没有独立 pause/resume 动词,暂停恢复就是 edit 的 patch.active;CAS 用
578
+ // expectedUpdatedAt 透传,冲突回 409。宿主没有 schedule 服务时统一 503。
579
+ route('/api/mobile/v1/schedule/create', scheduleEdit.handleScheduleCreate)
580
+ route('/api/mobile/v1/schedule/update', scheduleEdit.handleScheduleEdit)
581
+ route('/api/mobile/v1/schedule/pause', scheduleEdit.handleSchedulePause)
582
+ route('/api/mobile/v1/schedule/resume', scheduleEdit.handleScheduleResume)
583
+ route('/api/mobile/v1/schedule/run', scheduleEdit.handleScheduleRun)
584
+
585
+ // 文件的新建 / 删除 / 重命名(见 mobile/file-ops.js):只在 allowed-zone(工作区 ∪ 当前
586
+ // 会话目录)里动,realpath 校验堵软链逃逸,删除拒符号链接。能力面没有「任意文件写」。
587
+ route('/api/mobile/v1/file/create', fileOps.handleFileCreate)
588
+ route('/api/mobile/v1/file/delete', fileOps.handleFileDelete)
589
+ route('/api/mobile/v1/file/rename', fileOps.handleFileRename)
590
+
591
+ // 后台任务的取消(见 mobile/jobs-control.js):只能取消,启动恒 501(宿主 start 是内部
592
+ // seam,放开等于移动端跑任意命令,撞「不暴露 shell」)。
593
+ route('/api/mobile/v1/jobs/cancel', jobsControl.handleJobsCancel)
594
+ route('/api/mobile/v1/jobs/start', jobsControl.handleJobsStart)
565
595
 
566
596
  // 桌面端 设置 → 插件 的只读部署快照,由 dsh-host-plugin-inventory 拥有,
567
597
  // 经 typertGateway 命名空间 pluginInventory 取,见 mobile/plugins.js。
@@ -633,6 +663,13 @@ function registerMobileRoutes(deps) {
633
663
  route('/api/mobile/v1/extension/card-state', extensions.handleExtensionCardState)
634
664
  route('/api/mobile/v1/extension/action', extensions.handleExtensionAction)
635
665
 
666
+ // 扩展页请宿主代取数据(能力 host.data,见 mobile/extension-data.js)。
667
+ //
668
+ // 通用通道:声明了 hostData 的扩展页(从本地 file:// 加载,够不到宿主服务)把命中的请求
669
+ // 交给宿主,宿主按登记表去问目标服务、把结果原样带回。只到登记过的服务、只走它 allow 的
670
+ // 路径前缀,不暴露 shell / 系统设置 / 任意文件读写 —— 与其余扩展路由同一档边界。
671
+ route('/api/mobile/v1/extension/data', extensionData.handleExtensionData)
672
+
636
673
  // 扩展**开发包**:作者放在电脑上的本地扩展目录(`$DSH_HOME/extension-dev`),手机上直接
637
674
  // 列包、取文件,不用每改一行都发一次分发系统(见 mobile/extension-dev.js)。
638
675
  //
@@ -647,6 +684,8 @@ function registerMobileRoutes(deps) {
647
684
  // 能力面与开发包那两条同一档 —— 不暴露 shell / 系统设置 / 任意文件读写,也不给写。
648
685
  route('/api/mobile/v1/extension/catalog', extensionCatalog.handleCatalog)
649
686
  route('/api/mobile/v1/extension/catalog/file', extensionCatalog.handleCatalogFile)
687
+ // 整包一次下发(zip → base64),手机下载一个包再本地解包,不再逐文件拉几百次。
688
+ route('/api/mobile/v1/extension/catalog/archive', extensionCatalog.handleCatalogArchive)
650
689
 
651
690
  // 分享文件的落地通道(App 把手机分享进来的文件分片传上来,见 mobile/uploads.js)。
652
691
  // 手机端没有「把文件交给宿主」的路:图片能进 session.send 的 base64 内容块,其它格式
@@ -750,6 +789,9 @@ function registerMobileRoutes(deps) {
750
789
  // 知识库清单与操作节奏:都只读 / 改设置,同样不发动作。节奏那条 GET 读档、POST 改档。
751
790
  route('/api/mobile/v1/phone/knowledge', phoneRoutes.knowledge)
752
791
  route('/api/mobile/v1/phone/pacing', phoneRoutes.pacing)
792
+ // 逐 App 访问控制(见 mobile/phone/access.js 与原生 AppAccess.kt):用户拉黑哪些 App
793
+ // 不许 AI 碰。GET 读名单、POST 改一行。只是本地开关,不扩能力面。
794
+ route('/api/mobile/v1/phone/app-access', phoneRoutes.appAccess)
753
795
  registerUpgradeRoute({ path: '/api/mobile/v1/phone/control', handler: phoneService.handleUpgrade })
754
796
  }
755
797
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mobile-plugin",
3
- "version": "0.5.28",
3
+ "version": "0.5.29",
4
4
  "description": "Mobile API (/api/mobile/v1) and relay client for the DSH mobile app — desktop profile build (dsh 0.2.x, peers resolved by the host)",
5
5
  "license": "MIT",
6
6
  "icon": "./icon.png",
@@ -44,6 +44,7 @@
44
44
  "icon.png",
45
45
  "assets/pets/**",
46
46
  "knowledge/**/*.md",
47
+ "skills/**",
47
48
  "extensions/**",
48
49
  "README.md"
49
50
  ],
@@ -63,11 +64,11 @@
63
64
  "zod": "4.6.5"
64
65
  },
65
66
  "optionalDependencies": {
66
- "dsh-mobile-tailcat-darwin-arm64": "1.1.0",
67
- "dsh-mobile-tailcat-darwin-x64": "1.1.0",
68
- "dsh-mobile-tailcat-linux-arm64": "1.1.0",
69
- "dsh-mobile-tailcat-linux-x64": "1.1.0",
70
- "dsh-mobile-tailcat-win32-x64": "1.1.0"
67
+ "dsh-mobile-tailcat-darwin-arm64": "1.1.1",
68
+ "dsh-mobile-tailcat-darwin-x64": "1.1.1",
69
+ "dsh-mobile-tailcat-linux-arm64": "1.1.1",
70
+ "dsh-mobile-tailcat-linux-x64": "1.1.1",
71
+ "dsh-mobile-tailcat-win32-x64": "1.1.1"
71
72
  },
72
73
  "peerDependencies": {
73
74
  "@deepseek-ai/cordis": ">=4.0.1",