@fenixforce/edition-mobile 0.1.0

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 (41) hide show
  1. package/dist/index.js +230 -0
  2. package/dist/lib/agent.d.ts +44 -0
  3. package/dist/lib/app-context.d.ts +31 -0
  4. package/dist/lib/boot.d.ts +22 -0
  5. package/dist/lib/entry.d.ts +48 -0
  6. package/dist/lib/providers/hybrid-router.d.ts +60 -0
  7. package/dist/lib/providers/local-inference.d.ts +31 -0
  8. package/dist/lib/storage/identity-store.d.ts +13 -0
  9. package/dist/lib/storage/memory-search.d.ts +30 -0
  10. package/dist/lib/storage/sqlite-adapter.d.ts +22 -0
  11. package/dist/lib/sync/client.d.ts +81 -0
  12. package/dist/lib/sync/triggers.d.ts +59 -0
  13. package/dist/lib/tools/calendar.d.ts +76 -0
  14. package/dist/lib/tools/camera.d.ts +40 -0
  15. package/dist/lib/tools/contacts.d.ts +59 -0
  16. package/dist/lib/tools/location.d.ts +58 -0
  17. package/dist/lib/tools/notifications.d.ts +79 -0
  18. package/dist/lib/tools/registry.d.ts +26 -0
  19. package/dist/lib/tools/reminders.d.ts +65 -0
  20. package/dist/platforms/android-xr/XRAgentService.d.ts +27 -0
  21. package/dist/platforms/android-xr/XRContextCapture.d.ts +47 -0
  22. package/dist/platforms/android-xr/XRGestureHandler.d.ts +41 -0
  23. package/dist/platforms/android-xr/XRNotificationBridge.d.ts +35 -0
  24. package/dist/platforms/android-xr/XRVoiceInterface.d.ts +72 -0
  25. package/dist/platforms/android-xr/display/ConversationOverlay.d.ts +20 -0
  26. package/dist/platforms/android-xr/display/DisplayManager.d.ts +44 -0
  27. package/dist/platforms/android-xr/display/GlanceCard.d.ts +21 -0
  28. package/dist/platforms/android-xr/display/MemoryHUD.d.ts +22 -0
  29. package/dist/platforms/android-xr/display/NotificationPill.d.ts +13 -0
  30. package/dist/platforms/android-xr/xr-entry.d.ts +23 -0
  31. package/dist/platforms/quest/QuestAdapter.d.ts +66 -0
  32. package/dist/platforms/quest/QuestPanelConfig.d.ts +45 -0
  33. package/dist/platforms/quest/quest-entry.d.ts +29 -0
  34. package/dist/platforms/visionos/AgentBridge.d.ts +39 -0
  35. package/dist/platforms/visionos/VisionToolAdapters.d.ts +53 -0
  36. package/dist/platforms/visionos/VisionVoiceInterface.d.ts +32 -0
  37. package/dist/platforms/visionos/visionos-entry.d.ts +24 -0
  38. package/dist/src/hil/push-notifier.d.ts +25 -0
  39. package/dist/src/index.d.ts +72 -0
  40. package/dist/watch/WatchBridge.d.ts +63 -0
  41. package/package.json +51 -0
