neoagent 3.2.1-beta.1 → 3.2.1-beta.11

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 (198) hide show
  1. package/extensions/chrome-browser/background.mjs +318 -88
  2. package/extensions/chrome-browser/http.mjs +136 -0
  3. package/extensions/chrome-browser/protocol.mjs +654 -90
  4. package/flutter_app/lib/main_chat.dart +118 -739
  5. package/flutter_app/lib/main_controller.dart +157 -24
  6. package/flutter_app/lib/main_integrations.dart +607 -8
  7. package/flutter_app/lib/main_models.dart +7 -2
  8. package/flutter_app/lib/main_operations.dart +334 -370
  9. package/flutter_app/lib/main_security.dart +266 -112
  10. package/flutter_app/lib/main_settings.dart +342 -3
  11. package/flutter_app/lib/main_shared.dart +14 -11
  12. package/flutter_app/lib/src/backend_client.dart +97 -0
  13. package/flutter_app/lib/src/desktop_companion_actions.dart +185 -31
  14. package/flutter_app/lib/src/desktop_companion_io.dart +319 -86
  15. package/flutter_app/windows/runner/flutter_window.cpp +143 -32
  16. package/landing/index.html +3 -1
  17. package/lib/manager.js +106 -89
  18. package/lib/schema_migrations.js +115 -13
  19. package/package.json +30 -15
  20. package/runtime/paths.js +49 -5
  21. package/server/db/database.js +2 -2
  22. package/server/guest-agent.cli.package.json +13 -0
  23. package/server/guest_agent.js +85 -40
  24. package/server/http/middleware.js +24 -0
  25. package/server/http/routes.js +12 -6
  26. package/server/public/.last_build_id +1 -1
  27. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  28. package/server/public/flutter_bootstrap.js +2 -2
  29. package/server/public/main.dart.js +81930 -80509
  30. package/server/routes/admin.js +1 -1
  31. package/server/routes/android.js +30 -34
  32. package/server/routes/behavior.js +80 -0
  33. package/server/routes/browser.js +23 -15
  34. package/server/routes/desktop.js +18 -1
  35. package/server/routes/integrations.js +107 -1
  36. package/server/routes/memory.js +1 -0
  37. package/server/routes/settings.js +20 -5
  38. package/server/routes/social_reach.js +12 -3
  39. package/server/routes/social_video.js +4 -0
  40. package/server/services/agents/manager.js +1 -1
  41. package/server/services/ai/capabilityHealth.js +62 -96
  42. package/server/services/ai/compaction.js +7 -2
  43. package/server/services/ai/history.js +45 -6
  44. package/server/services/ai/integrated_tools/http_request.js +8 -0
  45. package/server/services/ai/loop/agent_engine_core.js +452 -176
  46. package/server/services/ai/loop/blank_recovery.js +5 -4
  47. package/server/services/ai/loop/callbacks.js +1 -0
  48. package/server/services/ai/loop/completion_judge.js +291 -8
  49. package/server/services/ai/loop/conversation_loop.js +515 -342
  50. package/server/services/ai/loop/messaging_delivery.js +176 -57
  51. package/server/services/ai/loop/model_call_guard.js +91 -0
  52. package/server/services/ai/loop/model_io.js +20 -45
  53. package/server/services/ai/loop/progress_classification.js +2 -0
  54. package/server/services/ai/loop/tool_dispatch.js +19 -8
  55. package/server/services/ai/loopPolicy.js +48 -21
  56. package/server/services/ai/messagingFallback.js +17 -17
  57. package/server/services/ai/model_discovery.js +227 -0
  58. package/server/services/ai/model_failure_cache.js +108 -0
  59. package/server/services/ai/model_identity.js +71 -0
  60. package/server/services/ai/models.js +68 -163
  61. package/server/services/ai/providerRetry.js +17 -59
  62. package/server/services/ai/provider_selector.js +166 -0
  63. package/server/services/ai/providers/anthropic.js +2 -2
  64. package/server/services/ai/providers/claudeCode.js +21 -33
  65. package/server/services/ai/providers/githubCopilot.js +41 -20
  66. package/server/services/ai/providers/google.js +135 -97
  67. package/server/services/ai/providers/grok.js +4 -3
  68. package/server/services/ai/providers/grokOauth.js +19 -27
  69. package/server/services/ai/providers/nvidia.js +10 -5
  70. package/server/services/ai/providers/ollama.js +111 -84
  71. package/server/services/ai/providers/ollama_stream.js +142 -0
  72. package/server/services/ai/providers/openai.js +39 -5
  73. package/server/services/ai/providers/openaiCodex.js +11 -4
  74. package/server/services/ai/providers/openrouter.js +29 -7
  75. package/server/services/ai/providers/provider_error.js +36 -0
  76. package/server/services/ai/settings.js +26 -2
  77. package/server/services/ai/systemPrompt.js +32 -123
  78. package/server/services/ai/taskAnalysis.js +104 -11
  79. package/server/services/ai/toolEvidence.js +256 -29
  80. package/server/services/ai/tools.js +248 -117
  81. package/server/services/android/controller.js +770 -237
  82. package/server/services/android/process.js +140 -0
  83. package/server/services/android/sdk_download.js +143 -0
  84. package/server/services/android/uia.js +6 -5
  85. package/server/services/artifacts/store.js +24 -0
  86. package/server/services/behavior/config.js +251 -0
  87. package/server/services/behavior/defaults.js +68 -0
  88. package/server/services/behavior/delivery.js +176 -0
  89. package/server/services/behavior/index.js +43 -0
  90. package/server/services/behavior/model_client.js +35 -0
  91. package/server/services/behavior/modules/agent_identity.js +37 -0
  92. package/server/services/behavior/modules/channel_style.js +28 -0
  93. package/server/services/behavior/modules/index.js +29 -0
  94. package/server/services/behavior/modules/norms.js +101 -0
  95. package/server/services/behavior/modules/persona.js +48 -0
  96. package/server/services/behavior/modules/persona_prompt.js +33 -0
  97. package/server/services/behavior/modules/social_memory.js +86 -0
  98. package/server/services/behavior/modules/social_observability.js +94 -0
  99. package/server/services/behavior/modules/social_signals.js +41 -0
  100. package/server/services/behavior/modules/theory_of_mind.js +110 -0
  101. package/server/services/behavior/modules/turn_taking.js +237 -0
  102. package/server/services/behavior/pipeline.js +285 -0
  103. package/server/services/behavior/registry.js +78 -0
  104. package/server/services/behavior/signals.js +107 -0
  105. package/server/services/behavior/state.js +99 -0
  106. package/server/services/behavior/system_prompt.js +75 -0
  107. package/server/services/browser/controller.js +843 -385
  108. package/server/services/browser/extension/gateway.js +40 -16
  109. package/server/services/browser/extension/protocol.js +15 -1
  110. package/server/services/browser/extension/provider.js +71 -47
  111. package/server/services/browser/extension/registry.js +155 -34
  112. package/server/services/cli/executor.js +62 -9
  113. package/server/services/credentials/bitwarden_cli.js +322 -0
  114. package/server/services/credentials/broker.js +594 -0
  115. package/server/services/desktop/gateway.js +41 -4
  116. package/server/services/desktop/protocol.js +3 -0
  117. package/server/services/desktop/provider.js +39 -42
  118. package/server/services/desktop/registry.js +137 -52
  119. package/server/services/integrations/bitwarden/constants.js +14 -0
  120. package/server/services/integrations/bitwarden/provider.js +197 -0
  121. package/server/services/integrations/bitwarden/snapshot.js +65 -0
  122. package/server/services/integrations/figma/provider.js +78 -12
  123. package/server/services/integrations/github/common.js +11 -6
  124. package/server/services/integrations/github/provider.js +52 -53
  125. package/server/services/integrations/google/provider.js +55 -19
  126. package/server/services/integrations/home_assistant/network.js +17 -20
  127. package/server/services/integrations/home_assistant/provider.js +7 -5
  128. package/server/services/integrations/home_assistant/tools.js +17 -5
  129. package/server/services/integrations/http.js +51 -0
  130. package/server/services/integrations/manager.js +159 -53
  131. package/server/services/integrations/microsoft/provider.js +80 -13
  132. package/server/services/integrations/neoarchive/provider.js +55 -29
  133. package/server/services/integrations/neorecall/client.js +17 -10
  134. package/server/services/integrations/neorecall/provider.js +20 -11
  135. package/server/services/integrations/notion/provider.js +16 -13
  136. package/server/services/integrations/oauth_provider.js +115 -51
  137. package/server/services/integrations/registry.js +2 -0
  138. package/server/services/integrations/slack/provider.js +98 -9
  139. package/server/services/integrations/spotify/provider.js +67 -71
  140. package/server/services/integrations/trello/provider.js +21 -7
  141. package/server/services/integrations/weather/provider.js +18 -12
  142. package/server/services/integrations/whatsapp/provider.js +76 -16
  143. package/server/services/manager.js +110 -1
  144. package/server/services/memory/embedding_index.js +20 -8
  145. package/server/services/memory/embeddings.js +151 -90
  146. package/server/services/memory/ingestion.js +50 -9
  147. package/server/services/memory/ingestion_documents.js +13 -3
  148. package/server/services/memory/manager.js +66 -52
  149. package/server/services/messaging/access_policy.js +10 -6
  150. package/server/services/messaging/automation.js +240 -34
  151. package/server/services/messaging/discord.js +11 -1
  152. package/server/services/messaging/formatting_guides.js +5 -4
  153. package/server/services/messaging/http_platforms.js +37 -16
  154. package/server/services/messaging/inbound_queue.js +143 -28
  155. package/server/services/messaging/inbound_store.js +257 -0
  156. package/server/services/messaging/manager.js +344 -51
  157. package/server/services/messaging/telegram.js +10 -1
  158. package/server/services/messaging/typing_keepalive.js +5 -2
  159. package/server/services/messaging/whatsapp.js +33 -14
  160. package/server/services/network/http.js +210 -0
  161. package/server/services/network/safe_request.js +307 -0
  162. package/server/services/runtime/backends/local-vm.js +227 -67
  163. package/server/services/runtime/docker-vm-manager.js +9 -0
  164. package/server/services/runtime/guest_bootstrap.js +30 -4
  165. package/server/services/runtime/guest_image.js +43 -12
  166. package/server/services/runtime/manager.js +77 -23
  167. package/server/services/runtime/validation.js +7 -6
  168. package/server/services/security/tool_categories.js +6 -0
  169. package/server/services/social_reach/channels/github.js +10 -4
  170. package/server/services/social_reach/channels/reddit.js +4 -4
  171. package/server/services/social_reach/channels/rss.js +2 -2
  172. package/server/services/social_reach/channels/social_video.js +13 -8
  173. package/server/services/social_reach/channels/v2ex.js +21 -8
  174. package/server/services/social_reach/channels/x.js +2 -2
  175. package/server/services/social_reach/channels/xueqiu.js +5 -5
  176. package/server/services/social_reach/service.js +9 -6
  177. package/server/services/social_reach/utils.js +65 -14
  178. package/server/services/social_video/captions.js +2 -2
  179. package/server/services/social_video/service.js +343 -68
  180. package/server/services/tasks/integration_runtime.js +18 -8
  181. package/server/services/tasks/runtime.js +39 -4
  182. package/server/services/voice/agentBridge.js +17 -4
  183. package/server/services/voice/bufferedLiveRelayAdapter.js +5 -0
  184. package/server/services/voice/liveSession.js +31 -0
  185. package/server/services/voice/message.js +1 -1
  186. package/server/services/voice/openaiSpeech.js +33 -8
  187. package/server/services/voice/providers.js +233 -151
  188. package/server/services/voice/runtime.js +2 -2
  189. package/server/services/voice/runtimeManager.js +118 -20
  190. package/server/services/voice/turnRunner.js +6 -0
  191. package/server/services/wearable/firmware_manifest.js +51 -13
  192. package/server/services/wearable/service.js +1 -0
  193. package/server/utils/abort.js +96 -0
  194. package/server/utils/cloud-security.js +110 -3
  195. package/server/utils/files.js +31 -0
  196. package/server/utils/image_payload.js +95 -0
  197. package/server/utils/logger.js +19 -0
  198. 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(/&quot;/g, '"')
