@elizaos/capacitor-agent 2.0.0-beta.1 → 2.0.3-beta.3

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.
@@ -1,8 +1,12 @@
1
1
  import Foundation
2
2
  import Capacitor
3
+ import WebKit
3
4
 
4
5
  private let maxRequestBodyBytes = 10 * 1024 * 1024
5
6
  private let maxResponseBodyBytes = 10 * 1024 * 1024
7
+ private let localAgentPort = 31337
8
+ private let localAgentIpcScheme = "eliza-local-agent"
9
+ private let localAgentIpcHost = "ipc"
6
10
 
7
11
  private struct AgentEndpoint {
8
12
  let baseURL: URL
@@ -16,12 +20,14 @@ private struct AgentHTTPResponse {
16
20
  let body: String
17
21
  }
18
22
 
19
- /// Eliza Agent Plugin — iOS HTTP bridge.
23
+ /// Eliza Agent Plugin — iOS bridge.
20
24
  ///
21
- /// iOS does not bundle a Bun runtime. This plugin bridges the Capacitor
22
- /// Agent API to an explicitly configured HTTP agent endpoint, such as a
23
- /// local Mac dev server or a remote Eliza agent. Without that endpoint it
24
- /// fails with an actionable error instead of pretending a local agent exists.
25
+ /// Remote/cloud modes bridge the Capacitor Agent API to an explicitly
26
+ /// configured HTTP agent endpoint, such as a local Mac dev server or a remote
27
+ /// Eliza agent. Local dev/sideload mode uses a path-only in-app identity; full
28
+ /// Bun foreground traffic goes through the ElizaBunRuntime Capacitor bridge,
29
+ /// while this plugin keeps the foreground WebView ITTP route kernel as a
30
+ /// compatibility path. It never starts an iOS local TCP listener.
25
31
  @objc(AgentPlugin)
26
32
  public class AgentPlugin: CAPPlugin, CAPBridgedPlugin {
27
33
  public let identifier = "AgentPlugin"
@@ -36,8 +42,29 @@ public class AgentPlugin: CAPPlugin, CAPBridgedPlugin {
36
42
  ]
37
43
 
38
44
  private static var conversationIdByBaseURL: [String: String] = [:]
45
+ private static var localStartedAt: Date?
46
+ private static let apiBaseConfigKeys = [
47
+ "apiBase",
48
+ "baseUrl",
49
+ "baseURL",
50
+ "agentApiBase",
51
+ "ELIZA_AGENT_API_BASE",
52
+ "ELIZA_API_BASE",
53
+ "ELIZA_IOS_API_BASE",
54
+ "ELIZA_IOS_REMOTE_API_BASE",
55
+ "ELIZA_MOBILE_API_BASE",
56
+ "VITE_ELIZA_IOS_API_BASE",
57
+ "VITE_ELIZA_MOBILE_API_BASE",
58
+ "VITE_ELIZA_IOS_API_BASE",
59
+ ]
39
60
 
40
61
  @objc func start(_ call: CAPPluginCall) {
62
+ if isLocalAgentMode(call: call) {
63
+ Self.localStartedAt = Self.localStartedAt ?? Date()
64
+ call.resolve(localAgentStatus(state: "running", error: nil))
65
+ return
66
+ }
67
+
41
68
  guard let endpoint = resolveEndpoint(call: call) else {
42
69
  call.reject(missingEndpointMessage())
43
70
  return
@@ -60,6 +87,12 @@ public class AgentPlugin: CAPPlugin, CAPBridgedPlugin {
60
87
  }
61
88
 
62
89
  @objc func stop(_ call: CAPPluginCall) {
90
+ if isLocalAgentMode(call: call) {
91
+ Self.localStartedAt = nil
92
+ call.resolve(["ok": true])
93
+ return
94
+ }
95
+
63
96
  guard let endpoint = resolveEndpoint(call: call) else {
64
97
  call.reject(missingEndpointMessage())
65
98
  return
@@ -82,6 +115,12 @@ public class AgentPlugin: CAPPlugin, CAPBridgedPlugin {
82
115
  }
83
116
 
84
117
  @objc func getStatus(_ call: CAPPluginCall) {
118
+ if isLocalAgentMode(call: call) {
119
+ Self.localStartedAt = Self.localStartedAt ?? Date()
120
+ call.resolve(localAgentStatus(state: "running", error: nil))
121
+ return
122
+ }
123
+
85
124
  guard let endpoint = resolveEndpoint(call: call) else {
86
125
  call.resolve(status(state: "error", agentName: nil, port: nil, startedAt: nil, error: missingEndpointMessage()))
87
126
  return
@@ -119,6 +158,25 @@ public class AgentPlugin: CAPPlugin, CAPBridgedPlugin {
119
158
  call.reject("Agent.chat requires non-empty text")
120
159
  return
121
160
  }
161
+ if isLocalAgentMode(call: call) {
162
+ let timeout = timeoutMs(from: call)
163
+ ensureLocalConversation(timeoutMs: timeout) { conversationResult in
164
+ switch conversationResult {
165
+ case .success(let conversationId):
166
+ self.sendLocalChatMessage(conversationId: conversationId, text: text, timeoutMs: timeout, retryOnMissingConversation: true) { result in
167
+ switch result {
168
+ case .success(let payload):
169
+ call.resolve(payload)
170
+ case .failure(let error):
171
+ call.reject(error.localizedDescription)
172
+ }
173
+ }
174
+ case .failure(let error):
175
+ call.reject(error.localizedDescription)
176
+ }
177
+ }
178
+ return
179
+ }
122
180
  guard let endpoint = resolveEndpoint(call: call) else {
123
181
  call.reject(missingEndpointMessage())
124
182
  return
@@ -142,6 +200,14 @@ public class AgentPlugin: CAPPlugin, CAPBridgedPlugin {
142
200
  }
143
201
 
144
202
  @objc func getLocalAgentToken(_ call: CAPPluginCall) {
203
+ if isLocalAgentMode(call: call) {
204
+ call.resolve([
205
+ "available": false,
206
+ "token": NSNull(),
207
+ ])
208
+ return
209
+ }
210
+
145
211
  let token = resolveEndpoint(call: call)?.token
146
212
  call.resolve([
147
213
  "available": token != nil,
@@ -150,10 +216,6 @@ public class AgentPlugin: CAPPlugin, CAPBridgedPlugin {
150
216
  }
151
217
 
152
218
  @objc func request(_ call: CAPPluginCall) {
153
- guard let endpoint = resolveEndpoint(call: call) else {
154
- call.reject(missingEndpointMessage())
155
- return
156
- }
157
219
  guard let path = call.getString("path")?.trimmingCharacters(in: .whitespacesAndNewlines),
158
220
  isSafeLocalPath(path) else {
159
221
  call.reject("Agent.request requires a local path that starts with / and is not an absolute URL")
@@ -171,6 +233,28 @@ public class AgentPlugin: CAPPlugin, CAPBridgedPlugin {
171
233
  }
172
234
 
173
235
  let headers = call.getObject("headers") ?? [:]
236
+ if isLocalAgentMode(call: call) {
237
+ sendLocalIttpRequest(
238
+ path: path,
239
+ method: method,
240
+ headers: headers,
241
+ body: body,
242
+ timeoutMs: timeoutMs(from: call)
243
+ ) { result in
244
+ switch result {
245
+ case .success(let response):
246
+ call.resolve(self.agentHTTPResponseObject(response))
247
+ case .failure(let error):
248
+ call.reject("iOS local agent request failed: \(error.localizedDescription)")
249
+ }
250
+ }
251
+ return
252
+ }
253
+
254
+ guard let endpoint = resolveEndpoint(call: call) else {
255
+ call.reject(missingEndpointMessage())
256
+ return
257
+ }
174
258
  sendJSON(
175
259
  endpoint: endpoint,
176
260
  path: path,
@@ -193,6 +277,165 @@ public class AgentPlugin: CAPPlugin, CAPBridgedPlugin {
193
277
  }
194
278
  }
195
279
 
280
+ private func ensureLocalConversation(
281
+ timeoutMs: Int,
282
+ completion: @escaping (Result<String, Error>) -> Void
283
+ ) {
284
+ let baseKey = "ios-local-ittp"
285
+ if let existing = Self.conversationIdByBaseURL[baseKey], !existing.isEmpty {
286
+ completion(.success(existing))
287
+ return
288
+ }
289
+
290
+ sendLocalIttpRequest(
291
+ path: "/api/conversations",
292
+ method: "POST",
293
+ headers: ["Content-Type": "application/json"],
294
+ body: "{\"title\":\"Quick Chat\"}",
295
+ timeoutMs: timeoutMs
296
+ ) { result in
297
+ switch result {
298
+ case .success(let response):
299
+ guard self.isHTTPSuccess(response.status) else {
300
+ completion(.failure(self.pluginError(self.httpErrorMessage(prefix: "Failed to create local conversation", response: response))))
301
+ return
302
+ }
303
+ guard let payload = self.parseJSONObject(response.body),
304
+ let conversation = payload["conversation"] as? JSObject,
305
+ let id = conversation["id"] as? String,
306
+ !id.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
307
+ completion(.failure(self.pluginError("Local conversation create response missing id")))
308
+ return
309
+ }
310
+ Self.conversationIdByBaseURL[baseKey] = id
311
+ completion(.success(id))
312
+ case .failure(let error):
313
+ completion(.failure(self.pluginError("Failed to create local conversation: \(error.localizedDescription)")))
314
+ }
315
+ }
316
+ }
317
+
318
+ private func sendLocalChatMessage(
319
+ conversationId: String,
320
+ text: String,
321
+ timeoutMs: Int,
322
+ retryOnMissingConversation: Bool,
323
+ completion: @escaping (Result<JSObject, Error>) -> Void
324
+ ) {
325
+ let path = "/api/conversations/\(urlEncode(conversationId))/messages"
326
+ let bodyObject: JSObject = [
327
+ "text": text,
328
+ "channelType": "DM",
329
+ ]
330
+ guard let bodyData = try? JSONSerialization.data(withJSONObject: bodyObject, options: []),
331
+ let body = String(data: bodyData, encoding: .utf8) else {
332
+ completion(.failure(pluginError("Failed to encode local chat request")))
333
+ return
334
+ }
335
+
336
+ sendLocalIttpRequest(
337
+ path: path,
338
+ method: "POST",
339
+ headers: ["Content-Type": "application/json"],
340
+ body: body,
341
+ timeoutMs: timeoutMs
342
+ ) { result in
343
+ switch result {
344
+ case .success(let response):
345
+ if response.status == 404 && retryOnMissingConversation {
346
+ Self.conversationIdByBaseURL.removeValue(forKey: "ios-local-ittp")
347
+ self.ensureLocalConversation(timeoutMs: timeoutMs) { nextConversation in
348
+ switch nextConversation {
349
+ case .success(let nextId):
350
+ self.sendLocalChatMessage(
351
+ conversationId: nextId,
352
+ text: text,
353
+ timeoutMs: timeoutMs,
354
+ retryOnMissingConversation: false,
355
+ completion: completion
356
+ )
357
+ case .failure(let error):
358
+ completion(.failure(error))
359
+ }
360
+ }
361
+ return
362
+ }
363
+ guard self.isHTTPSuccess(response.status) else {
364
+ completion(.failure(self.pluginError(self.httpErrorMessage(prefix: "Local chat request failed", response: response))))
365
+ return
366
+ }
367
+ let payload = self.parseJSONObject(response.body) ?? [:]
368
+ let responseText = (payload["text"] as? String) ?? ""
369
+ let agentName = (payload["agentName"] as? String) ?? "Agent"
370
+ completion(.success([
371
+ "text": responseText,
372
+ "agentName": agentName,
373
+ ]))
374
+ case .failure(let error):
375
+ completion(.failure(self.pluginError("Local chat request failed: \(error.localizedDescription)")))
376
+ }
377
+ }
378
+ }
379
+
380
+ private func sendLocalIttpRequest(
381
+ path: String,
382
+ method: String,
383
+ headers: JSObject = [:],
384
+ body: String? = nil,
385
+ timeoutMs: Int,
386
+ completion: @escaping (Result<AgentHTTPResponse, Error>) -> Void
387
+ ) {
388
+ guard let webView = bridge?.webView else {
389
+ completion(.success(localIttpUnavailableResponse()))
390
+ return
391
+ }
392
+
393
+ let payload: JSObject = [
394
+ "path": path,
395
+ "method": method,
396
+ "headers": headers,
397
+ "body": body ?? NSNull(),
398
+ "timeoutMs": timeoutMs,
399
+ ]
400
+
401
+ let source = """
402
+ const handler = window.__ELIZA_IOS_LOCAL_AGENT_REQUEST__;
403
+ if (typeof handler !== "function") {
404
+ return {
405
+ status: 503,
406
+ statusText: "Service Unavailable",
407
+ headers: { "content-type": "application/json" },
408
+ body: JSON.stringify({
409
+ ok: false,
410
+ error: "ios_ittp_handler_unavailable",
411
+ reason: "The WebView ITTP local-agent request bridge is not installed yet."
412
+ })
413
+ };
414
+ }
415
+ return await handler(options);
416
+ """
417
+
418
+ if #available(iOS 14.0, *) {
419
+ DispatchQueue.main.async {
420
+ Task { @MainActor in
421
+ do {
422
+ let value = try await webView.callAsyncJavaScript(
423
+ source,
424
+ arguments: ["options": payload],
425
+ in: nil,
426
+ contentWorld: .page
427
+ )
428
+ completion(self.parseAgentHTTPResponse(value))
429
+ } catch {
430
+ completion(.failure(error))
431
+ }
432
+ }
433
+ }
434
+ } else {
435
+ completion(.success(localIttpUnavailableResponse()))
436
+ }
437
+ }
438
+
196
439
  private func ensureConversation(
197
440
  endpoint: AgentEndpoint,
198
441
  timeoutMs: Int,
@@ -377,20 +620,7 @@ public class AgentPlugin: CAPPlugin, CAPBridgedPlugin {
377
620
  private func resolveEndpoint(call: CAPPluginCall? = nil) -> AgentEndpoint? {
378
621
  guard let rawBaseURL = readConfiguredString(
379
622
  call: call,
380
- keys: [
381
- "apiBase",
382
- "baseUrl",
383
- "baseURL",
384
- "agentApiBase",
385
- "ELIZA_AGENT_API_BASE",
386
- "ELIZA_API_BASE",
387
- "MILADY_IOS_API_BASE",
388
- "MILADY_IOS_REMOTE_API_BASE",
389
- "MILADY_MOBILE_API_BASE",
390
- "VITE_MILADY_IOS_API_BASE",
391
- "VITE_MILADY_MOBILE_API_BASE",
392
- "VITE_ELIZA_IOS_API_BASE",
393
- ]
623
+ keys: Self.apiBaseConfigKeys
394
624
  ) else {
395
625
  return nil
396
626
  }
@@ -412,11 +642,11 @@ public class AgentPlugin: CAPPlugin, CAPBridgedPlugin {
412
642
  "agentApiToken",
413
643
  "ELIZA_AGENT_API_TOKEN",
414
644
  "ELIZA_API_TOKEN",
415
- "MILADY_IOS_API_TOKEN",
416
- "MILADY_IOS_REMOTE_API_TOKEN",
417
- "MILADY_MOBILE_API_TOKEN",
418
- "VITE_MILADY_IOS_API_TOKEN",
419
- "VITE_MILADY_MOBILE_API_TOKEN",
645
+ "ELIZA_IOS_API_TOKEN",
646
+ "ELIZA_IOS_REMOTE_API_TOKEN",
647
+ "ELIZA_MOBILE_API_TOKEN",
648
+ "VITE_ELIZA_IOS_API_TOKEN",
649
+ "VITE_ELIZA_MOBILE_API_TOKEN",
420
650
  "VITE_ELIZA_IOS_API_TOKEN",
421
651
  ]
422
652
  )
@@ -462,6 +692,69 @@ public class AgentPlugin: CAPPlugin, CAPBridgedPlugin {
462
692
  return nil
463
693
  }
464
694
 
695
+ private func isLocalAgentMode(call: CAPPluginCall? = nil) -> Bool {
696
+ if let endpoint = resolveEndpoint(call: call),
697
+ isLocalAgentEndpoint(endpoint.baseURL) {
698
+ return true
699
+ }
700
+
701
+ if let rawBaseURL = readConfiguredString(
702
+ call: call,
703
+ keys: Self.apiBaseConfigKeys
704
+ ), isLocalAgentIdentity(rawBaseURL) {
705
+ return true
706
+ }
707
+
708
+ guard let rawMode = readConfiguredString(
709
+ call: call,
710
+ keys: [
711
+ "mode",
712
+ "runtimeMode",
713
+ "agentRuntimeMode",
714
+ "ELIZA_IOS_RUNTIME_MODE",
715
+ "ELIZA_MOBILE_RUNTIME_MODE",
716
+ "VITE_ELIZA_IOS_RUNTIME_MODE",
717
+ "VITE_ELIZA_MOBILE_RUNTIME_MODE",
718
+ ]
719
+ ) else {
720
+ return false
721
+ }
722
+
723
+ switch rawMode.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() {
724
+ case "local", "ios-local", "sideload-local", "dev-local":
725
+ return true
726
+ default:
727
+ return false
728
+ }
729
+ }
730
+
731
+ private func isLocalAgentEndpoint(_ url: URL) -> Bool {
732
+ guard let scheme = url.scheme?.lowercased(),
733
+ let host = url.host?.lowercased() else { return false }
734
+ if scheme == localAgentIpcScheme && host == localAgentIpcHost {
735
+ return true
736
+ }
737
+ guard scheme == "http" else { return false }
738
+ return (host == "127.0.0.1" || host == "localhost") && (url.port ?? 80) == localAgentPort
739
+ }
740
+
741
+ private func isLocalAgentIdentity(_ value: String) -> Bool {
742
+ let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
743
+ guard let url = URL(string: trimmed) else { return false }
744
+ return isLocalAgentEndpoint(url)
745
+ }
746
+
747
+ private func localAgentStatus(state: String, error: String?) -> JSObject {
748
+ let startedAt = Self.localStartedAt.map { $0.timeIntervalSince1970 * 1000 }
749
+ return status(
750
+ state: state,
751
+ agentName: "Eliza",
752
+ port: nil,
753
+ startedAt: startedAt,
754
+ error: error
755
+ )
756
+ }
757
+
465
758
  private func normalizedStatus(
466
759
  _ payload: JSObject,
467
760
  fallbackState: String,
@@ -543,6 +836,61 @@ public class AgentPlugin: CAPPlugin, CAPBridgedPlugin {
543
836
  return json
544
837
  }
545
838
 
839
+ private func parseAgentHTTPResponse(_ value: Any?) -> Result<AgentHTTPResponse, Error> {
840
+ guard let payload = value as? JSObject else {
841
+ return .failure(pluginError("iOS local ITTP bridge returned a non-object response"))
842
+ }
843
+ let status = (payload["status"] as? Int)
844
+ ?? (payload["status"] as? NSNumber)?.intValue
845
+ ?? 500
846
+ let statusText = payload["statusText"] as? String
847
+ ?? HTTPURLResponse.localizedString(forStatusCode: status)
848
+ let rawHeaders = payload["headers"] as? JSObject ?? [:]
849
+ var headers: [String: String] = [:]
850
+ for (key, value) in rawHeaders {
851
+ headers[key.lowercased()] = String(describing: value)
852
+ }
853
+ let body = payload["body"] as? String ?? ""
854
+ if let bodyBytes = body.data(using: .utf8), bodyBytes.count > maxResponseBodyBytes {
855
+ return .failure(pluginError("Response body is too large"))
856
+ }
857
+ return .success(AgentHTTPResponse(
858
+ status: status,
859
+ statusText: statusText,
860
+ headers: headers,
861
+ body: body
862
+ ))
863
+ }
864
+
865
+ private func agentHTTPResponseObject(_ response: AgentHTTPResponse) -> JSObject {
866
+ var headers: JSObject = [:]
867
+ for (key, value) in response.headers {
868
+ headers[key] = value
869
+ }
870
+ return [
871
+ "status": response.status,
872
+ "statusText": response.statusText,
873
+ "headers": headers,
874
+ "body": response.body,
875
+ ]
876
+ }
877
+
878
+ private func localIttpUnavailableResponse() -> AgentHTTPResponse {
879
+ let bodyObject: JSObject = [
880
+ "ok": false,
881
+ "error": "ios_ittp_handler_unavailable",
882
+ "reason": localIttpOnlyMessage(),
883
+ ]
884
+ let bodyData = try? JSONSerialization.data(withJSONObject: bodyObject, options: [])
885
+ let body = bodyData.flatMap { String(data: $0, encoding: .utf8) } ?? "{\"ok\":false,\"error\":\"ios_ittp_handler_unavailable\"}"
886
+ return AgentHTTPResponse(
887
+ status: 503,
888
+ statusText: HTTPURLResponse.localizedString(forStatusCode: 503),
889
+ headers: ["content-type": "application/json"],
890
+ body: body
891
+ )
892
+ }
893
+
546
894
  private func httpErrorMessage(prefix: String, response: AgentHTTPResponse) -> String {
547
895
  let body = response.body.trimmingCharacters(in: .whitespacesAndNewlines)
548
896
  if body.isEmpty {
@@ -552,7 +900,11 @@ public class AgentPlugin: CAPPlugin, CAPBridgedPlugin {
552
900
  }
553
901
 
554
902
  private func missingEndpointMessage() -> String {
555
- return "iOS Agent requires a configured HTTP endpoint. Set Agent.apiBase in capacitor.config, an Info.plist/UserDefaults key such as MILADY_IOS_API_BASE or ELIZA_AGENT_API_BASE, or a simulator environment variable. iOS does not bundle a Bun local runtime."
903
+ return "iOS Agent requires a configured HTTP endpoint for remote/cloud mode, or runtimeMode=local for dev/sideload local mode. Set Agent.apiBase in capacitor.config, an Info.plist/UserDefaults key such as ELIZA_IOS_API_BASE or ELIZA_AGENT_API_BASE, or a simulator environment variable."
904
+ }
905
+
906
+ private func localIttpOnlyMessage() -> String {
907
+ return "iOS local agent requests require the WebView ITTP route kernel bridge. Start the app WebView before calling native Agent.request or Agent.chat in local mode."
556
908
  }
557
909
 
558
910
  private func urlEncode(_ value: String) -> String {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elizaos/capacitor-agent",
3
- "version": "2.0.0-beta.1",
3
+ "version": "2.0.3-beta.3",
4
4
  "description": "Starts, stops, and monitors the embedded Eliza agent runtime.",
5
5
  "keywords": [
6
6
  "agent-runtime",
@@ -14,6 +14,8 @@
14
14
  "exports": {
15
15
  ".": {
16
16
  "types": "./dist/esm/index.d.ts",
17
+ "bun": "./src/index.ts",
18
+ "development": "./src/index.ts",
17
19
  "import": "./dist/esm/index.js",
18
20
  "require": "./dist/plugin.cjs.js"
19
21
  },
@@ -33,19 +35,21 @@
33
35
  "repository": {
34
36
  "type": "git",
35
37
  "url": "https://github.com/elizaOS/eliza.git",
36
- "directory": "packages/native-plugins/agent"
38
+ "directory": "plugins/plugin-native-agent"
37
39
  },
38
40
  "scripts": {
39
- "build": "bun run clean && tsc && bun --bun rollup -c rollup.config.mjs",
40
- "clean": "node ../../../scripts/rm-path-recursive.mjs dist",
41
+ "build": "node ../../packages/scripts/with-package-build-lock.mjs plugins/plugin-native-agent -- bun run build:unlocked",
42
+ "clean": "node ../../packages/scripts/rm-path-recursive.mjs dist",
43
+ "test": "vitest run",
41
44
  "prepublishOnly": "bun run build",
42
- "watch": "tsc --watch"
45
+ "watch": "tsc --watch",
46
+ "build:unlocked": "bun run clean && tsc && bunx rollup -c rollup.config.mjs"
43
47
  },
44
48
  "devDependencies": {
45
49
  "@capacitor/core": "^8.3.1",
46
- "rimraf": "^6.0.0",
47
50
  "rollup": "^4.60.2",
48
- "typescript": "^6.0.3"
51
+ "typescript": "^6.0.3",
52
+ "vitest": "^4.0.0"
49
53
  },
50
54
  "peerDependencies": {
51
55
  "@capacitor/core": "^8.3.1"
@@ -61,5 +65,6 @@
61
65
  "android": {
62
66
  "src": "android"
63
67
  }
64
- }
68
+ },
69
+ "gitHead": "f54b0f4eaed317d59fa7dbcdce20f4cdb0734420"
65
70
  }