package/dist/index.js ADDED
@@ -0,0 +1,230 @@
1
+ import{createKernel as Te,bootKernel as Se,KERNEL_VERSION as Oe,EDITION_MOBILE as Pe,agentLoop as Ie,ProviderManager as Me,ToolRegistry as we,ToolRouter as De}from"@fenixforce/kernel";function h(){return crypto.randomUUID()}function S(){return new Date().toISOString()}function T(e){return e?new Date(e):new Date}function C(e){if(!e)return{};try{return JSON.parse(e)}catch{return{}}}function U(e){return{id:e.id,conversationId:e.conversation_id,userId:e.user_id,role:e.role,content:e.content,channel:e.channel,tokenCount:e.token_count??null,metadata:C(e.metadata),createdAt:T(e.created_at)}}function le(e){return{id:e.id,userId:e.user_id,title:e.title??null,channel:e.channel,metadata:C(e.metadata),createdAt:T(e.created_at),updatedAt:T(e.updated_at)}}function Le(e){return{id:e.id,userId:e.user_id,content:e.content,category:e.category,embedding:e.embedding?JSON.parse(e.embedding):null,heat:e.heat,accessCount:e.access_count,lastAccessedAt:T(e.last_accessed_at),createdAt:T(e.created_at),updatedAt:T(e.updated_at)}}function ue(e){return{userId:e.user_id,summary:e.summary,updatedAt:T(e.updated_at)}}function pe(e){return{id:e.id,userId:e.user_id,conversationId:e.conversation_id??null,content:e.content,category:e.category,reviewed:!!e.reviewed,createdAt:T(e.created_at),reviewedAt:e.reviewed_at?T(e.reviewed_at):null}}function ge(e){return{id:e.id,conversationId:e.conversation_id,artifactType:e.artifact_type,title:e.title,content:e.content,metadata:C(e.metadata),createdAt:T(e.created_at),updatedAt:T(e.updated_at)}}function me(e){return{id:e.id,userId:e.user_id,conversationId:e.conversation_id??null,provider:e.provider,model:e.model,inputTokens:e.input_tokens,outputTokens:e.output_tokens,costUsd:e.cost_usd,createdAt:T(e.created_at)}}function b(e){return{id:e.id,type:e.type,status:e.status,priority:e.priority,payload:C(e.payload),result:e.result?C(e.result):null,error:e.error??null,attempts:e.attempts,maxAttempts:e.max_attempts,claimedAt:e.claimed_at?T(e.claimed_at):null,completedAt:e.completed_at?T(e.completed_at):null,createdAt:T(e.created_at),updatedAt:T(e.updated_at)}}function P(e){return{id:e.id,userId:e.user_id,sourceType:e.source_type,uri:e.uri,status:e.status,metadata:C(e.metadata),createdAt:T(e.created_at),updatedAt:T(e.updated_at)}}function ye(e){return{id:e.id,sourceId:e.source_id,content:e.content,embedding:e.embedding?JSON.parse(e.embedding):null,chunkIndex:e.chunk_index,metadata:C(e.metadata),createdAt:T(e.created_at)}}function F(e){return{id:e.id,userId:e.user_id,provider:e.provider,encryptedData:Buffer.from(e.encrypted_data),iv:Buffer.from(e.iv),authTag:Buffer.from(e.auth_tag),createdAt:T(e.created_at),updatedAt:T(e.updated_at)}}function B(e){return{id:e.id,workspaceId:e.workspace_id,fileName:e.file_name,content:e.content,metadata:C(e.metadata),createdAt:T(e.created_at),updatedAt:T(e.updated_at)}}var Ne=`
2
+ CREATE TABLE IF NOT EXISTS conversations (
3
+ id TEXT PRIMARY KEY,
4
+ user_id TEXT NOT NULL,
5
+ title TEXT,
6
+ channel TEXT NOT NULL DEFAULT 'web',
7
+ metadata TEXT NOT NULL DEFAULT '{}',
8
+ created_at TEXT NOT NULL,
9
+ updated_at TEXT NOT NULL
10
+ );
11
+
12
+ CREATE TABLE IF NOT EXISTS messages (
13
+ id TEXT PRIMARY KEY,
14
+ conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
15
+ user_id TEXT NOT NULL,
16
+ role TEXT NOT NULL,
17
+ content TEXT NOT NULL,
18
+ channel TEXT NOT NULL DEFAULT 'web',
19
+ token_count INTEGER,
20
+ metadata TEXT NOT NULL DEFAULT '{}',
21
+ created_at TEXT NOT NULL
22
+ );
23
+ CREATE INDEX IF NOT EXISTS idx_messages_conv ON messages(conversation_id, created_at);
24
+
25
+ CREATE TABLE IF NOT EXISTS memory_segments (
26
+ id TEXT PRIMARY KEY,
27
+ user_id TEXT NOT NULL,
28
+ content TEXT NOT NULL,
29
+ category TEXT NOT NULL DEFAULT 'fact',
30
+ embedding TEXT,
31
+ heat REAL NOT NULL DEFAULT 1.0,
32
+ access_count INTEGER NOT NULL DEFAULT 0,
33
+ last_accessed_at TEXT NOT NULL,
34
+ created_at TEXT NOT NULL,
35
+ updated_at TEXT NOT NULL
36
+ );
37
+
38
+ CREATE TABLE IF NOT EXISTS user_profiles (
39
+ user_id TEXT PRIMARY KEY,
40
+ summary TEXT NOT NULL,
41
+ updated_at TEXT NOT NULL
42
+ );
43
+
44
+ CREATE TABLE IF NOT EXISTS observations (
45
+ id TEXT PRIMARY KEY,
46
+ user_id TEXT NOT NULL,
47
+ conversation_id TEXT,
48
+ content TEXT NOT NULL,
49
+ category TEXT NOT NULL DEFAULT 'general',
50
+ reviewed INTEGER NOT NULL DEFAULT 0,
51
+ created_at TEXT NOT NULL,
52
+ reviewed_at TEXT
53
+ );
54
+
55
+ CREATE TABLE IF NOT EXISTS brain_artifacts (
56
+ id TEXT PRIMARY KEY,
57
+ conversation_id TEXT NOT NULL,
58
+ artifact_type TEXT NOT NULL,
59
+ title TEXT NOT NULL,
60
+ content TEXT NOT NULL,
61
+ metadata TEXT NOT NULL DEFAULT '{}',
62
+ created_at TEXT NOT NULL,
63
+ updated_at TEXT NOT NULL
64
+ );
65
+
66
+ CREATE TABLE IF NOT EXISTS cost_logs (
67
+ id TEXT PRIMARY KEY,
68
+ user_id TEXT NOT NULL,
69
+ conversation_id TEXT,
70
+ provider TEXT NOT NULL,
71
+ model TEXT NOT NULL,
72
+ input_tokens INTEGER NOT NULL DEFAULT 0,
73
+ output_tokens INTEGER NOT NULL DEFAULT 0,
74
+ cost_usd REAL NOT NULL DEFAULT 0,
75
+ created_at TEXT NOT NULL
76
+ );
77
+
78
+ CREATE TABLE IF NOT EXISTS jobs (
79
+ id TEXT PRIMARY KEY,
80
+ type TEXT NOT NULL,
81
+ status TEXT NOT NULL DEFAULT 'pending',
82
+ priority INTEGER NOT NULL DEFAULT 0,
83
+ payload TEXT NOT NULL DEFAULT '{}',
84
+ result TEXT,
85
+ error TEXT,
86
+ attempts INTEGER NOT NULL DEFAULT 0,
87
+ max_attempts INTEGER NOT NULL DEFAULT 3,
88
+ claimed_at TEXT,
89
+ completed_at TEXT,
90
+ created_at TEXT NOT NULL,
91
+ updated_at TEXT NOT NULL
92
+ );
93
+
94
+ CREATE TABLE IF NOT EXISTS content_sources (
95
+ id TEXT PRIMARY KEY,
96
+ user_id TEXT NOT NULL,
97
+ source_type TEXT NOT NULL,
98
+ uri TEXT NOT NULL,
99
+ status TEXT NOT NULL DEFAULT 'pending',
100
+ metadata TEXT NOT NULL DEFAULT '{}',
101
+ created_at TEXT NOT NULL,
102
+ updated_at TEXT NOT NULL
103
+ );
104
+
105
+ CREATE TABLE IF NOT EXISTS content_chunks (
106
+ id TEXT PRIMARY KEY,
107
+ source_id TEXT NOT NULL REFERENCES content_sources(id) ON DELETE CASCADE,
108
+ content TEXT NOT NULL,
109
+ embedding TEXT,
110
+ chunk_index INTEGER NOT NULL DEFAULT 0,
111
+ metadata TEXT NOT NULL DEFAULT '{}',
112
+ created_at TEXT NOT NULL
113
+ );
114
+
115
+ CREATE TABLE IF NOT EXISTS encrypted_credentials (
116
+ id TEXT PRIMARY KEY,
117
+ user_id TEXT NOT NULL,
118
+ provider TEXT NOT NULL,
119
+ encrypted_data BLOB NOT NULL,
120
+ iv BLOB NOT NULL,
121
+ auth_tag BLOB NOT NULL,
122
+ created_at TEXT NOT NULL,
123
+ updated_at TEXT NOT NULL,
124
+ UNIQUE (user_id, provider)
125
+ );
126
+
127
+ CREATE TABLE IF NOT EXISTS identity_files (
128
+ id TEXT PRIMARY KEY,
129
+ workspace_id TEXT NOT NULL,
130
+ file_name TEXT NOT NULL,
131
+ content TEXT NOT NULL,
132
+ metadata TEXT NOT NULL DEFAULT '{}',
133
+ created_at TEXT NOT NULL,
134
+ updated_at TEXT NOT NULL,
135
+ UNIQUE (workspace_id, file_name)
136
+ );
137
+
138
+ CREATE TABLE IF NOT EXISTS cost_events (
139
+ id TEXT PRIMARY KEY,
140
+ user_id TEXT NOT NULL,
141
+ event_type TEXT NOT NULL,
142
+ amount REAL NOT NULL DEFAULT 0,
143
+ currency TEXT NOT NULL DEFAULT 'USD',
144
+ metadata TEXT NOT NULL DEFAULT '{}',
145
+ created_at TEXT NOT NULL
146
+ );
147
+ `;function v(e){return{async initialize(){e.execSync("PRAGMA journal_mode = WAL;"),e.execSync("PRAGMA foreign_keys = ON;"),e.execSync(Ne)},async close(){},async saveMessage(t){let n=h(),o=S();return e.runSync(`INSERT INTO messages (id, conversation_id, user_id, role, content, channel, token_count, metadata, created_at)
148
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,[n,t.conversationId,t.userId,t.role,t.content,t.channel,t.tokenCount,JSON.stringify(t.metadata),o]),U(e.getFirstSync("SELECT * FROM messages WHERE id = ?",[n]))},async getMessagesByConversation(t,n=50){return e.getAllSync("SELECT * FROM messages WHERE conversation_id = ? ORDER BY created_at ASC LIMIT ?",[t,n]).map(U)},async getMessageById(t){let n=e.getFirstSync("SELECT * FROM messages WHERE id = ?",[t]);return n?U(n):null},async createConversation(t){let n=h(),o=S();return e.runSync(`INSERT INTO conversations (id, user_id, title, channel, metadata, created_at, updated_at)
149
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,[n,t.userId,t.title,t.channel,JSON.stringify(t.metadata),o,o]),le(e.getFirstSync("SELECT * FROM conversations WHERE id = ?",[n]))},async listConversations(t,n){let o=n?.limit??50,i=n?.offset??0;return e.getAllSync("SELECT * FROM conversations WHERE user_id = ? ORDER BY updated_at DESC LIMIT ? OFFSET ?",[t,o,i]).map(le)},async deleteConversation(t){e.runSync("DELETE FROM conversations WHERE id = ?",[t])},async saveMemorySegment(t){let n=h(),o=S();return e.runSync(`INSERT INTO memory_segments (id, user_id, content, category, embedding, heat, access_count, last_accessed_at, created_at, updated_at)
150
+ VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?)`,[n,t.userId,t.content,t.category,t.embedding?JSON.stringify(t.embedding):null,t.heat,o,o,o]),Le(e.getFirstSync("SELECT * FROM memory_segments WHERE id = ?",[n]))},async searchMemoryByEmbedding(t,n,o=10){return[]},async getProfile(t){let n=e.getFirstSync("SELECT * FROM user_profiles WHERE user_id = ?",[t]);return n?ue(n):null},async upsertProfile(t,n){let o=S();return e.runSync(`INSERT INTO user_profiles (user_id, summary, updated_at) VALUES (?, ?, ?)
151
+ ON CONFLICT (user_id) DO UPDATE SET summary = excluded.summary, updated_at = excluded.updated_at`,[t,n,o]),ue(e.getFirstSync("SELECT * FROM user_profiles WHERE user_id = ?",[t]))},async updateHeatScores(t,n){if(t.length===0)return;let o=S(),i=t.map(()=>"?").join(",");e.runSync(`UPDATE memory_segments SET heat = heat + ?, access_count = access_count + 1, last_accessed_at = ?
152
+ WHERE id IN (${i})`,[n,o,...t])},async saveObservation(t){let n=h(),o=S();return e.runSync(`INSERT INTO observations (id, user_id, conversation_id, content, category, reviewed, created_at)
153
+ VALUES (?, ?, ?, ?, ?, 0, ?)`,[n,t.userId,t.conversationId,t.content,t.category,o]),pe(e.getFirstSync("SELECT * FROM observations WHERE id = ?",[n]))},async listPendingObservations(t,n){let o=n?.limit??50,i=n?.offset??0;return e.getAllSync("SELECT * FROM observations WHERE user_id = ? AND reviewed = 0 ORDER BY created_at DESC LIMIT ? OFFSET ?",[t,o,i]).map(pe)},async markObservationReviewed(t){e.runSync("UPDATE observations SET reviewed = 1, reviewed_at = ? WHERE id = ?",[S(),t])},async saveBrainArtifact(t){let n=h(),o=S();return e.runSync(`INSERT INTO brain_artifacts (id, conversation_id, artifact_type, title, content, metadata, created_at, updated_at)
154
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,[n,t.conversationId,t.artifactType,t.title,t.content,JSON.stringify(t.metadata),o,o]),ge(e.getFirstSync("SELECT * FROM brain_artifacts WHERE id = ?",[n]))},async loadBrainArtifacts(t){return e.getAllSync("SELECT * FROM brain_artifacts WHERE conversation_id = ? ORDER BY created_at ASC",[t]).map(ge)},async deleteBrainArtifact(t){e.runSync("DELETE FROM brain_artifacts WHERE id = ?",[t])},async insertCostLog(t){let n=h(),o=S();return e.runSync(`INSERT INTO cost_logs (id, user_id, conversation_id, provider, model, input_tokens, output_tokens, cost_usd, created_at)
155
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,[n,t.userId,t.conversationId,t.provider,t.model,t.inputTokens,t.outputTokens,t.costUsd,o]),me(e.getFirstSync("SELECT * FROM cost_logs WHERE id = ?",[n]))},async queryCostLogs(t){return e.getAllSync("SELECT * FROM cost_logs WHERE user_id = ? AND created_at >= ? AND created_at <= ? ORDER BY created_at DESC",[t.userId,t.startDate.toISOString(),t.endDate.toISOString()]).map(me)},async createJob(t){let n=h(),o=S();return e.runSync(`INSERT INTO jobs (id, type, status, priority, payload, attempts, max_attempts, created_at, updated_at)
156
+ VALUES (?, ?, 'pending', ?, ?, 0, ?, ?, ?)`,[n,t.type,t.priority,JSON.stringify(t.payload),t.maxAttempts,o,o]),b(e.getFirstSync("SELECT * FROM jobs WHERE id = ?",[n]))},async claimNextJob(t){let n=t&&t.length>0?`AND type IN (${t.map(()=>"?").join(",")})`:"",o=t&&t.length>0?t:[],i=e.getFirstSync(`SELECT * FROM jobs WHERE status = 'pending' ${n} ORDER BY priority DESC, created_at ASC LIMIT 1`,o);if(!i)return null;let s=S();return e.runSync("UPDATE jobs SET status = 'running', claimed_at = ?, attempts = attempts + 1, updated_at = ? WHERE id = ?",[s,s,i.id]),b(e.getFirstSync("SELECT * FROM jobs WHERE id = ?",[i.id]))},async updateJobStatus(t,n,o,i){let s=S(),a=n==="completed"||n==="failed"||n==="dead"?s:null;return e.runSync("UPDATE jobs SET status = ?, result = ?, error = ?, completed_at = COALESCE(?, completed_at), updated_at = ? WHERE id = ?",[n,o?JSON.stringify(o):null,i??null,a,s,t]),b(e.getFirstSync("SELECT * FROM jobs WHERE id = ?",[t]))},async getJobById(t){let n=e.getFirstSync("SELECT * FROM jobs WHERE id = ?",[t]);return n?b(n):null},async listJobs(t,n){let o=[],i=[];t.status&&(o.push("status = ?"),i.push(t.status)),t.type&&(o.push("type = ?"),i.push(t.type));let s=o.length>0?`WHERE ${o.join(" AND ")}`:"",a=n?.limit??50,l=n?.offset??0;return i.push(a,l),e.getAllSync(`SELECT * FROM jobs ${s} ORDER BY created_at DESC LIMIT ? OFFSET ?`,i).map(b)},async markDeadJobs(t){let n=S(),o=new Date(Date.now()-t*1e3).toISOString(),{changes:i}=e.runSync("UPDATE jobs SET status = 'dead', updated_at = ?, completed_at = ? WHERE status = 'running' AND claimed_at < ?",[n,n,o]);return i},async createContentSource(t){let n=h(),o=S();return e.runSync(`INSERT INTO content_sources (id, user_id, source_type, uri, status, metadata, created_at, updated_at)
157
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,[n,t.userId,t.sourceType,t.uri,t.status,JSON.stringify(t.metadata),o,o]),P(e.getFirstSync("SELECT * FROM content_sources WHERE id = ?",[n]))},async updateContentSourceStatus(t,n){return e.runSync("UPDATE content_sources SET status = ?, updated_at = ? WHERE id = ?",[n,S(),t]),P(e.getFirstSync("SELECT * FROM content_sources WHERE id = ?",[t]))},async getContentSourceById(t){let n=e.getFirstSync("SELECT * FROM content_sources WHERE id = ?",[t]);return n?P(n):null},async listContentSources(t,n){let o=n?.limit??50,i=n?.offset??0;return e.getAllSync("SELECT * FROM content_sources WHERE user_id = ? ORDER BY created_at DESC LIMIT ? OFFSET ?",[t,o,i]).map(P)},async bulkInsertContentChunks(t){if(t.length===0)return[];let n=[],o=S();for(let s of t){let a=h();n.push(a),e.runSync(`INSERT INTO content_chunks (id, source_id, content, embedding, chunk_index, metadata, created_at)
158
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,[a,s.sourceId,s.content,s.embedding?JSON.stringify(s.embedding):null,s.chunkIndex,JSON.stringify(s.metadata),o])}let i=n.map(()=>"?").join(",");return e.getAllSync(`SELECT * FROM content_chunks WHERE id IN (${i}) ORDER BY chunk_index`,n).map(ye)},async searchContentChunksByEmbedding(t,n,o=10){return[]},async searchContentChunksByText(t,n,o=10){if(t.length===0)return[];let i=t.map(()=>"?").join(",");return e.getAllSync(`SELECT * FROM content_chunks WHERE source_id IN (${i}) AND content LIKE ? LIMIT ?`,[...t,`%${n}%`,o]).map(s=>({chunk:ye(s),score:1}))},async deleteContentChunksBySource(t){let{changes:n}=e.runSync("DELETE FROM content_chunks WHERE source_id = ?",[t]);return n},async saveCredential(t){let n=h(),o=S();return e.runSync(`INSERT INTO encrypted_credentials (id, user_id, provider, encrypted_data, iv, auth_tag, created_at, updated_at)
159
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
160
+ ON CONFLICT (user_id, provider) DO UPDATE
161
+ SET encrypted_data = excluded.encrypted_data, iv = excluded.iv, auth_tag = excluded.auth_tag, updated_at = excluded.updated_at`,[n,t.userId,t.provider,t.encryptedData,t.iv,t.authTag,o,o]),F(e.getFirstSync("SELECT * FROM encrypted_credentials WHERE user_id = ? AND provider = ?",[t.userId,t.provider]))},async getCredential(t,n){let o=e.getFirstSync("SELECT * FROM encrypted_credentials WHERE user_id = ? AND provider = ?",[t,n]);return o?F(o):null},async deleteCredential(t){e.runSync("DELETE FROM encrypted_credentials WHERE id = ?",[t])},async listCredentials(t){return e.getAllSync("SELECT * FROM encrypted_credentials WHERE user_id = ? ORDER BY provider",[t]).map(F)},async saveIdentityFile(t){let n=h(),o=S();return e.runSync(`INSERT INTO identity_files (id, workspace_id, file_name, content, metadata, created_at, updated_at)
162
+ VALUES (?, ?, ?, ?, ?, ?, ?)
163
+ ON CONFLICT (workspace_id, file_name) DO UPDATE
164
+ SET content = excluded.content, metadata = excluded.metadata, updated_at = excluded.updated_at`,[n,t.workspaceId,t.fileName,t.content,JSON.stringify(t.metadata),o,o]),B(e.getFirstSync("SELECT * FROM identity_files WHERE workspace_id = ? AND file_name = ?",[t.workspaceId,t.fileName]))},async getIdentityFile(t){let n=e.getFirstSync("SELECT * FROM identity_files WHERE workspace_id = ? ORDER BY created_at DESC LIMIT 1",[t]);return n?B(n):null},async listIdentityFiles(){return e.getAllSync("SELECT * FROM identity_files ORDER BY created_at DESC").map(B)},async query(t,n){return e.getAllSync(t,n)}}}function xe(e){let r=0;for(let t of e.messages)typeof t.content=="string"&&(r+=t.content.length);return Math.ceil(r/4)}function fe(e,r){let t=r.isOnline?.()??!0,n=r.localProvider;return!t&&n?n:e.tools?.length&&n&&!n.capabilities().nativeToolCalling||n&&xe(e)>n.capabilities().maxContextTokens?r.cloudProvider:r.preferLocal&&n?n:t?r.cloudProvider:n??r.cloudProvider}function A(e){return{id:"hybrid-router",async complete(r){return fe(r,e).complete(r)},async*stream(r){let t=fe(r,e);t.stream&&(yield*t.stream(r))},capabilities(){let r=e.cloudProvider.capabilities(),t=e.localProvider?.capabilities();return{nativeToolCalling:r.nativeToolCalling||(t?.nativeToolCalling??!1),vision:r.vision||(t?.vision??!1),streaming:r.streaming||(t?.streaming??!1),structuredOutput:r.structuredOutput||(t?.structuredOutput??!1),maxContextTokens:Math.max(r.maxContextTokens,t?.maxContextTokens??0)}}}}var X="mobile",ke={...Pe},Ee={edition:X,channels:["telegram","web","sms"],tools:["category:llm","category:file","category:memory","category:notification"],layers:["memory"]};function Ue(e){return Te({...Ee,...e})}async function Fe(e){return Se({edition:X,storageBackend:"sqlite",tools:["memory","files","tasks"],maxTurns:10,tokenBudget:64e3,...e})}async function L(e){let r=v(e.db);await r.initialize();let t=A({cloudProvider:e.cloudProvider,localProvider:e.localProvider,preferLocal:e.preferLocal??!1,isOnline:e.isOnline}),n=new we;n.register({name:"memory.search",description:"Search memory segments",parameters:{query:{type:"string",required:!0}},handler:async l=>({profile:await r.getProfile(String(l.query))})});let o=new De(n),i=new Me;i.register("chat",t);let s=crypto.randomUUID(),a={providers:i,toolRouter:o,sessionId:s,maxTurns:e.maxTurns??10,tokenBudget:e.tokenBudget??64e3,systemPrompt:"You are Fenix, a helpful AI assistant running on mobile. Keep responses concise."};return{run(l){return Ie(a,l)},storage:r,provider:t,sessionId:s}}var Be=`
165
+ CREATE VIRTUAL TABLE IF NOT EXISTS memory_fts
166
+ USING fts5(content, category, content=memory_segments, content_rowid=rowid);
167
+
168
+ -- Insert trigger: populate FTS on memory_segments insert
169
+ CREATE TRIGGER IF NOT EXISTS memory_fts_insert AFTER INSERT ON memory_segments
170
+ BEGIN
171
+ INSERT INTO memory_fts(rowid, content, category)
172
+ VALUES (NEW.rowid, NEW.content, NEW.category);
173
+ END;
174
+
175
+ -- Delete trigger: remove from FTS on memory_segments delete
176
+ CREATE TRIGGER IF NOT EXISTS memory_fts_delete AFTER DELETE ON memory_segments
177
+ BEGIN
178
+ INSERT INTO memory_fts(memory_fts, rowid, content, category)
179
+ VALUES ('delete', OLD.rowid, OLD.content, OLD.category);
180
+ END;
181
+
182
+ -- Update trigger: delete then re-insert
183
+ CREATE TRIGGER IF NOT EXISTS memory_fts_update AFTER UPDATE ON memory_segments
184
+ BEGIN
185
+ INSERT INTO memory_fts(memory_fts, rowid, content, category)
186
+ VALUES ('delete', OLD.rowid, OLD.content, OLD.category);
187
+ INSERT INTO memory_fts(rowid, content, category)
188
+ VALUES (NEW.rowid, NEW.content, NEW.category);
189
+ END;
190
+ `;function Xe(e,r){if(e.length!==r.length||e.length===0)return 0;let t=0,n=0,o=0;for(let s=0;s<e.length;s++)t+=e[s]*r[s],n+=e[s]*e[s],o+=r[s]*r[s];let i=Math.sqrt(n)*Math.sqrt(o);return i===0?0:t/i}function he(e,r,t){let n=new Map;for(let o=0;o<e.length;o++)n.set(e[o],r*(1/(t+o+1)));return n}function N(e){let{db:r}=e,t=e.rrfK??60,n=e.ftsWeight??1,o=e.embeddingWeight??1;r.execSync(Be);function i(a,l=10){let g=r.getAllSync(`SELECT m.id, m.content, m.category, fts.rank
191
+ FROM memory_fts fts
192
+ JOIN memory_segments m ON m.rowid = fts.rowid
193
+ WHERE memory_fts MATCH ?
194
+ ORDER BY fts.rank
195
+ LIMIT ?`,[a,l]),c=g.length>0?Math.abs(g[0].rank):1;return g.map(d=>({id:d.id,content:d.content,category:d.category,confidence:c>0?Math.abs(d.rank)/c:0,score:-d.rank}))}async function s(a,l,g=10){let c=i(a,g*2),f=r.getAllSync("SELECT id, content, category, embedding FROM memory_segments WHERE embedding IS NOT NULL").map(p=>({id:p.id,content:p.content,category:p.category,score:Xe(l,JSON.parse(p.embedding))})).sort((p,y)=>y.score-p.score).slice(0,g*2),_=he(c.map(p=>p.id),n,t),E=he(f.map(p=>p.id),o,t),m=new Map;for(let p of c)m.set(p.id,{content:p.content,category:p.category});for(let p of f)m.has(p.id)||m.set(p.id,{content:p.content,category:p.category});let u=[];for(let[p,y]of m){let O=(_.get(p)??0)+(E.get(p)??0),de=n*(1/(t+1))+o*(1/(t+1));u.push({id:p,content:y.content,category:y.category,confidence:de>0?O/de:0,score:O})}return u.sort((p,y)=>y.score-p.score),u.slice(0,g)}return{searchFTS:i,searchHybrid:s}}function Ve(e){return{name:"device.calendar",description:"Read and create calendar events on this device",parameters:{action:{type:"string",description:"list | create | search",required:!0},startDate:{type:"string",description:"ISO date for query range start"},endDate:{type:"string",description:"ISO date for query range end"},title:{type:"string",description:"Event title (for create)"},location:{type:"string",description:"Event location (for create)"},query:{type:"string",description:"Search query"}},handler:async r=>{let{status:t}=await e.requestPermissions();if(t!=="granted")return{error:"Calendar permission denied"};let n=String(r.action);if(n==="list"){let o=r.startDate?new Date(String(r.startDate)):new Date,i=r.endDate?new Date(String(r.endDate)):new Date(o.getTime()+7*864e5),a=(await e.getCalendarsAsync()).map(g=>g.id);return{events:(await e.getEventsAsync(a,o,i)).map(_e)}}if(n==="create"){let o=await e.getCalendarsAsync();return o.length===0?{error:"No calendars available"}:{created:!0,eventId:await e.createEventAsync(o[0].id,{title:String(r.title??"Untitled"),startDate:r.startDate?new Date(String(r.startDate)):new Date,endDate:r.endDate?new Date(String(r.endDate)):new Date(Date.now()+36e5),location:r.location?String(r.location):void 0})}}if(n==="search"){let o=new Date(Date.now()-2592e6),i=new Date(Date.now()+90*864e5),a=(await e.getCalendarsAsync()).map(d=>d.id),l=await e.getEventsAsync(a,o,i),g=String(r.query??"").toLowerCase();return{events:l.filter(d=>d.title.toLowerCase().includes(g)||d.notes?.toLowerCase().includes(g)).map(_e)}}return{error:`Unknown action: ${n}`}}}}function _e(e){return{id:e.id,title:e.title,start:e.startDate,end:e.endDate,location:e.location}}function He(e){return{name:"device.contacts",description:"Search and read contacts on this device",parameters:{action:{type:"string",description:"search | list",required:!0},query:{type:"string",description:"Name to search for"},limit:{type:"number",description:"Max results (default 10)"}},handler:async r=>{let{status:t}=await e.requestPermissions();if(t!=="granted")return{error:"Contacts permission denied"};let n=String(r.action),o=Number(r.limit)||10,{data:i}=await e.getContactsAsync({fields:["phoneNumbers","emails"],pageSize:200});if(n==="list")return{contacts:i.slice(0,o).map(Ce)};if(n==="search"){let s=String(r.query??"").toLowerCase();return{contacts:i.filter(l=>(l.name??`${l.firstName??""} ${l.lastName??""}`).toLowerCase().includes(s)).slice(0,o).map(Ce)}}return{error:`Unknown action: ${n}`}}}}function Ce(e){return{id:e.id,name:e.name??`${e.firstName??""} ${e.lastName??""}`.trim(),phone:e.phoneNumbers?.[0]?.number??null,email:e.emails?.[0]?.email??null}}function We(e){return{name:"device.camera",description:"Capture a photo or pick from gallery",parameters:{action:{type:"string",description:"capture | pick",required:!0}},handler:async r=>{let t=String(r.action);if(t==="capture"){let{status:n}=await e.requestCameraPermissions();if(n!=="granted")return{error:"Camera permission denied"};let o=await e.takePicture();return{uri:o.uri,width:o.width,height:o.height}}if(t==="pick"){let n=await e.pickImage();return n?{uri:n.uri,width:n.width,height:n.height}:{error:"No image selected"}}return{error:`Unknown action: ${t}`}}}}function Ge(e){return{name:"device.location",description:"Get current location or reverse geocode coordinates",parameters:{action:{type:"string",description:"current | geocode",required:!0},latitude:{type:"number",description:"Latitude (for geocode)"},longitude:{type:"number",description:"Longitude (for geocode)"}},handler:async r=>{let{status:t}=await e.requestForegroundPermissions();if(t!=="granted")return{error:"Location permission denied"};let n=String(r.action);if(n==="current"){let o=await e.getCurrentPosition(),[i]=await e.reverseGeocode(o.coords.latitude,o.coords.longitude);return{latitude:o.coords.latitude,longitude:o.coords.longitude,altitude:o.coords.altitude,address:i?`${i.street??""}, ${i.city??""}, ${i.region??""}, ${i.country??""}`.replace(/^, |, $/g,""):null}}if(n==="geocode"){let o=Number(r.latitude),i=Number(r.longitude);if(isNaN(o)||isNaN(i))return{error:"Invalid coordinates"};let[s]=await e.reverseGeocode(o,i);return s??{error:"No results"}}return{error:`Unknown action: ${n}`}}}}function je(e){return{name:"device.reminders",description:"Read and create reminders on this device (iOS)",parameters:{action:{type:"string",description:"list | create",required:!0},title:{type:"string",description:"Reminder title (for create)"},dueDate:{type:"string",description:"ISO due date (for create)"},notes:{type:"string",description:"Notes (for create)"}},handler:async r=>{let{status:t}=await e.requestRemindersPermissions();if(t!=="granted")return{error:"Reminders permission denied"};let n=String(r.action),o=await e.getCalendarsAsync("reminder");if(o.length===0)return{error:"No reminder calendars available"};let i=o.map(s=>s.id);return n==="list"?{reminders:(await e.getRemindersAsync(i)).map(a=>({id:a.id,title:a.title,dueDate:a.dueDate??null,completed:a.completed}))}:n==="create"?{created:!0,reminderId:await e.createReminderAsync(i[0],{title:String(r.title??"Untitled reminder"),dueDate:r.dueDate?new Date(String(r.dueDate)):void 0,notes:r.notes?String(r.notes):void 0})}:{error:`Unknown action: ${n}`}}}}function Qe(e){return{name:"device.notifications",description:"Schedule, list, and cancel local notifications",parameters:{action:{type:"string",description:"schedule | list | cancel",required:!0},title:{type:"string",description:"Notification title (for schedule)"},body:{type:"string",description:"Notification body (for schedule)"},delaySeconds:{type:"number",description:"Delay in seconds (for schedule, default immediate)"},notificationId:{type:"string",description:"Notification ID (for cancel)"}},handler:async r=>{let{status:t}=await e.requestPermissions();if(t!=="granted")return{error:"Notification permission denied"};let n=String(r.action);if(n==="schedule"){let o=Number(r.delaySeconds)||0;return{scheduled:!0,notificationId:await e.scheduleNotification({title:String(r.title??"Fenix"),body:String(r.body??"")},o>0?{seconds:o}:null)}}return n==="list"?{notifications:(await e.getPendingNotifications()).map(i=>({id:i.identifier,title:i.content.title,body:i.content.body}))}:n==="cancel"?r.notificationId?(await e.cancelNotification(String(r.notificationId)),{cancelled:!0}):{error:"notificationId required"}:{error:`Unknown action: ${n}`}}}}var I=new Set(["shell","code_execute","browser","mcp_server"]);function V(e){let r=e.tools.filter(i=>!I.has(i.name));function t(){return e.isOnline?.()??!0?r:r.filter(s=>!s.requiresNetwork)}function n(i){if(I.has(i))return;let s=r.find(a=>a.name===i);if(s&&!(s.requiresNetwork&&!(e.isOnline?.()??!0)))return s}function o(i){return I.has(i)}return{listAvailable:t,get:n,isExcluded:o,EXCLUDED:[...I]}}var qe="default",Ye={"soul.md":["# Soul","You are Fenix, a helpful AI assistant.","You are thoughtful, concise, and respectful."].join(`
196
+ `),"persona.md":["# Persona","Friendly and professional. Adapt to the user's communication style."].join(`
197
+ `),"user.md":["# User","No user profile yet. Learn about the user through conversation."].join(`
198
+ `),"knowledge.md":["# Knowledge","No domain knowledge loaded yet."].join(`
199
+ `)};function x(e,r=qe){function t(){for(let[n,o]of Object.entries(Ye))if(!e.getFirstSync("SELECT id FROM identity_files WHERE workspace_id = ? AND file_name = ?",[r,n])){let s=crypto.randomUUID(),a=new Date().toISOString();e.runSync(`INSERT INTO identity_files (id, workspace_id, file_name, content, metadata, created_at, updated_at)
200
+ VALUES (?, ?, ?, ?, '{}', ?, ?)`,[s,r,n,o,a,a])}}return t(),{async read(n){return e.getFirstSync("SELECT content FROM identity_files WHERE workspace_id = ? AND file_name = ?",[r,n])?.content??null},async write(n,o){let i=new Date().toISOString();if(e.getFirstSync("SELECT id FROM identity_files WHERE workspace_id = ? AND file_name = ?",[r,n]))e.runSync("UPDATE identity_files SET content = ?, updated_at = ? WHERE workspace_id = ? AND file_name = ?",[o,i,r,n]);else{let a=crypto.randomUUID();e.runSync(`INSERT INTO identity_files (id, workspace_id, file_name, content, metadata, created_at, updated_at)
201
+ VALUES (?, ?, ?, ?, '{}', ?, ?)`,[a,r,n,o,i,i])}},async list(){return e.getAllSync("SELECT file_name FROM identity_files WHERE workspace_id = ? ORDER BY file_name",[r]).map(o=>o.file_name)},async getVersion(n){let o=e.getFirstSync("SELECT updated_at FROM identity_files WHERE workspace_id = ? AND file_name = ?",[r,n]);return o?new Date(o.updated_at).getTime():0}}}async function $e(e){let r=e.userId??"default",t=v(e.db);await t.initialize();let n=N({db:e.db}),o=x(e.db),i=await L({db:e.db,cloudProvider:e.cloudProvider,localProvider:e.localProvider,preferLocal:e.preferLocal,isOnline:e.isOnline});return{db:e.db,storage:t,identityStore:o,memorySearch:n,agent:i,userId:r,syncEnabled:!1,syncPending:!1,lastSyncTime:null,isOnline:e.isOnline??(()=>!0)}}function Ke(e,r,t){let n=!1;return{id:`embedded:${e.split("/").pop()??"local"}`,async complete(o){if(!t)throw new Error("Embedded inference engine not available. Install react-native-llama.");n||(await t.load(e,r),n=!0);let i=o.messages.map(a=>a.role==="system"?`<|system|>
202
+ ${a.content}
203
+ `:a.role==="user"?`<|user|>
204
+ ${a.content}
205
+ `:`<|assistant|>
206
+ ${a.content}
207
+ `).join("")+`<|assistant|>
208
+ `,s=await t.complete(i,o.maxTokens??512,o.temperature??.7);return{content:s,finishReason:"stop",usage:{inputTokens:Math.ceil(i.length/4),outputTokens:Math.ceil(s.length/4)}}},capabilities(){return{nativeToolCalling:!1,vision:!1,streaming:!1,structuredOutput:!1,maxContextTokens:r}}}}function ze(e,r,t=globalThis.fetch){let n=e.replace(/\/$/,"");return{id:`ollama:${r}`,async complete(o){let i=o.messages.map(l=>({role:l.role,content:l.content??""})),s=await t(`${n}/api/chat`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model:r,messages:i,stream:!1,options:{temperature:o.temperature??.7,num_predict:o.maxTokens??512}})});if(!s.ok)throw new Error(`Ollama error: ${s.status} ${s.statusText}`);let a=await s.json();return{content:a.message.content,finishReason:"stop",usage:{inputTokens:a.prompt_eval_count??0,outputTokens:a.eval_count??0}}},capabilities(){return{nativeToolCalling:!1,vision:!1,streaming:!0,structuredOutput:!1,maxContextTokens:4096}}}}function H(e){return e.type==="embedded"?Ke(e.modelPath??"",e.contextSize??2048):ze(e.ollamaUrl??"http://localhost:11434",e.model??"qwen3.5:2b",e.fetchFn)}var Je=`
209
+ CREATE TABLE IF NOT EXISTS sync_state (
210
+ key TEXT PRIMARY KEY,
211
+ value TEXT NOT NULL
212
+ );
213
+
214
+ CREATE TABLE IF NOT EXISTS sync_conflicts (
215
+ id TEXT PRIMARY KEY,
216
+ entity_type TEXT NOT NULL,
217
+ entity_id TEXT NOT NULL,
218
+ local_version INTEGER NOT NULL,
219
+ remote_version INTEGER NOT NULL,
220
+ local_data TEXT NOT NULL,
221
+ remote_data TEXT NOT NULL,
222
+ resolved_to TEXT NOT NULL,
223
+ created_at TEXT NOT NULL
224
+ );
225
+ `;function Ze(e,r){return r.syncVersion>e.syncVersion?"remote":e.syncVersion>r.syncVersion?"local":new Date(r.updatedAt)>=new Date(e.updatedAt)?"remote":"local"}async function M(e,r,t,n){let o=null;for(let i=0;i<=n;i++)try{let s=await t(e,r);if(!s.ok)throw new Error(`HTTP ${s.status}: ${s.statusText}`);return s}catch(s){if(o=s instanceof Error?s:new Error(String(s)),i<n){let a=Math.pow(2,i+1)*1e3;await new Promise(l=>setTimeout(l,a))}}throw o}function W(e){let{serverUrl:r,authToken:t,db:n}=e,o=e.fetchFn??globalThis.fetch,i=e.maxRetries??3;n.execSync(Je);let s=r.replace(/\/$/,""),a={"Content-Type":"application/json",Authorization:`Bearer ${t}`};function l(m){return n.getFirstSync("SELECT value FROM sync_state WHERE key = ?",[m])?.value??null}function g(m,u){n.runSync(`INSERT INTO sync_state (key, value) VALUES (?, ?)
226
+ ON CONFLICT (key) DO UPDATE SET value = excluded.value`,[m,u])}function c(m,u,p){let y=crypto.randomUUID();n.runSync(`INSERT INTO sync_conflicts (id, entity_type, entity_id, local_version, remote_version, local_data, remote_data, resolved_to, created_at)
227
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,[y,m.entityType,m.entityId,m.localVersion,m.remoteVersion,JSON.stringify(u),JSON.stringify(p),m.resolvedTo,new Date().toISOString()])}function d(m){let u=n.getAllSync("SELECT id, content, category, heat, updated_at FROM memory_segments WHERE updated_at > ? ORDER BY updated_at ASC",[m]).map(y=>({id:y.id,content:y.content,category:y.category,confidence:y.heat,syncVersion:1,updatedAt:y.updated_at})),p=n.getAllSync("SELECT id, conversation_id, role, content, created_at FROM messages WHERE created_at > ? ORDER BY created_at ASC",[m]).map(y=>({id:y.id,conversationId:y.conversation_id,role:y.role,content:y.content,syncVersion:1,createdAt:y.created_at}));return{facts:u,messages:p}}function f(m){for(let u of m){let p=n.getFirstSync("SELECT id, heat, updated_at FROM memory_segments WHERE id = ?",[u.id]);if(p){let y=Ze({syncVersion:1,updatedAt:p.updated_at},{syncVersion:u.syncVersion,updatedAt:u.updatedAt});y==="remote"&&n.runSync("UPDATE memory_segments SET content = ?, category = ?, heat = ?, updated_at = ? WHERE id = ?",[u.content,u.category,u.confidence,u.updatedAt,u.id]),c({entityType:"fact",entityId:u.id,localVersion:1,remoteVersion:u.syncVersion,localUpdatedAt:p.updated_at,remoteUpdatedAt:u.updatedAt,resolvedTo:y},p,u)}else{let y=u.updatedAt;n.runSync(`INSERT INTO memory_segments (id, user_id, content, category, heat, access_count, last_accessed_at, created_at, updated_at)
228
+ VALUES (?, 'default', ?, ?, ?, 0, ?, ?, ?)`,[u.id,u.content,u.category,u.confidence,y,y,y])}}}function _(m){for(let u of m)n.getFirstSync("SELECT id FROM messages WHERE id = ?",[u.id])||n.runSync(`INSERT INTO messages (id, conversation_id, user_id, role, content, channel, metadata, created_at)
229
+ VALUES (?, ?, 'default', ?, ?, 'sync', '{}', ?)`,[u.id,u.conversationId,u.role,u.content,u.createdAt])}function E(m){for(let u of m){let p=n.getFirstSync("SELECT updated_at FROM identity_files WHERE workspace_id = 'default' AND file_name = ?",[u.fileName]);if(p)new Date(u.updatedAt)>new Date(p.updated_at)&&n.runSync("UPDATE identity_files SET content = ?, updated_at = ? WHERE workspace_id = 'default' AND file_name = ?",[u.content,u.updatedAt,u.fileName]);else{let y=crypto.randomUUID();n.runSync(`INSERT INTO identity_files (id, workspace_id, file_name, content, metadata, created_at, updated_at)
230
+ VALUES (?, 'default', ?, ?, '{}', ?, ?)`,[y,u.fileName,u.content,u.updatedAt,u.updatedAt])}}}return{async status(){return(await M(`${s}/sync/status`,{method:"GET",headers:a},o,i)).json()},async push(m){let p=await(await M(`${s}/sync/push`,{method:"POST",headers:a,body:JSON.stringify(m)},o,i)).json();if(p.conflicts.length>0)for(let y of p.conflicts)c(y,null,null);return p},async pull(m){let p=await(await M(`${s}/sync/pull?since=${encodeURIComponent(m)}`,{method:"GET",headers:a},o,i)).json();return f(p.facts),_(p.messages),E(p.identity),p},async ack(m){await M(`${s}/sync/ack`,{method:"POST",headers:a,body:JSON.stringify({receivedUpTo:m})},o,i),g("last_sync",m)},async fullSync(){let m=l("last_sync")??"1970-01-01T00:00:00.000Z",u=d(m),p=await this.push(u),y=await this.pull(m),O=y.serverTime??p.serverTime??new Date().toISOString();return await this.ack(O),{pushed:p,pulled:y}}}}var G="fenix-background-sync";function j(e){let{syncClient:r,isOnline:t,onSyncStart:n,onSyncEnd:o,registerBackgroundTask:i,unregisterBackgroundTask:s}=e,a=e.debounceMs??3e5,l=e.backgroundIntervalMs??18e5,g=null,c=!1;async function d(f){if(!t())return{success:!1,source:f,error:"offline"};if(c)return{success:!1,source:f,error:"sync_in_progress"};if(n?.(f)===!1)return{success:!1,source:f,error:"cancelled"};c=!0;try{let{pushed:E,pulled:m}=await r.fullSync(),u={success:!0,source:f,pushed:E.accepted,pulled:m.facts.length+m.messages.length+m.identity.length};return o?.(f,u),u}catch(E){let m={success:!1,source:f,error:E instanceof Error?E.message:String(E)};return o?.(f,m),m}finally{c=!1}}return{async onForeground(){return d("foreground")},onConversationIdle(){g&&clearTimeout(g),g=setTimeout(()=>{g=null,d("post_conversation")},a)},async syncNow(){return d("manual")},async onPushNotification(){return d("push")},async registerBackground(){i&&await i(G,l)},async unregisterBackground(){s&&await s(G)},executeSync:d,dispose(){g&&(clearTimeout(g),g=null)}}}async function R(e){let r=e.userId??"default",t=e.isOnline??(()=>!0),n=v(e.db);await n.initialize();let o=N({db:e.db}),i=x(e.db),s=e.localModel?H(e.localModel):void 0,a=A({cloudProvider:e.cloudProvider,localProvider:s,preferLocal:e.preferLocal??!1,isOnline:t}),l=V({tools:e.tools??[],isOnline:t}),g=await L({db:e.db,cloudProvider:e.cloudProvider,localProvider:s,preferLocal:e.preferLocal,isOnline:t}),c=null,d=null,f=!!e.sync;return e.sync&&(c=W({serverUrl:e.sync.serverUrl,authToken:e.sync.authToken,db:e.db}),d=j({syncClient:c,isOnline:t,registerBackgroundTask:e.registerBackgroundTask,unregisterBackgroundTask:e.unregisterBackgroundTask}),await d.registerBackground(),d.onForeground()),{db:e.db,storage:n,identityStore:i,memorySearch:o,agent:g,userId:r,syncEnabled:f,syncPending:!1,lastSyncTime:null,isOnline:t,syncClient:c,syncTriggers:d,toolRegistry:l,dispose(){d?.dispose()}}}function et(e){let{onMessage:r,connectivity:t}=e,n=[],o=!1;async function i(l){let g={type:l.type??"text_input",payload:String(l.payload??""),timestamp:String(l.timestamp??new Date().toISOString())};if(g.payload.trim()){await s("thinking"),o=!0;try{let{response:c,suggestions:d}=await r(g.payload);t.isReachable()?await t.sendMessage({type:"response",payload:w(c),suggestions:d.slice(0,3)}):t.transferUserInfo({type:"response",payload:w(c),suggestions:d.slice(0,3)})}catch(c){let d=c instanceof Error?c.message:"Something went wrong";t.isReachable()&&await t.sendMessage({type:"error",payload:w(d)})}finally{o=!1,await s("idle")}}}async function s(l){try{t.isReachable()&&await t.sendMessage({type:"status",status:l})}catch{}}async function a(l,g){let c={type:"response",payload:w(l)};g?.length&&(c.suggestions=g.slice(0,3)),t.isReachable()?await t.sendMessage(c):t.transferUserInfo(c)}return{start(){return t.onMessage(i)},sendStatus:s,sendResponse:a,isWatchReachable(){return t.isReachable()},getPendingQueue(){return[...n]}}}function w(e,r=500){return e.length<=r?e:e.slice(0,r-3)+"..."}function tt(e){let r=[];return e.includes("?")&&r.push("Yes","No"),e.toLowerCase().includes("remind")&&r.push("Set reminder"),(e.toLowerCase().includes("meeting")||e.toLowerCase().includes("calendar"))&&r.push("Show calendar"),r.length===0&&r.push("Tell me more","Thanks"),r.slice(0,3)}var nt={trigger:"send_message",grip:"push_to_talk",thumbstick_click:"cancel"};function Q(e={}){let r={...nt,...e.mapping},t=e.voiceCapture;return{handleControllerEvent(n){if(!n.pressed)return null;let o=null;switch(n.button){case"trigger":o=r.trigger==="none"?null:r.trigger;break;case"grip":o=r.grip==="none"?null:r.grip;break;case"thumbstick_click":o=r.thumbstick_click==="none"?null:r.thumbstick_click;break;default:o=null}return o&&e.onAction?.(o,n),o},async startVoice(){if(!t)throw new Error("Voice capture not available on this device");await t.startRecording()},async stopVoice(){return!t||!t.isRecording()?null:t.stopRecording()},isRecordingVoice(){return t?.isRecording()??!1},getMapping(){return{...r}},setMapping(n){Object.assign(r,n)}}}var D={width:1024,height:768,backgroundOpacity:.95,fontScale:1.2,minTouchTarget:48,showSystemKeyboard:!0};function q(e=D){return{baseFontSize:Math.round(16*e.fontScale),headingFontSize:Math.round(24*e.fontScale),minTouchTarget:e.minTouchTarget,panelWidth:e.width,panelHeight:e.height,backgroundOpacity:e.backgroundOpacity}}async function rt(e){let r=await R(e),t={...D,...e.panel},n=q(t),o=Q(e.controller);return{fenix:r,controller:o,styles:n,panel:t,dispose(){r.dispose()}}}function Y(e){let{onTranscription:r,wakeWordDetector:t}=e,n=e.isOnline??(()=>!0),o=!1,i=!1;function s(){return!n()&&e.offlineSTT?e.offlineSTT:e.sttProvider}function a(){return!n()&&e.offlineTTS?e.offlineTTS:e.ttsProvider}async function l(){if(o)return"";o=!0;try{let c=await s().stopListening();if(!c.trim())return"";let d=await r(c);return d&&await a().speak(d),d}finally{o=!1}}return{start(){i=!0,t?.start(async()=>{o||await s().startListening(()=>{})})},stop(){i=!1,t?.stop(),a().stop()},async pushToTalk(){return await s().startListening(()=>{}),l()},async speak(g){await a().speak(g)},isBusy(){return o}}}function $(e){let{camera:r}=e,t=e.isOnline??(()=>!0);function n(){return!t()&&e.offlineVisionProvider?e.offlineVisionProvider:e.visionProvider}return{async describeView(o){if(!r.hasPermission()&&!await r.requestPermission())return"Camera permission not granted.";let i=await r.captureFrame("main");return n().describe(i,o??"Describe what you see in this image.")},async captureFrame(){return r.hasPermission()||await r.requestPermission(),r.captureFrame("main")},isAvailable(){return r.hasPermission()}}}var K={touchpad:{tap:"send_message",double_tap:"pin_card",long_press:"activate_agent",swipe_up:"scroll_up",swipe_down:"scroll_down",swipe_left:"dismiss",swipe_right:"none"},head:{nod:"confirm",shake:"deny",tilt_left:"none",tilt_right:"dismiss"},button:{camera_short:"none",camera_long:"activate_agent"}};function z(e={}){let r={touchpad:{...K.touchpad,...e.mapping?.touchpad},head:{...K.head,...e.mapping?.head},button:{...K.button,...e.mapping?.button}};return{handleEvent(t){let n="none";switch(t.source){case"touchpad":n=r.touchpad[t.gesture]??"none";break;case"head":n=r.head[t.gesture]??"none";break;case"button":n=r.button[t.gesture]??"none";break}return n!=="none"&&e.onAction?.(n,t),n},getMapping(){return{touchpad:{...r.touchpad},head:{...r.head},button:{...r.button}}},setMapping(t){t.touchpad&&Object.assign(r.touchpad,t.touchpad),t.head&&Object.assign(r.head,t.head),t.button&&Object.assign(r.button,t.button)}}}function J(e){let r=new Map,t=e.allowedApps?new Set(e.allowedApps):null;return{async handleNotification(n){t&&!t.has(n.app)||(r.set(n.id,n),n.priority==="urgent"&&e.onSpeak&&await e.onSpeak(`Urgent from ${n.app}: ${n.title}. ${n.body}`),e.onDisplay?.(n))},getUnread(){return Array.from(r.values()).sort((n,o)=>new Date(o.timestamp).getTime()-new Date(n.timestamp).getTime())},markRead(n){r.delete(n)},clearAll(){r.clear()},async speakSummary(){let n=Array.from(r.values());if(n.length===0){await e.onSpeak?.("No new notifications.");return}let o;if(e.summarize)o=await e.summarize(n);else if(n.length===1){let i=n[0];o=`You have one notification from ${i.app}: ${i.title}`}else{let i=[...new Set(n.map(s=>s.app))];o=`You have ${n.length} notifications from ${i.join(", ")}`}await e.onSpeak?.(o)}}}function Z(e){let r=!1,t=Y({...e.voice,onTranscription:async a=>{if(["looking at","see","what is this","what's this","describe"].some(c=>a.toLowerCase().includes(c))&&n.isAvailable()){let c=await n.captureFrame();return e.processQuery(a,c.data)}return e.processQuery(a)}}),n=$(e.context),o=z({...e.gesture,onAction:(a,l)=>{s(a),e.gesture?.onAction?.(a,l)}}),i=J({...e.notification,onSpeak:async a=>{await t.speak(a),e.notification?.onSpeak?.(a)}});function s(a){switch(a){case"activate_agent":t.isBusy()||t.pushToTalk();break;case"dismiss":break;case"confirm":case"deny":break}}return{start(){r=!0,t.start()},stop(){r=!1,t.stop()},isRunning(){return r},voice:t,context:n,gesture:o,notification:i}}async function ot(e){let r=await R(e),t=Z({voice:e.voice,context:e.context,gesture:e.gesture,notification:e.notification,async processQuery(n,o){let i="",s=r.agent.run({text:n});for await(let a of s)a&&typeof a=="object"&&a.type==="token"&&(i+=a.token??"");return i||"I couldn't process that. Try again."}});return t.start(),{fenix:r,xr:t,dispose(){t.stop(),r.dispose()}}}function k(e,r,t={}){let n=t.maxBodyLength??120,o=r.length>n?r.slice(0,n-3)+"...":r;return{id:crypto.randomUUID(),title:e.slice(0,40),body:o,timestamp:new Date().toISOString(),pinned:!1,autoDismissMs:t.autoDismissMs??8e3}}function it(e){return{...e,pinned:!0,autoDismissMs:0}}function st(e){return{...e,pinned:!1,autoDismissMs:8e3}}function ee(e,r,t="low"){return{id:crypto.randomUUID(),app:e,text:r.slice(0,60),priority:t,timestamp:new Date().toISOString(),autoDismissMs:t==="high"?6e3:4e3}}function te(e=8){return{messages:[],visible:!1,scrollOffset:0,maxVisibleMessages:e}}function ne(e,r,t){let n={id:crypto.randomUUID(),role:r,content:t,timestamp:new Date().toISOString()},o=[...e.messages,n],i=Math.max(0,o.length-e.maxVisibleMessages);return{...e,messages:o,scrollOffset:i,visible:!0}}function at(e,r){let t=r==="up"?-1:1,n=Math.max(0,e.messages.length-e.maxVisibleMessages),o=Math.max(0,Math.min(n,e.scrollOffset+t));return{...e,scrollOffset:o}}function ct(e){return e.messages.slice(e.scrollOffset,e.scrollOffset+e.maxVisibleMessages)}function re(e=3){return{annotations:[],visible:!1,maxAnnotations:e}}function oe(e,r){let t=r.sort((n,o)=>o.confidence-n.confidence).slice(0,e.maxAnnotations).map(n=>({id:n.id,content:n.content.length>80?n.content.slice(0,77)+"...":n.content,relevance:n.confidence,timestamp:new Date().toISOString()}));return{...e,annotations:t,visible:t.length>0}}function ie(e){return{...e,visible:!1,annotations:[]}}function dt(e){return e.split(/\s+/).filter(Boolean).length}function lt(e,r){let t=e.split(/\s+/).filter(Boolean);return t.length<=r?e:t.slice(0,r).join(" ")+"..."}function ut(e){let{displayType:r,compressResponse:t,onSpeak:n}=e,o=e.monocularWordBudget??50,i=e.binocularWordBudget??150,s={currentCard:null,notifications:[],conversation:te(),memory:re()};async function a(l){let g=r==="monocular"?o:i;return dt(l)<=g?l:t?t(l,g):lt(l,g)}return{async showResponse(l,g){if(r==="none"){await n?.(l);return}let c=await a(l);r==="monocular"?s.currentCard=k("Fenix",c):(s.conversation=ne(s.conversation,"assistant",c),s.currentCard=k("Fenix",c))},showNotification(l,g,c){if(r==="none"){n?.(`${l}: ${g}`);return}let d=ee(l,g,c);s.notifications=[...s.notifications,d]},showMemoryContext(l){r!=="none"&&(s.memory=oe(s.memory,l))},dismiss(){s.currentCard=null,s.notifications=[],s.memory=ie(s.memory)},getDisplayType(){return r},getState(){return{...s}}}}function se(e){let{agent:r,identityStore:t,memorySearch:n,onResponse:o}=e,i=new Map;function s(c,d,f){o({id:c,type:d,payload:f})}async function a(c){let d=new AbortController;i.set(c.id,d);try{let f=c.payload.text,_="",E=r.run({text:f});for await(let m of E){if(d.signal.aborted)return;if(m&&typeof m=="object"&&m.type==="token"){let u=m.token??"";_+=u,s(c.id,"stream_token",{token:u})}}s(c.id,"stream_end",{response:_})}catch(f){d.signal.aborted||s(c.id,"error",{message:f.message??"Agent error"})}finally{i.delete(c.id)}}async function l(c){try{let d=c.payload.query,f=c.payload.limit??10,_=n.searchFTS(d,f);s(c.id,"result",{results:_})}catch(d){s(c.id,"error",{message:d.message??"Search error"})}}async function g(c){try{if(c.type==="identity_read"){let d=c.payload.file,f=t.read(d);s(c.id,"result",{file:d,content:f})}else{let d=c.payload.file,f=c.payload.content;t.write(d,f),s(c.id,"result",{file:d,written:!0})}}catch(d){s(c.id,"error",{message:d.message??"Identity error"})}}return{async handleRequest(c){switch(c.type){case"query":return a(c);case"cancel":let d=c.payload.requestId;i.get(d)?.abort(),i.delete(d),s(c.id,"result",{cancelled:d});return;case"memory_search":return l(c);case"identity_read":case"identity_write":return g(c);case"status":s(c.id,"result",{activeRequests:[...i.keys()],agentReady:!0});return}},cancelRequest(c){i.get(c)?.abort(),i.delete(c)},getActiveRequests(){return[...i.keys()]},dispose(){for(let c of i.values())c.abort();i.clear()}}}function ae(e){let{stt:r,tts:t,onTranscription:n}=e;return{async startListening(){t.isSpeaking()&&t.stop(),await r.startListening()},async stopAndProcess(){let o=await r.stopListening();if(!o.trim())return"";let i=await n(o);return await t.speak(i),i},async speak(o){await t.speak(o)},cancelSpeech(){t.stop()},isListening(){return r.isListening()},isSpeaking(){return t.isSpeaking()}}}function ve(e){return{name:"scene_understanding",description:"Identify objects and surfaces in the user's physical environment using ARKit scene understanding.",parameters:{},async handler(){let r=await e.getSceneObjects();return{objects:r.map(t=>({label:t.label,confidence:Math.round(t.confidence*100),position:`(${t.boundingBox.x.toFixed(1)}, ${t.boundingBox.y.toFixed(1)}, ${t.boundingBox.z.toFixed(1)})`})),count:r.length}}}}function Re(e){return{name:"spatial_anchor",description:"Place or retrieve spatial anchors in the user's environment. Anchors persist across sessions.",parameters:{action:{type:"string",description:"place | list | remove"},label:{type:"string",description:"Label for the anchor (place only)"},position:{type:"object",description:"{ x, y, z } coordinates (place only)"},id:{type:"string",description:"Anchor ID (remove only)"}},async handler(r){let t=r.action;return t==="list"?{anchors:await e.getAnchors()}:t==="place"?{placed:await e.placeAnchor(r.label,r.position)}:t==="remove"?{removed:await e.removeAnchor(r.id)}:{error:"Unknown action. Use place, list, or remove."}}}}function be(e){return{name:"hand_tracking",description:"Read current hand poses and gestures via ARKit hand tracking.",parameters:{},async handler(){return{hands:(await e.getHandPoses()).map(t=>({hand:t.hand,gesture:t.gesture,confidence:Math.round(t.confidence*100)}))}}}}function Ae(e){return{name:"shareplay",description:"Start or end a SharePlay session for collaborative spatial experiences.",parameters:{action:{type:"string",description:"start | end"},sessionId:{type:"string",description:"Session identifier (start only)"}},async handler(r){return r.action==="start"?e.startSharePlay?{started:await e.startSharePlay(r.sessionId)}:{error:"SharePlay not available"}:r.action==="end"?e.endSharePlay?(await e.endSharePlay(),{ended:!0}):{error:"SharePlay not available"}:{error:"Unknown action. Use start or end."}}}}function ce(e){return[ve(e),Re(e),be(e),Ae(e)]}async function pt(e){let r=e.tools??[];e.nativeAPIs&&r.push(...ce(e.nativeAPIs));let t=await R({...e,tools:r}),n=se({agent:t.agent,identityStore:t.identityStore,memorySearch:t.memorySearch,onResponse:e.onBridgeResponse??(()=>{})}),o=null;return e.voice&&(o=ae(e.voice)),{fenix:t,bridge:n,voice:o,mode:e.mode,dispose(){n.dispose(),t.dispose()}}}export{D as DEFAULT_PANEL_CONFIG,X as EDITION,Oe as KERNEL_VERSION,Ee as MOBILE_KERNEL_CONFIG,ke as MOBILE_MANIFEST,G as SYNC_TASK_NAME,ne as appendMessage,$e as bootApp,Se as bootKernel,Fe as bootMobile,se as createAgentBridge,Ve as createCalendarTool,We as createCameraTool,He as createContactsTool,$ as createContextCapture,te as createConversationOverlay,ut as createDisplayManager,z as createGestureHandler,k as createGlanceCard,be as createHandTrackingTool,A as createHybridRouter,x as createIdentityStore,Te as createKernel,H as createLocalProvider,Ge as createLocationTool,re as createMemoryHUD,N as createMemorySearch,L as createMobileAgent,V as createMobileToolRegistry,J as createNotificationBridge,ee as createNotificationPill,Qe as createNotificationsTool,Q as createQuestAdapter,je as createRemindersTool,v as createSQLiteAdapter,ve as createSceneUnderstandingTool,Ae as createSharePlayTool,Re as createSpatialAnchorTool,W as createSyncClient,j as createSyncTriggers,ce as createVisionOSTools,ae as createVisionVoiceInterface,Y as createVoiceInterface,et as createWatchBridge,Z as createXRAgentService,ie as dismissHUD,tt as generateSuggestions,q as getQuestStyleOverrides,ct as getVisibleMessages,ot as initializeAndroidXR,R as initializeFenixMobile,rt as initializeQuestApp,pt as initializeVisionOS,it as pinCard,at as scrollOverlay,Ue as startMobile,st as unpinCard,oe as updateAnnotations};
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Kernel bridge — connects the mobile runtime to @fenixforce/kernel.
3
+ * All kernel interaction flows through this module.
4
+ */
5
+ import { createKernel, bootKernel, KERNEL_VERSION, type KernelConfig, type BootConfig, type Kernel, type RunningKernel, type EditionManifest } from "@fenixforce/kernel";
6
+ import { createSQLiteAdapter, type SQLiteDB } from "./storage/sqlite-adapter.js";
7
+ import { type LLMProvider } from "./providers/hybrid-router.js";
8
+ export declare const EDITION: "mobile";
9
+ export declare const MOBILE_MANIFEST: EditionManifest;
10
+ export declare const MOBILE_KERNEL_CONFIG: KernelConfig;
11
+ export declare function startMobile(overrides?: Partial<KernelConfig>): Kernel;
12
+ /** Boot a fully-wired Mobile kernel with agent loop, providers, and restricted tools. */
13
+ export declare function bootMobile(overrides?: Partial<BootConfig>): Promise<RunningKernel>;
14
+ export interface MobileAgentConfig {
15
+ /** SQLiteDB instance (expo-sqlite or bun:sqlite shim). */
16
+ db: SQLiteDB;
17
+ /** Cloud LLM provider (e.g. OpenRouter, Anthropic). */
18
+ cloudProvider: LLMProvider;
19
+ /** Optional local model provider (e.g. Ollama). */
20
+ localProvider?: LLMProvider;
21
+ /** Prefer local model when available. Default false. */
22
+ preferLocal?: boolean;
23
+ /** Maximum agent turns. Default 10. */
24
+ maxTurns?: number;
25
+ /** Token budget per session. Default 64_000. */
26
+ tokenBudget?: number;
27
+ /** Override connectivity check for testing. */
28
+ isOnline?: () => boolean;
29
+ }
30
+ export interface MobileAgent {
31
+ /** Run the agent loop for a user message, yielding events. */
32
+ run(input: {
33
+ text?: string;
34
+ }): AsyncGenerator<unknown>;
35
+ /** The underlying storage adapter. */
36
+ readonly storage: ReturnType<typeof createSQLiteAdapter>;
37
+ /** The hybrid provider router. */
38
+ readonly provider: LLMProvider;
39
+ /** Session ID. */
40
+ readonly sessionId: string;
41
+ }
42
+ /** Create a mobile agent wired with SQLite storage, hybrid provider, and restricted tools. */
43
+ export declare function createMobileAgent(config: MobileAgentConfig): Promise<MobileAgent>;
44
+ export { createKernel, bootKernel, KERNEL_VERSION };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * App-wide service types — initialized once at app launch,
3
+ * consumed by all screens via React context.
4
+ *
5
+ * The actual React context + provider lives in components/AppProvider.tsx
6
+ * (excluded from the library build). This module exports only types.
7
+ */
8
+ import type { StorageAdapter } from "@fenixforce/kernel";
9
+ import type { SQLiteDB } from "./storage/sqlite-adapter.js";
10
+ import type { IdentityStore } from "./storage/identity-store.js";
11
+ import type { MobileAgent } from "./agent.js";
12
+ export interface AppServices {
13
+ db: SQLiteDB;
14
+ storage: StorageAdapter;
15
+ identityStore: IdentityStore;
16
+ memorySearch: {
17
+ searchFTS: (query: string, limit?: number) => Array<{
18
+ id: string;
19
+ content: string;
20
+ category: string;
21
+ confidence: number;
22
+ score: number;
23
+ }>;
24
+ };
25
+ agent: MobileAgent;
26
+ userId: string;
27
+ syncEnabled: boolean;
28
+ syncPending: boolean;
29
+ lastSyncTime: string | null;
30
+ isOnline: () => boolean;
31
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Boot sequence — initializes all mobile services and returns
3
+ * an AppServices object ready for the context provider.
4
+ */
5
+ import { type SQLiteDB } from "./storage/sqlite-adapter.js";
6
+ import type { LLMProvider } from "./providers/hybrid-router.js";
7
+ import type { AppServices } from "./app-context.js";
8
+ export interface BootOptions {
9
+ /** The SQLite database handle (expo-sqlite openDatabaseSync or bun:sqlite shim). */
10
+ db: SQLiteDB;
11
+ /** Cloud LLM provider. */
12
+ cloudProvider: LLMProvider;
13
+ /** Optional on-device model provider. */
14
+ localProvider?: LLMProvider;
15
+ /** Prefer local model when available. */
16
+ preferLocal?: boolean;
17
+ /** Connectivity check function. */
18
+ isOnline?: () => boolean;
19
+ /** User identifier. Defaults to "default". */
20
+ userId?: string;
21
+ }
22
+ export declare function bootApp(opts: BootOptions): Promise<AppServices>;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Mobile Edition Entry Point — initializes all Fenix Mobile services.
3
+ * Called from app/_layout.tsx on app launch. This is the single place
4
+ * that wires storage, providers, tools, identity, agent, and sync.
5
+ */
6
+ import { type SQLiteDB } from "./storage/sqlite-adapter.js";
7
+ import { type LLMProvider } from "./providers/hybrid-router.js";
8
+ import { type LocalModelConfig } from "./providers/local-inference.js";
9
+ import { createMobileToolRegistry, type MobileTool } from "./tools/registry.js";
10
+ import { type SyncClient } from "./sync/client.js";
11
+ import { type SyncTriggers } from "./sync/triggers.js";
12
+ import type { AppServices } from "./app-context.js";
13
+ export interface FenixMobileConfig {
14
+ /** SQLite database handle (expo-sqlite openDatabaseSync or bun:sqlite shim). */
15
+ db: SQLiteDB;
16
+ /** Cloud provider for online inference. */
17
+ cloudProvider: LLMProvider;
18
+ /** Local model configuration (embedded or Ollama). Null to disable. */
19
+ localModel?: LocalModelConfig;
20
+ /** Prefer local model when both are available. Default false. */
21
+ preferLocal?: boolean;
22
+ /** Connectivity check. Default: () => true. */
23
+ isOnline?: () => boolean;
24
+ /** User identifier. Default: "default". */
25
+ userId?: string;
26
+ /** Mobile-specific tools (calendar, contacts, etc.). */
27
+ tools?: MobileTool[];
28
+ /** Sync server URL + auth token. Null to disable sync. */
29
+ sync?: {
30
+ serverUrl: string;
31
+ authToken: string;
32
+ };
33
+ /** Secure store for API keys. */
34
+ secureStore?: {
35
+ getItemAsync(key: string): Promise<string | null>;
36
+ setItemAsync(key: string, value: string): Promise<void>;
37
+ };
38
+ /** Background task registration (expo-task-manager). */
39
+ registerBackgroundTask?: (taskName: string, intervalMs: number) => Promise<void>;
40
+ unregisterBackgroundTask?: (taskName: string) => Promise<void>;
41
+ }
42
+ export interface FenixMobileContext extends AppServices {
43
+ syncClient: SyncClient | null;
44
+ syncTriggers: SyncTriggers | null;
45
+ toolRegistry: ReturnType<typeof createMobileToolRegistry>;
46
+ dispose(): void;
47
+ }
48
+ export declare function initializeFenixMobile(config: FenixMobileConfig): Promise<FenixMobileContext>;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Hybrid Provider Router — routes LLM requests to cloud or local
3
+ * models based on connectivity, task complexity, and user preference.
4
+ */
5
+ export interface CompletionParams {
6
+ model?: string;
7
+ messages: Array<{
8
+ role: string;
9
+ content: string | null;
10
+ tool_calls?: unknown[];
11
+ }>;
12
+ temperature?: number;
13
+ maxTokens?: number;
14
+ tools?: Array<{
15
+ type: "function";
16
+ function: {
17
+ name: string;
18
+ description?: string;
19
+ parameters?: unknown;
20
+ };
21
+ }>;
22
+ stop?: string | string[];
23
+ }
24
+ export interface CompletionResult {
25
+ content: string;
26
+ toolCalls?: Array<{
27
+ id: string;
28
+ type: "function";
29
+ function: {
30
+ name: string;
31
+ arguments: string;
32
+ };
33
+ }>;
34
+ finishReason: "stop" | "tool_calls" | "length" | "content_filter" | null;
35
+ usage?: {
36
+ inputTokens: number;
37
+ outputTokens: number;
38
+ };
39
+ }
40
+ export interface ProviderCapabilities {
41
+ nativeToolCalling: boolean;
42
+ vision: boolean;
43
+ streaming: boolean;
44
+ structuredOutput: boolean;
45
+ maxContextTokens: number;
46
+ }
47
+ export interface LLMProvider {
48
+ readonly id: string;
49
+ complete(params: CompletionParams): Promise<CompletionResult>;
50
+ stream?(params: CompletionParams): AsyncIterable<unknown>;
51
+ capabilities(): ProviderCapabilities;
52
+ }
53
+ export interface HybridRouterConfig {
54
+ cloudProvider: LLMProvider;
55
+ localProvider?: LLMProvider;
56
+ preferLocal: boolean;
57
+ /** Override connectivity check for testing. */
58
+ isOnline?: () => boolean;
59
+ }
60
+ export declare function createHybridRouter(config: HybridRouterConfig): LLMProvider;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * On-device LLM inference for offline operation.
3
+ *
4
+ * Path A (embedded): llama.cpp via React Native binding.
5
+ * GGUF quantized models run directly on device.
6
+ * Target: Qwen 3.5 2B (Q4_K_M, ~1.5GB) for phones,
7
+ * Qwen 3.5 4B for tablets/newer devices.
8
+ *
9
+ * Path B (Ollama): Connects to Ollama on local network.
10
+ * Uses the Ollama HTTP API at a user-configured URL.
11
+ */
12
+ import type { LLMProvider } from "./hybrid-router.js";
13
+ export interface LocalModelConfig {
14
+ type: "embedded" | "ollama";
15
+ /** Path to GGUF model file on device (embedded mode). */
16
+ modelPath?: string;
17
+ /** Context window size for embedded model. Default 2048. */
18
+ contextSize?: number;
19
+ /** Ollama base URL, e.g. http://192.168.1.100:11434. */
20
+ ollamaUrl?: string;
21
+ /** Ollama model name, e.g. "qwen3.5:2b". */
22
+ model?: string;
23
+ /** Override fetch for testing. */
24
+ fetchFn?: typeof globalThis.fetch;
25
+ }
26
+ export interface EmbeddedInference {
27
+ load(modelPath: string, contextSize: number): Promise<void>;
28
+ complete(prompt: string, maxTokens: number, temperature: number): Promise<string>;
29
+ unload(): void;
30
+ }
31
+ export declare function createLocalProvider(config: LocalModelConfig): LLMProvider;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Identity file storage — stores identity files (soul.md, persona.md, etc.)
3
+ * in the SQLite identity_files table instead of the filesystem.
4
+ * Provides the same read/write interface the kernel expects.
5
+ */
6
+ import type { SQLiteDB } from "./sqlite-adapter.js";
7
+ export interface IdentityStore {
8
+ read(fileName: string): Promise<string | null>;
9
+ write(fileName: string, content: string): Promise<void>;
10
+ list(): Promise<string[]>;
11
+ getVersion(fileName: string): Promise<number>;
12
+ }
13
+ export declare function createIdentityStore(db: SQLiteDB, workspaceId?: string): IdentityStore;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Local memory retrieval using SQLite FTS5 for keyword search,
3
+ * with optional hybrid fusion when cached embeddings are available.
4
+ * Uses the same RRF algorithm as the kernel's dual-search.
5
+ */
6
+ import type { SQLiteDB } from "./sqlite-adapter.js";
7
+ export interface MemorySearchResult {
8
+ id: string;
9
+ content: string;
10
+ category: string;
11
+ confidence: number;
12
+ score: number;
13
+ }
14
+ export interface CachedEmbedding {
15
+ id: string;
16
+ embedding: number[];
17
+ }
18
+ export interface MemorySearchConfig {
19
+ db: SQLiteDB;
20
+ /** RRF constant k. Default 60. */
21
+ rrfK?: number;
22
+ /** Weight for FTS5 results in hybrid fusion. Default 1.0. */
23
+ ftsWeight?: number;
24
+ /** Weight for embedding results in hybrid fusion. Default 1.0. */
25
+ embeddingWeight?: number;
26
+ }
27
+ export declare function createMemorySearch(config: MemorySearchConfig): {
28
+ searchFTS: (query: string, limit?: number) => MemorySearchResult[];
29
+ searchHybrid: (query: string, queryEmbedding: number[], limit?: number) => Promise<MemorySearchResult[]>;
30
+ };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * SQLite storage adapter — implements the kernel's StorageAdapter interface
3
+ * backed by a single SQLite database file. Designed for expo-sqlite on device
4
+ * and testable with bun:sqlite via the SQLiteDB abstraction.
5
+ *
6
+ * Key differences from the PostgreSQL adapter:
7
+ * - No pgvector — embedding search returns empty results (handled separately)
8
+ * - UUIDs generated in TypeScript via crypto.randomUUID()
9
+ * - JSONB → TEXT with JSON.parse/stringify at the boundary
10
+ * - TIMESTAMPTZ → TEXT as ISO 8601 strings
11
+ * - All queries use synchronous SQLite methods
12
+ */
13
+ import type { StorageAdapter } from "@fenixforce/kernel";
14
+ export interface SQLiteDB {
15
+ execSync(sql: string): void;
16
+ runSync(sql: string, params?: unknown[]): {
17
+ changes: number;
18
+ };
19
+ getFirstSync<T = Record<string, unknown>>(sql: string, params?: unknown[]): T | null;
20
+ getAllSync<T = Record<string, unknown>>(sql: string, params?: unknown[]): T[];
21
+ }
22
+ export declare function createSQLiteAdapter(db: SQLiteDB): StorageAdapter;