@beeos-ai/device-agent 0.7.1 → 0.8.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.
Files changed (68) hide show
  1. package/dist/agent-runtime/skills/sqlite-store.d.ts +60 -0
  2. package/dist/agent-runtime/skills/sqlite-store.js +234 -0
  3. package/dist/agent-runtime/skills/sqlite-store.js.map +1 -0
  4. package/dist/app/in-flight-turn.d.ts +39 -0
  5. package/dist/app/in-flight-turn.js.map +1 -1
  6. package/dist/app/jsonl-store.js +20 -7
  7. package/dist/app/jsonl-store.js.map +1 -1
  8. package/dist/app/plugin-commands/screenshot-command.d.ts +11 -10
  9. package/dist/app/plugin-commands/screenshot-command.js +19 -18
  10. package/dist/app/plugin-commands/screenshot-command.js.map +1 -1
  11. package/dist/app/plugin-commands/types.d.ts +50 -11
  12. package/dist/app/plugin-commands/types.js +5 -2
  13. package/dist/app/plugin-commands/types.js.map +1 -1
  14. package/dist/app/session-manager.d.ts +20 -11
  15. package/dist/app/session-manager.js +83 -14
  16. package/dist/app/session-manager.js.map +1 -1
  17. package/dist/bootstrap.js +11 -0
  18. package/dist/bootstrap.js.map +1 -1
  19. package/dist/config/loader.d.ts +4 -0
  20. package/dist/config/loader.js +18 -2
  21. package/dist/config/loader.js.map +1 -1
  22. package/dist/config/schema.d.ts +5 -0
  23. package/dist/config/schema.js +42 -0
  24. package/dist/config/schema.js.map +1 -1
  25. package/dist/inbound/cli/main.js +12 -0
  26. package/dist/inbound/cli/main.js.map +1 -1
  27. package/dist/messaging/token-provider.d.ts +43 -0
  28. package/dist/messaging/token-provider.js +65 -0
  29. package/dist/messaging/token-provider.js.map +1 -0
  30. package/dist/plugins/acp/index.js +1 -1
  31. package/dist/plugins/acp/index.js.map +1 -1
  32. package/dist/plugins/message-bus/busy-reply.d.ts +39 -0
  33. package/dist/plugins/message-bus/busy-reply.js +30 -0
  34. package/dist/plugins/message-bus/busy-reply.js.map +1 -0
  35. package/dist/plugins/message-bus/inbound.d.ts +55 -0
  36. package/dist/plugins/message-bus/inbound.js +123 -0
  37. package/dist/plugins/message-bus/inbound.js.map +1 -0
  38. package/dist/plugins/message-bus/index.d.ts +147 -0
  39. package/dist/plugins/message-bus/index.js +392 -0
  40. package/dist/plugins/message-bus/index.js.map +1 -0
  41. package/dist/plugins/message-bus/outbound.d.ts +43 -0
  42. package/dist/plugins/message-bus/outbound.js +149 -0
  43. package/dist/plugins/message-bus/outbound.js.map +1 -0
  44. package/dist/plugins/message-bus/turn-context.d.ts +57 -0
  45. package/dist/plugins/message-bus/turn-context.js +53 -0
  46. package/dist/plugins/message-bus/turn-context.js.map +1 -0
  47. package/dist/test-lan-autoglm.d.ts +7 -0
  48. package/dist/test-lan-autoglm.js +46 -0
  49. package/dist/test-lan-autoglm.js.map +1 -0
  50. package/dist/test-mem-breakdown.d.ts +1 -0
  51. package/dist/test-mem-breakdown.js +103 -0
  52. package/dist/test-mem-breakdown.js.map +1 -0
  53. package/dist/test-mem.d.ts +1 -0
  54. package/dist/test-mem.js +60 -0
  55. package/dist/test-mem.js.map +1 -0
  56. package/dist/test-mobile-use.d.ts +1 -0
  57. package/dist/test-mobile-use.js +18 -0
  58. package/dist/test-mobile-use.js.map +1 -0
  59. package/dist/test-screenshot-loop.d.ts +1 -0
  60. package/dist/test-screenshot-loop.js +43 -0
  61. package/dist/test-screenshot-loop.js.map +1 -0
  62. package/dist/test-screenshot.d.ts +1 -0
  63. package/dist/test-screenshot.js +10 -0
  64. package/dist/test-screenshot.js.map +1 -0
  65. package/dist/test-ui-xml-dump.d.ts +15 -0
  66. package/dist/test-ui-xml-dump.js +49 -0
  67. package/dist/test-ui-xml-dump.js.map +1 -0
  68. package/package.json +2 -1
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Outbound — bridge AgentHost events back to the MS IM channel.
3
+ *
4
+ * Subscribes to `AgentHost.on(listener)` ONCE at plugin start, then
5
+ * publishes Centrifugo `ch:{channelId}` envelopes whose shape matches
6
+ * the contract the rest of the BeeOS platform expects:
7
+ *
8
+ * - `agent_reply_delta` — streaming chunks. Carries an inner
9
+ * `update.sessionUpdate` field that callers (A2A / OpenAPI) demux
10
+ * on. Only `agent_message_chunk` is treated as user-visible
11
+ * reply text by `backend/pkg/convmsg/extract.go::extractNestedDelta`;
12
+ * `agent_thought_chunk` is intentionally filtered out so reasoning
13
+ * never leaks into status text.
14
+ *
15
+ * - `agent_reply` — terminal reply on `task:end`. `payload.text` for
16
+ * success, `payload.error` for failure. Matches
17
+ * `chatinvoke/invoker.go::extractReplyFinal`.
18
+ *
19
+ * Each envelope carries `in_reply_to` set to the original request's
20
+ * `message_id`, which is what the L0 `/channels/{id}/wait` endpoint
21
+ * matches on.
22
+ *
23
+ * The listener filters by taskId via the in-flight register — events
24
+ * for tasks NOT submitted through the message-bus inbound (e.g. ACP
25
+ * `session/prompt` traffic) are ignored, so the two surfaces never
26
+ * collide.
27
+ */
28
+ /**
29
+ * Install the outbound listener on the host. Returns an `unsubscribe`
30
+ * function — call it from the plugin's `stop()` so we don't keep
31
+ * publishing after the SDK has been disconnected.
32
+ */
33
+ export function installMessageBusOutbound(opts) {
34
+ const listener = (event) => {
35
+ void handleEvent(event, opts);
36
+ };
37
+ return opts.host.on(listener);
38
+ }
39
+ async function handleEvent(event, opts) {
40
+ const ctx = opts.register.getByTaskId(event.taskId);
41
+ if (!ctx)
42
+ return; // Not an MS-originated turn — ignored.
43
+ try {
44
+ switch (event.type) {
45
+ case "task:start":
46
+ // Intentionally not emitted. beeos-claw does not publish an
47
+ // `agent_status` delta on prompt start either — see
48
+ // [acp-server.ts onStreamDelta](agents/beeos-claw/src/acp-server.ts);
49
+ // the first wire publish is the first real LLM delta. The
50
+ // server-side `extractNestedDelta` only honours
51
+ // `agent_message_chunk` anyway, so synthesising an extra
52
+ // `agent_status` envelope would be pure noise. KISS — skip.
53
+ return;
54
+ case "task:text":
55
+ await sendDelta(opts, ctx, "agent_message_chunk", event.text);
56
+ return;
57
+ case "task:reasoning":
58
+ await sendDelta(opts, ctx, "agent_thought_chunk", event.text);
59
+ return;
60
+ case "task:notice":
61
+ // Notices (warnings / non-fatal errors) surface as user-visible
62
+ // chunks so the operator sees something. Same `agent_message_chunk`
63
+ // sessionUpdate so they get included in the final concatenated
64
+ // text by extractNestedDelta. Payload is the canonical
65
+ // `{ session_key, update }` shape — no extra fields, matching
66
+ // beeos-claw's onStreamDelta output exactly.
67
+ await sendDelta(opts, ctx, "agent_message_chunk", event.text);
68
+ return;
69
+ case "task:tool":
70
+ case "task:step":
71
+ // Tool calls and step transitions are debug-grade today —
72
+ // streaming them would dilute the agent_message_chunk text.
73
+ // Skip silently; callers that want richer telemetry should
74
+ // subscribe to the personal channel directly.
75
+ return;
76
+ case "task:end": {
77
+ const ok = !event.error;
78
+ const finalText = (event.result?.finalMessage ??
79
+ event.result?.text ??
80
+ "") || "";
81
+ // Wire shape mirrors beeos-claw `[acp-server.ts:498/:514]`
82
+ // (agents/beeos-claw/src/acp-server.ts):
83
+ // success → { session_key, text }
84
+ // failure → { session_key, error } (text optional)
85
+ const payload = ok
86
+ ? { session_key: ctx.sessionKey, text: finalText }
87
+ : {
88
+ session_key: ctx.sessionKey,
89
+ error: event.error ?? "unknown_error",
90
+ text: finalText,
91
+ };
92
+ await opts.sdk.sendToChannel(ctx.channelId, "agent_reply", payload, { inReplyTo: ctx.inboundMessageId });
93
+ opts.logger.info({
94
+ taskId: event.taskId,
95
+ channelId: ctx.channelId,
96
+ ok,
97
+ durationMs: event.completedAt - ctx.startedAt,
98
+ }, "message-bus: turn finished");
99
+ opts.register.clear(event.taskId);
100
+ return;
101
+ }
102
+ }
103
+ }
104
+ catch (err) {
105
+ // Never throw out of the AgentHost listener — that would crash the
106
+ // entire event fan-out. Log and continue; the next event for the
107
+ // same task will retry the publish path.
108
+ opts.logger.error({
109
+ err: err instanceof Error ? err.message : String(err),
110
+ eventType: event.type,
111
+ taskId: event.taskId,
112
+ channelId: ctx.channelId,
113
+ }, "message-bus: outbound publish failed");
114
+ }
115
+ }
116
+ /**
117
+ * Publish a single `agent_reply_delta` envelope.
118
+ *
119
+ * Wire shape locked-in to match beeos-claw
120
+ * [`acp-server.ts onStreamDelta` (acp-server.ts:438-450)](agents/beeos-claw/src/acp-server.ts):
121
+ *
122
+ * ```json
123
+ * {
124
+ * "session_key": "agent:{id}:invoke:ctx-...",
125
+ * "update": {
126
+ * "sessionUpdate": "agent_message_chunk" | "agent_thought_chunk",
127
+ * "content": { "type": "text", "text": "..." }
128
+ * }
129
+ * }
130
+ * ```
131
+ *
132
+ * Server-side `backend/pkg/convmsg/extract.go::extractNestedDelta`
133
+ * filters on `update.sessionUpdate === "agent_message_chunk"` for
134
+ * user-visible reply text — `agent_thought_chunk` is dropped so
135
+ * reasoning never leaks into status text. The `session_key` field is
136
+ * forward-compatible: no current backend consumer reads it (verified
137
+ * via `rg session_key backend/`), but emitting it preserves byte-for-
138
+ * byte parity with beeos-claw for downstream observers.
139
+ */
140
+ async function sendDelta(opts, ctx, sessionUpdate, text) {
141
+ await opts.sdk.sendToChannel(ctx.channelId, "agent_reply_delta", {
142
+ session_key: ctx.sessionKey,
143
+ update: {
144
+ sessionUpdate,
145
+ content: { type: "text", text },
146
+ },
147
+ }, { inReplyTo: ctx.inboundMessageId });
148
+ }
149
+ //# sourceMappingURL=outbound.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbound.js","sourceRoot":"","sources":["../../../src/plugins/message-bus/outbound.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * In-flight Message-Service turn register.
3
+ *
4
+ * Holds the channel + reply correlator for the SINGLE turn that's
5
+ * currently executing on the host. Single-entry by design — device-agent
6
+ * runs one ACP session and the SessionManager serialises turns inside
7
+ * that session, so a second concurrent `chat_message` would be a logic
8
+ * error and is rejected via `busy-reply.ts`.
9
+ *
10
+ * The taskId is allocated by `host.submitTask(...)` and used to filter
11
+ * the `host.on(listener)` event stream so we never publish another
12
+ * turn's deltas to the wrong MS channel.
13
+ */
14
+ export interface MessagingTurnContext {
15
+ /** Allocated by `host.submitTask` — used to filter AgentEvent stream. */
16
+ taskId: string;
17
+ /** IM channel the reply is published on (`ch:{channelId}`). */
18
+ channelId: string;
19
+ /** Request `message_id` echoed back as `in_reply_to` on every reply. */
20
+ inboundMessageId: string;
21
+ /**
22
+ * Stable session anchor (e.g. `agent:{id}:invoke:ctx-{contextId}` or
23
+ * `agent:{id}:invoke:ch-{channelId}`) derived by `extractChatPrompt`.
24
+ * Embedded as `payload.session_key` on every outbound publish so the
25
+ * wire format matches beeos-claw `[acp-server.ts:443]`
26
+ * (agents/beeos-claw/src/acp-server.ts) — downstream consumers
27
+ * (dashboards, A2A SSE projection) use it to group chunks belonging
28
+ * to the same logical conversation turn.
29
+ */
30
+ sessionKey: string;
31
+ /** Optional context-id, surfaced for logging only. */
32
+ contextId?: string;
33
+ /** Wall-clock timestamp the turn was claimed (ms). For diagnostics. */
34
+ startedAt: number;
35
+ }
36
+ /**
37
+ * Single-entry in-flight register.
38
+ *
39
+ * Methods are intentionally synchronous — they run on the WS publication
40
+ * callback (low latency) and on AgentHost listener invocations.
41
+ */
42
+ export declare class TurnContextRegister {
43
+ private current;
44
+ isBusy(): boolean;
45
+ /**
46
+ * Claim the register for a new turn. Returns the entry on success or
47
+ * `null` when a previous turn is still in flight (caller MUST then
48
+ * use the busy-reply path).
49
+ */
50
+ claim(entry: Omit<MessagingTurnContext, "startedAt">): MessagingTurnContext | null;
51
+ current_(): MessagingTurnContext | null;
52
+ getByTaskId(taskId: string): MessagingTurnContext | null;
53
+ /** Idempotent — clearing twice is a no-op. */
54
+ clear(taskId: string): void;
55
+ /** Force-clear (used during plugin stop / disconnect). */
56
+ reset(): void;
57
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * In-flight Message-Service turn register.
3
+ *
4
+ * Holds the channel + reply correlator for the SINGLE turn that's
5
+ * currently executing on the host. Single-entry by design — device-agent
6
+ * runs one ACP session and the SessionManager serialises turns inside
7
+ * that session, so a second concurrent `chat_message` would be a logic
8
+ * error and is rejected via `busy-reply.ts`.
9
+ *
10
+ * The taskId is allocated by `host.submitTask(...)` and used to filter
11
+ * the `host.on(listener)` event stream so we never publish another
12
+ * turn's deltas to the wrong MS channel.
13
+ */
14
+ /**
15
+ * Single-entry in-flight register.
16
+ *
17
+ * Methods are intentionally synchronous — they run on the WS publication
18
+ * callback (low latency) and on AgentHost listener invocations.
19
+ */
20
+ export class TurnContextRegister {
21
+ current = null;
22
+ isBusy() {
23
+ return this.current !== null;
24
+ }
25
+ /**
26
+ * Claim the register for a new turn. Returns the entry on success or
27
+ * `null` when a previous turn is still in flight (caller MUST then
28
+ * use the busy-reply path).
29
+ */
30
+ claim(entry) {
31
+ if (this.current)
32
+ return null;
33
+ this.current = { ...entry, startedAt: Date.now() };
34
+ return this.current;
35
+ }
36
+ current_() {
37
+ return this.current;
38
+ }
39
+ getByTaskId(taskId) {
40
+ return this.current && this.current.taskId === taskId ? this.current : null;
41
+ }
42
+ /** Idempotent — clearing twice is a no-op. */
43
+ clear(taskId) {
44
+ if (this.current && this.current.taskId === taskId) {
45
+ this.current = null;
46
+ }
47
+ }
48
+ /** Force-clear (used during plugin stop / disconnect). */
49
+ reset() {
50
+ this.current = null;
51
+ }
52
+ }
53
+ //# sourceMappingURL=turn-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turn-context.js","sourceRoot":"","sources":["../../../src/plugins/message-bus/turn-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAyBH;;;;;GAKG;AACH,MAAM,OAAO,mBAAmB;IACtB,OAAO,GAAgC,IAAI,CAAC;IAEpD,MAAM;QACJ,OAAO,IAAI,CAAC,OAAO,KAAK,IAAI,CAAC;IAC/B,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,KAA8C;QAClD,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAC9B,IAAI,CAAC,OAAO,GAAG,EAAE,GAAG,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;QACnD,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,QAAQ;QACN,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,WAAW,CAAC,MAAc;QACxB,OAAO,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IAC9E,CAAC;IAED,8CAA8C;IAC9C,KAAK,CAAC,MAAc;QAClB,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YACnD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACtB,CAAC;IACH,CAAC;IAED,0DAA0D;IAC1D,KAAK;QACH,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;IACtB,CAAC;CACF"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 验证局域网 OpenAI 兼容 AutoGLM 与 device-agent-ts 内层链路(adapter=autoglm + OpenAIClient)。
3
+ * 需要:adb 已连机、本机可访问 GUI_AGENT_BASE_URL、.env 已配置。
4
+ *
5
+ * npx tsx src/test-lan-autoglm.ts
6
+ */
7
+ import "dotenv/config";
@@ -0,0 +1,46 @@
1
+ /**
2
+ * 验证局域网 OpenAI 兼容 AutoGLM 与 device-agent-ts 内层链路(adapter=autoglm + OpenAIClient)。
3
+ * 需要:adb 已连机、本机可访问 GUI_AGENT_BASE_URL、.env 已配置。
4
+ *
5
+ * npx tsx src/test-lan-autoglm.ts
6
+ */
7
+ import "dotenv/config";
8
+ import { AndroidDevice } from "./device/android.js";
9
+ import { OpenAIClient } from "./model/client.js";
10
+ const TASK = process.env.LAN_VLM_SMOKE_TASK ??
11
+ "看当前截图,只输出一行下一步操作(不要复述用户原话里的示例代码):用 do(action=\"Tap\", element=[x,y]) 点击你认为该点的位置,坐标 0–1000。";
12
+ async function main() {
13
+ const base = process.env.GUI_AGENT_BASE_URL ?? "";
14
+ const model = process.env.GUI_AGENT_MODEL ?? "";
15
+ if (!base || !model) {
16
+ console.error("请设置 GUI_AGENT_BASE_URL、GUI_AGENT_MODEL(及 API_KEY)");
17
+ process.exit(1);
18
+ }
19
+ console.log(`[lan-autoglm] base=${base} model=${model} adapter=${process.env.GUI_AGENT_ADAPTER ?? "(auto)"} stream=${process.env.GUI_AGENT_STREAM ?? "1"}`);
20
+ const device = new AndroidDevice(process.env.GUI_AGENT_DEVICE_ID ?? undefined);
21
+ const ss = await device.takeScreenshot();
22
+ const currentApp = await device.getCurrentApp();
23
+ const client = new OpenAIClient({
24
+ adapter: process.env.GUI_AGENT_ADAPTER ?? "autoglm",
25
+ });
26
+ const screenInfo = {
27
+ screenshotB64: ss.base64Data,
28
+ width: ss.width,
29
+ height: ss.height,
30
+ currentApp,
31
+ uiXml: null,
32
+ };
33
+ const messages = client.buildMessages(TASK, screenInfo, [], true, (process.env.GUI_AGENT_LANG ?? "cn"));
34
+ console.log(`[lan-autoglm] requesting… (${messages.length} messages, jpeg b64 ~${Math.round(ss.base64Data.length / 1024)} KB)`);
35
+ const t0 = Date.now();
36
+ const resp = await client.request(messages, true);
37
+ const ms = Date.now() - t0;
38
+ console.log("\n--- raw (prefix) ---\n", resp.rawContent.slice(0, 1500), resp.rawContent.length > 1500 ? "\n…" : "");
39
+ console.log("\n--- parsed ---\n", JSON.stringify(client.parseResponse(resp.rawContent), null, 2));
40
+ console.log(`\n[lan-autoglm] ok in ${ms} ms`);
41
+ }
42
+ main().catch((e) => {
43
+ console.error(e);
44
+ process.exit(1);
45
+ });
46
+ //# sourceMappingURL=test-lan-autoglm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-lan-autoglm.js","sourceRoot":"","sources":["../src/test-lan-autoglm.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ import "dotenv/config";
@@ -0,0 +1,103 @@
1
+ import "dotenv/config";
2
+ // Memory diagnostic: peel back each layer to see where the 200MB goes.
3
+ // Run with: npx tsx --expose-gc src/test-mem-breakdown.ts
4
+ function mb(n) { return (n / 1024 / 1024).toFixed(1); }
5
+ function rss() { return process.memoryUsage().rss; }
6
+ function heap() { return process.memoryUsage().heapUsed; }
7
+ function ext() { return process.memoryUsage().external; }
8
+ function snap(label, baseline) {
9
+ const m = process.memoryUsage();
10
+ const delta = baseline !== undefined ? ` (Δrss=${mb(m.rss - baseline)}MB)` : "";
11
+ console.log(`[${label.padEnd(28)}] rss=${mb(m.rss)}MB heap=${mb(m.heapUsed)}MB ext=${mb(m.external)}MB${delta}`);
12
+ return m.rss;
13
+ }
14
+ async function waitGc() {
15
+ if (global.gc) {
16
+ global.gc();
17
+ global.gc();
18
+ }
19
+ await new Promise(r => setTimeout(r, 50));
20
+ }
21
+ console.log("=".repeat(78));
22
+ console.log("Memory Breakdown Diagnostic");
23
+ console.log("=".repeat(78));
24
+ // ── Layer 0: bare node ──────────────────────────────────────────────────────
25
+ const bare = snap("0. bare node (tsx loader)");
26
+ // ── Layer 1: dotenv + types (already loaded at top, but measure current) ────
27
+ const l1 = snap("1. + dotenv", bare);
28
+ // ── Layer 2: load openai SDK ────────────────────────────────────────────────
29
+ await import("openai");
30
+ const l2 = snap("2. + openai sdk", l1);
31
+ // ── Layer 3: load sharp (triggers libvips mmap) ─────────────────────────────
32
+ const sharpMod = await import("sharp");
33
+ const l3 = snap("3. + sharp (libvips mmap)", l2);
34
+ // ── Layer 4: load our Device layer ──────────────────────────────────────────
35
+ const { AndroidDevice } = await import("./device/android.js");
36
+ const l4 = snap("4. + device/android", l3);
37
+ // ── Layer 5: instantiate device + check adb exists ──────────────────────────
38
+ const dev = new AndroidDevice(process.env.GUI_AGENT_DEVICE_ID);
39
+ const l5 = snap("5. + new AndroidDevice()", l4);
40
+ // ── Layer 6: load agent + model adapters ────────────────────────────────────
41
+ const { Agent } = await import("./agent.js");
42
+ const { OpenAIClient } = await import("./model/client.js");
43
+ await import("./model/adapters/index.js");
44
+ const l6 = snap("6. + agent + all adapters", l5);
45
+ // ── Layer 7: instantiate model client (openai client w/ keep-alive) ─────────
46
+ const client = new OpenAIClient({ adapter: process.env.GUI_AGENT_ADAPTER });
47
+ const l7 = snap("7. + new OpenAIClient()", l6);
48
+ // ── Layer 8: take one screenshot (triggers sharp pipeline + buffer) ─────────
49
+ await dev.takeScreenshot();
50
+ await waitGc();
51
+ const l8 = snap("8. + 1 screenshot (post-gc)", l7);
52
+ // ── Layer 9: take 5 screenshots back-to-back ────────────────────────────────
53
+ let peak9 = l8;
54
+ for (let i = 0; i < 5; i++) {
55
+ await dev.takeScreenshot();
56
+ peak9 = Math.max(peak9, rss());
57
+ }
58
+ await waitGc();
59
+ const l9 = snap("9. + 5 screenshots (post-gc)", l8);
60
+ console.log(` └─ intra-loop peak rss=${mb(peak9)}MB`);
61
+ // ── Layer 10: instantiate agent ─────────────────────────────────────────────
62
+ const agent = new Agent(client, dev, { maxSteps: 3, verbose: false });
63
+ const l10 = snap("10. + new Agent()", l9);
64
+ // ── Layer 11: run 1 full agent step (includes API call if connected) ────────
65
+ let peak11 = l10;
66
+ const before = rss();
67
+ try {
68
+ await agent.run("打开系统设置");
69
+ peak11 = Math.max(peak11, rss());
70
+ }
71
+ catch (e) {
72
+ // ignore — we just want memory delta
73
+ }
74
+ await waitGc();
75
+ const l11 = snap("11. + agent.run() (post-gc)", l10);
76
+ console.log(` └─ intra-run peak rss=${mb(peak11)}MB (Δ from pre-run=${mb(peak11 - before)}MB)`);
77
+ // ── Summary ─────────────────────────────────────────────────────────────────
78
+ console.log("=".repeat(78));
79
+ console.log("Component Breakdown (incremental RSS additions, MB)");
80
+ console.log("=".repeat(78));
81
+ const rows = [
82
+ ["Node.js + tsx loader baseline", bare],
83
+ ["+ dotenv", l1 - bare],
84
+ ["+ openai SDK", l2 - l1],
85
+ ["+ sharp (libvips native mmap)", l3 - l2],
86
+ ["+ device layer (adb client)", l4 - l3],
87
+ ["+ AndroidDevice instance", l5 - l4],
88
+ ["+ agent + all model adapters", l6 - l5],
89
+ ["+ OpenAI client (keep-alive pool)", l7 - l6],
90
+ ["+ first screenshot (warm cache)", l8 - l7],
91
+ ["+ 4 more screenshots", l9 - l8],
92
+ ["+ Agent instance", l10 - l9],
93
+ ["+ agent.run() full cycle", l11 - l10],
94
+ ];
95
+ for (const [label, delta] of rows) {
96
+ console.log(` ${label.padEnd(42)} ${(delta / 1024 / 1024).toFixed(1).padStart(6)} MB`);
97
+ }
98
+ console.log("-".repeat(78));
99
+ console.log(` TOTAL steady-state RSS: ${mb(l11).padStart(6)} MB`);
100
+ console.log(` Peak RSS during run: ${mb(peak11).padStart(6)} MB (transient request buffer)`);
101
+ console.log("=".repeat(78));
102
+ process.exit(0);
103
+ //# sourceMappingURL=test-mem-breakdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-mem-breakdown.js","sourceRoot":"","sources":["../src/test-mem-breakdown.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ import "dotenv/config";
@@ -0,0 +1,60 @@
1
+ import "dotenv/config";
2
+ import { mobileUse } from "./api.js";
3
+ const task = process.env.TASK ?? "打开系统设置,告诉我 Android 版本号";
4
+ const samples = [];
5
+ const start = Date.now();
6
+ function snap() {
7
+ const m = process.memoryUsage();
8
+ return {
9
+ t: Date.now() - start,
10
+ rss: m.rss,
11
+ heapUsed: m.heapUsed,
12
+ heapTotal: m.heapTotal,
13
+ external: m.external,
14
+ arrayBuffers: m.arrayBuffers,
15
+ };
16
+ }
17
+ function fmt(n) { return (n / 1024 / 1024).toFixed(1); }
18
+ function logSample(label, s) {
19
+ console.log(`[mem t=${(s.t / 1000).toFixed(1)}s ${label.padEnd(12)}] ` +
20
+ `rss=${fmt(s.rss)}MB heapUsed=${fmt(s.heapUsed)}MB heapTotal=${fmt(s.heapTotal)}MB ` +
21
+ `ext=${fmt(s.external)}MB arrBuf=${fmt(s.arrayBuffers)}MB`);
22
+ }
23
+ const initial = snap();
24
+ logSample("init", initial);
25
+ // 200ms sampling loop in background
26
+ const interval = setInterval(() => samples.push(snap()), 200);
27
+ interval.unref();
28
+ console.log(`[test-mobile-use] task = ${task}`);
29
+ console.log("=".repeat(70));
30
+ mobileUse(task)
31
+ .then((result) => {
32
+ const done = snap();
33
+ console.log("=".repeat(70));
34
+ logSample("done", done);
35
+ // Stats
36
+ if (global.gc) {
37
+ global.gc();
38
+ }
39
+ const afterGc = snap();
40
+ if (global.gc)
41
+ logSample("post-gc", afterGc);
42
+ const peakRss = samples.reduce((m, s) => Math.max(m, s.rss), initial.rss);
43
+ const peakHeap = samples.reduce((m, s) => Math.max(m, s.heapUsed), initial.heapUsed);
44
+ const peakExt = samples.reduce((m, s) => Math.max(m, s.external), initial.external);
45
+ console.log("-".repeat(70));
46
+ console.log(`[summary] samples=${samples.length} over ${(done.t / 1000).toFixed(1)}s`);
47
+ console.log(`[summary] peak rss = ${fmt(peakRss)} MB`);
48
+ console.log(`[summary] peak heapUsed = ${fmt(peakHeap)} MB`);
49
+ console.log(`[summary] peak external = ${fmt(peakExt)} MB (sharp native buffers live here)`);
50
+ console.log(`[summary] growth: rss ${fmt(done.rss - initial.rss)} MB, heapUsed ${fmt(done.heapUsed - initial.heapUsed)} MB`);
51
+ console.log("\n[result]", result);
52
+ process.exit(0);
53
+ })
54
+ .catch((e) => {
55
+ const done = snap();
56
+ logSample("error", done);
57
+ console.error("[test-mobile-use] error:", e);
58
+ process.exit(1);
59
+ });
60
+ //# sourceMappingURL=test-mem.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-mem.js","sourceRoot":"","sources":["../src/test-mem.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ import "dotenv/config";
@@ -0,0 +1,18 @@
1
+ import "dotenv/config";
2
+ import { mobileUse } from "./api.js";
3
+ const task = process.env.TASK ?? "打开系统设置,然后告诉我 Android 版本号是多少";
4
+ const maxSteps = parseInt(process.env.GUI_AGENT_MAX_STEPS ?? "30", 10);
5
+ console.log(`[test-mobile-use] task = ${task}`);
6
+ console.log("=".repeat(60));
7
+ mobileUse(task, maxSteps)
8
+ .then((result) => {
9
+ console.log("=".repeat(60));
10
+ console.log("[test-mobile-use] result:", result);
11
+ process.exit(0);
12
+ })
13
+ .catch((e) => {
14
+ console.error("=".repeat(60));
15
+ console.error("[test-mobile-use] error:", e);
16
+ process.exit(1);
17
+ });
18
+ //# sourceMappingURL=test-mobile-use.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-mobile-use.js","sourceRoot":"","sources":["../src/test-mobile-use.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC;AACvB,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,6BAA6B,CAAC;AAC/D,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC;AAEvE,OAAO,CAAC,GAAG,CAAC,4BAA4B,IAAI,EAAE,CAAC,CAAC;AAChD,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;AAE5B,SAAS,CAAC,IAAI,EAAE,QAAQ,CAAC;KACtB,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;IACf,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;IAC5B,OAAO,CAAC,GAAG,CAAC,2BAA2B,EAAE,MAAM,CAAC,CAAC;IACjD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC;KACD,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;IACX,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9B,OAAO,CAAC,KAAK,CAAC,0BAA0B,EAAE,CAAC,CAAC,CAAC;IAC7C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ import "dotenv/config";
@@ -0,0 +1,43 @@
1
+ import "dotenv/config";
2
+ import { AndroidDevice } from "./device/android.js";
3
+ const ITER = parseInt(process.env.ITER ?? "100", 10);
4
+ const dev = new AndroidDevice(process.env.GUI_AGENT_DEVICE_ID);
5
+ function fmt(n) { return (n / 1024 / 1024).toFixed(1); }
6
+ function snap(label, extra = "") {
7
+ const m = process.memoryUsage();
8
+ console.log(`[${label.padEnd(20)}] rss=${fmt(m.rss)}MB heap=${fmt(m.heapUsed)}/${fmt(m.heapTotal)}MB ` +
9
+ `ext=${fmt(m.external)}MB arrBuf=${fmt(m.arrayBuffers)}MB ${extra}`);
10
+ }
11
+ snap("boot");
12
+ // Prime sharp / adb
13
+ await dev.takeScreenshot();
14
+ snap("after 1 ss");
15
+ let totalB64 = 0;
16
+ let maxRss = 0;
17
+ const t0 = Date.now();
18
+ for (let i = 0; i < ITER; i++) {
19
+ const ss = await dev.takeScreenshot();
20
+ totalB64 += ss.base64Data.length;
21
+ const rss = process.memoryUsage().rss;
22
+ if (rss > maxRss)
23
+ maxRss = rss;
24
+ if (i % 10 === 9) {
25
+ snap(`iter ${i + 1}`, `avg b64=${Math.round(totalB64 / (i + 1) / 1024)}KB`);
26
+ }
27
+ }
28
+ const dur = (Date.now() - t0) / 1000;
29
+ console.log("-".repeat(70));
30
+ console.log(`[result] iterations=${ITER}, total ${dur.toFixed(1)}s, avg ${(dur * 1000 / ITER).toFixed(0)}ms/screenshot`);
31
+ console.log(`[result] avg base64 length = ${Math.round(totalB64 / ITER / 1024)} KB`);
32
+ console.log(`[result] peak RSS observed = ${fmt(maxRss)} MB`);
33
+ snap("before gc");
34
+ if (global.gc) {
35
+ global.gc();
36
+ global.gc();
37
+ }
38
+ snap("after gc");
39
+ // Wait a bit for native to release
40
+ await new Promise((r) => setTimeout(r, 1000));
41
+ snap("after +1s wait");
42
+ process.exit(0);
43
+ //# sourceMappingURL=test-screenshot-loop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-screenshot-loop.js","sourceRoot":"","sources":["../src/test-screenshot-loop.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ import "dotenv/config";
@@ -0,0 +1,10 @@
1
+ import "dotenv/config";
2
+ import { AndroidDevice } from "./device/android.js";
3
+ const dev = new AndroidDevice(process.env.GUI_AGENT_DEVICE_ID);
4
+ const t0 = Date.now();
5
+ const ss = await dev.takeScreenshot();
6
+ const t1 = Date.now();
7
+ console.log(`screen w=${ss.width} h=${ss.height}`);
8
+ console.log(`base64 length=${ss.base64Data.length} (~${Math.round(ss.base64Data.length * 3 / 4 / 1024)} KB binary)`);
9
+ console.log(`elapsed=${t1 - t0}ms`);
10
+ //# sourceMappingURL=test-screenshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-screenshot.js","sourceRoot":"","sources":["../src/test-screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC;AACvB,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,MAAM,GAAG,GAAG,IAAI,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;AAC/D,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;AACtB,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,cAAc,EAAE,CAAC;AACtC,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;AACtB,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,KAAK,MAAM,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;AACnD,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,CAAC,UAAU,CAAC,MAAM,MAAM,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,aAAa,CAAC,CAAC;AACrH,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Dump current-screen UI XML for debugging `ui_xml` parsing.
3
+ *
4
+ * Writes under `.debug/ui-xml-dumps/` (override with UI_XML_DUMP_DIR):
5
+ * - `<stamp>-raw.xml` — full uiautomator dump (no truncation)
6
+ * - `<stamp>-tool.txt` — exact string `ui_xml` would return (same truncation / JSON rules)
7
+ * - `<stamp>-parsed.json` — `{ query, nodes }` from {@link searchUiXmlNodes}
8
+ *
9
+ * Usage:
10
+ * npx tsx src/test-ui-xml-dump.ts # query = "" → parsed.json lists all nodes (can be large)
11
+ * npx tsx src/test-ui-xml-dump.ts 设置 # filter substring
12
+ *
13
+ * Requires adb + device (GUI_AGENT_DEVICE_ID if multiple).
14
+ */
15
+ import "dotenv/config";