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,405 @@
1
+ // A community plugin's own page in the window (docs/subsystems/
2
+ // plugin-runtime.md, P4): package.json's magpie.page, in a tab of the
3
+ // sidebar's Plugins group, after the built-in pages. The page runs in a
4
+ // frame sandboxed to scripts alone, in an origin of its own: it has no
5
+ // way to the window, to magpie's API (apiFramed) or to the network (its
6
+ // policy, pluginpage.go). What it asks of the window comes by message,
7
+ // from that frame only, and the window answers only what the page may:
8
+ // the theme, its own options, and the reads its package declared
9
+ // (magpie.permissions), which /api/plugin-pages/read checks again.
10
+ // A page that throws, or never says hello, shows the kernel's "This
11
+ // page stopped working" with its Try again.
12
+ //
13
+ // Panels (magpie.panels, P5) are framed the same way in a page's
14
+ // .plugin-slot, as tall as they draw, and one that breaks says so in its
15
+ // place. A page may take a built-in one's place (magpie.page.replaces),
16
+ // and its actions (magpie.actions, P6) are row menu items that open it:
17
+ // plugin-runtime.md says the rest.
18
+ (() => {
19
+ if (mode !== "window") return;
20
+ // how long a page has to say hello before it is taken as broken
21
+ const HELLO_MS = 10e3;
22
+ // how long a page the reader left keeps its frame: one left longer is
23
+ // let go, and loads again the next time it is opened, unless its
24
+ // package keeps it (magpie.page.keepAlive)
25
+ const IDLE_MS = 10 * 60e3;
26
+ const pages = new Map(); // key → { entry, frame, hello, timer, shown, idle }
27
+ const panels = new Map(); // frame → { entry, frame, context, hello, timer, body }
28
+ let panelList = []; // /api/plugin-panels: every panel of a plugin on
29
+ let ctx = null;
30
+ // a built-in page → the plugin's page in its place (magpie.page.replaces)
31
+ let replaced = new Map();
32
+ magpie.replacing = (v) => replaced.get(v) || v;
33
+ magpie.plugin("plugin-pages", (c) => { ctx = c; });
34
+
35
+ const TOKENS = ["bg", "card", "fg", "fg-2", "muted", "faint", "line", "accent", "accent-soft", "accent-fg", "green", "red", "amber", "font", "code"];
36
+ function theme() {
37
+ const css = getComputedStyle(document.documentElement);
38
+ const tokens = {};
39
+ for (const k of TOKENS) tokens[k] = css.getPropertyValue("--" + k).trim();
40
+ const dark = document.documentElement.dataset.theme === "dark"
41
+ || (document.documentElement.dataset.theme !== "light" && matchMedia("(prefers-color-scheme: dark)").matches);
42
+ return { dark, lang: locale, tokens };
43
+ }
44
+
45
+ function post(win, msg) { win?.postMessage({ magpie: 1, ...msg }, "*"); }
46
+
47
+ // the theme or the language changed: every page open is told
48
+ function pushTheme() {
49
+ const data = theme();
50
+ for (const p of [...pages.values(), ...panels.values()]) if (p.hello) post(p.frame?.contentWindow, { event: "theme", data });
51
+ }
52
+ new MutationObserver(pushTheme).observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme", "lang", "class", "style"] });
53
+ matchMedia("(prefers-color-scheme: dark)").addEventListener?.("change", pushTheme);
54
+
55
+ function broke(key, why) { ctx?.broke(key, new Error(why)); }
56
+
57
+ // a page's or a panel's title in the window's language: its titles
58
+ // (magpie.page.titles, {"zh": "…"}) by the language, then by its first
59
+ // part (zh for zh-TW), else its title
60
+ function titled(e) {
61
+ const m = e.titles || {};
62
+ return m[locale] || m[locale.split("-")[0]] || e.title;
63
+ }
64
+
65
+ // a page is told when the reader leaves it and comes back
66
+ // (magpie.onVisibility): a frame in a hidden view isn't hidden to itself
67
+ function seen(p, visible) {
68
+ if (p.shown === visible) return;
69
+ p.shown = visible;
70
+ if (p.hello) post(p.frame?.contentWindow, { event: "visibility", data: { visible } });
71
+ }
72
+
73
+ // the page's frame, made the first time the page is opened (and again
74
+ // after Try again, or once it was let go): a plugin's page costs
75
+ // nothing until it is looked at
76
+ function open(key) {
77
+ const p = pages.get(key);
78
+ if (!p) return undefined;
79
+ clearTimeout(p.idle);
80
+ if (p.frame?.isConnected) { seen(p, true); return p.ready; }
81
+ const main = $("#view-" + key);
82
+ const f = document.createElement("iframe");
83
+ f.className = "plugin-frame";
84
+ // scripts, and nothing else: no same origin (no cookie, no storage of
85
+ // the window's, no reach into it), no forms, popups or top navigation
86
+ f.setAttribute("sandbox", "allow-scripts");
87
+ f.setAttribute("referrerpolicy", "no-referrer");
88
+ f.setAttribute("allow", "");
89
+ f.title = titled(p.entry);
90
+ f.src = p.entry.url;
91
+ p.frame = f;
92
+ p.hello = false;
93
+ p.shown = true;
94
+ p.ready = new Promise((resolve, reject) => {
95
+ p.said = resolve;
96
+ clearTimeout(p.timer);
97
+ p.timer = setTimeout(() => { if (!p.hello) reject(new Error(t("{name}'s page didn't answer", { name: titled(p.entry) }))); }, HELLO_MS);
98
+ });
99
+ main.replaceChildren(f);
100
+ return p.ready;
101
+ }
102
+
103
+ // the reader went to another page: this one is told, and its frame is
104
+ // let go after IDLE_MS there, with what it holds (its scripts, timers,
105
+ // connections); one that is back by then, or keeps itself, stays
106
+ function leave(key) {
107
+ const p = pages.get(key);
108
+ if (!p?.frame) return;
109
+ seen(p, false);
110
+ clearTimeout(p.idle);
111
+ if (p.entry.keepAlive) return;
112
+ p.idle = setTimeout(() => {
113
+ if (view === key || !p.frame) return;
114
+ clearTimeout(p.timer);
115
+ p.frame.remove();
116
+ p.frame = null;
117
+ p.hello = false;
118
+ }, IDLE_MS);
119
+ }
120
+
121
+ async function answer(p, m) {
122
+ const e = p.entry;
123
+ switch (m.method) {
124
+ case "hello":
125
+ p.hello = true;
126
+ clearTimeout(p.timer);
127
+ p.said?.();
128
+ // a panel is told where it is: the provider whose editor it is in;
129
+ // a page served to a browser, where its local service is through
130
+ // this server (pageservice.go), relative to the page
131
+ return { ...theme(), context: p.context || {}, service: (!p.context && e.service) || "", visible: p.shown ?? true };
132
+ case "theme":
133
+ return theme();
134
+ case "options.get":
135
+ return api("plugin-pages/options", { spec: e.spec });
136
+ case "options.set": {
137
+ const o = m.params?.options;
138
+ if (!o || typeof o !== "object" || Array.isArray(o)) throw new Error(t("A page's options are an object"));
139
+ return api("plugin-pages/options", { spec: e.spec, options: o });
140
+ }
141
+ case "read": {
142
+ const name = String(m.params?.name || "");
143
+ // what its package didn't declare is refused here, and again by
144
+ // the server
145
+ if (!e.reads.includes(name)) throw new Error(t("{name} doesn't ask to read {what}", { name: titled(e), what: name }));
146
+ return api("plugin-pages/read", { spec: e.spec, read: name, params: m.params?.params || {} });
147
+ }
148
+ }
149
+ throw new Error(t("No such call: {what}", { what: String(m.method) }));
150
+ }
151
+
152
+ addEventListener("message", async (ev) => {
153
+ // only a page's own frame is listened to: another window's message,
154
+ // or one that names a page, is nothing
155
+ let key = "", p = null;
156
+ for (const [k, x] of pages) if (x.frame && ev.source === x.frame.contentWindow) { key = k; p = x; break; }
157
+ if (!p) {
158
+ for (const [f, x] of panels) {
159
+ if (!f.isConnected) { panels.delete(f); clearTimeout(x.timer); continue; }
160
+ if (ev.source === f.contentWindow) { p = x; break; }
161
+ }
162
+ }
163
+ if (!p) return;
164
+ const m = ev.data;
165
+ if (!m || typeof m !== "object" || m.magpie !== 1 || typeof m.method !== "string") return;
166
+ if (m.method === "error") {
167
+ const why = String(m.params?.message || t("The page hit an error"));
168
+ if (p.context) panelBroke(p, why); else broke(key, why);
169
+ return;
170
+ }
171
+ // a panel is as tall as what it draws; a page fills its view
172
+ if (m.method === "size") {
173
+ const h = Number(m.params?.height);
174
+ if (p.context && Number.isFinite(h)) p.frame.style.height = Math.min(Math.max(Math.ceil(h), 0), PANEL_MAX) + "px";
175
+ return;
176
+ }
177
+ const src = ev.source;
178
+ try {
179
+ post(src, { id: m.id, result: await answer(p, m) });
180
+ } catch (err) {
181
+ post(src, { id: m.id, error: String(err?.message || err) });
182
+ }
183
+ });
184
+
185
+ // the sidebar's tab for each page, in a Plugins group of its own after
186
+ // the built-in pages
187
+ function tab(e) {
188
+ const b = document.createElement("button");
189
+ b.dataset.view = e.key;
190
+ b.dataset.pluginPage = "";
191
+ if (e.icon) {
192
+ const img = document.createElement("img");
193
+ img.className = "nav-ic";
194
+ img.src = e.icon;
195
+ img.alt = "";
196
+ img.width = img.height = 16;
197
+ b.append(img);
198
+ } else {
199
+ const s = svg(PUZZLE, 16, 1.5);
200
+ s.classList.add("nav-ic");
201
+ s.setAttribute("aria-hidden", "true");
202
+ b.append(s);
203
+ }
204
+ b.append(el("span", "", titled(e)));
205
+ b.onclick = () => { show(e.key); b.blur(); };
206
+ return b;
207
+ }
208
+
209
+ async function load() {
210
+ loadPanels();
211
+ let list;
212
+ try { list = await api("plugin-pages"); } catch { return; }
213
+ // an answer that isn't a list is not known: the tabs drawn stay
214
+ if (!Array.isArray(list)) return;
215
+ const nav = $("#nav");
216
+ const keys = new Set(list.map((e) => e.key));
217
+ // a page whose plugin went off, or away: its tab goes, and the window
218
+ // leaves it if it was open
219
+ for (const b of nav.querySelectorAll("button[data-plugin-page]")) if (!keys.has(b.dataset.view)) b.remove();
220
+ // a built-in page replaced by a plugin's that went is back in its place
221
+ const replacing = new Map(list.filter((e) => e.replaces).map((e) => [e.replaces, e.key]));
222
+ for (const b of nav.querySelectorAll("button[data-replaced]")) if (!replacing.has(b.dataset.view)) b.removeAttribute("data-replaced");
223
+ replaced = replacing;
224
+ for (const [k, p] of pages) {
225
+ if (keys.has(k)) continue;
226
+ p.frame?.remove();
227
+ p.frame = null;
228
+ p.gone = true;
229
+ if (view === k) show("plugins");
230
+ }
231
+ let head = nav.querySelector(".nav-gl[data-plugin-pages]");
232
+ const own = list.filter((e) => !e.replaces);
233
+ if (!own.length) head?.remove();
234
+ if (!head && own.length) {
235
+ head = el("span", "nav-gl", t("Plugins"));
236
+ head.dataset.pluginPages = "";
237
+ head.dataset.en = "Plugins";
238
+ head.setAttribute("data-t", "");
239
+ nav.append(head);
240
+ }
241
+ let at = head;
242
+ for (const e of list) {
243
+ let p = pages.get(e.key);
244
+ if (!p) {
245
+ p = { entry: e };
246
+ pages.set(e.key, p);
247
+ ctx.slots.add("page", { key: e.key, title: titled(e), html: "", mount(main) { main.classList.add("plugin-page"); p.frame = null; }, open: () => open(e.key), leave: () => leave(e.key) });
248
+ } else if (p.entry.url !== e.url || p.gone) {
249
+ p.frame?.remove();
250
+ p.frame = null;
251
+ }
252
+ p.gone = false;
253
+ p.entry = e;
254
+ const part = magpie.page(e.key);
255
+ if (part) part.title = titled(e);
256
+ let b = nav.querySelector(`button[data-plugin-page][data-view="${e.key}"]`);
257
+ if (!b) b = tab(e);
258
+ else b.querySelector("span").textContent = titled(e);
259
+ b.classList.toggle("on", view === e.key);
260
+ // in the place of the built-in page it replaces, which steps aside
261
+ // (and the reader on it is taken to the plugin's); else in the
262
+ // Plugins group
263
+ const built = e.replaces && nav.querySelector(`button[data-view="${e.replaces}"]:not([data-plugin-page])`);
264
+ if (built) {
265
+ built.dataset.replaced = e.key;
266
+ built.after(b);
267
+ if (view === e.replaces) show(e.key);
268
+ continue;
269
+ }
270
+ at.after(b);
271
+ at = b;
272
+ }
273
+ navSubs();
274
+ // a reload of a plugin's page, or the one the window was left on,
275
+ // comes back to it once its tab is there
276
+ if (view === "agents" && !opened) {
277
+ opened = true;
278
+ let want = params.get("view") || "";
279
+ if (!want) { try { want = localStorage.getItem("magpie.lastView") || ""; } catch { /* none kept */ } }
280
+ if (pages.has(want)) show(want);
281
+ }
282
+ }
283
+ let opened = false;
284
+
285
+ // ---- actions ----
286
+
287
+ // the row menu's items for menu ("provider", "agent"), each told
288
+ // context: { provider: { id, name } } or { agent: { id, name } }
289
+ magpie.actions = (menu, context) => {
290
+ const items = [];
291
+ for (const [key, p] of pages) {
292
+ if (p.gone) continue;
293
+ for (const a of p.entry.actions || []) {
294
+ if (a.menu !== menu) continue;
295
+ items.push({ name: titled(a), literal: true, icon: PUZZLE, sep: !items.length, run: () => act(key, a.id, context) });
296
+ }
297
+ }
298
+ return items;
299
+ };
300
+
301
+ async function act(key, id, context) {
302
+ // a reader who keeps an unsaved edit where they are stays there
303
+ if (await show(key) === false) return;
304
+ const p = pages.get(key);
305
+ try { await open(key); } catch { return; } // its page says it broke
306
+ post(p?.frame?.contentWindow, { event: "action", data: { id, ...context } });
307
+ }
308
+
309
+ // ---- panels ----
310
+
311
+ // how tall a panel can be, so one that says it is endless isn't
312
+ const PANEL_MAX = 2400;
313
+
314
+ function panelBroke(p, why) {
315
+ if (p.broken) return;
316
+ p.broken = true;
317
+ clearTimeout(p.timer);
318
+ panels.delete(p.frame);
319
+ const card = el("div", "plugin-panel-broke");
320
+ card.setAttribute("role", "alert");
321
+ card.append(el("b", "", t("{name} stopped working", { name: titled(p.entry) })), el("span", "note", why));
322
+ const again = el("button", "text", t("Try again"));
323
+ again.type = "button";
324
+ again.onclick = () => panelFrame(p.entry, p.context, p.body);
325
+ card.append(again);
326
+ p.body.replaceChildren(card);
327
+ console.error(`[magpie] ${p.entry.name}: panel ${p.entry.key}: ${why}`);
328
+ }
329
+
330
+ // a panel's frame in body, told context once it says hello
331
+ function panelFrame(e, context, body) {
332
+ const f = document.createElement("iframe");
333
+ f.className = "plugin-frame plugin-panel-frame";
334
+ f.setAttribute("sandbox", "allow-scripts");
335
+ f.setAttribute("referrerpolicy", "no-referrer");
336
+ f.setAttribute("allow", "");
337
+ f.title = titled(e);
338
+ f.src = e.url;
339
+ const p = { entry: e, frame: f, context, body, hello: false, broken: false };
340
+ p.timer = setTimeout(() => { if (!p.hello) panelBroke(p, t("{name}'s page didn't answer", { name: titled(e) })); }, HELLO_MS);
341
+ panels.set(f, p);
342
+ body.replaceChildren(f);
343
+ }
344
+
345
+ // fill a slot with the panels of its name: drawn again only when they
346
+ // changed, so a panel already open goes on as it is
347
+ function fill(box) {
348
+ const name = box.dataset.slot;
349
+ const list = panelList.filter((e) => e.slot === name);
350
+ const sig = list.map((e) => e.key + " " + e.url + " " + titled(e)).join("|");
351
+ if (box.dataset.sig === sig && box.childElementCount === list.length) return;
352
+ box.dataset.sig = sig;
353
+ const context = box.pluginContext || {};
354
+ const parts = list.map((e) => {
355
+ const sec = el("section", "plugin-panel");
356
+ sec.dataset.panel = e.key;
357
+ const head = el("div", "row-head");
358
+ head.append(el("span", "label", titled(e)));
359
+ if (titled(e) !== e.name) head.append(el("span", "note", e.name));
360
+ const body = el("div", "plugin-panel-body");
361
+ sec.append(head, body);
362
+ panelFrame(e, context, body);
363
+ return sec;
364
+ });
365
+ box.replaceChildren(...parts);
366
+ box.hidden = !parts.length;
367
+ // Settings' Plugins tab comes or goes with its panels, in the sidebar too
368
+ if (name === "settings" && typeof setSetTab === "function") { setSetTab(setTab); navSubs(); }
369
+ }
370
+
371
+ // a slot app.js draws, with what its panels are told (a provider's
372
+ // editor: { provider: { id, name } })
373
+ magpie.slot = (name, context) => {
374
+ const box = document.createElement("div");
375
+ box.className = "plugin-slot";
376
+ box.dataset.slot = name;
377
+ box.pluginContext = context || {};
378
+ box.hidden = true;
379
+ fill(box);
380
+ return box;
381
+ };
382
+
383
+ // a page's markup written after the panels came (a built-in page
384
+ // registered late, or put back by Try again): its slots filled now
385
+ magpie.fillSlots = (root) => { for (const box of root.querySelectorAll(".plugin-slot[data-slot]")) fill(box); };
386
+
387
+ async function loadPanels() {
388
+ let list;
389
+ try { list = await api("plugin-panels"); } catch { return; }
390
+ // an answer that isn't a list is not known: the panels drawn stay
391
+ if (!Array.isArray(list)) return;
392
+ panelList = list;
393
+ for (const box of document.querySelectorAll(".plugin-slot[data-slot]")) fill(box);
394
+ }
395
+
396
+ window.loadPluginPages = load;
397
+ load();
398
+ // a new language: the tabs and the panels' heads are titled in it
399
+ let lang = document.documentElement.lang;
400
+ new MutationObserver(() => {
401
+ if (document.documentElement.lang === lang) return;
402
+ lang = document.documentElement.lang;
403
+ load();
404
+ }).observe(document.documentElement, { attributes: true, attributeFilter: ["lang"] });
405
+ })();