neoagent 3.2.1-beta.0 → 3.2.1-beta.10
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/docs/agent-run-lifecycle.md +10 -0
- package/extensions/chrome-browser/background.mjs +318 -88
- package/extensions/chrome-browser/http.mjs +136 -0
- package/extensions/chrome-browser/protocol.mjs +654 -90
- package/flutter_app/lib/main_chat.dart +118 -739
- package/flutter_app/lib/main_controller.dart +111 -20
- package/flutter_app/lib/main_integrations.dart +607 -8
- package/flutter_app/lib/main_models.dart +3 -0
- package/flutter_app/lib/main_operations.dart +334 -321
- package/flutter_app/lib/main_security.dart +266 -112
- package/flutter_app/lib/main_settings.dart +4 -3
- package/flutter_app/lib/main_shared.dart +14 -11
- package/flutter_app/lib/src/backend_client.dart +78 -0
- package/flutter_app/lib/src/desktop_companion_actions.dart +185 -31
- package/flutter_app/lib/src/desktop_companion_io.dart +319 -86
- package/flutter_app/windows/runner/flutter_window.cpp +143 -32
- package/landing/index.html +3 -1
- package/lib/manager.js +106 -89
- package/lib/schema_migrations.js +145 -13
- package/package.json +30 -15
- package/runtime/paths.js +49 -5
- package/server/db/database.js +4 -4
- package/server/guest-agent.cli.package.json +13 -0
- package/server/guest_agent.js +85 -40
- package/server/http/middleware.js +24 -0
- package/server/http/routes.js +11 -6
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +2 -2
- package/server/public/main.dart.js +73083 -72209
- package/server/routes/admin.js +1 -1
- package/server/routes/agents.js +35 -2
- package/server/routes/android.js +30 -34
- package/server/routes/browser.js +23 -15
- package/server/routes/desktop.js +18 -1
- package/server/routes/integrations.js +107 -1
- package/server/routes/memory.js +1 -0
- package/server/routes/settings.js +16 -5
- package/server/routes/social_reach.js +12 -3
- package/server/routes/social_video.js +4 -0
- package/server/services/agents/manager.js +1 -1
- package/server/services/ai/capabilityHealth.js +62 -96
- package/server/services/ai/compaction.js +7 -2
- package/server/services/ai/history.js +45 -6
- package/server/services/ai/integrated_tools/http_request.js +8 -0
- package/server/services/ai/loop/agent_engine_core.js +496 -166
- package/server/services/ai/loop/blank_recovery.js +5 -4
- package/server/services/ai/loop/callbacks.js +1 -0
- package/server/services/ai/loop/completion_judge.js +121 -5
- package/server/services/ai/loop/conversation_loop.js +620 -340
- package/server/services/ai/loop/lifecycle.js +108 -0
- package/server/services/ai/loop/messaging_delivery.js +129 -57
- package/server/services/ai/loop/model_call_guard.js +91 -0
- package/server/services/ai/loop/model_io.js +48 -56
- package/server/services/ai/loop/progress_classification.js +2 -0
- package/server/services/ai/loop/tool_dispatch.js +28 -8
- package/server/services/ai/loopPolicy.js +48 -21
- package/server/services/ai/messagingFallback.js +17 -17
- package/server/services/ai/model_discovery.js +227 -0
- package/server/services/ai/model_failure_cache.js +108 -0
- package/server/services/ai/model_identity.js +71 -0
- package/server/services/ai/models.js +68 -163
- package/server/services/ai/providerRetry.js +17 -59
- package/server/services/ai/provider_selector.js +166 -0
- package/server/services/ai/providers/anthropic.js +4 -4
- package/server/services/ai/providers/claudeCode.js +21 -33
- package/server/services/ai/providers/githubCopilot.js +41 -20
- package/server/services/ai/providers/google.js +135 -97
- package/server/services/ai/providers/grok.js +6 -5
- package/server/services/ai/providers/grokOauth.js +19 -27
- package/server/services/ai/providers/nvidia.js +12 -7
- package/server/services/ai/providers/ollama.js +114 -86
- package/server/services/ai/providers/ollama_stream.js +142 -0
- package/server/services/ai/providers/openai.js +41 -7
- package/server/services/ai/providers/openaiCodex.js +13 -4
- package/server/services/ai/providers/openrouter.js +31 -9
- package/server/services/ai/providers/provider_error.js +36 -0
- package/server/services/ai/settings.js +26 -2
- package/server/services/ai/systemPrompt.js +19 -12
- package/server/services/ai/taskAnalysis.js +58 -10
- package/server/services/ai/terminal_reply.js +18 -0
- package/server/services/ai/toolEvidence.js +350 -29
- package/server/services/ai/tools.js +190 -111
- package/server/services/android/controller.js +770 -237
- package/server/services/android/process.js +140 -0
- package/server/services/android/sdk_download.js +143 -0
- package/server/services/android/uia.js +6 -5
- package/server/services/artifacts/store.js +24 -0
- package/server/services/browser/controller.js +843 -385
- package/server/services/browser/extension/gateway.js +40 -16
- package/server/services/browser/extension/protocol.js +15 -1
- package/server/services/browser/extension/provider.js +71 -47
- package/server/services/browser/extension/registry.js +155 -34
- package/server/services/cli/executor.js +62 -9
- package/server/services/credentials/bitwarden_cli.js +322 -0
- package/server/services/credentials/broker.js +594 -0
- package/server/services/desktop/gateway.js +41 -4
- package/server/services/desktop/protocol.js +3 -0
- package/server/services/desktop/provider.js +39 -42
- package/server/services/desktop/registry.js +137 -52
- package/server/services/integrations/bitwarden/constants.js +14 -0
- package/server/services/integrations/bitwarden/provider.js +197 -0
- package/server/services/integrations/bitwarden/snapshot.js +65 -0
- package/server/services/integrations/figma/provider.js +78 -12
- package/server/services/integrations/github/common.js +11 -6
- package/server/services/integrations/github/provider.js +52 -53
- package/server/services/integrations/google/provider.js +55 -19
- package/server/services/integrations/home_assistant/network.js +17 -20
- package/server/services/integrations/home_assistant/provider.js +7 -5
- package/server/services/integrations/home_assistant/tools.js +17 -5
- package/server/services/integrations/http.js +51 -0
- package/server/services/integrations/manager.js +159 -53
- package/server/services/integrations/microsoft/provider.js +80 -13
- package/server/services/integrations/neoarchive/provider.js +55 -29
- package/server/services/integrations/neorecall/client.js +17 -10
- package/server/services/integrations/neorecall/provider.js +20 -11
- package/server/services/integrations/notion/provider.js +16 -13
- package/server/services/integrations/oauth_provider.js +115 -51
- package/server/services/integrations/registry.js +2 -0
- package/server/services/integrations/slack/provider.js +98 -9
- package/server/services/integrations/spotify/provider.js +67 -71
- package/server/services/integrations/trello/provider.js +21 -7
- package/server/services/integrations/weather/provider.js +18 -12
- package/server/services/integrations/whatsapp/provider.js +76 -16
- package/server/services/manager.js +110 -1
- package/server/services/memory/embedding_index.js +20 -8
- package/server/services/memory/embeddings.js +151 -90
- package/server/services/memory/ingestion.js +50 -9
- package/server/services/memory/ingestion_documents.js +13 -3
- package/server/services/memory/manager.js +52 -19
- package/server/services/messaging/automation.js +85 -10
- package/server/services/messaging/formatting_guides.js +7 -4
- package/server/services/messaging/http_platforms.js +33 -13
- package/server/services/messaging/inbound_queue.js +78 -24
- package/server/services/messaging/inbound_store.js +224 -0
- package/server/services/messaging/manager.js +326 -51
- package/server/services/messaging/typing_keepalive.js +5 -2
- package/server/services/messaging/whatsapp.js +22 -14
- package/server/services/network/http.js +210 -0
- package/server/services/network/safe_request.js +307 -0
- package/server/services/runtime/backends/local-vm.js +227 -67
- package/server/services/runtime/docker-vm-manager.js +9 -0
- package/server/services/runtime/guest_bootstrap.js +30 -4
- package/server/services/runtime/guest_image.js +43 -12
- package/server/services/runtime/manager.js +77 -23
- package/server/services/runtime/validation.js +7 -6
- package/server/services/security/tool_categories.js +6 -0
- package/server/services/social_reach/channels/github.js +10 -4
- package/server/services/social_reach/channels/reddit.js +4 -4
- package/server/services/social_reach/channels/rss.js +2 -2
- package/server/services/social_reach/channels/social_video.js +12 -7
- package/server/services/social_reach/channels/v2ex.js +21 -8
- package/server/services/social_reach/channels/x.js +2 -2
- package/server/services/social_reach/channels/xueqiu.js +5 -5
- package/server/services/social_reach/service.js +9 -6
- package/server/services/social_reach/utils.js +65 -14
- package/server/services/social_video/service.js +160 -50
- package/server/services/tasks/integration_runtime.js +18 -8
- package/server/services/tasks/runtime.js +39 -4
- package/server/services/voice/agentBridge.js +17 -4
- package/server/services/voice/bufferedLiveRelayAdapter.js +5 -0
- package/server/services/voice/liveSession.js +31 -0
- package/server/services/voice/message.js +1 -1
- package/server/services/voice/openaiSpeech.js +33 -8
- package/server/services/voice/providers.js +233 -151
- package/server/services/voice/runtime.js +2 -2
- package/server/services/voice/runtimeManager.js +118 -20
- package/server/services/voice/turnRunner.js +6 -0
- package/server/services/wearable/firmware_manifest.js +51 -13
- package/server/services/wearable/service.js +1 -0
- package/server/utils/abort.js +96 -0
- package/server/utils/cloud-security.js +110 -3
- package/server/utils/files.js +31 -0
- package/server/utils/image_payload.js +95 -0
- package/server/utils/retry.js +107 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { spawn } = require('node:child_process');
|
|
4
|
+
|
|
5
|
+
const DEFAULT_TIMEOUT_MS = 20_000;
|
|
6
|
+
const DEFAULT_MAX_OUTPUT_BYTES = 4 * 1024 * 1024;
|
|
7
|
+
const MAX_TIMEOUT_MS = 30 * 60 * 1000;
|
|
8
|
+
|
|
9
|
+
function clampNumber(value, fallback, min, max) {
|
|
10
|
+
const parsed = Number(value);
|
|
11
|
+
if (!Number.isFinite(parsed)) return fallback;
|
|
12
|
+
return Math.min(max, Math.max(min, Math.round(parsed)));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function createProcessError(message, code, details = {}) {
|
|
16
|
+
const error = new Error(message);
|
|
17
|
+
error.code = code;
|
|
18
|
+
Object.assign(error, details);
|
|
19
|
+
return error;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function runProcess(command, args = [], options = {}) {
|
|
23
|
+
const timeoutMs = clampNumber(options.timeoutMs, DEFAULT_TIMEOUT_MS, 100, MAX_TIMEOUT_MS);
|
|
24
|
+
const maxOutputBytes = clampNumber(
|
|
25
|
+
options.maxOutputBytes,
|
|
26
|
+
DEFAULT_MAX_OUTPUT_BYTES,
|
|
27
|
+
1024,
|
|
28
|
+
1024 * 1024 * 1024,
|
|
29
|
+
);
|
|
30
|
+
const encoding = options.encoding === null ? null : (options.encoding || 'utf8');
|
|
31
|
+
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
const child = spawn(command, args, {
|
|
34
|
+
cwd: options.cwd,
|
|
35
|
+
env: options.env,
|
|
36
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
37
|
+
windowsHide: true,
|
|
38
|
+
});
|
|
39
|
+
const stdout = [];
|
|
40
|
+
const stderr = [];
|
|
41
|
+
let stdoutBytes = 0;
|
|
42
|
+
let stderrBytes = 0;
|
|
43
|
+
let settled = false;
|
|
44
|
+
let forceKillTimer = null;
|
|
45
|
+
|
|
46
|
+
const decode = (chunks) => {
|
|
47
|
+
const buffer = Buffer.concat(chunks);
|
|
48
|
+
return encoding === null ? buffer : buffer.toString(encoding);
|
|
49
|
+
};
|
|
50
|
+
const cleanup = () => {
|
|
51
|
+
clearTimeout(timeout);
|
|
52
|
+
options.signal?.removeEventListener('abort', onAbort);
|
|
53
|
+
};
|
|
54
|
+
const settle = (error, result = null) => {
|
|
55
|
+
if (settled) return;
|
|
56
|
+
settled = true;
|
|
57
|
+
cleanup();
|
|
58
|
+
if (error) reject(error);
|
|
59
|
+
else resolve(result);
|
|
60
|
+
};
|
|
61
|
+
const terminate = (error) => {
|
|
62
|
+
try { child.kill('SIGTERM'); } catch {}
|
|
63
|
+
forceKillTimer = setTimeout(() => {
|
|
64
|
+
try { child.kill('SIGKILL'); } catch {}
|
|
65
|
+
}, 1000);
|
|
66
|
+
forceKillTimer.unref?.();
|
|
67
|
+
settle(error);
|
|
68
|
+
};
|
|
69
|
+
const onAbort = () => {
|
|
70
|
+
const error = options.signal?.reason instanceof Error
|
|
71
|
+
? options.signal.reason
|
|
72
|
+
: createProcessError('Command was aborted.', 'ABORT_ERR');
|
|
73
|
+
if (!(options.signal?.reason instanceof Error)) error.name = 'AbortError';
|
|
74
|
+
terminate(error);
|
|
75
|
+
};
|
|
76
|
+
const collect = (target, chunk, streamName) => {
|
|
77
|
+
if (settled) return;
|
|
78
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
79
|
+
if (streamName === 'stdout') stdoutBytes += buffer.length;
|
|
80
|
+
else stderrBytes += buffer.length;
|
|
81
|
+
if (stdoutBytes + stderrBytes > maxOutputBytes) {
|
|
82
|
+
terminate(createProcessError(
|
|
83
|
+
`Command exceeded the ${maxOutputBytes}-byte output limit.`,
|
|
84
|
+
'PROCESS_OUTPUT_LIMIT',
|
|
85
|
+
));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
target.push(buffer);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const timeout = setTimeout(() => {
|
|
92
|
+
terminate(createProcessError(
|
|
93
|
+
`Command timed out after ${timeoutMs} ms.`,
|
|
94
|
+
'PROCESS_TIMEOUT',
|
|
95
|
+
{ timeoutMs },
|
|
96
|
+
));
|
|
97
|
+
}, timeoutMs);
|
|
98
|
+
timeout.unref?.();
|
|
99
|
+
|
|
100
|
+
child.stdout?.on('data', (chunk) => collect(stdout, chunk, 'stdout'));
|
|
101
|
+
child.stderr?.on('data', (chunk) => collect(stderr, chunk, 'stderr'));
|
|
102
|
+
child.once('error', (error) => settle(error));
|
|
103
|
+
child.once('close', (exitCode, signal) => {
|
|
104
|
+
if (forceKillTimer) clearTimeout(forceKillTimer);
|
|
105
|
+
if (settled) return;
|
|
106
|
+
const result = {
|
|
107
|
+
stdout: decode(stdout),
|
|
108
|
+
stderr: decode(stderr),
|
|
109
|
+
exitCode,
|
|
110
|
+
signal: signal || null,
|
|
111
|
+
};
|
|
112
|
+
if (exitCode === 0) {
|
|
113
|
+
settle(null, result);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const stderrText = Buffer.concat(stderr).toString('utf8').trim();
|
|
117
|
+
const stdoutText = Buffer.concat(stdout).toString('utf8').trim();
|
|
118
|
+
settle(createProcessError(
|
|
119
|
+
stderrText || stdoutText || `Command exited with code ${exitCode}.`,
|
|
120
|
+
'PROCESS_EXIT',
|
|
121
|
+
result,
|
|
122
|
+
));
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
if (options.signal?.aborted) {
|
|
126
|
+
onAbort();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
options.signal?.addEventListener('abort', onAbort, { once: true });
|
|
130
|
+
if (options.input != null) child.stdin?.end(options.input);
|
|
131
|
+
else child.stdin?.end();
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
module.exports = {
|
|
136
|
+
DEFAULT_MAX_OUTPUT_BYTES,
|
|
137
|
+
DEFAULT_TIMEOUT_MS,
|
|
138
|
+
clampNumber,
|
|
139
|
+
runProcess,
|
|
140
|
+
};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('node:crypto');
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const https = require('node:https');
|
|
6
|
+
const os = require('node:os');
|
|
7
|
+
const { Transform } = require('node:stream');
|
|
8
|
+
const { pipeline } = require('node:stream/promises');
|
|
9
|
+
const { createAbortError } = require('../../utils/abort');
|
|
10
|
+
const { clampNumber } = require('./process');
|
|
11
|
+
|
|
12
|
+
// Google publishes the current filenames and checksums at:
|
|
13
|
+
// https://developer.android.com/studio#command-line-tools-only
|
|
14
|
+
const COMMAND_LINE_TOOLS_VERSION = '15859902';
|
|
15
|
+
const COMMAND_LINE_TOOLS_RELEASES = {
|
|
16
|
+
darwin: {
|
|
17
|
+
arm64: {
|
|
18
|
+
url: `https://dl.google.com/android/repository/commandlinetools-mac_arm64-${COMMAND_LINE_TOOLS_VERSION}_latest.zip`,
|
|
19
|
+
sha256: '835b62a26162b229b441d1f6d4680383815a270809eb33522c0d480fa5002c4e',
|
|
20
|
+
},
|
|
21
|
+
x64: {
|
|
22
|
+
url: `https://dl.google.com/android/repository/commandlinetools-mac_x86_64-${COMMAND_LINE_TOOLS_VERSION}_latest.zip`,
|
|
23
|
+
sha256: 'c5a6378ab5cf7e0d5701921405115befff13e9ff7417fb588389338f8bd050f3',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
linux: {
|
|
27
|
+
url: `https://dl.google.com/android/repository/commandlinetools-linux-${COMMAND_LINE_TOOLS_VERSION}_latest.zip`,
|
|
28
|
+
sha256: '4e4c464f145a7512b57d088ac6c278c03c9eea610886b35a5e0804e74eedf583',
|
|
29
|
+
},
|
|
30
|
+
win32: {
|
|
31
|
+
url: `https://dl.google.com/android/repository/commandlinetools-win-${COMMAND_LINE_TOOLS_VERSION}_latest.zip`,
|
|
32
|
+
sha256: '90ae805d20434428bffcb699c290860f19bb5f66a67e6b330067e3de801fb04a',
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function resolveCommandLineToolsRelease(platform = process.platform, arch = os.arch()) {
|
|
37
|
+
const release = COMMAND_LINE_TOOLS_RELEASES[platform];
|
|
38
|
+
if (!release) throw new Error(`No Android command-line-tools download for platform: ${platform}`);
|
|
39
|
+
if (platform !== 'darwin') return release;
|
|
40
|
+
if (arch !== 'arm64' && arch !== 'x64') {
|
|
41
|
+
throw new Error(`No Android command-line-tools download for macOS architecture: ${arch}`);
|
|
42
|
+
}
|
|
43
|
+
return release[arch];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function assertExpectedDigest(actualDigest, expectedDigest) {
|
|
47
|
+
const expected = String(expectedDigest || '').trim().toLowerCase();
|
|
48
|
+
if (!/^[a-f0-9]{64}$/.test(expected)) {
|
|
49
|
+
throw new Error('Android SDK download is missing a valid SHA-256 checksum.');
|
|
50
|
+
}
|
|
51
|
+
const actualBuffer = Buffer.from(actualDigest, 'hex');
|
|
52
|
+
const expectedBuffer = Buffer.from(expected, 'hex');
|
|
53
|
+
if (!crypto.timingSafeEqual(actualBuffer, expectedBuffer)) {
|
|
54
|
+
const error = new Error('Android SDK download failed SHA-256 verification.');
|
|
55
|
+
error.code = 'ANDROID_SDK_CHECKSUM_MISMATCH';
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function downloadFile(url, destination, options = {}) {
|
|
61
|
+
const maxRedirects = clampNumber(options.maxRedirects, 5, 0, 10);
|
|
62
|
+
const timeoutMs = clampNumber(options.timeoutMs, 60_000, 1000, 10 * 60 * 1000);
|
|
63
|
+
const maxBytes = clampNumber(options.maxBytes, 500 * 1024 * 1024, 1024, 1024 * 1024 * 1024);
|
|
64
|
+
const temporary = `${destination}.${process.pid}.${Math.random().toString(16).slice(2)}.part`;
|
|
65
|
+
|
|
66
|
+
const openResponse = (target, redirectsLeft) => new Promise((resolve, reject) => {
|
|
67
|
+
if (options.signal?.aborted) {
|
|
68
|
+
reject(createAbortError(options.signal, 'Android SDK download was aborted.'));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const parsed = new URL(target);
|
|
72
|
+
if (parsed.protocol !== 'https:') {
|
|
73
|
+
reject(new Error(`Refusing non-HTTPS Android SDK download: ${parsed.protocol}`));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const request = https.get(parsed, (response) => {
|
|
77
|
+
options.signal?.removeEventListener('abort', onAbort);
|
|
78
|
+
const status = Number(response.statusCode || 0);
|
|
79
|
+
if (status >= 300 && status < 400 && response.headers.location) {
|
|
80
|
+
response.resume();
|
|
81
|
+
if (redirectsLeft <= 0) {
|
|
82
|
+
reject(new Error('Android SDK download exceeded its redirect limit.'));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
resolve(openResponse(new URL(response.headers.location, parsed).toString(), redirectsLeft - 1));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (status !== 200) {
|
|
89
|
+
response.resume();
|
|
90
|
+
reject(new Error(`Android SDK download failed with HTTP ${status}.`));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const contentLength = Number(response.headers['content-length'] || 0);
|
|
94
|
+
if (contentLength > maxBytes) {
|
|
95
|
+
response.destroy();
|
|
96
|
+
reject(new Error(`Android SDK download exceeds the ${maxBytes}-byte limit.`));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
resolve(response);
|
|
100
|
+
});
|
|
101
|
+
const onAbort = () => request.destroy(createAbortError(options.signal, 'Android SDK download was aborted.'));
|
|
102
|
+
options.signal?.addEventListener('abort', onAbort, { once: true });
|
|
103
|
+
request.setTimeout(timeoutMs, () => request.destroy(new Error('Android SDK download timed out.')));
|
|
104
|
+
request.once('error', (error) => {
|
|
105
|
+
options.signal?.removeEventListener('abort', onAbort);
|
|
106
|
+
reject(error);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
const response = await openResponse(url, maxRedirects);
|
|
112
|
+
const hash = crypto.createHash('sha256');
|
|
113
|
+
let received = 0;
|
|
114
|
+
const verifier = new Transform({
|
|
115
|
+
transform(chunk, _encoding, callback) {
|
|
116
|
+
received += chunk.length;
|
|
117
|
+
if (received > maxBytes) {
|
|
118
|
+
callback(new Error(`Android SDK download exceeds the ${maxBytes}-byte limit.`));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
hash.update(chunk);
|
|
122
|
+
callback(null, chunk);
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
await pipeline(response, verifier, fs.createWriteStream(temporary, { mode: 0o600 }), {
|
|
126
|
+
signal: options.signal,
|
|
127
|
+
});
|
|
128
|
+
assertExpectedDigest(hash.digest('hex'), options.expectedSha256);
|
|
129
|
+
fs.renameSync(temporary, destination);
|
|
130
|
+
} catch (error) {
|
|
131
|
+
if (options.signal?.aborted) {
|
|
132
|
+
throw createAbortError(options.signal, 'Android SDK download was aborted.');
|
|
133
|
+
}
|
|
134
|
+
throw error;
|
|
135
|
+
} finally {
|
|
136
|
+
try { fs.unlinkSync(temporary); } catch {}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
module.exports = {
|
|
141
|
+
downloadFile,
|
|
142
|
+
resolveCommandLineToolsRelease,
|
|
143
|
+
};
|
|
@@ -12,12 +12,13 @@ const DEFAULT_BOUNDS = Object.freeze({
|
|
|
12
12
|
function decodeXml(value) {
|
|
13
13
|
return String(value || '')
|
|
14
14
|
.replace(/"/g, '"')
|
|
15
|
-
.replace(/&
|
|
15
|
+
.replace(/'/g, "'")
|
|
16
16
|
.replace(/</g, '<')
|
|
17
17
|
.replace(/>/g, '>')
|
|
18
18
|
.replace(/ /g, '\n')
|
|
19
19
|
.replace(/ /g, '\r')
|
|
20
|
-
.replace(/'/g, "'")
|
|
20
|
+
.replace(/'/g, "'")
|
|
21
|
+
.replace(/&/g, '&');
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
function parseBounds(raw) {
|
|
@@ -54,7 +55,7 @@ function parseNodeAttributes(raw) {
|
|
|
54
55
|
|
|
55
56
|
function parseUiDump(xml) {
|
|
56
57
|
const nodes = [];
|
|
57
|
-
const nodeRe = /<node\b([^>]*)
|
|
58
|
+
const nodeRe = /<node\b([^>]*)>/g;
|
|
58
59
|
let match = nodeRe.exec(String(xml || ''));
|
|
59
60
|
|
|
60
61
|
while (match) {
|
|
@@ -123,8 +124,8 @@ function scoreNode(node, selector = {}) {
|
|
|
123
124
|
else return -1;
|
|
124
125
|
}
|
|
125
126
|
|
|
126
|
-
if (selector.clickable === true && node.clickable)
|
|
127
|
-
if (selector.clickable === true
|
|
127
|
+
if (selector.clickable === true && !node.clickable) return -1;
|
|
128
|
+
if (selector.clickable === true) score += 40;
|
|
128
129
|
if (node.enabled) score += 10;
|
|
129
130
|
if (node.bounds.width > 0 && node.bounds.height > 0) score += 10;
|
|
130
131
|
|
|
@@ -3,6 +3,7 @@ const path = require('path');
|
|
|
3
3
|
const { v4: uuidv4 } = require('uuid');
|
|
4
4
|
const db = require('../../db/database');
|
|
5
5
|
const { DATA_DIR } = require('../../../runtime/paths');
|
|
6
|
+
const { writeBufferAtomic } = require('../../utils/files');
|
|
6
7
|
|
|
7
8
|
const ARTIFACTS_ROOT = path.join(DATA_DIR, 'artifacts');
|
|
8
9
|
fs.mkdirSync(ARTIFACTS_ROOT, { recursive: true });
|
|
@@ -121,6 +122,29 @@ class ArtifactStore {
|
|
|
121
122
|
};
|
|
122
123
|
}
|
|
123
124
|
|
|
125
|
+
async createBufferArtifact(userId, options = {}) {
|
|
126
|
+
const buffer = Buffer.isBuffer(options.content)
|
|
127
|
+
? options.content
|
|
128
|
+
: Buffer.from(options.content || []);
|
|
129
|
+
const allocation = this.allocateFile(userId, options);
|
|
130
|
+
try {
|
|
131
|
+
await writeBufferAtomic(allocation.storagePath, buffer, {
|
|
132
|
+
signal: options.signal,
|
|
133
|
+
});
|
|
134
|
+
db.prepare('UPDATE artifacts SET byte_size = ? WHERE id = ?')
|
|
135
|
+
.run(buffer.length, allocation.artifactId);
|
|
136
|
+
return {
|
|
137
|
+
...allocation,
|
|
138
|
+
filePath: allocation.storagePath,
|
|
139
|
+
byteSize: buffer.length,
|
|
140
|
+
};
|
|
141
|
+
} catch (error) {
|
|
142
|
+
await fs.promises.rm(allocation.storagePath, { force: true }).catch(() => {});
|
|
143
|
+
db.prepare('DELETE FROM artifacts WHERE id = ?').run(allocation.artifactId);
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
124
148
|
getArtifactForUser(userId, artifactId) {
|
|
125
149
|
const row = db.prepare(`
|
|
126
150
|
SELECT id, user_id, kind, backend, content_type, storage_path, original_filename, byte_size, metadata_json, created_at
|