capacitor-native-agent 0.9.8 → 0.9.9

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.
@@ -3,6 +3,7 @@ package com.t6x.plugins.nativeagent
3
3
  import android.content.Context
4
4
  import java.util.Locale
5
5
  import java.util.UUID
6
+ import java.util.concurrent.Executors
6
7
  import kotlinx.coroutines.runBlocking
7
8
  import org.json.JSONArray
8
9
  import org.json.JSONObject
@@ -13,6 +14,15 @@ import uniffi.native_agent_ffi.MemoryProvider
13
14
  class MemoryProviderImpl(private val context: Context) : MemoryProvider {
14
15
  fun isAvailable(): Boolean = LanceDBBridge.getOrCreateHandle(context) != null
15
16
 
17
+ // UniFFI methods on LanceDbHandle are suspend → require runBlocking. The
18
+ // MemoryProvider callback is invoked by Rust on a JNA-managed native
19
+ // thread; running coroutines (and uniffiRustCallAsync's own JNA callbacks)
20
+ // on that thread races with JNA's thread-detach logic, producing
21
+ // SIGABRT "attempting to detach while still running code" on Android ART.
22
+ // Hop to a dedicated JVM-owned worker thread so the JNA callback thread
23
+ // only blocks on Future.get(), never executes coroutine continuations.
24
+ private fun <T> onWorker(block: () -> T): T = WORKER.submit(block).get()
25
+
16
26
  override fun store(key: String, text: String, metadataJson: String?): String {
17
27
  val handle = LanceDBBridge.getOrCreateHandle(context) ?: return unavailableJson()
18
28
  val resolvedKey = key.ifBlank {
@@ -20,8 +30,10 @@ class MemoryProviderImpl(private val context: Context) : MemoryProvider {
20
30
  }
21
31
  val embedding = localHashEmbed(text, LanceDBBridge.EMBEDDING_DIM)
22
32
  return runCatching {
23
- runBlocking {
24
- handle.store(resolvedKey, DEFAULT_AGENT_ID, text, embedding, metadataJson)
33
+ onWorker {
34
+ runBlocking {
35
+ handle.store(resolvedKey, DEFAULT_AGENT_ID, text, embedding, metadataJson)
36
+ }
25
37
  }
26
38
  JSONObject()
27
39
  .put("success", true)
@@ -34,15 +46,17 @@ class MemoryProviderImpl(private val context: Context) : MemoryProvider {
34
46
  val handle = LanceDBBridge.getOrCreateHandle(context) ?: return unavailableJson()
35
47
  val embedding = localHashEmbed(query, LanceDBBridge.EMBEDDING_DIM)
36
48
  return runCatching {
37
- val results = runBlocking {
38
- handle.hybridSearch(
39
- embedding,
40
- query,
41
- limit,
42
- "agent_id = '$DEFAULT_AGENT_ID'",
43
- null,
44
- null,
45
- )
49
+ val results = onWorker {
50
+ runBlocking {
51
+ handle.hybridSearch(
52
+ embedding,
53
+ query,
54
+ limit,
55
+ "agent_id = '$DEFAULT_AGENT_ID'",
56
+ null,
57
+ null,
58
+ )
59
+ }
46
60
  }
47
61
  hybridResultsJson(results)
48
62
  }.getOrElse { errorJson(it) }
@@ -54,8 +68,10 @@ class MemoryProviderImpl(private val context: Context) : MemoryProvider {
54
68
  return JSONObject().put("error", "Provide query or key.").toString()
55
69
  }
56
70
  return runCatching {
57
- runBlocking {
58
- handle.delete(key)
71
+ onWorker {
72
+ runBlocking {
73
+ handle.delete(key)
74
+ }
59
75
  }
60
76
  JSONObject()
61
77
  .put("success", true)
@@ -68,8 +84,10 @@ class MemoryProviderImpl(private val context: Context) : MemoryProvider {
68
84
  val handle = LanceDBBridge.getOrCreateHandle(context) ?: return unavailableJson()
69
85
  val embedding = localHashEmbed(query, LanceDBBridge.EMBEDDING_DIM)
70
86
  return runCatching {
71
- val results = runBlocking {
72
- handle.search(embedding, maxResults, "agent_id = '$DEFAULT_AGENT_ID'")
87
+ val results = onWorker {
88
+ runBlocking {
89
+ handle.search(embedding, maxResults, "agent_id = '$DEFAULT_AGENT_ID'")
90
+ }
73
91
  }
74
92
  searchResultsJson(results)
75
93
  }.getOrElse { errorJson(it) }
@@ -78,8 +96,10 @@ class MemoryProviderImpl(private val context: Context) : MemoryProvider {
78
96
  override fun list(prefix: String?, limit: UInt?): String {
79
97
  val handle = LanceDBBridge.getOrCreateHandle(context) ?: return unavailableJson()
80
98
  return runCatching {
81
- val keys = runBlocking {
82
- handle.list(prefix, limit)
99
+ val keys = onWorker {
100
+ runBlocking {
101
+ handle.list(prefix, limit)
102
+ }
83
103
  }
84
104
  JSONArray(keys).toString()
85
105
  }.getOrElse { errorJson(it) }
@@ -131,6 +151,9 @@ class MemoryProviderImpl(private val context: Context) : MemoryProvider {
131
151
 
132
152
  private companion object {
133
153
  private const val DEFAULT_AGENT_ID = "main"
154
+ private val WORKER = Executors.newSingleThreadExecutor { r ->
155
+ Thread(r, "memory-provider-worker").apply { isDaemon = true }
156
+ }
134
157
  private val WHITESPACE = Regex("\\s+")
135
158
  private val UINT_MAX_DOUBLE = 0xffffffffu.toDouble()
136
159
  private const val FNV_OFFSET = 0x811c9dc5u
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capacitor-native-agent",
3
- "version": "0.9.8",
3
+ "version": "0.9.9",
4
4
  "description": "Native AI agent loop for Capacitor — runs LLM completions, tool execution, and cron jobs in native Rust, enabling background execution.",
5
5
  "main": "dist/esm/index.js",
6
6
  "types": "dist/esm/index.d.ts",