@huanlin/dsh-plugin-android-use 0.1.0

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/README.md ADDED
@@ -0,0 +1,144 @@
1
+ <p align="center">
2
+ <a href="https://dshfind.com/zh/plugins/huanlinoto/dsh-plugin-android-use"><img src="https://dshfind.com/api/card/huanlinoto/dsh-plugin-android-use?lang=zh" alt="dsh-plugin-android-use card"></a>
3
+ </p>
4
+
5
+ # dsh-android-use
6
+
7
+ DSH 插件:让 AI 通过 adb 操作安卓手机。提供 10 个工具覆盖设备发现、屏幕感知、输入操作和应用管理,让模型能够自主操控 Android 设备。
8
+
9
+ ## 安装
10
+
11
+ ```sh
12
+ # 从本地 checkout 开发安装:
13
+ dsh plugin --profile web add link:D:\Projects\deepseek-harness\dsh-plugin-android-use
14
+
15
+ # 从 npm 安装(发布后):
16
+ dsh plugin --profile web add @huanlin/dsh-plugin-android-use
17
+ ```
18
+
19
+ 预构建策略:`lib/` 入库,无 `prepare` 脚本,npm 安装开箱即用,无需 `allowBuilds`。
20
+
21
+ **前提条件**:本机已安装 adb 并在 PATH 上(或通过 `adbPath` 配置指定路径),已通过 `adb connect <ip:port>` 或 USB 连接 Android 设备。
22
+
23
+ ## 配置
24
+
25
+ 在 DSH GUI 设置页或 `cordis.patch.yml` 中配置:
26
+
27
+ | 字段 | 类型 | 默认值 | 说明 |
28
+ |------|------|--------|------|
29
+ | `adbPath` | string | `'adb'` | adb 可执行路径。默认从 PATH 查找。 |
30
+ | `defaultSerial` | string? | - | 默认设备 serial。省略则单设备自动选择;多设备时模型需传 `serial` 参数。 |
31
+ | `inputTextMode` | `'input' \| 'adbkeyboard'` | `'input'` | 文本输入模式。`input` 仅支持 ASCII(用 `adb shell input text`);`adbkeyboard` 支持 Unicode(需设备安装 ADBKeyboard IME)。 |
32
+
33
+ ## 工具
34
+
35
+ ### 设备发现
36
+
37
+ | 工具 | 参数 | 说明 |
38
+ |------|------|------|
39
+ | `android_list_devices` | 无 | 列出所有已连接的安卓设备(serial、state、product、model)。 |
40
+ | `android_device_info` | `serial?` | 获取设备详细信息:型号、品牌、Android 版本、SDK、屏幕分辨率、密度、屏幕状态。 |
41
+
42
+ ### 屏幕感知
43
+
44
+ | 工具 | 参数 | 说明 |
45
+ |------|------|------|
46
+ | `android_screenshot` | `serial?` | 截屏并保存到 attachment store。仅当当前模型路由支持 image 输入时向模型发图;否则仅返回元数据(感知走 `android_ui_dump`)。 |
47
+ | `android_ui_dump` | `serial?` | 转储 accessibility 树(UI 层级),返回节点列表(文本、bounds、center 坐标、交互标志)。**主要屏幕感知方式**——DeepSeek 无视觉能力时靠它理解屏幕。 |
48
+
49
+ ### 输入操作
50
+
51
+ | 工具 | 参数 | 说明 |
52
+ |------|------|------|
53
+ | `android_tap` | `x, y` (必填), `duration_ms?`, `times?` | 点击屏幕坐标。`duration_ms > 0` 为长按。 |
54
+ | `android_swipe` | `x1, y1, x2, y2` (必填), `duration_ms?` | 从一点滑动到另一点。 |
55
+ | `android_press_key` | `key` (必填, 命名或整数), `times?` | 按键。命名键:`home`/`back`/`app_switch`/`power`/`enter`/`volume_up` 等,或裸 keycode 整数。 |
56
+ | `android_input_text` | `text` (必填), `submit?` | 在聚焦输入框中输入文本。`submit: true` 输入后按回车。 |
57
+
58
+ ### 应用管理
59
+
60
+ | 工具 | 参数 | 说明 |
61
+ |------|------|------|
62
+ | `android_open_app` | `package` (必填), `activity?` | 打开应用。有 activity 用 `am start`;无 activity 用 `monkey` 启动默认 Activity。 |
63
+ | `android_foreground_app` | `serial?` | 获取当前前台应用和 Activity(通过 `dumpsys window`)。 |
64
+
65
+ ## 开发
66
+
67
+ ```sh
68
+ pnpm install # 安装开发依赖(schemastery、typescript、vitest、tsdown)
69
+ pnpm run typecheck # tsc --noEmit 类型检查
70
+ pnpm test # vitest run 单元测试(132 用例)
71
+ pnpm run build # tsc + tsdown → lib/(index.js + client.js 双产物)
72
+ ```
73
+
74
+ 类型检查需要 junction 到本地 DSH 源码树(私有 peer 依赖不在 npm 上;junction 必须在 `pnpm install` 之后创建):
75
+
76
+ ```powershell
77
+ # 在 node_modules/@deepseek-ai/ 下创建 junction(指向本地 DSH 源码 checkout)
78
+ $nm = "node_modules/@deepseek-ai"
79
+ $src = "C:\Users\Administrator\.dsh\source\current"
80
+ @{
81
+ "cordis" = "$src\vendor\cordis"
82
+ "dsh-tools" = "$src\packages\core\tools"
83
+ "dsh-attachment" = "$src\packages\attachment\attachment"
84
+ "dsh-llm" = "$src\packages\llm\llm"
85
+ "dsh-client-ui-slots" = "$src\packages\client\ui-slots"
86
+ "dsh-client-ui-tool" = "$src\packages\client\ui-tool"
87
+ "dsh-client-ui-session" = "$src\packages\client\ui-session"
88
+ "dsh-client-ui-conversation" = "$src\packages\client\ui-conversation"
89
+ "dsh-client-ui-renderer" = "$src\packages\client\ui-renderer"
90
+ }.GetEnumerator() | ForEach-Object {
91
+ New-Item -ItemType Junction -Path (Join-Path $nm $_.Key) -Target $_.Value | Out-Null
92
+ }
93
+ ```
94
+
95
+ ## 检查
96
+
97
+ 合规自检(参见 `plugin-development-guide.md` §10):
98
+
99
+ - [x] **零源码 patch**:未修改 DSH checkout 任何文件
100
+ - [x] B1: `package.json` 声明 `dsh.bundle.patch`
101
+ - [x] B2: 插件自带 `cordis.patch.yml`(insert 行 id/name/config 齐全)
102
+ - [x] B3: patch 行 `name` 用包名(Loader 从 profile node_modules 解析)
103
+ - [x] F1: `files` 含 `lib/` + `cordis.patch.yml`
104
+ - [x] F2: `peerDependencies` 含 `@deepseek-ai/cordis` + 用到的 `@deepseek-ai/*`(不用 devDependencies 冒充)
105
+ - [x] F3: typecheck/test/build script 齐全
106
+ - [x] A6: 不导出 default
107
+ - [x] C4: 工具返回规范 JSON 值 + render 投影分离
108
+ - [x] C5: adb 缺失/设备离线等基础设施失败 throw;取消不 throw
109
+ - [x] C6: 尊重 `exec.signal` 取消在途 adb 子进程
110
+ - [x] G: Unit 测试(`tests/*.spec.ts`,132 用例,含真实 launcher dump fixture)
111
+
112
+ ## 目录结构
113
+
114
+ ```
115
+ dsh-plugin-android-use/
116
+ ├── src/
117
+ │ ├── index.ts # 入口:name、inject、Config(Schemastery)、apply
118
+ │ ├── adb.ts # AdbClient: spawn 封装 + serial 解析 + 设备列表解析
119
+ │ ├── xml.ts # uiautomator XML 解析(零依赖)
120
+ │ ├── keys.ts # 命名按键表(home/back/app_switch/... → keycode)
121
+ │ ├── registry.ts # registerTools(ctx, deps) — 依赖注入入口
122
+ │ ├── client/ # 浏览器半:tool.call.toolview 卡片(tap / screenshot)
123
+ │ │ ├── index.ts # client 插件:slots.inject 注册两张卡片
124
+ │ │ ├── TapCard.tsx # android_tap 卡片(前后截图对照)
125
+ │ │ └── ScreenshotCard.tsx # android_screenshot 卡片
126
+ │ └── tools/
127
+ │ ├── device.ts # list_devices / device_info
128
+ │ ├── screen.ts # screenshot / ui_dump
129
+ │ ├── input.ts # tap / swipe / press_key / input_text
130
+ │ └── apps.ts # open_app / foreground_app
131
+ ├── tests/
132
+ │ ├── adb.spec.ts # 设备列表解析、serial 解析序
133
+ │ ├── xml.spec.ts # XML 解析(真实 launcher dump fixture)
134
+ │ ├── keys.spec.ts # 命名按键 → keycode 映射
135
+ │ ├── input.spec.ts # 文本转义、非 ASCII 报错、adbkeyboard 编码
136
+ │ ├── tools.spec.ts # fake AdbClient + 全工具 schema/execute/render 验证
137
+ │ └── fixtures/
138
+ │ └── launcher_dump.xml # 真实 PJF110 设备 launcher dump
139
+ ├── cordis.patch.yml # bundle 层:插入 dsh-android-use 插件行
140
+ ├── package.json # dsh.bundle.patch 声明 + peerDeps
141
+ ├── tsconfig.json # NodeNext、ES2022、strict
142
+ ├── tsdown.config.ts # 双 entry:index.js(node)+ client.js(DSSH 模块加载器包裹)
143
+ └── vitest.config.ts # vitest forks pool
144
+ ```
@@ -0,0 +1,10 @@
1
+ # dsh-android-use bundle layer: inserts the dsh-android-use plugin row.
2
+ # Loaded by `dsh plugin --profile <name> add @huanlin/dsh-plugin-android-use`.
3
+ # The `name` is the package entry; Loader resolves it from profile node_modules.
4
+ # Defaults: adb from PATH, input mode 'input' (ASCII), no default serial.
5
+ - insert:
6
+ - id: dsh-android-use
7
+ name: '@huanlin/dsh-plugin-android-use'
8
+ config:
9
+ adbPath: adb
10
+ inputTextMode: input
package/lib/client.js ADDED
@@ -0,0 +1,326 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@huanlin/dsh-plugin-android-use",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react = require("react");
8
+ let react_jsx_runtime = require("react/jsx-runtime");
9
+ //#region \0dsh-css:D:\Projects\deepseek-harness\dsh-plugin-android-use\src\client\TapCard.module.css.mjs
10
+ const css$1 = "/* TapCard toolview: a compact card showing the annotated pre-tap screenshot\n * with tap coordinates and action metadata.\n *\n * Every color resolves through a --dsw-alias-* token (no literal colors). */\n\n.d8shy4r_card {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 10px 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n overflow: hidden;\n}\n\n/* ---- Header: title + action badge ---- */\n\n.d8shy4r_header {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n}\n\n.d8shy4r_stateDot {\n flex: none;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--dsw-alias-label-caption);\n transition: background 0.15s ease;\n}\n\n.d8shy4r_card[data-state='running'] .d8shy4r_stateDot {\n background: var(--dsw-alias-brand-primary);\n animation: dsh-android-tap-pulse 1.4s ease-in-out infinite;\n}\n\n.d8shy4r_card[data-state='completed'] .d8shy4r_stateDot {\n background: var(--dsw-alias-state-success-primary);\n}\n\n.d8shy4r_card[data-state='error'] .d8shy4r_stateDot {\n background: var(--dsw-alias-state-error-primary);\n}\n\n@keyframes dsh-android-tap-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.4; }\n}\n\n.d8shy4r_title {\n flex: 1 1 auto;\n font-size: 13px;\n font-weight: 500;\n line-height: 20px;\n color: var(--dsw-alias-label-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.d8shy4r_badge {\n flex: none;\n font-size: 11px;\n line-height: 18px;\n padding: 0 6px;\n border-radius: 4px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-tertiary);\n font-variant-numeric: tabular-nums;\n}\n\n/* ---- Screenshot images (pre-tap + post-tap grid) ---- */\n\n.d8shy4r_imageGrid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 8px;\n}\n\n.d8shy4r_imageSlot {\n display: flex;\n flex-direction: column;\n gap: 4px;\n min-width: 0;\n}\n\n.d8shy4r_imageLabel {\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-label-tertiary);\n font-weight: 500;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.d8shy4r_imageWrap {\n position: relative;\n border-radius: 6px;\n overflow: hidden;\n background: var(--dsw-alias-bg-layer-2);\n max-height: 280px;\n display: flex;\n justify-content: center;\n}\n\n.d8shy4r_image {\n display: block;\n max-width: 100%;\n max-height: 280px;\n object-fit: contain;\n}\n\n.d8shy4r_placeholder {\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 80px;\n font-size: 12px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* ---- Footer: coordinates + metadata ---- */\n\n.d8shy4r_meta {\n display: flex;\n flex-wrap: wrap;\n gap: 8px 16px;\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-label-tertiary);\n font-variant-numeric: tabular-nums;\n}\n\n.d8shy4r_metaItem {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .d8shy4r_card[data-state='running'] .d8shy4r_stateDot {\n animation: none;\n }\n}\n";
11
+ const classMap$1 = {
12
+ "card": "d8shy4r_card",
13
+ "header": "d8shy4r_header",
14
+ "stateDot": "d8shy4r_stateDot",
15
+ "title": "d8shy4r_title",
16
+ "badge": "d8shy4r_badge",
17
+ "imageGrid": "d8shy4r_imageGrid",
18
+ "imageSlot": "d8shy4r_imageSlot",
19
+ "imageLabel": "d8shy4r_imageLabel",
20
+ "imageWrap": "d8shy4r_imageWrap",
21
+ "image": "d8shy4r_image",
22
+ "placeholder": "d8shy4r_placeholder",
23
+ "meta": "d8shy4r_meta",
24
+ "metaItem": "d8shy4r_metaItem"
25
+ };
26
+ const tagId$1 = "@huanlin/dsh-plugin-android-use/TapCard.module.css";
27
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
28
+ const tag = document.createElement("style");
29
+ tag.dataset.plugin = "@huanlin/dsh-plugin-android-use";
30
+ tag.dataset.pluginCss = tagId$1;
31
+ tag.textContent = css$1;
32
+ document.head.appendChild(tag);
33
+ }
34
+ //#endregion
35
+ //#region src/client/TapCard.tsx
36
+ /**
37
+ * TapCard — the toolcall card for the `android_tap` tool.
38
+ *
39
+ * Renders both the pre-tap screenshot (annotated with the tap marker) and
40
+ * the post-tap screenshot (showing the result after tap) inline, along with
41
+ * coordinates and action metadata. Images are loaded through the
42
+ * `uiConversation.imageUrl` service, which converts the ImageAttachmentRef
43
+ * into a session-authorized blob URL.
44
+ *
45
+ * @module @huanlin/dsh-plugin-android-use/client/TapCard
46
+ */
47
+ /** Extract all image blocks from a settled tool result, in order. */
48
+ function findImages(block) {
49
+ return block.content.filter((c) => c.type === "image");
50
+ }
51
+ /** Extract the first text block (summary) from a settled tool result. */
52
+ function findText$1(block) {
53
+ return block.content.find((c) => c.type === "text")?.text;
54
+ }
55
+ /** Parse tap coordinates from the running block's argsRaw. */
56
+ function parseTapArgs(argsRaw) {
57
+ try {
58
+ const parsed = JSON.parse(argsRaw);
59
+ return {
60
+ x: typeof parsed.x === "number" ? parsed.x : void 0,
61
+ y: typeof parsed.y === "number" ? parsed.y : void 0
62
+ };
63
+ } catch {
64
+ return {};
65
+ }
66
+ }
67
+ /**
68
+ * Resolve an attachmentId to a browser URL through the uiConversation service.
69
+ * Caches by attachmentId so re-renders don't re-fetch.
70
+ */
71
+ function useImageUrl$1(uiConversation, sessionId, attachment) {
72
+ const [url, setUrl] = (0, react.useState)(void 0);
73
+ (0, react.useEffect)(() => {
74
+ if (attachment === void 0 || sessionId === void 0) {
75
+ setUrl(void 0);
76
+ return;
77
+ }
78
+ let cancelled = false;
79
+ uiConversation.imageUrl(sessionId, attachment).then((resolved) => {
80
+ if (!cancelled) setUrl(resolved);
81
+ }).catch(() => {
82
+ if (!cancelled) setUrl(void 0);
83
+ });
84
+ return () => {
85
+ cancelled = true;
86
+ };
87
+ }, [
88
+ uiConversation,
89
+ sessionId,
90
+ attachment
91
+ ]);
92
+ return url;
93
+ }
94
+ /**
95
+ * Render one `android_tap` tool call as a card with pre-tap and post-tap screenshots.
96
+ */
97
+ function TapCard({ block, toolName, sessionId, uiConversation }) {
98
+ const settled = "kind" in block ? block : void 0;
99
+ const running = "kind" in block ? void 0 : block;
100
+ const images = settled !== void 0 ? findImages(settled) : [];
101
+ const text = settled !== void 0 ? findText$1(settled) : void 0;
102
+ const runArgs = running !== void 0 ? parseTapArgs(running.argsRaw) : void 0;
103
+ const settledArgs = settled?.call != null ? parseTapArgs(settled.call.argsRaw) : void 0;
104
+ const coords = runArgs ?? settledArgs ?? {
105
+ x: void 0,
106
+ y: void 0
107
+ };
108
+ const preImage = images[0];
109
+ const postImage = images[1];
110
+ const preUrl = useImageUrl$1(uiConversation, sessionId, preImage?.attachment);
111
+ const postUrl = useImageUrl$1(uiConversation, sessionId, postImage?.attachment);
112
+ const state = settled === void 0 ? "running" : settled.isError ? "error" : "completed";
113
+ const title = coords.x !== void 0 && coords.y !== void 0 ? `Tap (${coords.x}, ${coords.y})` : toolName;
114
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
115
+ className: classMap$1.card,
116
+ "data-tool": toolName,
117
+ "data-state": state,
118
+ children: [
119
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
120
+ className: classMap$1.header,
121
+ children: [
122
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
123
+ className: classMap$1.stateDot,
124
+ "aria-hidden": true
125
+ }),
126
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
127
+ className: classMap$1.title,
128
+ children: title
129
+ }),
130
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
131
+ className: classMap$1.badge,
132
+ children: state
133
+ })
134
+ ]
135
+ }),
136
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
137
+ className: classMap$1.imageGrid,
138
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
139
+ className: classMap$1.imageSlot,
140
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
141
+ className: classMap$1.imageLabel,
142
+ children: "Pre-tap (annotated)"
143
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
144
+ className: classMap$1.imageWrap,
145
+ children: preUrl !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
146
+ className: classMap$1.image,
147
+ src: preUrl,
148
+ alt: `Pre-tap screenshot at (${coords.x ?? "?"}, ${coords.y ?? "?"})`
149
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
150
+ className: classMap$1.placeholder,
151
+ children: settled === void 0 ? "Capturing…" : preImage !== void 0 ? "Loading…" : "No screenshot"
152
+ })
153
+ })]
154
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
155
+ className: classMap$1.imageSlot,
156
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
157
+ className: classMap$1.imageLabel,
158
+ children: "Post-tap (result)"
159
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
160
+ className: classMap$1.imageWrap,
161
+ children: postUrl !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
162
+ className: classMap$1.image,
163
+ src: postUrl,
164
+ alt: "Post-tap screenshot showing the result"
165
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
166
+ className: classMap$1.placeholder,
167
+ children: settled === void 0 ? "Capturing…" : postImage !== void 0 ? "Loading…" : "No screenshot"
168
+ })
169
+ })]
170
+ })]
171
+ }),
172
+ text !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
173
+ className: classMap$1.meta,
174
+ children: text.split("\n").map((line, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
175
+ className: classMap$1.metaItem,
176
+ children: line
177
+ }, i))
178
+ })
179
+ ]
180
+ });
181
+ }
182
+ //#endregion
183
+ //#region \0dsh-css:D:\Projects\deepseek-harness\dsh-plugin-android-use\src\client\ScreenshotCard.module.css.mjs
184
+ const css = "/* ScreenshotCard toolview: a compact card showing the captured screenshot.\n *\n * Every color resolves through a --dsw-alias-* token (no literal colors). */\n\n.d8godkw_card {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 10px 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n overflow: hidden;\n}\n\n/* ---- Header: title + state badge ---- */\n\n.d8godkw_header {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n}\n\n.d8godkw_stateDot {\n flex: none;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--dsw-alias-label-caption);\n transition: background 0.15s ease;\n}\n\n.d8godkw_card[data-state='running'] .d8godkw_stateDot {\n background: var(--dsw-alias-brand-primary);\n animation: dsh-android-screenshot-pulse 1.4s ease-in-out infinite;\n}\n\n.d8godkw_card[data-state='completed'] .d8godkw_stateDot {\n background: var(--dsw-alias-state-success-primary);\n}\n\n.d8godkw_card[data-state='error'] .d8godkw_stateDot {\n background: var(--dsw-alias-state-error-primary);\n}\n\n@keyframes dsh-android-screenshot-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.4; }\n}\n\n.d8godkw_title {\n flex: 1 1 auto;\n font-size: 13px;\n font-weight: 500;\n line-height: 20px;\n color: var(--dsw-alias-label-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.d8godkw_badge {\n flex: none;\n font-size: 11px;\n line-height: 18px;\n padding: 0 6px;\n border-radius: 4px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-tertiary);\n font-variant-numeric: tabular-nums;\n}\n\n/* ---- Screenshot image ---- */\n\n.d8godkw_imageWrap {\n position: relative;\n border-radius: 6px;\n overflow: hidden;\n background: var(--dsw-alias-bg-layer-2);\n max-height: 320px;\n display: flex;\n justify-content: center;\n}\n\n.d8godkw_image {\n display: block;\n max-width: 100%;\n max-height: 320px;\n object-fit: contain;\n}\n\n.d8godkw_placeholder {\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 80px;\n font-size: 12px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* ---- Footer: metadata ---- */\n\n.d8godkw_meta {\n display: flex;\n flex-wrap: wrap;\n gap: 8px 16px;\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-label-tertiary);\n font-variant-numeric: tabular-nums;\n}\n\n.d8godkw_metaItem {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .d8godkw_card[data-state='running'] .d8godkw_stateDot {\n animation: none;\n }\n}\n";
185
+ const classMap = {
186
+ "card": "d8godkw_card",
187
+ "header": "d8godkw_header",
188
+ "stateDot": "d8godkw_stateDot",
189
+ "title": "d8godkw_title",
190
+ "badge": "d8godkw_badge",
191
+ "imageWrap": "d8godkw_imageWrap",
192
+ "image": "d8godkw_image",
193
+ "placeholder": "d8godkw_placeholder",
194
+ "meta": "d8godkw_meta",
195
+ "metaItem": "d8godkw_metaItem"
196
+ };
197
+ const tagId = "@huanlin/dsh-plugin-android-use/ScreenshotCard.module.css";
198
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
199
+ const tag = document.createElement("style");
200
+ tag.dataset.plugin = "@huanlin/dsh-plugin-android-use";
201
+ tag.dataset.pluginCss = tagId;
202
+ tag.textContent = css;
203
+ document.head.appendChild(tag);
204
+ }
205
+ //#endregion
206
+ //#region src/client/ScreenshotCard.tsx
207
+ /**
208
+ * ScreenshotCard — the toolcall card for the `android_screenshot` tool.
209
+ *
210
+ * Renders the captured screenshot inline. The image is loaded through
211
+ * the `uiConversation.imageUrl` service, which converts the ImageAttachmentRef
212
+ * into a session-authorized blob URL.
213
+ *
214
+ * @module @huanlin/dsh-plugin-android-use/client/ScreenshotCard
215
+ */
216
+ function findImage(block) {
217
+ return block.content.find((c) => c.type === "image");
218
+ }
219
+ function findText(block) {
220
+ return block.content.find((c) => c.type === "text")?.text;
221
+ }
222
+ function useImageUrl(uiConversation, sessionId, attachment) {
223
+ const [url, setUrl] = (0, react.useState)(void 0);
224
+ (0, react.useEffect)(() => {
225
+ if (attachment === void 0 || sessionId === void 0) {
226
+ setUrl(void 0);
227
+ return;
228
+ }
229
+ let cancelled = false;
230
+ uiConversation.imageUrl(sessionId, attachment).then((resolved) => {
231
+ if (!cancelled) setUrl(resolved);
232
+ }).catch(() => {
233
+ if (!cancelled) setUrl(void 0);
234
+ });
235
+ return () => {
236
+ cancelled = true;
237
+ };
238
+ }, [
239
+ uiConversation,
240
+ sessionId,
241
+ attachment
242
+ ]);
243
+ return url;
244
+ }
245
+ /**
246
+ * Render one `android_screenshot` tool call as a card with the screenshot.
247
+ */
248
+ function ScreenshotCard({ block, toolName, sessionId, uiConversation }) {
249
+ const settled = "kind" in block ? block : void 0;
250
+ const image = settled !== void 0 ? findImage(settled) : void 0;
251
+ const text = settled !== void 0 ? findText(settled) : void 0;
252
+ const imageUrl = useImageUrl(uiConversation, sessionId, image?.attachment);
253
+ const state = settled === void 0 ? "running" : settled.isError ? "error" : "completed";
254
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
255
+ className: classMap.card,
256
+ "data-tool": toolName,
257
+ "data-state": state,
258
+ children: [
259
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
260
+ className: classMap.header,
261
+ children: [
262
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
263
+ className: classMap.stateDot,
264
+ "aria-hidden": true
265
+ }),
266
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
267
+ className: classMap.title,
268
+ children: toolName
269
+ }),
270
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
271
+ className: classMap.badge,
272
+ children: state
273
+ })
274
+ ]
275
+ }),
276
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
277
+ className: classMap.imageWrap,
278
+ children: imageUrl !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
279
+ className: classMap.image,
280
+ src: imageUrl,
281
+ alt: "Android screenshot"
282
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
283
+ className: classMap.placeholder,
284
+ children: settled === void 0 ? "Capturing…" : image !== void 0 ? "Loading…" : "No screenshot"
285
+ })
286
+ }),
287
+ text !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
288
+ className: classMap.meta,
289
+ children: text.split("\n").map((line, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
290
+ className: classMap.metaItem,
291
+ children: line
292
+ }, i))
293
+ })
294
+ ]
295
+ });
296
+ }
297
+ //#endregion
298
+ //#region src/client/index.ts
299
+ /** Required services: slot registry + Conversation image cache. */
300
+ const inject = ["slots", "uiConversation"];
301
+ /**
302
+ * Client plugin body: register the `android_tap` and `android_screenshot` toolview slots.
303
+ * @param ctx - client root context.
304
+ */
305
+ function apply(ctx) {
306
+ const tapInjected = () => ({ uiConversation: ctx.uiConversation });
307
+ ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
308
+ name: "tool.call.toolview",
309
+ key: "android_tap",
310
+ inject: tapInjected
311
+ }, TapCard));
312
+ const screenshotInjected = () => ({ uiConversation: ctx.uiConversation });
313
+ ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
314
+ name: "tool.call.toolview",
315
+ key: "android_screenshot",
316
+ inject: screenshotInjected
317
+ }, ScreenshotCard));
318
+ }
319
+ //#endregion
320
+ exports.apply = apply;
321
+ exports.inject = inject;
322
+ return module.exports;
323
+ }
324
+ });
325
+
326
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","names":["findText","useImageUrl","useState","css","useState","css"],"sources":["../src/client/TapCard.tsx","../src/client/ScreenshotCard.tsx","../src/client/index.ts"],"sourcesContent":["/**\n * TapCard — the toolcall card for the `android_tap` tool.\n *\n * Renders both the pre-tap screenshot (annotated with the tap marker) and\n * the post-tap screenshot (showing the result after tap) inline, along with\n * coordinates and action metadata. Images are loaded through the\n * `uiConversation.imageUrl` service, which converts the ImageAttachmentRef\n * into a session-authorized blob URL.\n *\n * @module @huanlin/dsh-plugin-android-use/client/TapCard\n */\n\nimport { useEffect, useState } from 'react'\nimport type { ToolCallViewProps } from '@deepseek-ai/dsh-client-ui-tool/client'\nimport type { InjectFace } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { ToolResultNode, UiConversation } from '@deepseek-ai/dsh-client-ui-conversation/client'\nimport type { ContentBlock, ImageBlock } from '@deepseek-ai/dsh-llm'\nimport type { ImageAttachmentRef } from '@deepseek-ai/dsh-attachment'\nimport css from './TapCard.module.css'\n\n/** Inject face: the Conversation image cache for durable URL resolution. */\nexport type TapCardInjected = {\n uiConversation: UiConversation\n}\n\ntype TapCardProps = ToolCallViewProps & InjectFace<TapCardInjected>\n\n/** Extract all image blocks from a settled tool result, in order. */\nfunction findImages(block: ToolResultNode): ImageBlock[] {\n return block.content.filter((c): c is ImageBlock => c.type === 'image')\n}\n\n/** Extract the first text block (summary) from a settled tool result. */\nfunction findText(block: ToolResultNode): string | undefined {\n const text = block.content.find((c): c is Extract<ContentBlock, { type: 'text' }> => c.type === 'text')\n return text?.text\n}\n\n/** Parse tap coordinates from the running block's argsRaw. */\nfunction parseTapArgs(argsRaw: string): { x?: number; y?: number } {\n try {\n const parsed = JSON.parse(argsRaw) as { x?: unknown; y?: unknown }\n return {\n x: typeof parsed.x === 'number' ? parsed.x : undefined,\n y: typeof parsed.y === 'number' ? parsed.y : undefined,\n }\n } catch {\n return {}\n }\n}\n\n/**\n * Resolve an attachmentId to a browser URL through the uiConversation service.\n * Caches by attachmentId so re-renders don't re-fetch.\n */\nfunction useImageUrl(\n uiConversation: UiConversation,\n sessionId: TapCardProps['sessionId'] | undefined,\n attachment: ImageAttachmentRef | undefined,\n): string | undefined {\n const [url, setUrl] = useState<string | undefined>(undefined)\n useEffect(() => {\n if (attachment === undefined || sessionId === undefined) {\n setUrl(undefined)\n return\n }\n let cancelled = false\n uiConversation.imageUrl(sessionId, attachment)\n .then((resolved) => { if (!cancelled) setUrl(resolved) })\n .catch(() => { if (!cancelled) setUrl(undefined) })\n return () => { cancelled = true }\n }, [uiConversation, sessionId, attachment])\n return url\n}\n\n/**\n * Render one `android_tap` tool call as a card with pre-tap and post-tap screenshots.\n */\nexport function TapCard({ block, toolName, sessionId, uiConversation }: TapCardProps) {\n const settled = 'kind' in block ? block : undefined\n const running = 'kind' in block ? undefined : block\n\n const images = settled !== undefined ? findImages(settled) : []\n const text = settled !== undefined ? findText(settled) : undefined\n const runArgs = running !== undefined ? parseTapArgs(running.argsRaw) : undefined\n const settledArgs = settled?.call != null ? parseTapArgs(settled.call.argsRaw) : undefined\n const coords = runArgs ?? settledArgs ?? { x: undefined, y: undefined }\n\n const preImage = images[0]\n const postImage = images[1]\n const preUrl = useImageUrl(uiConversation, sessionId, preImage?.attachment)\n const postUrl = useImageUrl(uiConversation, sessionId, postImage?.attachment)\n\n const state = settled === undefined ? 'running' : (settled.isError ? 'error' : 'completed')\n\n const title = coords.x !== undefined && coords.y !== undefined\n ? `Tap (${coords.x}, ${coords.y})`\n : toolName\n\n return (\n <div className={css.card} data-tool={toolName} data-state={state}>\n <div className={css.header}>\n <span className={css.stateDot} aria-hidden />\n <span className={css.title}>{title}</span>\n <span className={css.badge}>{state}</span>\n </div>\n <div className={css.imageGrid}>\n <div className={css.imageSlot}>\n <span className={css.imageLabel}>Pre-tap (annotated)</span>\n <div className={css.imageWrap}>\n {preUrl !== undefined ? (\n <img className={css.image} src={preUrl} alt={`Pre-tap screenshot at (${coords.x ?? '?'}, ${coords.y ?? '?'})`} />\n ) : (\n <span className={css.placeholder}>\n {settled === undefined ? 'Capturing…' : preImage !== undefined ? 'Loading…' : 'No screenshot'}\n </span>\n )}\n </div>\n </div>\n <div className={css.imageSlot}>\n <span className={css.imageLabel}>Post-tap (result)</span>\n <div className={css.imageWrap}>\n {postUrl !== undefined ? (\n <img className={css.image} src={postUrl} alt=\"Post-tap screenshot showing the result\" />\n ) : (\n <span className={css.placeholder}>\n {settled === undefined ? 'Capturing…' : postImage !== undefined ? 'Loading…' : 'No screenshot'}\n </span>\n )}\n </div>\n </div>\n </div>\n {text !== undefined && (\n <div className={css.meta}>\n {text.split('\\n').map((line, i) => (\n <span key={i} className={css.metaItem}>{line}</span>\n ))}\n </div>\n )}\n </div>\n )\n}\n","/**\n * ScreenshotCard — the toolcall card for the `android_screenshot` tool.\n *\n * Renders the captured screenshot inline. The image is loaded through\n * the `uiConversation.imageUrl` service, which converts the ImageAttachmentRef\n * into a session-authorized blob URL.\n *\n * @module @huanlin/dsh-plugin-android-use/client/ScreenshotCard\n */\n\nimport { useEffect, useState } from 'react'\nimport type { ToolCallViewProps } from '@deepseek-ai/dsh-client-ui-tool/client'\nimport type { InjectFace } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { ToolResultNode, UiConversation } from '@deepseek-ai/dsh-client-ui-conversation/client'\nimport type { ContentBlock, ImageBlock } from '@deepseek-ai/dsh-llm'\nimport type { ImageAttachmentRef } from '@deepseek-ai/dsh-attachment'\nimport css from './ScreenshotCard.module.css'\n\n/** Inject face: the Conversation image cache for durable URL resolution. */\nexport type ScreenshotCardInjected = {\n uiConversation: UiConversation\n}\n\ntype ScreenshotCardProps = ToolCallViewProps & InjectFace<ScreenshotCardInjected>\n\nfunction findImage(block: ToolResultNode): ImageBlock | undefined {\n return block.content.find((c): c is ImageBlock => c.type === 'image')\n}\n\nfunction findText(block: ToolResultNode): string | undefined {\n const text = block.content.find((c): c is Extract<ContentBlock, { type: 'text' }> => c.type === 'text')\n return text?.text\n}\n\nfunction useImageUrl(\n uiConversation: UiConversation,\n sessionId: ScreenshotCardProps['sessionId'] | undefined,\n attachment: ImageAttachmentRef | undefined,\n): string | undefined {\n const [url, setUrl] = useState<string | undefined>(undefined)\n useEffect(() => {\n if (attachment === undefined || sessionId === undefined) {\n setUrl(undefined)\n return\n }\n let cancelled = false\n uiConversation.imageUrl(sessionId, attachment)\n .then((resolved) => { if (!cancelled) setUrl(resolved) })\n .catch(() => { if (!cancelled) setUrl(undefined) })\n return () => { cancelled = true }\n }, [uiConversation, sessionId, attachment])\n return url\n}\n\n/**\n * Render one `android_screenshot` tool call as a card with the screenshot.\n */\nexport function ScreenshotCard({ block, toolName, sessionId, uiConversation }: ScreenshotCardProps) {\n const settled = 'kind' in block ? block : undefined\n\n const image = settled !== undefined ? findImage(settled) : undefined\n const text = settled !== undefined ? findText(settled) : undefined\n const imageUrl = useImageUrl(uiConversation, sessionId, image?.attachment)\n\n const state = settled === undefined ? 'running' : (settled.isError ? 'error' : 'completed')\n\n return (\n <div className={css.card} data-tool={toolName} data-state={state}>\n <div className={css.header}>\n <span className={css.stateDot} aria-hidden />\n <span className={css.title}>{toolName}</span>\n <span className={css.badge}>{state}</span>\n </div>\n <div className={css.imageWrap}>\n {imageUrl !== undefined ? (\n <img className={css.image} src={imageUrl} alt=\"Android screenshot\" />\n ) : (\n <span className={css.placeholder}>\n {settled === undefined ? 'Capturing…' : image !== undefined ? 'Loading…' : 'No screenshot'}\n </span>\n )}\n </div>\n {text !== undefined && (\n <div className={css.meta}>\n {text.split('\\n').map((line, i) => (\n <span key={i} className={css.metaItem}>{line}</span>\n ))}\n </div>\n )}\n </div>\n )\n}\n","/**\n * dsh-plugin-android-use — browser half.\n *\n * Single bundle, dual entry: this is the client half (exports `./client`).\n * Host half ships via `.` (see `src/index.ts`).\n *\n * Registers two `tool.call.toolview` keyed slots:\n * - key `android_tap` → TapCard (annotated pre-tap screenshot)\n * - key `android_screenshot` → ScreenshotCard (captured screenshot)\n *\n * `tool.call.toolview` is declared by `@deepseek-ai/dsh-client-ui-tool`, so\n * both registrations go through `ctx.slots.inject` (waits on the declaration,\n * leaves with this plugin's fiber). Durable image URLs resolve through the\n * `uiConversation` service (v0.1.2: `conversation.resolveImage` is gone).\n *\n * @module @huanlin/dsh-plugin-android-use/client\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\n// Type-only imports: pull the Context/SlotMap/StandardProps declaration\n// merges this plugin's compiled props depend on. Erased in the bundle.\nimport type {} from '@deepseek-ai/dsh-client-ui-renderer/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-slots'\nimport type {} from '@deepseek-ai/dsh-client-ui-tool/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-session/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\nimport { TapCard, type TapCardInjected } from './TapCard.tsx'\nimport { ScreenshotCard, type ScreenshotCardInjected } from './ScreenshotCard.tsx'\n\n/** Required services: slot registry + Conversation image cache. */\nexport const inject = ['slots', 'uiConversation']\n\n/**\n * Client plugin body: register the `android_tap` and `android_screenshot` toolview slots.\n * @param ctx - client root context.\n */\nexport function apply(ctx: Context): void {\n const tapInjected = (): TapCardInjected => ({ uiConversation: ctx.uiConversation })\n ctx.slots.inject('tool.call.toolview', () =>\n ctx.slots.register({\n name: 'tool.call.toolview',\n key: 'android_tap',\n inject: tapInjected,\n }, TapCard))\n\n const screenshotInjected = (): ScreenshotCardInjected => ({ uiConversation: ctx.uiConversation })\n ctx.slots.inject('tool.call.toolview', () =>\n ctx.slots.register({\n name: 'tool.call.toolview',\n key: 'android_screenshot',\n inject: screenshotInjected,\n }, ScreenshotCard))\n}\n"],"mappings":"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"}
package/lib/index.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ import z from "schemastery";
2
+ import { Context } from "@deepseek-ai/cordis";
3
+ //#region src/registry.d.ts
4
+ /** Validated plugin config consumed by the tools at execution time. */
5
+ interface ResolvedConfig {
6
+ defaultSerial: string | undefined;
7
+ inputTextMode: 'input' | 'adbkeyboard';
8
+ }
9
+ //#endregion
10
+ //#region src/index.d.ts
11
+ declare const name = "dsh-android-use";
12
+ declare const inject: string[];
13
+ interface Config {
14
+ adbPath?: string;
15
+ defaultSerial?: string;
16
+ inputTextMode?: 'input' | 'adbkeyboard';
17
+ }
18
+ declare const Config: z<Config>;
19
+ declare function resolveConfig(config: Config): {
20
+ adbPath: string;
21
+ } & ResolvedConfig;
22
+ declare function apply(ctx: Context, config?: Config): void;
23
+ //#endregion
24
+ export { Config, apply, inject, name, resolveConfig };