@greatlhd/ailo-desktop 1.0.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 (73) hide show
  1. package/copy-static.mjs +11 -0
  2. package/dist/browser_control.js +767 -0
  3. package/dist/browser_snapshot.js +174 -0
  4. package/dist/cli.js +36 -0
  5. package/dist/code_executor.js +95 -0
  6. package/dist/config_server.js +658 -0
  7. package/dist/connection_util.js +14 -0
  8. package/dist/constants.js +2 -0
  9. package/dist/desktop_state_store.js +57 -0
  10. package/dist/desktop_types.js +1 -0
  11. package/dist/desktop_verifier.js +40 -0
  12. package/dist/dingtalk-handler.js +173 -0
  13. package/dist/dingtalk-types.js +1 -0
  14. package/dist/email_handler.js +501 -0
  15. package/dist/exec_tool.js +90 -0
  16. package/dist/feishu-handler.js +620 -0
  17. package/dist/feishu-types.js +8 -0
  18. package/dist/feishu-utils.js +162 -0
  19. package/dist/fs_tools.js +398 -0
  20. package/dist/index.js +433 -0
  21. package/dist/mcp/config-manager.js +64 -0
  22. package/dist/mcp/index.js +3 -0
  23. package/dist/mcp/rpc.js +109 -0
  24. package/dist/mcp/session.js +140 -0
  25. package/dist/mcp_manager.js +253 -0
  26. package/dist/mouse_keyboard.js +516 -0
  27. package/dist/qq-handler.js +153 -0
  28. package/dist/qq-types.js +15 -0
  29. package/dist/qq-ws.js +178 -0
  30. package/dist/screenshot.js +271 -0
  31. package/dist/skills_hub.js +212 -0
  32. package/dist/skills_manager.js +103 -0
  33. package/dist/static/AGENTS.md +25 -0
  34. package/dist/static/app.css +539 -0
  35. package/dist/static/app.html +292 -0
  36. package/dist/static/app.js +380 -0
  37. package/dist/static/chat.html +994 -0
  38. package/dist/time_tool.js +22 -0
  39. package/dist/utils.js +15 -0
  40. package/package.json +38 -0
  41. package/src/browser_control.ts +739 -0
  42. package/src/browser_snapshot.ts +196 -0
  43. package/src/cli.ts +44 -0
  44. package/src/code_executor.ts +101 -0
  45. package/src/config_server.ts +723 -0
  46. package/src/connection_util.ts +23 -0
  47. package/src/constants.ts +2 -0
  48. package/src/desktop_state_store.ts +64 -0
  49. package/src/desktop_types.ts +44 -0
  50. package/src/desktop_verifier.ts +45 -0
  51. package/src/dingtalk-types.ts +26 -0
  52. package/src/exec_tool.ts +93 -0
  53. package/src/feishu-handler.ts +722 -0
  54. package/src/feishu-types.ts +66 -0
  55. package/src/feishu-utils.ts +174 -0
  56. package/src/fs_tools.ts +411 -0
  57. package/src/index.ts +474 -0
  58. package/src/mcp/config-manager.ts +85 -0
  59. package/src/mcp/index.ts +7 -0
  60. package/src/mcp/rpc.ts +131 -0
  61. package/src/mcp/session.ts +182 -0
  62. package/src/mcp_manager.ts +273 -0
  63. package/src/mouse_keyboard.ts +526 -0
  64. package/src/qq-types.ts +49 -0
  65. package/src/qq-ws.ts +223 -0
  66. package/src/screenshot.ts +297 -0
  67. package/src/static/app.css +539 -0
  68. package/src/static/app.html +292 -0
  69. package/src/static/app.js +380 -0
  70. package/src/static/chat.html +994 -0
  71. package/src/time_tool.ts +24 -0
  72. package/src/utils.ts +22 -0
  73. package/tsconfig.json +13 -0
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Build role snapshot + refs from Playwright aria_snapshot output.
3
+ * Port of CoPaw's browser_snapshot.py to TypeScript.
4
+ */
5
+ const INTERACTIVE_ROLES = new Set([
6
+ "button", "link", "textbox", "checkbox", "radio", "combobox",
7
+ "listbox", "menuitem", "menuitemcheckbox", "menuitemradio",
8
+ "option", "searchbox", "slider", "spinbutton", "switch", "tab", "treeitem",
9
+ ]);
10
+ const CONTENT_ROLES = new Set([
11
+ "heading", "cell", "gridcell", "columnheader", "rowheader",
12
+ "listitem", "article", "region", "main", "navigation",
13
+ ]);
14
+ const STRUCTURAL_ROLES = new Set([
15
+ "generic", "group", "list", "table", "row", "rowgroup", "grid",
16
+ "treegrid", "menu", "menubar", "toolbar", "tablist", "tree",
17
+ "directory", "document", "application", "presentation", "none",
18
+ ]);
19
+ function getIndentLevel(line) {
20
+ const m = line.match(/^(\s*)/);
21
+ const INDENT_WIDTH = 2;
22
+ return m ? Math.floor(m[1].length / INDENT_WIDTH) : 0;
23
+ }
24
+ function createTracker() {
25
+ const counts = new Map();
26
+ const refsByKey = new Map();
27
+ function getKey(role, name) {
28
+ return `${role}:${name ?? ""}`;
29
+ }
30
+ return {
31
+ getKey,
32
+ getNextIndex(role, name) {
33
+ const key = getKey(role, name);
34
+ const current = counts.get(key) ?? 0;
35
+ counts.set(key, current + 1);
36
+ return current;
37
+ },
38
+ trackRef(role, name, ref) {
39
+ const key = getKey(role, name);
40
+ const list = refsByKey.get(key) ?? [];
41
+ list.push(ref);
42
+ refsByKey.set(key, list);
43
+ },
44
+ getDuplicateKeys() {
45
+ const dups = new Set();
46
+ for (const [k, refs] of refsByKey) {
47
+ if (refs.length > 1)
48
+ dups.add(k);
49
+ }
50
+ return dups;
51
+ },
52
+ };
53
+ }
54
+ function removeNthFromNonDuplicates(refs, tracker) {
55
+ const dupKeys = tracker.getDuplicateKeys();
56
+ for (const [, data] of refs) {
57
+ const key = tracker.getKey(data.role, data.name);
58
+ if (!dupKeys.has(key) && data.nth !== undefined) {
59
+ delete data.nth;
60
+ }
61
+ }
62
+ }
63
+ function compactTree(tree) {
64
+ const lines = tree.split("\n");
65
+ const result = [];
66
+ for (let i = 0; i < lines.length; i++) {
67
+ const line = lines[i];
68
+ if (line.includes("[ref=")) {
69
+ result.push(line);
70
+ continue;
71
+ }
72
+ if (line.includes(":") && !line.trimEnd().endsWith(":")) {
73
+ result.push(line);
74
+ continue;
75
+ }
76
+ const currentIndent = getIndentLevel(line);
77
+ let hasRelevant = false;
78
+ for (let j = i + 1; j < lines.length; j++) {
79
+ if (getIndentLevel(lines[j]) <= currentIndent)
80
+ break;
81
+ if (lines[j].includes("[ref=")) {
82
+ hasRelevant = true;
83
+ break;
84
+ }
85
+ }
86
+ if (hasRelevant)
87
+ result.push(line);
88
+ }
89
+ return result.join("\n");
90
+ }
91
+ const LINE_RE = /^(\s*-\s*)(\w+)(?:\s+"([^"]*)")?(.*)$/;
92
+ function processLine(line, refs, options, tracker, nextRef) {
93
+ const depth = getIndentLevel(line);
94
+ if (options.maxDepth !== undefined && depth > options.maxDepth)
95
+ return null;
96
+ const m = line.match(LINE_RE);
97
+ if (!m)
98
+ return options.interactive ? null : line;
99
+ const [, prefix, roleRaw, name, suffix] = m;
100
+ if (roleRaw.startsWith("/"))
101
+ return options.interactive ? null : line;
102
+ const role = roleRaw.toLowerCase();
103
+ const isInteractive = INTERACTIVE_ROLES.has(role);
104
+ const isContent = CONTENT_ROLES.has(role);
105
+ if (options.interactive && !isInteractive)
106
+ return null;
107
+ if (options.compact && STRUCTURAL_ROLES.has(role) && !name)
108
+ return null;
109
+ const shouldHaveRef = isInteractive || (isContent && !!name);
110
+ if (!shouldHaveRef)
111
+ return line;
112
+ const ref = nextRef();
113
+ const nth = tracker.getNextIndex(role, name);
114
+ tracker.trackRef(role, name, ref);
115
+ refs.set(ref, { role, name: name || undefined, nth });
116
+ let enhanced = `${prefix}${roleRaw}`;
117
+ if (name)
118
+ enhanced += ` "${name}"`;
119
+ enhanced += ` [ref=${ref}]`;
120
+ if (nth > 0)
121
+ enhanced += ` [nth=${nth}]`;
122
+ if (suffix)
123
+ enhanced += suffix;
124
+ return enhanced;
125
+ }
126
+ export function buildRoleSnapshotFromAria(ariaSnapshot, options = {}) {
127
+ const lines = ariaSnapshot.split("\n");
128
+ const refs = new Map();
129
+ const tracker = createTracker();
130
+ let counter = 0;
131
+ const nextRef = () => `e${++counter}`;
132
+ if (options.interactive) {
133
+ const resultLines = [];
134
+ for (const line of lines) {
135
+ const depth = getIndentLevel(line);
136
+ if (options.maxDepth !== undefined && depth > options.maxDepth)
137
+ continue;
138
+ const m = line.match(LINE_RE);
139
+ if (!m)
140
+ continue;
141
+ const [, , roleRaw, name, suffix] = m;
142
+ if (roleRaw.startsWith("/"))
143
+ continue;
144
+ const role = roleRaw.toLowerCase();
145
+ if (!INTERACTIVE_ROLES.has(role))
146
+ continue;
147
+ const ref = nextRef();
148
+ const nth = tracker.getNextIndex(role, name);
149
+ tracker.trackRef(role, name, ref);
150
+ refs.set(ref, { role, name: name || undefined, nth });
151
+ let enhanced = `- ${roleRaw}`;
152
+ if (name)
153
+ enhanced += ` "${name}"`;
154
+ enhanced += ` [ref=${ref}]`;
155
+ if (nth > 0)
156
+ enhanced += ` [nth=${nth}]`;
157
+ if (suffix?.includes("["))
158
+ enhanced += suffix;
159
+ resultLines.push(enhanced);
160
+ }
161
+ removeNthFromNonDuplicates(refs, tracker);
162
+ return { snapshot: resultLines.join("\n") || "(no interactive elements)", refs };
163
+ }
164
+ const resultLines = [];
165
+ for (const line of lines) {
166
+ const processed = processLine(line, refs, options, tracker, nextRef);
167
+ if (processed !== null)
168
+ resultLines.push(processed);
169
+ }
170
+ removeNthFromNonDuplicates(refs, tracker);
171
+ const tree = resultLines.join("\n") || "(empty)";
172
+ const snapshot = options.compact ? compactTree(tree) : tree;
173
+ return { snapshot, refs };
174
+ }
package/dist/cli.js ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * CLI init command for ailo-desktop.
3
+ * Usage: ailo-desktop init [--defaults]
4
+ */
5
+ import { createInterface } from "readline";
6
+ import { join } from "path";
7
+ import { writeConfig } from "@greatlhd/ailo-endpoint-sdk";
8
+ import { CONFIG_FILENAME } from "./constants.js";
9
+ const CONFIG_PATH = join(process.cwd(), CONFIG_FILENAME);
10
+ async function prompt(question, defaultVal = "") {
11
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
12
+ return new Promise((resolve) => {
13
+ const suffix = defaultVal ? ` [${defaultVal}]` : "";
14
+ rl.question(`${question}${suffix}: `, (answer) => {
15
+ rl.close();
16
+ resolve(answer.trim() || defaultVal);
17
+ });
18
+ });
19
+ }
20
+ export async function runInit(useDefaults = false) {
21
+ console.log("=== Ailo Desktop 初始化 ===\n");
22
+ const wsUrl = useDefaults ? "ws://127.0.0.1:19800/ws" : await prompt("Ailo WebSocket URL", "ws://127.0.0.1:19800/ws");
23
+ const apiKey = useDefaults ? "" : await prompt("API Key (留空稍后配置)");
24
+ const endpointId = useDefaults ? "desktop-01" : await prompt("端点 ID", "desktop-01");
25
+ const config = {
26
+ ailo: {
27
+ wsUrl,
28
+ apiKey: apiKey || "",
29
+ endpointId,
30
+ },
31
+ };
32
+ writeConfig(CONFIG_PATH, config);
33
+ console.log(`\n已写入 ${CONFIG_PATH}`);
34
+ console.log("\n初始化完成!运行 ailo-desktop 启动桌面端点。");
35
+ console.log("配置界面端口:启动时用 --port <端口> 指定,或运行后在控制台按提示输入。");
36
+ }
@@ -0,0 +1,95 @@
1
+ import { spawn, spawnSync } from "child_process";
2
+ import { writeFile, mkdtemp, rm } from "fs/promises";
3
+ import { join } from "path";
4
+ import { tmpdir, platform } from "os";
5
+ const MAX_OUTPUT = 50000;
6
+ const DEFAULT_TIMEOUT_MS = 120_000; // 2 分钟
7
+ /**
8
+ * 异步执行 Python / JavaScript 代码。
9
+ * 立即返回"已启动",代码在后台运行,完成后通过 sendSignal("tool_result") 推送结果给 LLM。
10
+ */
11
+ export async function executeCode(ctx, args) {
12
+ const language = String(args.language ?? "").trim().toLowerCase();
13
+ const code = String(args.code ?? "");
14
+ const cwd = args.cwd || undefined;
15
+ const timeoutSec = Math.max(5, Math.min(600, Number(args.timeout) || DEFAULT_TIMEOUT_MS / 1000));
16
+ const timeoutMs = timeoutSec * 1000;
17
+ if (!language)
18
+ throw new Error("language 必填 (python 或 javascript)");
19
+ if (!code.trim())
20
+ throw new Error("code 必填");
21
+ if (!["python", "javascript"].includes(language)) {
22
+ throw new Error(`不支持的语言: ${language},只支持 python 或 javascript`);
23
+ }
24
+ const dir = await mkdtemp(join(tmpdir(), "ailo-code-"));
25
+ const ext = language === "python" ? "py" : "mjs";
26
+ const file = join(dir, `script.${ext}`);
27
+ const cmd = language === "python" ? resolvePythonCmd() : "node";
28
+ await writeFile(file, code, "utf-8");
29
+ const env = {
30
+ ...process.env,
31
+ PYTHONIOENCODING: "utf-8",
32
+ PYTHONUTF8: "1",
33
+ };
34
+ let timeoutHandle = null;
35
+ let sent = false;
36
+ const proc = spawn(cmd, [file], { stdio: ["pipe", "pipe", "pipe"], env, cwd: cwd || dir });
37
+ const output = [];
38
+ proc.stdout?.on("data", (d) => output.push(d.toString("utf-8")));
39
+ proc.stderr?.on("data", (d) => output.push(d.toString("utf-8")));
40
+ const cleanup = () => {
41
+ if (timeoutHandle) {
42
+ clearTimeout(timeoutHandle);
43
+ timeoutHandle = null;
44
+ }
45
+ rm(dir, { recursive: true, force: true }).catch(() => { });
46
+ };
47
+ const sendResult = (content) => {
48
+ if (sent)
49
+ return;
50
+ sent = true;
51
+ ctx.sendSignal("tool_result", { content });
52
+ cleanup();
53
+ };
54
+ timeoutHandle = setTimeout(() => {
55
+ timeoutHandle = null;
56
+ try {
57
+ proc.kill("SIGTERM");
58
+ }
59
+ catch { }
60
+ setTimeout(() => {
61
+ try {
62
+ proc.kill("SIGKILL");
63
+ }
64
+ catch { }
65
+ }, 2000);
66
+ sendResult(`[execute_code 超时] ${language}\n超过 ${timeoutMs / 1000}s 未完成,已终止`);
67
+ }, timeoutMs);
68
+ proc.on("close", (exitCode) => {
69
+ const text = output.join("").trim();
70
+ const truncated = text.length > MAX_OUTPUT
71
+ ? text.slice(0, MAX_OUTPUT / 2) + "\n...[截断]...\n" + text.slice(-MAX_OUTPUT / 2)
72
+ : text;
73
+ sendResult(`[execute_code 完成] ${language}\nexit_code: ${exitCode}\n${truncated}`);
74
+ });
75
+ proc.on("error", (err) => {
76
+ sendResult(`[execute_code 失败] ${language}\n错误: ${err.message}`);
77
+ });
78
+ return `已启动 ${language} 代码执行`;
79
+ }
80
+ let _pythonCmd = null;
81
+ function resolvePythonCmd() {
82
+ if (_pythonCmd)
83
+ return _pythonCmd;
84
+ const candidates = platform() === "win32"
85
+ ? ["python", "python3", "py"]
86
+ : ["python3", "python"];
87
+ for (const cmd of candidates) {
88
+ const r = spawnSync(cmd, ["--version"], { encoding: "utf-8", timeout: 5000 });
89
+ if (r.status === 0) {
90
+ _pythonCmd = cmd;
91
+ return cmd;
92
+ }
93
+ }
94
+ throw new Error(`未找到 Python,请安装 python3 或 python(已尝试:${candidates.join(", ")})`);
95
+ }