dsh-mobile-plugin 0.5.27 → 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.
- package/extensions/catalog.json +10 -1
- package/extensions/magpie/agentcompact.js +104 -0
- package/extensions/magpie/app.css +4902 -0
- package/extensions/magpie/app.js +24474 -0
- package/extensions/magpie/autobackup.js +185 -0
- package/extensions/magpie/autoresetown.js +151 -0
- package/extensions/magpie/balfound.js +38 -0
- package/extensions/magpie/boot.js +23 -0
- package/extensions/magpie/cfgblocked.js +39 -0
- package/extensions/magpie/checkinnow.js +111 -0
- package/extensions/magpie/claudemode.js +92 -0
- package/extensions/magpie/compat.js +41 -0
- package/extensions/magpie/context.css +433 -0
- package/extensions/magpie/context.js +1576 -0
- package/extensions/magpie/ctxtext.js +223 -0
- package/extensions/magpie/fonts.js +42 -0
- package/extensions/magpie/i18n.js +18942 -0
- package/extensions/magpie/icons/ai21.svg +1 -0
- package/extensions/magpie/icons/ai302-color.svg +1 -0
- package/extensions/magpie/icons/ai360-color.svg +1 -0
- package/extensions/magpie/icons/aihubmix-color.svg +1 -0
- package/extensions/magpie/icons/aionui.svg +1 -0
- package/extensions/magpie/icons/air.svg +4 -0
- package/extensions/magpie/icons/alibaba-color.svg +1 -0
- package/extensions/magpie/icons/alibabacloud-color.svg +1 -0
- package/extensions/magpie/icons/alma.png +0 -0
- package/extensions/magpie/icons/anthropic.svg +1 -0
- package/extensions/magpie/icons/antigravity-color.svg +1 -0
- package/extensions/magpie/icons/anyscale-color.svg +1 -0
- package/extensions/magpie/icons/app-alma.png +0 -0
- package/extensions/magpie/icons/app-cc-switch.png +0 -0
- package/extensions/magpie/icons/apple-touch-icon.png +0 -0
- package/extensions/magpie/icons/aside.svg +28 -0
- package/extensions/magpie/icons/astrbot.svg +1 -0
- package/extensions/magpie/icons/atomcode.png +0 -0
- package/extensions/magpie/icons/aws.svg +1 -0
- package/extensions/magpie/icons/azure-color.svg +1 -0
- package/extensions/magpie/icons/azureai-color.svg +1 -0
- package/extensions/magpie/icons/baichuan-color.svg +1 -0
- package/extensions/magpie/icons/baidu-color.svg +1 -0
- package/extensions/magpie/icons/baiducloud-color.svg +1 -0
- package/extensions/magpie/icons/bailian-color.svg +1 -0
- package/extensions/magpie/icons/baseten.svg +1 -0
- package/extensions/magpie/icons/bedrock-color.svg +1 -0
- package/extensions/magpie/icons/burncloud-color.svg +1 -0
- package/extensions/magpie/icons/bytedance-color.svg +1 -0
- package/extensions/magpie/icons/cerebras.svg +1 -0
- package/extensions/magpie/icons/chatglm-color.svg +1 -0
- package/extensions/magpie/icons/cherryin-color.svg +1 -0
- package/extensions/magpie/icons/chutes.svg +1 -0
- package/extensions/magpie/icons/cindy.png +0 -0
- package/extensions/magpie/icons/claude-color.svg +1 -0
- package/extensions/magpie/icons/claudecode-color.svg +1 -0
- package/extensions/magpie/icons/cline.svg +1 -0
- package/extensions/magpie/icons/cloudflare-color.svg +1 -0
- package/extensions/magpie/icons/codebuddy-color.svg +1 -0
- package/extensions/magpie/icons/codex-color.svg +1 -0
- package/extensions/magpie/icons/cohere-color.svg +1 -0
- package/extensions/magpie/icons/cometapi-color.svg +1 -0
- package/extensions/magpie/icons/commandcode.svg +1 -0
- package/extensions/magpie/icons/copilot-color.svg +1 -0
- package/extensions/magpie/icons/coze.svg +1 -0
- package/extensions/magpie/icons/crush.png +0 -0
- package/extensions/magpie/icons/curl.svg +1 -0
- package/extensions/magpie/icons/cursor.svg +1 -0
- package/extensions/magpie/icons/deepinfra-color.svg +1 -0
- package/extensions/magpie/icons/deepmind-color.svg +1 -0
- package/extensions/magpie/icons/deepseek-color.svg +1 -0
- package/extensions/magpie/icons/deveco-color.svg +12 -0
- package/extensions/magpie/icons/devin.svg +1 -0
- package/extensions/magpie/icons/doubao-color.svg +1 -0
- package/extensions/magpie/icons/empryo-color.svg +1 -0
- package/extensions/magpie/icons/factory.svg +1 -0
- package/extensions/magpie/icons/favicon.svg +1 -0
- package/extensions/magpie/icons/featherless-color.svg +1 -0
- package/extensions/magpie/icons/fireworks-color.svg +1 -0
- package/extensions/magpie/icons/friendli.svg +1 -0
- package/extensions/magpie/icons/fx.svg +1 -0
- package/extensions/magpie/icons/gemini-color.svg +1 -0
- package/extensions/magpie/icons/geminicli-color.svg +1 -0
- package/extensions/magpie/icons/gemma-color.svg +1 -0
- package/extensions/magpie/icons/giteeai.svg +1 -0
- package/extensions/magpie/icons/github.svg +1 -0
- package/extensions/magpie/icons/githubcopilot.svg +1 -0
- package/extensions/magpie/icons/glama.svg +1 -0
- package/extensions/magpie/icons/gmicloud.svg +1 -0
- package/extensions/magpie/icons/google-color.svg +1 -0
- package/extensions/magpie/icons/googlecloud-color.svg +1 -0
- package/extensions/magpie/icons/goose.svg +1 -0
- package/extensions/magpie/icons/grok.svg +1 -0
- package/extensions/magpie/icons/groq.svg +1 -0
- package/extensions/magpie/icons/hanako.png +0 -0
- package/extensions/magpie/icons/hermes.svg +1 -0
- package/extensions/magpie/icons/huawei-color.svg +1 -0
- package/extensions/magpie/icons/huaweicloud-color.svg +1 -0
- package/extensions/magpie/icons/huggingface-color.svg +1 -0
- package/extensions/magpie/icons/hunyuan-color.svg +1 -0
- package/extensions/magpie/icons/hyperbolic-color.svg +1 -0
- package/extensions/magpie/icons/icon-512.png +0 -0
- package/extensions/magpie/icons/iflytekcloud-color.svg +1 -0
- package/extensions/magpie/icons/inference.svg +1 -0
- package/extensions/magpie/icons/infinigence-color.svg +1 -0
- package/extensions/magpie/icons/internlm-color.svg +1 -0
- package/extensions/magpie/icons/jcode.svg +1 -0
- package/extensions/magpie/icons/jina.svg +1 -0
- package/extensions/magpie/icons/kilo.svg +1 -0
- package/extensions/magpie/icons/kilocode.svg +1 -0
- package/extensions/magpie/icons/kimi-code-desktop.png +0 -0
- package/extensions/magpie/icons/kimi.svg +1 -0
- package/extensions/magpie/icons/kiro-color.svg +1 -0
- package/extensions/magpie/icons/kling-color.svg +1 -0
- package/extensions/magpie/icons/lambda.svg +1 -0
- package/extensions/magpie/icons/leptonai-color.svg +1 -0
- package/extensions/magpie/icons/liquid.svg +1 -0
- package/extensions/magpie/icons/liveagent-color.svg +1 -0
- package/extensions/magpie/icons/llmapi.svg +1 -0
- package/extensions/magpie/icons/lmstudio.svg +1 -0
- package/extensions/magpie/icons/lody.png +0 -0
- package/extensions/magpie/icons/longcat.svg +1 -0
- package/extensions/magpie/icons/magpie.svg +1 -0
- package/extensions/magpie/icons/meta-color.svg +1 -0
- package/extensions/magpie/icons/metaai-color.svg +1 -0
- package/extensions/magpie/icons/microsoft-color.svg +1 -0
- package/extensions/magpie/icons/mimocode.svg +1 -0
- package/extensions/magpie/icons/minimax-color.svg +1 -0
- package/extensions/magpie/icons/mistermorph-color.svg +1 -0
- package/extensions/magpie/icons/mistral-color.svg +1 -0
- package/extensions/magpie/icons/mlx-serve.png +0 -0
- package/extensions/magpie/icons/modelscope-color.svg +1 -0
- package/extensions/magpie/icons/monica-color.svg +1 -0
- package/extensions/magpie/icons/moonshot.svg +1 -0
- package/extensions/magpie/icons/morph-color.svg +1 -0
- package/extensions/magpie/icons/mothx.png +0 -0
- package/extensions/magpie/icons/nebius.svg +1 -0
- package/extensions/magpie/icons/newapi-color.svg +1 -0
- package/extensions/magpie/icons/nousresearch.svg +1 -0
- package/extensions/magpie/icons/nova-color.svg +1 -0
- package/extensions/magpie/icons/novita-color.svg +1 -0
- package/extensions/magpie/icons/nvidia-color.svg +1 -0
- package/extensions/magpie/icons/ollama.svg +1 -0
- package/extensions/magpie/icons/omlx.svg +1 -0
- package/extensions/magpie/icons/omo.svg +1 -0
- package/extensions/magpie/icons/omp.svg +1 -0
- package/extensions/magpie/icons/openai.svg +1 -0
- package/extensions/magpie/icons/openchamber.svg +11 -0
- package/extensions/magpie/icons/openclaw.svg +1 -0
- package/extensions/magpie/icons/opencode.svg +1 -0
- package/extensions/magpie/icons/openmausbot.svg +1 -0
- package/extensions/magpie/icons/openrouter.svg +1 -0
- package/extensions/magpie/icons/openwebui.svg +1 -0
- package/extensions/magpie/icons/parasail.svg +1 -0
- package/extensions/magpie/icons/penguin-color.svg +1 -0
- package/extensions/magpie/icons/perplexity-color.svg +1 -0
- package/extensions/magpie/icons/phala-color.svg +1 -0
- package/extensions/magpie/icons/pi.svg +1 -0
- package/extensions/magpie/icons/pipellm-color.svg +5 -0
- package/extensions/magpie/icons/poe.svg +1 -0
- package/extensions/magpie/icons/ppio-color.svg +1 -0
- package/extensions/magpie/icons/prime-agent.svg +1 -0
- package/extensions/magpie/icons/qiniu-color.svg +1 -0
- package/extensions/magpie/icons/qoder.svg +1 -0
- package/extensions/magpie/icons/qwen-color.svg +1 -0
- package/extensions/magpie/icons/qwenpaw.svg +1 -0
- package/extensions/magpie/icons/raycast.svg +1 -0
- package/extensions/magpie/icons/reasonix-color.svg +48 -0
- package/extensions/magpie/icons/reka.svg +1 -0
- package/extensions/magpie/icons/replicate.svg +1 -0
- package/extensions/magpie/icons/roocode.svg +1 -0
- package/extensions/magpie/icons/rtk.png +0 -0
- package/extensions/magpie/icons/sambanova-color.svg +1 -0
- package/extensions/magpie/icons/sensenova-color.svg +1 -0
- package/extensions/magpie/icons/siliconcloud-color.svg +1 -0
- package/extensions/magpie/icons/snow.png +0 -0
- package/extensions/magpie/icons/snowflake-color.svg +1 -0
- package/extensions/magpie/icons/sophnet-color.svg +1 -0
- package/extensions/magpie/icons/spark-color.svg +1 -0
- package/extensions/magpie/icons/stepfun-color.svg +1 -0
- package/extensions/magpie/icons/straico-color.svg +1 -0
- package/extensions/magpie/icons/submodel-color.svg +1 -0
- package/extensions/magpie/icons/t3code.svg +4 -0
- package/extensions/magpie/icons/targon-color.svg +1 -0
- package/extensions/magpie/icons/tencent-color.svg +1 -0
- package/extensions/magpie/icons/tencentcloud-color.svg +1 -0
- package/extensions/magpie/icons/tiangong-color.svg +1 -0
- package/extensions/magpie/icons/together-color.svg +1 -0
- package/extensions/magpie/icons/trae.svg +1 -0
- package/extensions/magpie/icons/typesafe.png +0 -0
- package/extensions/magpie/icons/upstage-color.svg +1 -0
- package/extensions/magpie/icons/venice-color.svg +1 -0
- package/extensions/magpie/icons/vercel.svg +1 -0
- package/extensions/magpie/icons/vertexai-color.svg +1 -0
- package/extensions/magpie/icons/vllm-color.svg +1 -0
- package/extensions/magpie/icons/volcengine-color.svg +1 -0
- package/extensions/magpie/icons/voyage-color.svg +1 -0
- package/extensions/magpie/icons/vscode.svg +1 -0
- package/extensions/magpie/icons/wenxin-color.svg +1 -0
- package/extensions/magpie/icons/windsurf.svg +1 -0
- package/extensions/magpie/icons/workbuddy-color.svg +1 -0
- package/extensions/magpie/icons/workersai-color.svg +1 -0
- package/extensions/magpie/icons/xai.svg +1 -0
- package/extensions/magpie/icons/xiaomimimo.svg +1 -0
- package/extensions/magpie/icons/xinference-color.svg +1 -0
- package/extensions/magpie/icons/yi.svg +1 -0
- package/extensions/magpie/icons/yuanbao-color.svg +1 -0
- package/extensions/magpie/icons/yylx.svg +5 -0
- package/extensions/magpie/icons/zai.svg +1 -0
- package/extensions/magpie/icons/zcode.png +0 -0
- package/extensions/magpie/icons/zed.svg +1 -0
- package/extensions/magpie/icons/zenmux.svg +1 -0
- package/extensions/magpie/icons/zeroone.svg +1 -0
- package/extensions/magpie/icons/zhipu-color.svg +1 -0
- package/extensions/magpie/icons/zoo-code.png +0 -0
- package/extensions/magpie/index.html +572 -0
- package/extensions/magpie/kernel.js +274 -0
- package/extensions/magpie/keyspend.js +63 -0
- package/extensions/magpie/layout.js +26 -0
- package/extensions/magpie/manifest.json +37 -0
- package/extensions/magpie/navlamp.js +47 -0
- package/extensions/magpie/omarchy.css +72 -0
- package/extensions/magpie/omarchy.js +46 -0
- package/extensions/magpie/passerrors.js +21 -0
- package/extensions/magpie/pluginpages.js +405 -0
- package/extensions/magpie/plugins/gateway/client.js +1194 -0
- package/extensions/magpie/plugins/gateway/keys.js +972 -0
- package/extensions/magpie/plugins/gateway/settings.js +459 -0
- package/extensions/magpie/plugins/library/client.js +6788 -0
- package/extensions/magpie/plugins/library/i18n.js +3228 -0
- package/extensions/magpie/plugins/library/library.css +848 -0
- package/extensions/magpie/plugins/otel/i18n.js +48 -0
- package/extensions/magpie/plugins/otel/page.js +189 -0
- package/extensions/magpie/plugins/redact/client.js +46 -0
- package/extensions/magpie/plugins/redact/demo.js +461 -0
- package/extensions/magpie/plugins/redact/i18n.js +432 -0
- package/extensions/magpie/plugins/redact/page.js +985 -0
- package/extensions/magpie/plugins/redact/redact.css +385 -0
- package/extensions/magpie/plugins/redact/settings.js +248 -0
- package/extensions/magpie/plugins/redact/xq_dark.jpg +0 -0
- package/extensions/magpie/plugins/redact/xq_dark.webm +0 -0
- package/extensions/magpie/plugins/redact/xq_light.jpg +0 -0
- package/extensions/magpie/plugins/redact/xq_light.webm +0 -0
- package/extensions/magpie/plugins/routing/client.js +8593 -0
- package/extensions/magpie/plugins/routing/i18n.js +2696 -0
- package/extensions/magpie/plugins/routing/routing.css +1111 -0
- package/extensions/magpie/plugins/search/settings.js +460 -0
- package/extensions/magpie/plugins/sessions/client.js +1622 -0
- package/extensions/magpie/plugins/sessions/i18n.js +360 -0
- package/extensions/magpie/plugins/sessions/sessions.css +181 -0
- package/extensions/magpie/plugins/sessions/settings.js +100 -0
- package/extensions/magpie/plugins/usage/client.js +150 -0
- package/extensions/magpie/plugins/usage/i18n.js +984 -0
- package/extensions/magpie/plugins/usage/page.js +4307 -0
- package/extensions/magpie/plugins.css +319 -0
- package/extensions/magpie/plugins.js +1926 -0
- package/extensions/magpie/profilerename.js +27 -0
- package/extensions/magpie/purposes.js +2 -0
- package/extensions/magpie/quitask.js +79 -0
- package/extensions/magpie/quotaall.js +142 -0
- package/extensions/magpie/ratewindows.js +123 -0
- package/extensions/magpie/registry.js +87 -0
- package/extensions/magpie/resetask.js +137 -0
- package/extensions/magpie/ruleedit.js +53 -0
- package/extensions/magpie/scrollends.js +91 -0
- package/extensions/magpie/settingshints.js +75 -0
- package/extensions/magpie/signsheets.js +86 -0
- package/extensions/magpie/start.js +26 -0
- package/extensions/magpie/syncfiles.js +216 -0
- package/extensions/magpie/theme.js +29 -0
- package/extensions/magpie/traywindows.js +41 -0
- package/extensions/magpie/webfonts.js +130 -0
- package/extensions/magpie/whatsnew.js +252 -0
- package/extensions/magpie.zip +0 -0
- package/knowledge/apps/baijiahao.md +30 -0
- package/knowledge/apps/bilibili.md +1 -0
- package/knowledge/apps/dedao.md +33 -0
- package/knowledge/apps/feishu.md +35 -0
- package/knowledge/apps/gmail.md +33 -0
- package/knowledge/apps/jike.md +31 -0
- package/knowledge/apps/kuaishou.md +34 -0
- package/knowledge/apps/linkedin.md +33 -0
- package/knowledge/apps/qqmail.md +30 -0
- package/knowledge/apps/telegram.md +27 -0
- package/knowledge/apps/toutiao.md +32 -0
- package/knowledge/apps/uber-eats.md +26 -0
- package/knowledge/apps/uber.md +27 -0
- package/knowledge/apps/whatsapp.md +27 -0
- package/knowledge/apps/x.md +32 -0
- package/knowledge/apps/xiaohongshu.md +1 -0
- package/knowledge/apps/zhihu.md +1 -0
- package/knowledge/sites/bilibili.com/SITE.md +50 -0
- package/knowledge/sites/bilibili.com/references/view-fields.md +17 -0
- package/knowledge/sites/github.com/SITE.md +51 -0
- package/knowledge/sites/github.com/references/repo-fields.md +13 -0
- package/knowledge/sites/x.com/SITE.md +47 -0
- package/knowledge/sites/xiaohongshu.com/SITE.md +48 -0
- package/knowledge/sites/xiaohongshu.com/references/note-fields.md +18 -0
- package/knowledge/sites/youtube.com/SITE.md +49 -0
- package/knowledge/sites/youtube.com/references/innertube-fields.md +14 -0
- package/knowledge/sites/zhihu.com/SITE.md +47 -0
- package/knowledge/sites/zhihu.com/references/api-fields.md +21 -0
- package/lib/mobile/extension-catalog.js +41 -1
- package/lib/mobile/extension-data.js +224 -0
- package/lib/mobile/extension-dev.js +3 -1
- package/lib/mobile/file-ops.js +252 -0
- package/lib/mobile/history-derive.js +80 -4
- package/lib/mobile/host-bridge/endpoints.js +48 -6
- package/lib/mobile/host-bridge/event-synthesis.js +33 -6
- package/lib/mobile/jobs-control.js +118 -0
- package/lib/mobile/memory.js +635 -0
- package/lib/mobile/model-badges.js +77 -0
- package/lib/mobile/pet-tools.js +50 -4
- package/lib/mobile/pets.js +214 -18
- package/lib/mobile/phone/access.js +85 -0
- package/lib/mobile/phone/evidence.js +228 -0
- package/lib/mobile/phone/gate.js +290 -0
- package/lib/mobile/phone/grounding.js +314 -0
- package/lib/mobile/phone/guard.js +465 -0
- package/lib/mobile/phone/irreversible.js +88 -8
- package/lib/mobile/phone/knowledge-tools.js +665 -0
- package/lib/mobile/phone/learn.js +329 -0
- package/lib/mobile/phone/redact.js +237 -0
- package/lib/mobile/phone/risk.js +95 -0
- package/lib/mobile/phone/routes.js +28 -1
- package/lib/mobile/phone/service.js +23 -0
- package/lib/mobile/phone/site-commands.js +271 -0
- package/lib/mobile/phone/step-summary.js +201 -0
- package/lib/mobile/phone/stop-summary.js +235 -0
- package/lib/mobile/phone/tool-groups.js +125 -0
- package/lib/mobile/phone/tools.js +589 -0
- package/lib/mobile/phone/untrusted.js +65 -0
- package/lib/mobile/phone-knowledge.js +20 -4
- package/lib/mobile/schedule-edit.js +500 -0
- package/lib/mobile/schedule.js +5 -0
- package/lib/mobile/send-continuation.js +52 -0
- package/lib/mobile/session-models.js +27 -31
- package/lib/mobile/session-send.js +12 -12
- package/lib/mobile/skill-import.js +338 -0
- package/lib/mobile/skill-store.js +524 -0
- package/lib/routes/mobile-api.js +42 -0
- package/package.json +7 -6
- package/skills/skill-authoring/SKILL.md +46 -0
- package/skills/skill-authoring/references/examples.md +82 -0
- package/skills/skill-authoring/references/frontmatter-and-fields.md +46 -0
- package/skills/skill-authoring/scripts/check_skill.py +167 -0
- package/skills/skill-authoring/scripts/manifest.json +3 -0
- package/knowledge/sites/bilibili.com.md +0 -27
- package/knowledge/sites/github.com.md +0 -27
- package/knowledge/sites/x.com.md +0 -28
- package/knowledge/sites/xiaohongshu.com.md +0 -27
- package/knowledge/sites/youtube.com.md +0 -28
- package/knowledge/sites/zhihu.com.md +0 -27
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 模型清单里那枚「徽标」的归一化:上游 `LlmModelBadge` → 手机端能直接画的 `{text, tone}`。
|
|
5
|
+
*
|
|
6
|
+
* 为什么单独成文件:`session-models.js` 有 220 行的拆分上限(见 `test/module-split.test.cjs`),
|
|
7
|
+
* 而这段是「一个字段怎么裁」的纯函数,搬出来既让那份模块守住上限,也方便单独测。
|
|
8
|
+
*
|
|
9
|
+
* 上游形状(`packages/llm/llm/src/types.ts` 的 `LlmModelBadge`):
|
|
10
|
+
* `{ text: '1017' | { en: '1017', 'zh-hans': '1017' }, tone: 'neutral' | 'info' | 'success' | 'warning' }`
|
|
11
|
+
* 手机端拿到的是**已经拍平的字符串**,App 不需要再决定用哪门语言。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** 徽标文案下发上限:选择器里那枚标签是单行小字,官方 `1017` 这类顶多几个字。 */
|
|
15
|
+
const MODEL_BADGE_TEXT_MAX = 32;
|
|
16
|
+
/** 上游 `LlmModelBadgeTone` 只有这四个值;不认识的当没给。 */
|
|
17
|
+
const MODEL_BADGE_TONES = ['neutral', 'info', 'success', 'warning'];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 徽标文案:字面量字符串直接用;本地化对象优先取当前语言、退英文(英文必填),
|
|
21
|
+
* 都取不到就退回表里第一条能用的字符串 —— 绝不让标签空着。
|
|
22
|
+
*
|
|
23
|
+
* 语言标签做一次宽松匹配:先精确(全小写),再按主语言子标签(`zh-cn` 的 `zh`
|
|
24
|
+
* 能对上键 `zh-hans`),最后英文。上游清单的键是「小写语言 id」。
|
|
25
|
+
*/
|
|
26
|
+
function badgeText(value, language) {
|
|
27
|
+
if (typeof value === 'string') return value.slice(0, MODEL_BADGE_TEXT_MAX);
|
|
28
|
+
if (!value || typeof value !== 'object') return '';
|
|
29
|
+
const want = String(language || 'en').toLowerCase();
|
|
30
|
+
const keys = Object.keys(value);
|
|
31
|
+
const ordered = [want, want.split('-')[0], 'en'];
|
|
32
|
+
for (const key of ordered) {
|
|
33
|
+
if (!key) continue;
|
|
34
|
+
const match = keys.find((k) => k.toLowerCase() === key)
|
|
35
|
+
|| keys.find((k) => k.toLowerCase().split('-')[0] === key);
|
|
36
|
+
const text = match ? value[match] : undefined;
|
|
37
|
+
if (typeof text === 'string' && text) return text.slice(0, MODEL_BADGE_TEXT_MAX);
|
|
38
|
+
}
|
|
39
|
+
for (const key of keys) {
|
|
40
|
+
if (typeof value[key] === 'string' && value[key]) return value[key].slice(0, MODEL_BADGE_TEXT_MAX);
|
|
41
|
+
}
|
|
42
|
+
return '';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** 徽标强调档:只认那四个值,其余原样丢掉。 */
|
|
46
|
+
function badgeTone(value) {
|
|
47
|
+
return typeof value === 'string' && MODEL_BADGE_TONES.includes(value) ? value : '';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 一个模型条目的徽标:没有、或文案裁完是空的,就返回空对象(**不挂这个键**,
|
|
52
|
+
* 老宿主的目录形状不变)。有 tone 才带 tone。
|
|
53
|
+
*
|
|
54
|
+
* @param model 宿主清单里的一个模型条目
|
|
55
|
+
* @param language 请求方语言(缺省英文)
|
|
56
|
+
*/
|
|
57
|
+
function modelBadgeOf(model, language) {
|
|
58
|
+
const badge = model && typeof model === 'object' ? model.badge : null;
|
|
59
|
+
if (!badge || typeof badge !== 'object') return {};
|
|
60
|
+
const text = badgeText(badge.text, language);
|
|
61
|
+
if (!text) return {};
|
|
62
|
+
const tone = badgeTone(badge.tone);
|
|
63
|
+
return { badge: { text, ...(tone ? { tone } : {}) } };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 请求方语言标签(`Accept-Language` 第一段,如 `zh-CN`),用来挑本地化徽标文案;
|
|
68
|
+
* 读不到退回 `en`。
|
|
69
|
+
*/
|
|
70
|
+
function requestLanguage(headers) {
|
|
71
|
+
const raw = headers && (headers['accept-language'] || headers['Accept-Language']);
|
|
72
|
+
if (typeof raw !== 'string') return 'en';
|
|
73
|
+
const first = raw.split(',')[0].trim().toLowerCase();
|
|
74
|
+
return first || 'en';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = { modelBadgeOf, badgeText, badgeTone, requestLanguage, MODEL_BADGE_TEXT_MAX, MODEL_BADGE_TONES };
|
package/lib/mobile/pet-tools.js
CHANGED
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
*
|
|
19
19
|
* 只碰这个库:id 形状卡死、图片只认单层 `.gif` / `.png`、单张与整包都有上限。**没有删除** ——
|
|
20
20
|
* 删是人的动作(设置页那边有个按钮),不该由模型顺手做掉。
|
|
21
|
+
*
|
|
22
|
+
* `pack` 整份透传给 `writePack`,不在工具这一层做字段白名单:包格式以后加可选字段(彩蛋 /
|
|
23
|
+
* 时序 / 待机池 / 点按反应 / 命中框…)时,工具这边不用跟着改一遍。判据只有一处,在 pets.js。
|
|
21
24
|
*/
|
|
22
25
|
|
|
23
26
|
const { defineTool } = require('@deepseek-ai/dsh-tools');
|
|
@@ -112,7 +115,7 @@ function mountTools(scoped) {
|
|
|
112
115
|
|
|
113
116
|
scoped.tools.register(defineTool({
|
|
114
117
|
name: 'pet_pack_write',
|
|
115
|
-
description: 'Create or replace a desktop-pet package on this computer, so it shows up on the user\'s phone under Settings → floating pet. Use this whenever the user wants to change their pet, make a new one, or drop in their own artwork. A pack is one directory with a pack.json plus the images it references. States are idle / working / attention / done / error / sleeping, and idle is required; a state you leave out falls back to idle. Pictures are GIF (animated) or PNG (static). Give each picture the PATH of the file on this computer — the host reads it — rather than its bytes; base64 is only for when you already hold the bytes. Existing packs with the same id are replaced wholesale, including a built-in one (warn the user before doing that). You cannot delete packs; that is the user\'s job in Settings.',
|
|
118
|
+
description: 'Create or replace a desktop-pet package on this computer, so it shows up on the user\'s phone under Settings → floating pet. Use this whenever the user wants to change their pet, make a new one, or drop in their own artwork. A pack is one directory with a pack.json plus the images it references. States are idle / working / attention / done / error / sleeping (plus optional thinking / juggling / sweeping / carrying / waking / notification), and idle is required; a state you leave out falls back to idle. Pictures are GIF (animated) or PNG (static). Give each picture the PATH of the file on this computer — the host reads it — rather than its bytes; base64 is only for when you already hold the bytes. Optional richer fields: bonus (an Easter egg per state), timings (minDisplay / autoReturn), idlePool (random idle extras), reactions (tap / tickle / drag art), hitBoxes (with wideAnims / sleepingAnims), roam ({anim, faces}), sleep ({afterMs}), tiers.juggling, pixelArt, scale, contentBox, source. Existing packs with the same id are replaced wholesale, including a built-in one (warn the user before doing that). You cannot delete packs; that is the user\'s job in Settings.',
|
|
116
119
|
parameters: {
|
|
117
120
|
id: {
|
|
118
121
|
type: 'string',
|
|
@@ -121,23 +124,66 @@ function mountTools(scoped) {
|
|
|
121
124
|
pack: {
|
|
122
125
|
type: 'object',
|
|
123
126
|
additionalProperties: true,
|
|
124
|
-
description: 'The whole pack.json as an object. Required keys: name and states (with at least idle). Optional: author, license, canvas {w,h}, anchor {x,baseline}, tiers.working [{min,file}] (largest min wins), loop [], sleepAfterMs. Every file named here must be one you pass in files[].',
|
|
127
|
+
description: 'The whole pack.json as an object. Required keys: name and states (with at least idle). Optional: author, license, source, pixelArt, scale, canvas {w,h}, anchor {x,baseline}, contentBox {x,y,w,h}, tiers.working / tiers.juggling [{min,file}] (largest min wins), loop [], sleepAfterMs (or the nested sleep.afterMs), bonus {state:{anims:[file],chance}}, timings {minDisplay:{state:ms},autoReturn:{state:ms}}, idlePool {everyMs:{mean,min,max},anims:[{file,ms,weight}]}, reactions {tapLeft,tapRight,tickle,drag}, hitBoxes {default,wide,sleeping,byAnim:{anim:{x,y,w,h}},wideAnims:[...],sleepingAnims:[...]}, roam {anim,faces:left|right|front}. Every file named here must be one you pass in files[].',
|
|
125
128
|
properties: {
|
|
126
129
|
name: { type: 'string', description: 'Shown in the picker on the phone.' },
|
|
127
130
|
author: { type: 'string', description: 'Credited in the picker footer. Fill it in whenever the artwork is not yours.' },
|
|
128
131
|
license: { type: 'string', description: 'License note, shown next to the author.' },
|
|
132
|
+
source: { type: 'string', description: 'Where the artwork came from, shown in the picker footer.' },
|
|
133
|
+
pixelArt: { type: 'boolean', description: 'Nearest-neighbour scaling. Default true.' },
|
|
134
|
+
scale: { type: 'number', description: 'Sheet pixels per art pixel, e.g. 5 for a 5x export.' },
|
|
135
|
+
contentBox: {
|
|
136
|
+
type: 'object',
|
|
137
|
+
additionalProperties: true,
|
|
138
|
+
description: 'The idle body in art pixels {x,y,w,h}: the hit box and where bubbles sit.',
|
|
139
|
+
},
|
|
129
140
|
states: {
|
|
130
141
|
type: 'object',
|
|
131
142
|
additionalProperties: true,
|
|
132
|
-
description: 'State name → { file }. Recognised: idle, working, attention, done, error, sleeping. At least idle is required.',
|
|
143
|
+
description: 'State name → { file }. Recognised: idle, working, attention, done, error, sleeping, plus the optional thinking / juggling / sweeping / carrying / waking / notification. At least idle is required.',
|
|
133
144
|
},
|
|
134
145
|
tiers: {
|
|
135
146
|
type: 'object',
|
|
136
147
|
additionalProperties: true,
|
|
137
|
-
description: 'Optional. tiers.working
|
|
148
|
+
description: 'Optional. tiers.working / tiers.juggling are [{min, file}]: pick the image by how many conversations (or sub-agents) are running.',
|
|
138
149
|
},
|
|
139
150
|
loop: { type: 'array', items: { type: 'string' }, description: 'States that loop. Defaults to idle, working, attention.' },
|
|
140
151
|
sleepAfterMs: { type: 'number', description: 'Idle time before switching to sleeping. Only meaningful when the sleeping state exists.' },
|
|
152
|
+
bonus: {
|
|
153
|
+
type: 'object',
|
|
154
|
+
additionalProperties: true,
|
|
155
|
+
description: 'Optional Easter egg: state → { anims: [file], chance: 0..1 }. An extra animation played instead of the state main art when the dice hit.',
|
|
156
|
+
},
|
|
157
|
+
timings: {
|
|
158
|
+
type: 'object',
|
|
159
|
+
additionalProperties: true,
|
|
160
|
+
description: 'Optional { minDisplay: {state: ms}, autoReturn: {state: ms} }: how long a one-off state stays up and when it folds back.',
|
|
161
|
+
},
|
|
162
|
+
idlePool: {
|
|
163
|
+
type: 'object',
|
|
164
|
+
additionalProperties: true,
|
|
165
|
+
description: 'Optional { everyMs: {mean,min,max}, anims: [{file,ms,weight}] }: random small idle animations while nothing is happening.',
|
|
166
|
+
},
|
|
167
|
+
reactions: {
|
|
168
|
+
type: 'object',
|
|
169
|
+
additionalProperties: true,
|
|
170
|
+
description: 'Optional { tapLeft, tapRight, tickle, drag }: the animation played for each touch gesture, each a file in this pack.',
|
|
171
|
+
},
|
|
172
|
+
hitBoxes: {
|
|
173
|
+
type: 'object',
|
|
174
|
+
additionalProperties: true,
|
|
175
|
+
description: 'Optional { default, wide, sleeping, byAnim: { anim: {x,y,w,h} }, wideAnims: [anim], sleepingAnims: [anim] } in art pixels: where the window takes touches. The host picks byAnim → sleepingAnims → wideAnims → default.',
|
|
176
|
+
},
|
|
177
|
+
roam: {
|
|
178
|
+
type: 'object',
|
|
179
|
+
additionalProperties: true,
|
|
180
|
+
description: 'Optional { anim, faces }: the walking animation and which way it looks (left / right / front). anim must be a file in this pack.',
|
|
181
|
+
},
|
|
182
|
+
sleep: {
|
|
183
|
+
type: 'object',
|
|
184
|
+
additionalProperties: true,
|
|
185
|
+
description: 'Optional { afterMs }: idle time before sleeping. The top-level sleepAfterMs is the older spelling; both are accepted.',
|
|
186
|
+
},
|
|
141
187
|
},
|
|
142
188
|
},
|
|
143
189
|
files: {
|
package/lib/mobile/pets.js
CHANGED
|
@@ -17,6 +17,14 @@
|
|
|
17
17
|
* 少一个目录、pack.json 读不出、schema 不是 1、id 不合规矩、图片超限、没有能用的 idle,
|
|
18
18
|
* 一律当作「这个包不存在」,不抛、不让整张列表变红。取文件那条才逐个回错误码。
|
|
19
19
|
*
|
|
20
|
+
* ## 可选的表现字段
|
|
21
|
+
*
|
|
22
|
+
* `bonus` / `timings` / `idlePool` / `reactions` / `hitBoxes` / `roam` / `sleep` /
|
|
23
|
+
* `tiers.juggling` / `pixelArt` / `scale` / `contentBox` / `source` 全部可选(形状见
|
|
24
|
+
* docs/pet-overlay.md §1.1 与 pet-align/CONTRACT.md §5):认得的收下来、引用的图得真在
|
|
25
|
+
* 包里,其余坏值逐项丢掉——一个字段写坏了不该让整只宠物消失。老包里没有这些键,照旧能跑。
|
|
26
|
+
* 睡觉时长有新旧两种写法:顶层 `sleepAfterMs`(老包)与 `sleep.afterMs`,两个都透传。
|
|
27
|
+
*
|
|
20
28
|
* ## 能力面
|
|
21
29
|
*
|
|
22
30
|
* 只认包目录内**单层**的 `.gif` / `.png`:id 与文件名都卡成不含斜杠的形状,取文件时再按
|
|
@@ -34,6 +42,15 @@ const { dshHome } = require('../shared/paths.js');
|
|
|
34
42
|
const ID_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
35
43
|
/** 认得的状态键。表外的键原样忽略,以后加状态时老 App 还能用这份包。 */
|
|
36
44
|
const KNOWN_STATES = Object.freeze(['idle', 'working', 'attention', 'done', 'error', 'sleeping']);
|
|
45
|
+
/**
|
|
46
|
+
* 与 dsh-go 对齐的那几个扩展态(`thinking` 等,见 pet-align/CONTRACT.md §2)。
|
|
47
|
+
*
|
|
48
|
+
* 它们能进 `states` / `loop` / `timings` / `bonus`,但**不进** `KNOWN_STATES`:那六个是
|
|
49
|
+
* 「一只包该有的基本状态」的清单,扩展态是「有素材就上」的增量,缺了不算包的毛病。
|
|
50
|
+
*/
|
|
51
|
+
const EXTRA_STATES = Object.freeze(['thinking', 'juggling', 'sweeping', 'carrying', 'waking', 'notification']);
|
|
52
|
+
/** 包能表达的全部状态键。 */
|
|
53
|
+
const PACK_STATES = Object.freeze([...KNOWN_STATES, ...EXTRA_STATES]);
|
|
37
54
|
/** 默认循环播的那几档(与 §1.1 一致)。 */
|
|
38
55
|
const DEFAULT_LOOP = Object.freeze(['idle', 'working', 'attention']);
|
|
39
56
|
/** 单张图上限:一张 GIF 一般几十到几百 KB,四 MB 是「明显不对劲」的那条线。 */
|
|
@@ -114,6 +131,164 @@ function pairOf(value, keys) {
|
|
|
114
131
|
return out;
|
|
115
132
|
}
|
|
116
133
|
|
|
134
|
+
/** 一个形状合规矩的名字:命中框的分组键、动画别名都用这个判据。 */
|
|
135
|
+
function isNameToken(name) {
|
|
136
|
+
return typeof name === 'string' && /^[a-z0-9][a-z0-9-]{0,63}$/i.test(name);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** `{x,y,w,h}` 一类的矩形:四个数齐、宽高为正才收。 */
|
|
140
|
+
function rectOf(value) {
|
|
141
|
+
const rect = pairOf(value, ['x', 'y', 'w', 'h']);
|
|
142
|
+
if (!rect || rect.w <= 0 || rect.h <= 0) return undefined;
|
|
143
|
+
return rect;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** 状态 → 毫秒的小表(`timings.minDisplay` 这一类):正的有限数才收,其余键忽略。 */
|
|
147
|
+
function msMapOf(value) {
|
|
148
|
+
if (!isRecord(value)) return undefined;
|
|
149
|
+
const out = {};
|
|
150
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
151
|
+
if (!PACK_STATES.includes(key)) continue;
|
|
152
|
+
const ms = finiteOf(raw);
|
|
153
|
+
if (ms === null || ms <= 0) continue;
|
|
154
|
+
out[key] = Math.round(ms);
|
|
155
|
+
}
|
|
156
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** 一张小表:只留 `keys` 里点名的格,值得是包里真有的图。 */
|
|
160
|
+
function fileMapOf(value, present, keys) {
|
|
161
|
+
if (!isRecord(value)) return undefined;
|
|
162
|
+
const out = {};
|
|
163
|
+
for (const key of keys) {
|
|
164
|
+
const file = textOf(value[key], 120);
|
|
165
|
+
if (!isImageName(file) || !present.has(file)) continue;
|
|
166
|
+
out[key] = file;
|
|
167
|
+
}
|
|
168
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** 彩蛋:`{ 状态: { anims: [图…], chance } }`,chance 夹到 0..1,空表不算错。 */
|
|
172
|
+
function bonusOf(value, present) {
|
|
173
|
+
if (!isRecord(value)) return undefined;
|
|
174
|
+
const out = {};
|
|
175
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
176
|
+
if (!PACK_STATES.includes(key) || !isRecord(raw)) continue;
|
|
177
|
+
const anims = [];
|
|
178
|
+
for (const item of Array.isArray(raw.anims) ? raw.anims : []) {
|
|
179
|
+
const file = textOf(item, 120);
|
|
180
|
+
if (!isImageName(file) || !present.has(file) || anims.includes(file)) continue;
|
|
181
|
+
anims.push(file);
|
|
182
|
+
}
|
|
183
|
+
const chance = finiteOf(raw.chance);
|
|
184
|
+
if (anims.length === 0 || chance === null || chance <= 0) continue;
|
|
185
|
+
out[key] = { anims, chance: Math.min(1, chance) };
|
|
186
|
+
}
|
|
187
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** 待机池:`{ everyMs: {mean,min,max}, anims: [{file,ms,weight}] }`,两项各自可选。 */
|
|
191
|
+
function idlePoolOf(value, present) {
|
|
192
|
+
if (!isRecord(value)) return undefined;
|
|
193
|
+
const anims = [];
|
|
194
|
+
for (const item of Array.isArray(value.anims) ? value.anims : []) {
|
|
195
|
+
if (!isRecord(item)) continue;
|
|
196
|
+
const file = textOf(item.file, 120);
|
|
197
|
+
if (!isImageName(file) || !present.has(file)) continue;
|
|
198
|
+
const ms = finiteOf(item.ms);
|
|
199
|
+
const weight = finiteOf(item.weight);
|
|
200
|
+
anims.push({
|
|
201
|
+
file,
|
|
202
|
+
...(ms !== null && ms > 0 ? { ms: Math.round(ms) } : {}),
|
|
203
|
+
...(weight !== null && weight > 0 ? { weight } : {}),
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
const every = pairOf(value.everyMs, ['mean', 'min', 'max']);
|
|
207
|
+
const everyMs = every && every.mean > 0 && every.min >= 0 && every.max >= every.min ? every : undefined;
|
|
208
|
+
if (anims.length === 0 && !everyMs) return undefined;
|
|
209
|
+
return {
|
|
210
|
+
...(everyMs ? { everyMs } : {}),
|
|
211
|
+
...(anims.length > 0 ? { anims } : {}),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** 一串动画名:名字形状合规矩、去重;空表不收。 */
|
|
216
|
+
function nameListOf(value) {
|
|
217
|
+
if (!Array.isArray(value)) return undefined;
|
|
218
|
+
const names = [];
|
|
219
|
+
for (const item of value) {
|
|
220
|
+
if (!isNameToken(item) || names.includes(item)) continue;
|
|
221
|
+
names.push(item);
|
|
222
|
+
}
|
|
223
|
+
return names.length > 0 ? names : undefined;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* 命中框:`default / wide / sleeping` 三档 + 按动画名分组的 `byAnim`,外加两张点名表
|
|
228
|
+
* `wideAnims` / `sleepingAnims`(原生据此选框:byAnim → sleepingAnims → wideAnims → default)。
|
|
229
|
+
* 坏的那一档丢掉,不影响其余。
|
|
230
|
+
*/
|
|
231
|
+
function hitBoxesOf(value) {
|
|
232
|
+
if (!isRecord(value)) return undefined;
|
|
233
|
+
const out = {};
|
|
234
|
+
for (const key of ['default', 'wide', 'sleeping']) {
|
|
235
|
+
const rect = rectOf(value[key]);
|
|
236
|
+
if (rect) out[key] = rect;
|
|
237
|
+
}
|
|
238
|
+
if (isRecord(value.byAnim)) {
|
|
239
|
+
const byAnim = {};
|
|
240
|
+
for (const [key, raw] of Object.entries(value.byAnim)) {
|
|
241
|
+
if (!isNameToken(key)) continue;
|
|
242
|
+
const rect = rectOf(raw);
|
|
243
|
+
if (rect) byAnim[key] = rect;
|
|
244
|
+
}
|
|
245
|
+
if (Object.keys(byAnim).length > 0) out.byAnim = byAnim;
|
|
246
|
+
}
|
|
247
|
+
for (const key of ['wideAnims', 'sleepingAnims']) {
|
|
248
|
+
const names = nameListOf(value[key]);
|
|
249
|
+
if (names) out[key] = names;
|
|
250
|
+
}
|
|
251
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** 游走:`{ anim, faces }`;anim 得是包里真有的单层图,faces 不认识就整个丢掉。 */
|
|
255
|
+
const FACINGS = Object.freeze(['left', 'right', 'front']);
|
|
256
|
+
function roamOf(value, present) {
|
|
257
|
+
if (!isRecord(value)) return undefined;
|
|
258
|
+
const file = textOf(value.anim, 120);
|
|
259
|
+
if (!isImageName(file) || !present.has(file)) return undefined;
|
|
260
|
+
if (!FACINGS.includes(value.faces)) return undefined;
|
|
261
|
+
return { anim: file, faces: value.faces };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** 睡觉:`{ afterMs }`,正整数才收。顶层的 sleepAfterMs 是另一套老写法,两者并存。 */
|
|
265
|
+
function sleepOf(value) {
|
|
266
|
+
if (!isRecord(value)) return undefined;
|
|
267
|
+
const afterMs = finiteOf(value.afterMs);
|
|
268
|
+
return afterMs !== null && afterMs > 0 ? { afterMs: Math.round(afterMs) } : undefined;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** 分档:`working` 与 `juggling` 同形(`[{min, file}]`,按 min 从大到小)。 */
|
|
272
|
+
function tiersOf(value, present) {
|
|
273
|
+
const out = {};
|
|
274
|
+
if (!isRecord(value)) return out;
|
|
275
|
+
for (const key of ['working', 'juggling']) {
|
|
276
|
+
if (!Array.isArray(value[key])) continue;
|
|
277
|
+
const rows = [];
|
|
278
|
+
for (const item of value[key]) {
|
|
279
|
+
if (!isRecord(item)) continue;
|
|
280
|
+
const min = finiteOf(item.min);
|
|
281
|
+
const file = textOf(item.file, 120);
|
|
282
|
+
if (min === null || min < 1) continue;
|
|
283
|
+
if (!isImageName(file) || !present.has(file)) continue;
|
|
284
|
+
rows.push({ min: Math.floor(min), file });
|
|
285
|
+
}
|
|
286
|
+
rows.sort((a, b) => b.min - a.min);
|
|
287
|
+
if (rows.length > 0) out[key] = rows;
|
|
288
|
+
}
|
|
289
|
+
return out;
|
|
290
|
+
}
|
|
291
|
+
|
|
117
292
|
/**
|
|
118
293
|
* 扫包目录里的图片:单层、名字合规矩、不点开头;超限当场返回 null(整包拒收)。
|
|
119
294
|
*
|
|
@@ -172,7 +347,7 @@ function buildPack(raw, id, entries) {
|
|
|
172
347
|
if (!isRecord(raw.states)) {
|
|
173
348
|
problems.push('states 必填,而且至少要有一个 idle。');
|
|
174
349
|
} else {
|
|
175
|
-
for (const key of
|
|
350
|
+
for (const key of PACK_STATES) {
|
|
176
351
|
const entry = raw.states[key];
|
|
177
352
|
if (!isRecord(entry)) continue;
|
|
178
353
|
const file = textOf(entry.file, 120);
|
|
@@ -182,26 +357,13 @@ function buildPack(raw, id, entries) {
|
|
|
182
357
|
if (!states.idle) problems.push('states 里至少要有一个能用的 idle(file 得是包里真有的 .gif / .png)。');
|
|
183
358
|
}
|
|
184
359
|
|
|
185
|
-
// tiers
|
|
186
|
-
const tiers =
|
|
187
|
-
if (isRecord(raw.tiers) && Array.isArray(raw.tiers.working)) {
|
|
188
|
-
const rows = [];
|
|
189
|
-
for (const item of raw.tiers.working) {
|
|
190
|
-
if (!isRecord(item)) continue;
|
|
191
|
-
const min = finiteOf(item.min);
|
|
192
|
-
const file = textOf(item.file, 120);
|
|
193
|
-
if (min === null || min < 1) continue;
|
|
194
|
-
if (!isImageName(file) || !present.has(file)) continue;
|
|
195
|
-
rows.push({ min: Math.floor(min), file });
|
|
196
|
-
}
|
|
197
|
-
rows.sort((a, b) => b.min - a.min);
|
|
198
|
-
if (rows.length > 0) tiers.working = rows;
|
|
199
|
-
}
|
|
360
|
+
// tiers:按「同时在跑几条」换图,min 从大到小匹配;`working` 与 `juggling` 同形。
|
|
361
|
+
const tiers = tiersOf(raw.tiers, present);
|
|
200
362
|
|
|
201
363
|
const loop = [];
|
|
202
364
|
if (Array.isArray(raw.loop)) {
|
|
203
365
|
for (const item of raw.loop) {
|
|
204
|
-
if (typeof item !== 'string' || !
|
|
366
|
+
if (typeof item !== 'string' || !PACK_STATES.includes(item) || loop.includes(item)) continue;
|
|
205
367
|
loop.push(item);
|
|
206
368
|
}
|
|
207
369
|
}
|
|
@@ -209,18 +371,41 @@ function buildPack(raw, id, entries) {
|
|
|
209
371
|
|
|
210
372
|
if (problems.length > 0) return { pack: null, problems };
|
|
211
373
|
|
|
374
|
+
const minDisplay = isRecord(raw.timings) ? msMapOf(raw.timings.minDisplay) : undefined;
|
|
375
|
+
const autoReturn = isRecord(raw.timings) ? msMapOf(raw.timings.autoReturn) : undefined;
|
|
376
|
+
const timings = minDisplay || autoReturn
|
|
377
|
+
? { ...(minDisplay ? { minDisplay } : {}), ...(autoReturn ? { autoReturn } : {}) }
|
|
378
|
+
: undefined;
|
|
379
|
+
const contentBox = rectOf(raw.contentBox);
|
|
380
|
+
// 缺省值与 dsh-go 一致:像素画、1:1。三层都按这一份读,别各猜各的。
|
|
381
|
+
const pixelArt = typeof raw.pixelArt === 'boolean' ? raw.pixelArt : true;
|
|
382
|
+
const scale = Math.max(1, Math.floor(finiteOf(raw.scale) ?? 1));
|
|
383
|
+
const source = textOrNull(raw.source) || undefined;
|
|
384
|
+
|
|
212
385
|
return {
|
|
213
386
|
pack: {
|
|
214
387
|
id,
|
|
215
388
|
name,
|
|
216
389
|
author: textOrNull(raw.author),
|
|
217
390
|
license: textOrNull(raw.license),
|
|
391
|
+
source,
|
|
392
|
+
pixelArt,
|
|
393
|
+
scale,
|
|
218
394
|
canvas: pairOf(raw.canvas, ['w', 'h']),
|
|
219
395
|
anchor: pairOf(raw.anchor, ['x', 'baseline']),
|
|
396
|
+
...(contentBox ? { contentBox } : {}),
|
|
220
397
|
states,
|
|
221
398
|
tiers,
|
|
222
399
|
loop: loop.length > 0 ? loop : [...DEFAULT_LOOP],
|
|
223
400
|
sleepAfterMs: sleepAfterMs !== null && sleepAfterMs > 0 ? sleepAfterMs : undefined,
|
|
401
|
+
// 扩展字段全部可选:老包没有这些键,App/原生按缺省处理。
|
|
402
|
+
bonus: bonusOf(raw.bonus, present),
|
|
403
|
+
idlePool: idlePoolOf(raw.idlePool, present),
|
|
404
|
+
reactions: fileMapOf(raw.reactions, present, ['tapLeft', 'tapRight', 'tickle', 'drag']),
|
|
405
|
+
hitBoxes: hitBoxesOf(raw.hitBoxes),
|
|
406
|
+
roam: roamOf(raw.roam, present),
|
|
407
|
+
sleep: sleepOf(raw.sleep),
|
|
408
|
+
timings,
|
|
224
409
|
size: entries.reduce((sum, entry) => sum + entry.size, 0),
|
|
225
410
|
files: entries,
|
|
226
411
|
},
|
|
@@ -289,7 +474,7 @@ function dirNames(root) {
|
|
|
289
474
|
.map((entry) => entry.name);
|
|
290
475
|
}
|
|
291
476
|
|
|
292
|
-
/** 只留列表要的形状:内部的 `dir`
|
|
477
|
+
/** 只留列表要的形状:内部的 `dir` 不出去。可选字段没填时是 undefined,JSON 里就不出现。 */
|
|
293
478
|
function publicPack(pack) {
|
|
294
479
|
return {
|
|
295
480
|
id: pack.id,
|
|
@@ -297,12 +482,23 @@ function publicPack(pack) {
|
|
|
297
482
|
builtin: pack.builtin,
|
|
298
483
|
author: pack.author,
|
|
299
484
|
license: pack.license,
|
|
485
|
+
source: pack.source,
|
|
486
|
+
pixelArt: pack.pixelArt,
|
|
487
|
+
scale: pack.scale,
|
|
300
488
|
canvas: pack.canvas,
|
|
301
489
|
anchor: pack.anchor,
|
|
490
|
+
contentBox: pack.contentBox,
|
|
302
491
|
states: pack.states,
|
|
303
492
|
tiers: pack.tiers,
|
|
304
493
|
loop: pack.loop,
|
|
305
494
|
sleepAfterMs: pack.sleepAfterMs,
|
|
495
|
+
bonus: pack.bonus,
|
|
496
|
+
timings: pack.timings,
|
|
497
|
+
idlePool: pack.idlePool,
|
|
498
|
+
reactions: pack.reactions,
|
|
499
|
+
hitBoxes: pack.hitBoxes,
|
|
500
|
+
roam: pack.roam,
|
|
501
|
+
sleep: pack.sleep,
|
|
306
502
|
size: pack.size,
|
|
307
503
|
files: pack.files,
|
|
308
504
|
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 逐 App 访问控制(清单 103):用户拉黑哪些 App 不许 AI 碰、哪些只允许分享。
|
|
5
|
+
*
|
|
6
|
+
* 这里是**策略真值**(插件侧、落盘):`blocked` 集与 `shareAllowed` 集,按包名记。
|
|
7
|
+
* 判据本身在原生 `AppAccess.kt`(执行面强制点),它每次读最新状态、不在任务开始时冻结;
|
|
8
|
+
* 插件侧这份是本机唯一真值,原生侧会在下发动作前按同一份名单判。
|
|
9
|
+
*
|
|
10
|
+
* 能力面:只是本地开关,不扩「能碰什么 App」之外的能力;不涉及 shell/任意文件写。
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
|
|
17
|
+
function storePath() {
|
|
18
|
+
const home = process.env.DSH_HOME || path.join(os.homedir(), '.dsh');
|
|
19
|
+
return path.join(home, 'storages', 'mobile-plugin', 'phone-app-access.json');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function readState() {
|
|
23
|
+
try {
|
|
24
|
+
const raw = fs.readFileSync(storePath(), 'utf8');
|
|
25
|
+
const parsed = JSON.parse(raw);
|
|
26
|
+
return {
|
|
27
|
+
blocked: Array.isArray(parsed.blocked) ? parsed.blocked.filter((x) => typeof x === 'string') : [],
|
|
28
|
+
shareAllowed: Array.isArray(parsed.shareAllowed) ? parsed.shareAllowed.filter((x) => typeof x === 'string') : [],
|
|
29
|
+
};
|
|
30
|
+
} catch {
|
|
31
|
+
return { blocked: [], shareAllowed: [] };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function writeState(state) {
|
|
36
|
+
const file = storePath();
|
|
37
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
38
|
+
const tmp = `${file}.tmp`;
|
|
39
|
+
fs.writeFileSync(tmp, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
|
|
40
|
+
fs.renameSync(tmp, file);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function normalizePackage(value) {
|
|
44
|
+
if (typeof value !== 'string') return '';
|
|
45
|
+
const pkg = value.trim();
|
|
46
|
+
// 包名形状:只允许点分标识符。挡住把路径/命令塞进来的调用。
|
|
47
|
+
if (!/^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)+$/.test(pkg)) return '';
|
|
48
|
+
return pkg;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 改一个 App 的准入。只传一个字段就只改那一个(另一个保持原样)。
|
|
53
|
+
* @returns {{ok:boolean, error?:string, state?:object}}
|
|
54
|
+
*/
|
|
55
|
+
function update(packageName, patch) {
|
|
56
|
+
const pkg = normalizePackage(packageName);
|
|
57
|
+
if (!pkg) return { ok: false, error: 'bad-package' };
|
|
58
|
+
const state = readState();
|
|
59
|
+
const set = (list, key) => {
|
|
60
|
+
const has = list.includes(pkg);
|
|
61
|
+
if (patch[key] === true && !has) list.push(pkg);
|
|
62
|
+
if (patch[key] === false && has) list.splice(list.indexOf(pkg), 1);
|
|
63
|
+
};
|
|
64
|
+
if (typeof patch.blocked === 'boolean') set(state.blocked, 'blocked');
|
|
65
|
+
if (typeof patch.shareAllowed === 'boolean') set(state.shareAllowed, 'shareAllowed');
|
|
66
|
+
// 拉了黑名单就不该还在分享白名单里,反之亦然。
|
|
67
|
+
if (state.blocked.includes(pkg)) state.shareAllowed = state.shareAllowed.filter((x) => x !== pkg);
|
|
68
|
+
writeState(state);
|
|
69
|
+
return { ok: true, state };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** 行视图:把两个集合合成一条 App 的开关(包名去重合并)。 */
|
|
73
|
+
function rows() {
|
|
74
|
+
const state = readState();
|
|
75
|
+
const seen = new Set();
|
|
76
|
+
const out = [];
|
|
77
|
+
for (const pkg of [...state.blocked, ...state.shareAllowed]) {
|
|
78
|
+
if (seen.has(pkg)) continue;
|
|
79
|
+
seen.add(pkg);
|
|
80
|
+
out.push({ packageName: pkg, blocked: state.blocked.includes(pkg), shareAllowed: state.shareAllowed.includes(pkg) });
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = { readState, writeState, update, rows, normalizePackage, storePath };
|