dsh-adb 1.4.0 → 1.5.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 +2 -0
- package/README.zh-CN.md +2 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +9 -4
- package/lib/tools/screenshot.d.ts +37 -0
- package/lib/tools/screenshot.js +88 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -42,6 +42,7 @@ Topics: `dsh-plugin` `dsh` `adb` `android` `automotive` `bench`
|
|
|
42
42
|
| `adb_device_report` | One-click health report: device identity + top-RSS processes + crash buffer (real crashes w/ stacks vs. boot markers) + W/E/F logcat aggregated by tag + storage + health verdict; each section degrades independently; persisted under `reportDir` |
|
|
43
43
|
| `adb_wait_for` | Wait until a device condition holds — device-online / boot-complete / process appeared / logcat keyword — polling up to a budget, instead of sleeping a fixed number of seconds; returns `matched:false` on timeout |
|
|
44
44
|
| `adb_operation_ledger` | Append-only device operation ledger (record/list/rollback): record installs/pushes/etc., list history, or roll an app back to its last known-good APK (`adb install -r`); persisted as `operations.json` — the trust base for agent-driven device modification |
|
|
45
|
+
| `adb_screenshot` | Capture the device screen into a local PNG (screencap → pull), returning the saved path, byte size, and pixel dimensions — durable evidence for crash scenes / UI states / test frames |
|
|
45
46
|
|
|
46
47
|
Errors are structured `AdbError` with stable codes: `ADB_NOT_FOUND`, `ADB_UNAVAILABLE`, `DEVICE_NOT_FOUND`, `NO_DEVICES`, `CONNECT_FAILED`, `INSTALL_FAILED`, `ADB_EXIT_<code>`, etc.
|
|
47
48
|
|
|
@@ -65,6 +66,7 @@ Set the `config` block in `cordis.patch.yml` (or a profile patch):
|
|
|
65
66
|
| `timeoutMs` | Per-command timeout | 30000 |
|
|
66
67
|
| `baselineDir` | Directory for `adb_perf_baseline` storage | `~/.dsh/storages/dsh-adb` |
|
|
67
68
|
| `reportDir` | Directory for `adb_device_report` storage | `<baselineDir>/reports` |
|
|
69
|
+
| `screenshotDir` | Directory for `adb_screenshot` storage | `<baselineDir>/screenshots` |
|
|
68
70
|
|
|
69
71
|
## Development
|
|
70
72
|
|
package/README.zh-CN.md
CHANGED
|
@@ -42,6 +42,7 @@ Topics:`dsh-plugin` `dsh` `adb` `android` `automotive` `bench`
|
|
|
42
42
|
| `adb_device_report` | 一键体检:设备信息 + Top RSS 进程 + 崩溃缓冲(真实崩溃带堆栈/启动标记分类)+ W/E/F 日志按 tag 聚合 + 存储用量 + 健康结论;每节独立降级;落盘到 `reportDir` |
|
|
43
43
|
| `adb_wait_for` | 等待原语:等设备上线 / 启动完成 / 进程出现 / logcat 出现关键字,轮询到预算上限,替代盲目 sleep;超时返回 `matched:false` |
|
|
44
44
|
| `adb_operation_ledger` | 操作回滚台账(record/list/rollback):追加记录安装/推送等操作,查询历史,或回滚到最近一次成功安装的 APK(`adb install -r`);落盘 `operations.json` —— agent 自主改设备的信任基础 |
|
|
45
|
+
| `adb_screenshot` | 截图落盘 PNG(screencap → pull),返回路径/字节数/像素尺寸 —— 崩溃现场、UI 状态、测试前后对比的持久证据 |
|
|
45
46
|
|
|
46
47
|
错误码:`ADB_NOT_FOUND`、`ADB_UNAVAILABLE`、`DEVICE_NOT_FOUND`、`NO_DEVICES`、`CONNECT_FAILED`、`INSTALL_FAILED`、`ADB_EXIT_<code>` 等,均为结构化 `AdbError`。
|
|
47
48
|
|
|
@@ -65,6 +66,7 @@ Topics:`dsh-plugin` `dsh` `adb` `android` `automotive` `bench`
|
|
|
65
66
|
| `timeoutMs` | 命令超时 | 30000 |
|
|
66
67
|
| `baselineDir` | `adb_perf_baseline` 基线存储目录 | `~/.dsh/storages/dsh-adb` |
|
|
67
68
|
| `reportDir` | `adb_device_report` 体检报告存储目录 | `<baselineDir>/reports` |
|
|
69
|
+
| `screenshotDir` | `adb_screenshot` 截图存储目录 | `<baselineDir>/screenshots` |
|
|
68
70
|
|
|
69
71
|
## 开发
|
|
70
72
|
|
package/lib/index.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export interface Config {
|
|
|
15
15
|
baselineDir?: string;
|
|
16
16
|
/** Directory for adb_device_report storage; defaults to <baselineDir>/reports. */
|
|
17
17
|
reportDir?: string;
|
|
18
|
+
/** Directory for adb_screenshot storage; defaults to <baselineDir>/screenshots. */
|
|
19
|
+
screenshotDir?: string;
|
|
18
20
|
}
|
|
19
21
|
export declare const Config: Schema<Config>;
|
|
20
22
|
export declare function apply(ctx: Context, config: Config): void;
|
package/lib/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { registerPerfTool } from './tools/perf.js';
|
|
|
10
10
|
import { registerPerfBaselineTool } from './tools/perf-baseline.js';
|
|
11
11
|
import { registerWaitTool } from './tools/wait.js';
|
|
12
12
|
import { registerOperationLedgerTool } from './tools/operation-ledger.js';
|
|
13
|
+
import { registerScreenshotTool } from './tools/screenshot.js';
|
|
13
14
|
import { registerRpc } from './rpc.js';
|
|
14
15
|
import { registerSkills } from './skill.js';
|
|
15
16
|
export const name = 'dsh-adb';
|
|
@@ -21,24 +22,28 @@ export const Config = Schema.object({
|
|
|
21
22
|
timeoutMs: Schema.number().default(30000).description('adb 命令超时(毫秒)'),
|
|
22
23
|
baselineDir: Schema.string().description('adb_perf_baseline 基线存储目录;缺省 ~/.dsh/storages/dsh-adb'),
|
|
23
24
|
reportDir: Schema.string().description('adb_device_report 报告存储目录;缺省 ~/.dsh/storages/dsh-adb/reports'),
|
|
25
|
+
screenshotDir: Schema.string().description('adb_screenshot 截图存储目录;缺省 ~/.dsh/storages/dsh-adb/screenshots'),
|
|
24
26
|
});
|
|
25
27
|
export function apply(ctx, config) {
|
|
26
28
|
const cfg = config;
|
|
27
|
-
const
|
|
29
|
+
const baselineDir = config.baselineDir ?? DEFAULT_BASELINE_DIR;
|
|
30
|
+
const reportDir = config.reportDir ?? `${baselineDir.replace(/\\/g, '/')}/reports`;
|
|
31
|
+
const screenshotDir = config.screenshotDir ?? `${baselineDir.replace(/\\/g, '/')}/screenshots`;
|
|
28
32
|
registerDeviceTools(ctx, cfg);
|
|
29
33
|
registerInstallTool(ctx, cfg);
|
|
30
34
|
registerFileTool(ctx, cfg);
|
|
31
35
|
registerLogcatTool(ctx, cfg);
|
|
32
36
|
registerPerfTool(ctx, cfg);
|
|
33
|
-
registerPerfBaselineTool(ctx, cfg,
|
|
37
|
+
registerPerfBaselineTool(ctx, cfg, baselineDir);
|
|
34
38
|
registerCrashReportTool(ctx, cfg);
|
|
35
39
|
registerDeviceReportTool(ctx, cfg, reportDir);
|
|
36
40
|
registerWaitTool(ctx, cfg);
|
|
37
|
-
registerOperationLedgerTool(ctx, cfg,
|
|
41
|
+
registerOperationLedgerTool(ctx, cfg, baselineDir);
|
|
42
|
+
registerScreenshotTool(ctx, cfg, screenshotDir);
|
|
38
43
|
registerSkills(ctx);
|
|
39
44
|
// The RPC channel needs the client connection, which mounts after this
|
|
40
45
|
// plugin starts in web compositions; register lazily so headless profiles
|
|
41
46
|
// (no connection) stay unaffected.
|
|
42
47
|
ctx.inject(['connection'], (readyCtx) => registerRpc(readyCtx, cfg, reportDir));
|
|
43
|
-
ctx.logger.info('[dsh-adb] loaded:
|
|
48
|
+
ctx.logger.info('[dsh-adb] loaded: 13 tools + web device panel rpc');
|
|
44
49
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import { type AdbConfig } from '../adb.js';
|
|
3
|
+
interface ScreenshotArgs {
|
|
4
|
+
serial?: string;
|
|
5
|
+
/** Local directory to save the PNG into; defaults to the plugin screenshotDir. */
|
|
6
|
+
dir?: string;
|
|
7
|
+
/** Basename for the PNG; defaults to <serial>-<epoch>.png. */
|
|
8
|
+
name?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ScreenshotResult {
|
|
11
|
+
savedTo: string;
|
|
12
|
+
bytes: number;
|
|
13
|
+
width?: number;
|
|
14
|
+
height?: number;
|
|
15
|
+
serial: string;
|
|
16
|
+
}
|
|
17
|
+
/** Stable local-path convention for screenshot saves: <dir>/<serial>-<epoch>.png. */
|
|
18
|
+
export declare function screenshotPathFor(dir: string, serial: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Capture the device screen into a local PNG: screencap on the device, pull to
|
|
21
|
+
* the local dir, then best-effort rm of the on-device temp. Returns the saved
|
|
22
|
+
* path, byte size, and (when the PNG header parses) pixel dimensions.
|
|
23
|
+
*/
|
|
24
|
+
export declare function captureScreenshot(ctx: Context, cfg: AdbConfig, signal: AbortSignal, args: ScreenshotArgs, screenshotDir: string): Promise<ScreenshotResult>;
|
|
25
|
+
/** Parse PNG dimensions from the IHDR chunk (pure; unit-tested). */
|
|
26
|
+
export declare function pngDimensions(bytes: Uint8Array): {
|
|
27
|
+
width: number;
|
|
28
|
+
height: number;
|
|
29
|
+
} | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* adb_screenshot: capture the device screen (screencap → pull) into a local
|
|
32
|
+
* PNG under the screenshot dir, and return its path + byte size. Screenshots
|
|
33
|
+
* are durable evidence: crash scenes, UI states, before/after test frames —
|
|
34
|
+
* the primitive behind the vision-based features (ROADMAP ③).
|
|
35
|
+
*/
|
|
36
|
+
export declare function registerScreenshotTool(ctx: Context, cfg: AdbConfig, screenshotDir: string): void;
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { AdbError, classifyFailure, jsonOutput, runAdb } from '../adb.js';
|
|
4
|
+
/** Stable local-path convention for screenshot saves: <dir>/<serial>-<epoch>.png. */
|
|
5
|
+
export function screenshotPathFor(dir, serial) {
|
|
6
|
+
return join(dir, `${serial.replace(/[^A-Za-z0-9._-]/g, '_')}-${Date.now()}.png`);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Capture the device screen into a local PNG: screencap on the device, pull to
|
|
10
|
+
* the local dir, then best-effort rm of the on-device temp. Returns the saved
|
|
11
|
+
* path, byte size, and (when the PNG header parses) pixel dimensions.
|
|
12
|
+
*/
|
|
13
|
+
export async function captureScreenshot(ctx, cfg, signal, args, screenshotDir) {
|
|
14
|
+
const serial = args.serial ?? cfg.defaultSerial;
|
|
15
|
+
const devicePath = `/data/local/tmp/dsh-shot-${Date.now()}.png`;
|
|
16
|
+
const dir = args.dir ?? screenshotDir;
|
|
17
|
+
mkdirSync(dir, { recursive: true });
|
|
18
|
+
const localPath = args.name !== undefined
|
|
19
|
+
? join(dir, args.name.endsWith('.png') ? args.name : `${args.name}.png`)
|
|
20
|
+
: screenshotPathFor(dir, serial ?? 'device');
|
|
21
|
+
try {
|
|
22
|
+
const cap = await runAdb(ctx, cfg, ['shell', 'screencap', '-p', devicePath], { signal, serial });
|
|
23
|
+
if (cap.exitCode !== 0)
|
|
24
|
+
throw classifyFailure(cap);
|
|
25
|
+
const pull = await runAdb(ctx, cfg, ['pull', devicePath, localPath], { signal, serial });
|
|
26
|
+
if (pull.exitCode !== 0)
|
|
27
|
+
throw classifyFailure(pull);
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
// Best-effort cleanup of the on-device temp file.
|
|
31
|
+
try {
|
|
32
|
+
await runAdb(ctx, cfg, ['shell', 'rm', '-f', devicePath], { signal, serial });
|
|
33
|
+
}
|
|
34
|
+
catch { /* cleanup is best-effort */ }
|
|
35
|
+
}
|
|
36
|
+
if (!existsSync(localPath)) {
|
|
37
|
+
throw new AdbError('SCREENSHOT_FAILED', `screencap succeeded but no file at ${localPath}`);
|
|
38
|
+
}
|
|
39
|
+
const bytes = readFileSync(localPath);
|
|
40
|
+
const dimensions = pngDimensions(bytes);
|
|
41
|
+
return {
|
|
42
|
+
savedTo: localPath,
|
|
43
|
+
bytes: bytes.length,
|
|
44
|
+
...(dimensions !== undefined ? { width: dimensions.width, height: dimensions.height } : {}),
|
|
45
|
+
serial: serial ?? 'default',
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** Parse PNG dimensions from the IHDR chunk (pure; unit-tested). */
|
|
49
|
+
export function pngDimensions(bytes) {
|
|
50
|
+
// PNG signature (8 bytes) + IHDR length (4) + "IHDR" (4) + width (4) + height (4).
|
|
51
|
+
if (bytes.length < 24)
|
|
52
|
+
return undefined;
|
|
53
|
+
const sig = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
54
|
+
for (let i = 0; i < 8; i++) {
|
|
55
|
+
if (bytes[i] !== sig[i])
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
59
|
+
return {
|
|
60
|
+
width: view.getUint32(16),
|
|
61
|
+
height: view.getUint32(20),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* adb_screenshot: capture the device screen (screencap → pull) into a local
|
|
66
|
+
* PNG under the screenshot dir, and return its path + byte size. Screenshots
|
|
67
|
+
* are durable evidence: crash scenes, UI states, before/after test frames —
|
|
68
|
+
* the primitive behind the vision-based features (ROADMAP ③).
|
|
69
|
+
*/
|
|
70
|
+
export function registerScreenshotTool(ctx, cfg, screenshotDir) {
|
|
71
|
+
ctx.tools.register({
|
|
72
|
+
name: 'adb_screenshot',
|
|
73
|
+
description: 'Capture the device screen into a local PNG (screencap on device → pull to host) and return the saved path, byte size, and pixel dimensions. Screenshots are durable evidence for crash scenes, UI states, or before/after test frames. Saves under the screenshotDir (default ~/.dsh/storages/dsh-adb/screenshots).',
|
|
74
|
+
parameters: {
|
|
75
|
+
type: 'object',
|
|
76
|
+
additionalProperties: false,
|
|
77
|
+
properties: {
|
|
78
|
+
serial: { type: 'string', description: 'Target device serial; defaults to the plugin defaultSerial.' },
|
|
79
|
+
dir: { type: 'string', description: 'Local directory to save into; defaults to the plugin screenshotDir.' },
|
|
80
|
+
name: { type: 'string', description: 'Basename for the PNG; defaults to <serial>-<epoch>.png.' },
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
output: jsonOutput(),
|
|
84
|
+
async execute(args, exec) {
|
|
85
|
+
return captureScreenshot(ctx, cfg, exec.signal, args, screenshotDir);
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-adb",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "ADB device & bench operations for DeepSeek Harness: device discovery, structured logcat, apk install, file pull/push, performance snapshots, perf baselines, crash reports, one-click device health reports, condition waits, operation ledger w/ rollback, web device panel (autocomplete, live logcat, profiler)",
|
|
3
|
+
"version": "1.5.0",
|
|
4
|
+
"description": "ADB device & bench operations for DeepSeek Harness: device discovery, structured logcat, apk install, file pull/push, performance snapshots, perf baselines, crash reports, one-click device health reports, condition waits, operation ledger w/ rollback, screenshots, web device panel (autocomplete, live logcat, profiler)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|