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
@@ -40,6 +40,14 @@ const AGENT_SETTING_KEYS = new Set([
40
40
  'cost_mode',
41
41
  'chat_history_window',
42
42
  'tool_replay_budget_chars',
43
+ 'tool_replay_budget_file_chars',
44
+ 'tool_replay_budget_browser_chars',
45
+ 'tool_replay_budget_command_chars',
46
+ 'max_iterations',
47
+ 'max_consecutive_read_only_iterations',
48
+ 'max_consecutive_tool_failures',
49
+ 'max_model_failure_recoveries',
50
+ 'compaction_threshold',
43
51
  'subagent_max_iterations',
44
52
  'subagent_max_children_per_run',
45
53
  'assistant_behavior_notes',
@@ -194,16 +202,19 @@ async function resetEnvBackedSettingValue(req, key) {
194
202
  // Get supported models metadata
195
203
  router.get('/meta/models', async (req, res) => {
196
204
  const agentId = resolveAgentId(req.session.userId, getAgentIdFromRequest(req));
197
- const models = await getSupportedModels(req.session.userId, agentId);
205
+ const models = await getSupportedModels(req.session.userId, agentId, { signal: req.signal });
198
206
  res.json({ models });
199
207
  });
200
208
 
201
209
  router.get('/meta/ai-providers', async (req, res) => {
202
210
  const agentId = resolveAgentId(req.session.userId, getAgentIdFromRequest(req));
203
- const [providers, models] = await Promise.all([
204
- getProviderHealthCatalog(req.session.userId, agentId),
205
- getSupportedModels(req.session.userId, agentId),
206
- ]);
211
+ const providers = await getProviderHealthCatalog(req.session.userId, agentId, {
212
+ signal: req.signal,
213
+ });
214
+ const models = await getSupportedModels(req.session.userId, agentId, {
215
+ providerCatalog: providers,
216
+ signal: req.signal,
217
+ });
207
218
 
208
219
  const modelCounts = models.reduce((acc, model) => {
209
220
  acc[model.provider] = (acc[model.provider] || 0) + 1;
@@ -335,6 +346,10 @@ router.put('/', async (req, res) => {
335
346
  });
336
347
 
337
348
  tx(Object.entries(normalizedBody));
349
+ if (Object.prototype.hasOwnProperty.call(normalizedBody, 'assistant_behavior_notes')) {
350
+ const { invalidateSystemPromptCache } = require('../services/ai/systemPrompt');
351
+ invalidateSystemPromptCache(userId, agentId);
352
+ }
338
353
 
339
354
  if (Object.keys(normalizedBody).some((key) => VOICE_SETTING_KEYS.has(key))) {
340
355
  const manager = req.app?.locals?.messagingManager;
@@ -25,7 +25,7 @@ router.get('/status', async (req, res) => {
25
25
  if (!service || typeof service.getStatus !== 'function') {
26
26
  return res.status(503).json({ error: 'Social reach service is unavailable.' });
27
27
  }
28
- return res.json(await service.getStatus(req.session.userId));
28
+ return res.json(await service.getStatus(req.session.userId, { signal: req.signal }));
29
29
  } catch (error) {
30
30
  return sendError(res, error);
31
31
  }
@@ -37,7 +37,11 @@ router.post('/read', async (req, res) => {
37
37
  if (!service || typeof service.read !== 'function') {
38
38
  return res.status(503).json({ error: 'Social reach service is unavailable.' });
39
39
  }
40
- return res.json(await service.read(req.session.userId, req.body || {}));
40
+ return res.json(await service.read(
41
+ req.session.userId,
42
+ req.body || {},
43
+ { signal: req.signal },
44
+ ));
41
45
  } catch (error) {
42
46
  return sendError(res, error);
43
47
  }
@@ -49,7 +53,11 @@ router.post('/search', async (req, res) => {
49
53
  if (!service || typeof service.search !== 'function') {
50
54
  return res.status(503).json({ error: 'Social reach service is unavailable.' });
51
55
  }
52
- return res.json(await service.search(req.session.userId, req.body || {}));
56
+ return res.json(await service.search(
57
+ req.session.userId,
58
+ req.body || {},
59
+ { signal: req.signal },
60
+ ));
53
61
  } catch (error) {
54
62
  return sendError(res, error);
55
63
  }
@@ -67,6 +75,7 @@ router.post('/cookies/import', async (req, res) => {
67
75
  }
68
76
  return res.json(await service.importCookiesFromExtension(req.session.userId, platform, {
69
77
  tokenId: req.body?.tokenId || req.body?.token_id || null,
78
+ signal: req.signal,
70
79
  }));
71
80
  } catch (error) {
72
81
  return sendError(res, error);
@@ -1,3 +1,5 @@
1
+ 'use strict';
2
+
1
3
  const express = require('express');
2
4
  const { requireAuth } = require('../middleware/auth');
3
5
  const { getAgentIdFromRequest, resolveAgentId } = require('../services/agents/manager');
@@ -18,6 +20,7 @@ router.get('/health', async (req, res) => {
18
20
  }
19
21
  const health = await service.getHealthStatus({
20
22
  forceRefresh: req.query?.refresh === '1' || req.query?.refresh === 'true',
23
+ signal: req.signal,
21
24
  });
22
25
  return res.json(health);
23
26
  } catch (error) {
@@ -46,6 +49,7 @@ router.post('/extract', async (req, res) => {
46
49
  includeFrame: req.body?.include_frame !== false,
47
50
  forceStt: req.body?.force_stt === true,
48
51
  agentId,
52
+ signal: req.signal,
49
53
  });
50
54
 
51
55
  if (result?.setup?.ready === false) {
@@ -77,7 +77,7 @@ function ensureMainAgent(userId) {
77
77
  userId,
78
78
  MAIN_AGENT_SLUG,
79
79
  'Main',
80
- 'Default personal assistant and fallback agent.',
80
+ 'Default personal AI contact and fallback agent.',
81
81
  'Handle general requests. Delegate to specialist agents only when their responsibilities clearly match.',
82
82
  '',
83
83
  );
@@ -39,6 +39,9 @@ async function getBrowserHealth(userId, app, engine) {
39
39
  const runtimeSettings = typeof runtimeManager?.getSettings === 'function'
40
40
  ? runtimeManager.getSettings(userId)
41
41
  : null;
42
+ const runtimeSnapshot = typeof runtimeManager?.getCapabilitySnapshot === 'function'
43
+ ? runtimeManager.getCapabilitySnapshot(userId)
44
+ : null;
42
45
  const extensionStatus = runtimeSettings?.browser_backend === 'extension'
43
46
  ? app?.locals?.browserExtensionRegistry?.getStatus(userId)
44
47
  : null;
@@ -63,122 +66,81 @@ async function getBrowserHealth(userId, app, engine) {
63
66
  });
64
67
  }
65
68
  }
66
- let controller = null;
67
- let resolutionError = null;
68
-
69
- if (runtimeManager && typeof runtimeManager.getBrowserProviderForUser === 'function') {
70
- try {
71
- controller = await runtimeManager.getBrowserProviderForUser(userId);
72
- } catch (err) {
73
- resolutionError = err;
74
- }
75
- }
76
-
77
- if (!controller) {
78
- resolutionError = resolutionError || new Error('Browser provider is unavailable. VM runtime is required.');
79
- }
80
-
81
- if (!controller && resolutionError) {
69
+ if (!runtimeManager) {
82
70
  return capabilityEntry({
83
- configured: true,
84
- healthy: false,
85
- degraded: true,
86
- summary: `Browser controller resolution failed: ${resolutionError.message}`,
87
- details: {
88
- error: resolutionError.message,
89
- },
71
+ summary: 'Browser runtime is not available in this environment.',
90
72
  });
91
73
  }
92
- let pageInfo = null;
93
- let launched = false;
94
- let error = null;
95
74
 
96
- try {
97
- launched = typeof controller?.isLaunched === 'function'
98
- ? await Promise.resolve(controller.isLaunched())
99
- : false;
100
- pageInfo = typeof controller?.getPageInfo === 'function' ? await controller.getPageInfo() : null;
101
- } catch (err) {
102
- error = err.message;
103
- }
75
+ // Capability reporting runs on every agent turn. It must never resolve a
76
+ // provider because the VM provider performs image builds and container boot.
77
+ // Tool execution remains the authoritative health check and starts/restarts
78
+ // the runtime only when the model actually uses a browser tool.
79
+ const preferredBackend = runtimeSettings?.browser_backend
80
+ || runtimeSnapshot?.browser?.preferredBackend
81
+ || 'vm';
82
+ const vmInitialized = runtimeSnapshot?.browser?.vmInitialized === true;
83
+ const extensionConnected = extensionStatus?.connected === true
84
+ || runtimeSnapshot?.browser?.extensionConnected === true;
85
+ const extensionPreferredButOffline = preferredBackend === 'extension' && !extensionConnected;
104
86
 
105
87
  return capabilityEntry({
106
- connected: launched,
88
+ connected: vmInitialized || extensionConnected,
107
89
  configured: true,
108
- healthy: !error,
109
- degraded: Boolean(error) || runtimeSettings?.browser_backend === 'extension',
110
- summary: error
111
- ? `Browser runtime error: ${error}`
112
- : runtimeSettings?.browser_backend === 'extension'
113
- ? `No extension device is active. Falling back to the vm browser runtime.`
114
- : (launched ? 'Browser runtime is ready.' : 'Browser runtime is available but not launched.'),
90
+ healthy: true,
91
+ degraded: extensionPreferredButOffline,
92
+ summary: extensionPreferredButOffline
93
+ ? 'No extension device is active. The VM browser will start on first use.'
94
+ : vmInitialized
95
+ ? 'Browser VM session is initialized.'
96
+ : 'Browser runtime is available and will start on first use.',
115
97
  details: {
116
- preferredBackend: runtimeSettings?.browser_backend || null,
117
- backend: runtimeSettings?.browser_backend === 'extension'
118
- ? 'vm'
119
- : runtimeSettings?.browser_backend || null,
120
- extensionConnected: extensionStatus?.connected === true,
98
+ preferredBackend,
99
+ backend: extensionConnected ? 'extension' : 'vm',
100
+ extensionConnected,
121
101
  activeTokenCount: Array.isArray(extensionStatus?.tokens)
122
102
  ? extensionStatus.tokens.filter((token) => token.status === 'active').length
123
103
  : 0,
124
- launched,
125
- pageInfo,
104
+ runtimeInitialized: vmInitialized,
126
105
  },
127
106
  });
128
107
  }
129
108
 
130
109
  async function getAndroidHealth(userId, app, engine) {
131
110
  const runtimeManager = app?.locals?.runtimeManager || engine?.runtimeManager || null;
132
- let controller = null;
133
- let resolutionError = null;
134
-
135
- if (runtimeManager && typeof runtimeManager.getAndroidProviderForUser === 'function') {
136
- try {
137
- controller = await runtimeManager.getAndroidProviderForUser(userId);
138
- } catch (err) {
139
- resolutionError = err;
140
- }
141
- }
142
-
143
- if (!controller) {
144
- resolutionError = resolutionError || new Error('Android provider is unavailable. VM runtime is required.');
145
- }
146
-
147
- if (!controller || typeof controller.getStatus !== 'function') {
111
+ if (!runtimeManager) {
148
112
  return capabilityEntry({
149
- degraded: Boolean(resolutionError),
150
- summary: resolutionError
151
- ? `Android controller resolution failed: ${resolutionError.message}`
152
- : 'Android controller is not available.',
153
- details: resolutionError ? { error: resolutionError.message } : {},
113
+ summary: 'Android runtime is not available in this environment.',
154
114
  });
155
115
  }
156
116
 
157
- try {
158
- const status = await controller.getStatus();
159
- const bootstrapped = status.bootstrapped === true;
160
- const canBootstrap = status.canBootstrap === true;
161
- return capabilityEntry({
162
- connected: Array.isArray(status.devices) && status.devices.some((device) => device.status === 'device'),
163
- configured: canBootstrap || bootstrapped,
164
- healthy: canBootstrap || bootstrapped,
165
- degraded: Boolean(status.lastStartError),
166
- summary: status.lastStartError
167
- ? `Android tooling reported: ${status.lastStartError}`
168
- : bootstrapped
169
- ? 'Android environment is ready on this host.'
170
- : (canBootstrap ? 'Android environment can be bootstrapped on this host.' : 'Android tooling cannot bootstrap on this host.'),
171
- details: status,
172
- });
173
- } catch (err) {
174
- return capabilityEntry({
175
- configured: true,
176
- healthy: false,
177
- degraded: true,
178
- summary: `Android status check failed: ${err.message}`,
179
- details: { error: err.message },
180
- });
181
- }
117
+ // Like browser health, Android health is a snapshot. Creating a controller or
118
+ // running adb here makes unrelated chat turns pay runtime startup/status costs.
119
+ const runtimeSnapshot = typeof runtimeManager.getCapabilitySnapshot === 'function'
120
+ ? runtimeManager.getCapabilitySnapshot(userId)
121
+ : null;
122
+ const status = runtimeSnapshot?.android?.status || null;
123
+ const bootstrapped = status?.bootstrapped === true;
124
+ const starting = status?.starting === true;
125
+ const lastStartError = String(status?.lastStartError || '').trim();
126
+ return capabilityEntry({
127
+ connected: bootstrapped,
128
+ configured: true,
129
+ healthy: !lastStartError,
130
+ degraded: Boolean(lastStartError),
131
+ summary: lastStartError
132
+ ? `Android tooling reported: ${lastStartError}`
133
+ : bootstrapped
134
+ ? 'Android environment is ready.'
135
+ : starting
136
+ ? 'Android environment is starting.'
137
+ : 'Android runtime is available and will initialize on first use.',
138
+ details: status || {
139
+ initialized: runtimeSnapshot?.android?.initialized === true,
140
+ bootstrapped: false,
141
+ starting: false,
142
+ },
143
+ });
182
144
  }
183
145
 
184
146
  function getMessagingHealth(userId, app, engine, agentId = null) {
@@ -342,7 +304,9 @@ function getTaskHealth(userId, agentId = null) {
342
304
  }
343
305
 
344
306
  async function getCapabilityHealth({ userId, agentId = null, app, engine }) {
345
- const providers = await getProviderHealthCatalog(userId, agentId);
307
+ const providers = await getProviderHealthCatalog(userId, agentId, {
308
+ probeLocal: false,
309
+ });
346
310
 
347
311
  return {
348
312
  providers,
@@ -363,6 +327,8 @@ async function getCapabilityHealth({ userId, agentId = null, app, engine }) {
363
327
  }
364
328
 
365
329
  module.exports = {
330
+ getAndroidHealth,
331
+ getBrowserHealth,
366
332
  getCapabilityHealth,
367
333
  summarizeCapabilityHealth,
368
334
  };
@@ -1,4 +1,4 @@
1
- async function compact(messages, provider, model, contextWindow = null) {
1
+ async function compact(messages, provider, model, contextWindow = null, options = {}) {
2
2
  const systemMsg = messages.find(m => m.role === 'system');
3
3
  const nonSystem = messages.filter(m => m.role !== 'system');
4
4
  const beforeTokens = estimateTokenCount(messages);
@@ -26,7 +26,11 @@ async function compact(messages, provider, model, contextWindow = null) {
26
26
  ];
27
27
 
28
28
  try {
29
- const response = await provider.chat(summaryPrompt, [], { model, maxTokens: 1600 });
29
+ const response = await provider.chat(summaryPrompt, [], {
30
+ model,
31
+ maxTokens: 1600,
32
+ signal: options.signal,
33
+ });
30
34
  const summary = response.content || 'Previous conversation context (summary unavailable).';
31
35
 
32
36
  const compactedMessages = [];
@@ -44,6 +48,7 @@ async function compact(messages, provider, model, contextWindow = null) {
44
48
  }
45
49
  return compactedMessages;
46
50
  } catch (err) {
51
+ if (options.signal?.aborted) throw err;
47
52
  console.error('Compaction failed:', err.message);
48
53
  const trimmed = [];
49
54
  if (systemMsg) trimmed.push(systemMsg);
@@ -1,5 +1,8 @@
1
+ 'use strict';
2
+
1
3
  const db = require('../../db/database');
2
4
  const { resolveAgentId } = require('../agents/manager');
5
+ const { throwIfAborted } = require('../../utils/abort');
3
6
 
4
7
  const WEB_SUMMARY_KEY = 'web_chat_summary';
5
8
  const WEB_SUMMARY_COUNT_KEY = 'web_chat_summary_count';
@@ -109,13 +112,21 @@ function serializeHistoryForSummary(messages) {
109
112
  }).join('\n');
110
113
  }
111
114
 
112
- async function summarizeMessages(provider, model, existingSummary, messages, label = 'conversation') {
115
+ async function summarizeMessages(
116
+ provider,
117
+ model,
118
+ existingSummary,
119
+ messages,
120
+ label = 'conversation',
121
+ options = {},
122
+ ) {
113
123
  if (!messages.length) return existingSummary || '';
124
+ throwIfAborted(options.signal, 'Conversation summary refresh aborted.');
114
125
 
115
126
  const prompt = [
116
127
  {
117
128
  role: 'system',
118
- content: 'Compress conversation context. Preserve user goals, constraints, preferences, decisions, promised follow-ups, recurring schedules, important facts, tool outcomes, and unresolved issues. Keep concrete details (names, dates, times, statuses) and avoid vague wording. Keep the same personality context. Output plain text only.'
129
+ content: 'Compress conversation context. Preserve user goals, constraints, preferences, decisions, promised follow-ups, recurring schedules, important facts, tool outcomes, unresolved issues, and any user-stated communication preferences. Keep concrete details (names, dates, times, statuses) and avoid vague wording. Output plain text only.'
119
130
  },
120
131
  {
121
132
  role: 'user',
@@ -127,7 +138,12 @@ async function summarizeMessages(provider, model, existingSummary, messages, lab
127
138
  }
128
139
  ];
129
140
 
130
- const response = await provider.chat(prompt, [], { model, maxTokens: 900 });
141
+ const response = await provider.chat(prompt, [], {
142
+ model,
143
+ maxTokens: 900,
144
+ signal: options.signal,
145
+ });
146
+ throwIfAborted(options.signal, 'Conversation summary refresh aborted.');
131
147
  return clampSummary(response.content || existingSummary || '');
132
148
  }
133
149
 
@@ -183,7 +199,15 @@ async function refreshWebChatSummary(userId, provider, model, recentLimit, force
183
199
  'SELECT role, content FROM conversation_history WHERE user_id = ? AND agent_id = ? ORDER BY created_at ASC LIMIT ? OFFSET ?'
184
200
  ).all(userId, agentId, newMessages, count);
185
201
 
186
- const nextSummary = clampSummary(await summarizeMessages(provider, model, summary, normalizeHistoryRows(rows), 'web chat'));
202
+ const nextSummary = clampSummary(await summarizeMessages(
203
+ provider,
204
+ model,
205
+ summary,
206
+ normalizeHistoryRows(rows),
207
+ 'web chat',
208
+ options,
209
+ ));
210
+ throwIfAborted(options.signal, 'Web chat summary refresh aborted.');
187
211
  const upsert = db.prepare(
188
212
  'INSERT INTO agent_settings (user_id, agent_id, key, value) VALUES (?, ?, ?, ?) ON CONFLICT(user_id, agent_id, key) DO UPDATE SET value = excluded.value'
189
213
  );
@@ -214,7 +238,14 @@ function getConversationContext(conversationId, recentLimit) {
214
238
  };
215
239
  }
216
240
 
217
- async function refreshConversationSummary(conversationId, provider, model, recentLimit, force = false) {
241
+ async function refreshConversationSummary(
242
+ conversationId,
243
+ provider,
244
+ model,
245
+ recentLimit,
246
+ force = false,
247
+ options = {},
248
+ ) {
218
249
  const convo = db.prepare(
219
250
  'SELECT summary, summary_message_count FROM conversations WHERE id = ?'
220
251
  ).get(conversationId);
@@ -233,7 +264,15 @@ async function refreshConversationSummary(conversationId, provider, model, recen
233
264
  'SELECT role, content, tool_calls, tool_call_id, name FROM conversation_messages WHERE conversation_id = ? ORDER BY created_at ASC LIMIT ? OFFSET ?'
234
265
  ).all(conversationId, newMessages, currentCount);
235
266
 
236
- const nextSummary = clampSummary(await summarizeMessages(provider, model, convo.summary || '', normalizeHistoryRows(rows), 'thread'));
267
+ const nextSummary = clampSummary(await summarizeMessages(
268
+ provider,
269
+ model,
270
+ convo.summary || '',
271
+ normalizeHistoryRows(rows),
272
+ 'thread',
273
+ options,
274
+ ));
275
+ throwIfAborted(options.signal, 'Conversation summary refresh aborted.');
237
276
  db.prepare(
238
277
  "UPDATE conversations SET summary = ?, summary_message_count = ?, last_summary = datetime('now') WHERE id = ?"
239
278
  ).run(nextSummary, targetCount, conversationId);
@@ -0,0 +1,8 @@
1
+ 'use strict';
2
+
3
+ const safeRequest = require('../../network/safe_request');
4
+
5
+ module.exports = {
6
+ ...safeRequest,
7
+ executeHttpRequest: safeRequest.executeSafeHttpRequest,
8
+ };