15
- .replace(/&amp;/g, '&')
15
+ .replace(/&apos;/g, "'")
16
16
  .replace(/&lt;/g, '<')
17
17
  .replace(/&gt;/g, '>')
18
18
  .replace(/&#10;/g, '\n')
19
19
  .replace(/&#13;/g, '\r')
20
- .replace(/&#39;/g, "'");
20
+ .replace(/&#39;/g, "'")
21
+ .replace(/&amp;/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([^>]*)\/>/g;
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) score += 40;
127
- if (selector.clickable === true && !node.clickable) score -= 60;
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
@@ -0,0 +1,251 @@
1
+ 'use strict';
2
+
3
+ const db = require('../../db/database');
4
+ const { isMainAgent } = require('../agents/manager');
5
+ const { MODULE_IDS, cloneDefaults, DEFAULT_MODULE_CONFIG } = require('./defaults');
6
+
7
+ const SETTINGS_KEY = 'behavior_modules_config';
8
+ const PARTICIPATION_MODES = new Set(['automatic', 'mention_only', 'always']);
9
+ const MODEL_PURPOSES = new Set(['fast', 'general']);
10
+ const DELIVERY_STYLES = new Set(['single', 'natural_bubbles']);
11
+
12
+ function clampNumber(value, min, max, fallback) {
13
+ const number = Number(value);
14
+ if (!Number.isFinite(number)) return fallback;
15
+ return Math.min(max, Math.max(min, number));
16
+ }
17
+
18
+ function asObject(value) {
19
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
20
+ }
21
+
22
+ function normalizeModules(rawModules, fallbackEnabled = true, sparse = false) {
23
+ const source = asObject(rawModules);
24
+ const modules = {};
25
+ for (const id of MODULE_IDS) {
26
+ if (sparse && !Object.prototype.hasOwnProperty.call(source, id)) continue;
27
+ const entry = asObject(source[id]);
28
+ modules[id] = {
29
+ enabled: entry.enabled == null ? fallbackEnabled : entry.enabled !== false,
30
+ };
31
+ }
32
+ return modules;
33
+ }
34
+
35
+ function normalizeOptionalString(value, fallback = null) {
36
+ if (value == null) return fallback;
37
+ const normalized = String(value).trim();
38
+ return normalized || null;
39
+ }
40
+
41
+ function normalizeLeafConfig(raw = {}, base = cloneDefaults(), sparse = false) {
42
+ const input = asObject(raw);
43
+ const output = {};
44
+ const assign = (key, value) => {
45
+ if (!sparse || Object.prototype.hasOwnProperty.call(input, key)) output[key] = value;
46
+ };
47
+ const enabled = input.enabled == null ? base.enabled !== false : input.enabled !== false;
48
+ assign('enabled', enabled);
49
+ if (!sparse || Object.prototype.hasOwnProperty.call(input, 'modules')) {
50
+ output.modules = normalizeModules(input.modules, enabled, sparse);
51
+ }
52
+ assign(
53
+ 'participationMode',
54
+ PARTICIPATION_MODES.has(String(input.participationMode || ''))
55
+ ? String(input.participationMode)
56
+ : (base.participationMode || DEFAULT_MODULE_CONFIG.participationMode),
57
+ );
58
+ assign('minimumNeedScore', clampNumber(
59
+ input.minimumNeedScore,
60
+ 0,
61
+ 1,
62
+ base.minimumNeedScore ?? DEFAULT_MODULE_CONFIG.minimumNeedScore,
63
+ ));
64
+ assign('batchWindowMs', Math.round(clampNumber(
65
+ input.batchWindowMs,
66
+ 0,
67
+ 5000,
68
+ base.batchWindowMs ?? DEFAULT_MODULE_CONFIG.batchWindowMs,
69
+ )));
70
+ assign('decisionContextMessageLimit', Math.round(clampNumber(
71
+ input.decisionContextMessageLimit,
72
+ 4,
73
+ 30,
74
+ base.decisionContextMessageLimit ?? DEFAULT_MODULE_CONFIG.decisionContextMessageLimit,
75
+ )));
76
+ assign(
77
+ 'decisionModelId',
78
+ normalizeOptionalString(input.decisionModelId, base.decisionModelId || null),
79
+ );
80
+ assign(
81
+ 'decisionModelPurpose',
82
+ MODEL_PURPOSES.has(String(input.decisionModelPurpose || ''))
83
+ ? String(input.decisionModelPurpose)
84
+ : (base.decisionModelPurpose || DEFAULT_MODULE_CONFIG.decisionModelPurpose),
85
+ );
86
+ assign(
87
+ 'deliveryStyle',
88
+ DELIVERY_STYLES.has(String(input.deliveryStyle || ''))
89
+ ? String(input.deliveryStyle)
90
+ : (base.deliveryStyle || DEFAULT_MODULE_CONFIG.deliveryStyle),
91
+ );
92
+ assign('maxBubbles', Math.round(clampNumber(
93
+ input.maxBubbles,
94
+ 1,
95
+ 5,
96
+ base.maxBubbles ?? DEFAULT_MODULE_CONFIG.maxBubbles,
97
+ )));
98
+ assign('bubbleGapMs', Math.round(clampNumber(
99
+ input.bubbleGapMs,
100
+ 0,
101
+ 5000,
102
+ base.bubbleGapMs ?? DEFAULT_MODULE_CONFIG.bubbleGapMs,
103
+ )));
104
+ assign('normsRefreshMessageGap', Math.round(clampNumber(
105
+ input.normsRefreshMessageGap,
106
+ 3,
107
+ 200,
108
+ base.normsRefreshMessageGap ?? DEFAULT_MODULE_CONFIG.normsRefreshMessageGap,
109
+ )));
110
+ assign('observabilityIntervalMinutes', Math.round(clampNumber(
111
+ input.observabilityIntervalMinutes,
112
+ 30,
113
+ 10080,
114
+ base.observabilityIntervalMinutes ?? DEFAULT_MODULE_CONFIG.observabilityIntervalMinutes,
115
+ )));
116
+ return output;
117
+ }
118
+
119
+ function normalizeStoredConfig(raw) {
120
+ const base = cloneDefaults();
121
+ const input = asObject(raw);
122
+ const normalized = {
123
+ schemaVersion: DEFAULT_MODULE_CONFIG.schemaVersion,
124
+ ...normalizeLeafConfig(input, base),
125
+ };
126
+ const platformOverrides = {};
127
+ for (const [platform, value] of Object.entries(asObject(input.platformOverrides))) {
128
+ const key = String(platform || '').trim();
129
+ if (!key) continue;
130
+ platformOverrides[key] = normalizeLeafConfig(value, normalized, true);
131
+ }
132
+ const roomOverrides = {};
133
+ const rawRoomOverrides = Object.keys(asObject(input.roomOverrides)).length
134
+ ? asObject(input.roomOverrides)
135
+ : asObject(input.groupOverrides);
136
+ for (const [roomKey, value] of Object.entries(rawRoomOverrides)) {
137
+ const key = String(roomKey || '').trim();
138
+ if (!key) continue;
139
+ roomOverrides[key] = normalizeLeafConfig(value, normalized, true);
140
+ }
141
+ return {
142
+ ...normalized,
143
+ platformOverrides,
144
+ roomOverrides,
145
+ };
146
+ }
147
+
148
+ function readSettingRow(userId, agentId) {
149
+ const row = db.prepare(
150
+ 'SELECT value FROM agent_settings WHERE user_id = ? AND agent_id = ? AND key = ?',
151
+ ).get(userId, agentId, SETTINGS_KEY);
152
+ if (row?.value != null) return row.value;
153
+ if (isMainAgent(userId, agentId)) {
154
+ const legacy = db.prepare(
155
+ 'SELECT value FROM user_settings WHERE user_id = ? AND key = ?',
156
+ ).get(userId, SETTINGS_KEY);
157
+ return legacy?.value;
158
+ }
159
+ return null;
160
+ }
161
+
162
+ function parseStoredValue(value) {
163
+ if (value == null || value === '') return cloneDefaults();
164
+ if (typeof value === 'object') return normalizeStoredConfig(value);
165
+ try {
166
+ return normalizeStoredConfig(JSON.parse(value));
167
+ } catch {
168
+ return cloneDefaults();
169
+ }
170
+ }
171
+
172
+ function getBehaviorConfig(userId, agentId = null) {
173
+ return parseStoredValue(readSettingRow(userId, agentId));
174
+ }
175
+
176
+ function setBehaviorConfig(userId, agentId, config) {
177
+ const normalized = normalizeStoredConfig(config);
178
+ db.prepare(
179
+ `INSERT INTO agent_settings (user_id, agent_id, key, value)
180
+ VALUES (?, ?, ?, ?)
181
+ ON CONFLICT(user_id, agent_id, key) DO UPDATE SET value = excluded.value`,
182
+ ).run(userId, agentId, SETTINGS_KEY, JSON.stringify(normalized));
183
+ return normalized;
184
+ }
185
+
186
+ function roomConfigKey(platform, chatId) {
187
+ return `${String(platform || '').trim()}::${String(chatId || '').trim()}`;
188
+ }
189
+
190
+ function mergeConfigs(base, override) {
191
+ if (!override) return base;
192
+ return {
193
+ ...base,
194
+ ...override,
195
+ modules: {
196
+ ...asObject(base.modules),
197
+ ...asObject(override.modules),
198
+ },
199
+ };
200
+ }
201
+
202
+ function resolveBehaviorConfig(userId, agentId, { platform = null, chatId = null, isGroup = false } = {}) {
203
+ const storedValue = readSettingRow(userId, agentId);
204
+ const stored = parseStoredValue(storedValue);
205
+ let effective = normalizeLeafConfig(stored, cloneDefaults());
206
+ let participationModeSource = storedValue == null ? 'default' : 'agent';
207
+ const platformKey = String(platform || '').trim();
208
+ if (platformKey && stored.platformOverrides?.[platformKey]) {
209
+ effective = mergeConfigs(effective, stored.platformOverrides[platformKey]);
210
+ if (Object.prototype.hasOwnProperty.call(stored.platformOverrides[platformKey], 'participationMode')) {
211
+ participationModeSource = 'platform';
212
+ }
213
+ }
214
+ if (isGroup && platformKey && chatId) {
215
+ const key = roomConfigKey(platformKey, chatId);
216
+ if (stored.roomOverrides?.[key]) {
217
+ effective = mergeConfigs(effective, stored.roomOverrides[key]);
218
+ if (Object.prototype.hasOwnProperty.call(stored.roomOverrides[key], 'participationMode')) {
219
+ participationModeSource = 'room';
220
+ }
221
+ }
222
+ }
223
+ effective.modules = normalizeModules(effective.modules, effective.enabled !== false);
224
+ effective.platformOverrides = stored.platformOverrides || {};
225
+ effective.roomOverrides = stored.roomOverrides || {};
226
+ effective.participationModeSource = participationModeSource;
227
+ effective.scope = {
228
+ platform: platformKey || null,
229
+ chatId: chatId ? String(chatId) : null,
230
+ isGroup: Boolean(isGroup),
231
+ roomKey: isGroup && platformKey && chatId ? roomConfigKey(platformKey, chatId) : null,
232
+ };
233
+ return effective;
234
+ }
235
+
236
+ function isModuleEnabled(config, moduleId) {
237
+ if (!config || config.enabled === false) return false;
238
+ const entry = config.modules?.[moduleId];
239
+ if (!entry) return false;
240
+ return entry.enabled !== false;
241
+ }
242
+
243
+ module.exports = {
244
+ SETTINGS_KEY,
245
+ getBehaviorConfig,
246
+ setBehaviorConfig,
247
+ resolveBehaviorConfig,
248
+ normalizeStoredConfig,
249
+ roomConfigKey,
250
+ isModuleEnabled,
251
+ };
@@ -0,0 +1,68 @@
1
+ 'use strict';
2
+
3
+ const MODULE_IDS = Object.freeze([
4
+ 'turn_taking',
5
+ 'social_memory',
6
+ 'norms',
7
+ 'persona',
8
+ 'agent_identity',
9
+ 'channel_style',
10
+ 'theory_of_mind',
11
+ 'social_signals',
12
+ 'social_observability',
13
+ 'delivery',
14
+ ]);
15
+
16
+ const DEFAULT_MODULE_CONFIG = Object.freeze({
17
+ schemaVersion: 1,
18
+ enabled: true,
19
+ participationMode: 'automatic',
20
+ minimumNeedScore: 0.72,
21
+ batchWindowMs: 900,
22
+ decisionContextMessageLimit: 12,
23
+ decisionModelId: null,
24
+ decisionModelPurpose: 'fast',
25
+ deliveryStyle: 'natural_bubbles',
26
+ maxBubbles: 4,
27
+ bubbleGapMs: 650,
28
+ normsRefreshMessageGap: 18,
29
+ observabilityIntervalMinutes: 360,
30
+ });
31
+
32
+ function cloneDefaults() {
33
+ return {
34
+ schemaVersion: DEFAULT_MODULE_CONFIG.schemaVersion,
35
+ enabled: DEFAULT_MODULE_CONFIG.enabled,
36
+ modules: {
37
+ turn_taking: { enabled: true },
38
+ social_memory: { enabled: true },
39
+ norms: { enabled: true },
40
+ persona: { enabled: true },
41
+ agent_identity: { enabled: true },
42
+ channel_style: { enabled: true },
43
+ theory_of_mind: { enabled: true },
44
+ social_signals: { enabled: true },
45
+ social_observability: { enabled: true },
46
+ delivery: { enabled: true },
47
+ },
48
+ participationMode: DEFAULT_MODULE_CONFIG.participationMode,
49
+ minimumNeedScore: DEFAULT_MODULE_CONFIG.minimumNeedScore,
50
+ batchWindowMs: DEFAULT_MODULE_CONFIG.batchWindowMs,
51
+ decisionContextMessageLimit: DEFAULT_MODULE_CONFIG.decisionContextMessageLimit,
52
+ decisionModelId: DEFAULT_MODULE_CONFIG.decisionModelId,
53
+ decisionModelPurpose: DEFAULT_MODULE_CONFIG.decisionModelPurpose,
54
+ deliveryStyle: DEFAULT_MODULE_CONFIG.deliveryStyle,
55
+ maxBubbles: DEFAULT_MODULE_CONFIG.maxBubbles,
56
+ bubbleGapMs: DEFAULT_MODULE_CONFIG.bubbleGapMs,
57
+ normsRefreshMessageGap: DEFAULT_MODULE_CONFIG.normsRefreshMessageGap,
58
+ observabilityIntervalMinutes: DEFAULT_MODULE_CONFIG.observabilityIntervalMinutes,
59
+ platformOverrides: {},
60
+ roomOverrides: {},
61
+ };
62
+ }
63
+
64
+ module.exports = {
65
+ MODULE_IDS,
66
+ DEFAULT_MODULE_CONFIG,
67
+ cloneDefaults,
68
+ };