Skip to content

fix: harden on-device AI lifecycle and agent guidance - #22

Merged
hyochan merged 5 commits into
mainfrom
codex/stabilize-ondevice-ai-lifecycle
Jul 11, 2026
Merged

hyochan merged 5 commits into
mainfrom
codex/stabilize-ondevice-ai-lifecycle

Conversation

@hyochan

@hyochan hyochan commented Jul 11, 2026 •

Copy link
Copy Markdown
Member

Summary

  • harden Apple model-package integrity with immutable artifact revisions, strict GGUF/mmproj checksums, package-level promotion, cancellation-safe single-flight downloads, and load/delete race guards
  • make external Apple llama.cpp loading atomic with a two-phase prepare/commit/discard bridge lifecycle; update the Expo and Flutter bridge implementations and reject unsafe legacy-only providers explicitly
  • make Android Prompt API capability, download, initialization, and wrapper routing report live state instead of fabricated readiness or model success
  • align Expo and React Native bridges with core behavior, including Web streaming, explicit unsupported paths, isolated callbacks, conversation identity, and Android API 31 requirements
  • remove sensitive production payload logging and replace caller-controlled SQL interpolation with prepared bindings
  • strengthen repository AI guidance, deterministic context generation, CI checks, issue/review workflows, and add the review-self stabilization skill
  • pin the Site workspace to the lockfile-resolved Prettier 3.8.1 so Bun 1.1 CI does not float to a formatter with different output
  • address final review findings across wrapper proofread options, streaming identity, explicit unsupported model APIs, capability probes, Apple registry/storage behavior, deterministic agent paths, and async test cleanup
  • close remaining review gaps in Apple package cancellation and profile transactions, ExecuTorch failure/cancellation privacy, and Expo Web translator/stream lifecycle safety

Root cause and impact

Several platform and wrapper layers had drifted from their behavioral sources of truth. Cached or synthetic capability data could advertise unavailable features, model operations could report success without a verified backend, and Apple model files were downloaded from a moving revision without complete package integrity. The external Apple bridge could also register globally before ModelManager revalidated its lifecycle token, allowing a late callback to race deletion.

The bridge now prepares without router side effects and commits only while the token and verified files are current. Existing third-party bridges that implement only the legacy one-phase protocol are rejected with an explicit migration error because adapting them silently would restore the race. Existing Apple external-model installs without the new package commit metadata are treated as unverified and must be downloaded again.

Site CI used Bun 1.1.0, which cannot consume the current text lockfile and re-resolved the caret Prettier range to 3.9.5. Pinning the already lockfile-resolved 3.8.1 makes formatting deterministic without changing a Locanara package version.

Test plan

  • Apple: swift build; swift test (177 tests); iOS Simulator example xcodebuild; model-integrity and rollback regression tests
  • Android: :locanara:test, :locanara:build, :example:assembleDebug (102 debug + 102 release SDK tests); SDK lint
  • Expo: TypeScript check; ESLint (0 errors, 2 existing warnings); 48 tests; build; Prettier check; generated Swift template parse
  • React Native: TypeScript check; 46 Jest tests; Bob/Nitrogen exact regeneration; 6 native JVM tests; wrapper Kotlin/C++ compile; Android example assemble
  • Flutter: 39 tests; bridge Swift parse; analyze reports 11 pre-existing info-only example findings
  • Web: lint; 23 tests; build
  • Site: typecheck; format check; build; clean-worktree Bun 1.1.0 format reproduction
  • Agent context: TypeScript check; 9 tests; Markdown lint; generated-output check
  • Repository: authored diff whitespace check; exact Nitro regeneration (two deterministic generator-owned trailing-space lines remain in generated C++/Swift output)
  • Independent final self-review: no P0/P1/P2 blocker

Known gaps / skipped checks

  • Deleting during native bridge prewarm invalidates the load immediately and prevents router registration, but the underlying prewarm may continue until its callback before being discarded. Prompt cancellation would require a cancellable provider API; this is a resource-delay gap, not a registration-after-delete correctness gap.
  • check:versions still fails on the pre-existing Apple version mirror mismatch (root 1.1.1 vs site 1.1.0). Version files were intentionally not changed.
  • Expo native Kotlin assembly was not run because this checkout has no generated Expo Android project and local Maven publication is prohibited.
  • The audit still tracks source-first GraphQL generated drift, broader Flutter parity/no-op paths, dependency advisories, and remaining issue docs: document Pipeline DSL with cross-platform code examples #14 acceptance criteria. This PR does not close docs: document Pipeline DSL with cross-platform code examples #14.

No package was published, deployed, released, or version-bumped.

Summary by CodeRabbit

  • New Features

    • Added streaming support for summarization, translation, and rewriting on Web, Expo, and React Native.
    • Preserved conversation IDs across chat and chat-streaming operations.
    • Improved Android on-device capability detection and Prompt API model download progress/state.
  • Bug Fixes

    • Improved streaming event ordering, subscription cleanup, and serialized concurrent streaming behavior.
    • Strengthened model download verification, cancellation, and lifecycle handling on Apple.
  • Documentation

    • Updated platform guides, API references, and versioning/instructions with clearer on-device and verification boundaries.
    • Updated command and knowledge documentation for consistent workflows.
  • Chores

    • Removed debug/sensitive prompt/response/document content from production logs.

@coderabbitai

coderabbitai Bot commented Jul 11, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR restructures agent guidance and generated context, adds CI verification workflows, updates Expo and React Native wrappers, centralizes Android capability handling, and hardens Apple model package downloads, storage verification, loading, cancellation, and persistence.

Changes

Agent workflows and guidance

Layer / File(s) Summary
Command, package, and policy workflows
.claude/commands/*, .claude/guides/*, AGENTS.md, SKILLS_INDEX.md
Documentation now routes work through explicit sources of truth, verification matrices, privacy boundaries, runtime capability checks, and restricted write/deployment actions.
Generated context and CI validation
knowledge/*, scripts/agent/*, .github/workflows/*, package.json
Context generation uses version maps, allowlists, drift checks, deterministic output, and markdown linting; CI verifies generated content, versions, tests, and read-only behavior.

Platform wrappers and runtime behavior

Layer / File(s) Summary
Expo and React Native wrapper contracts
libraries/expo-ondevice-ai/*, libraries/react-native-ondevice-ai/*
Wrappers call typed Locanara APIs, serialize streams per operation, preserve conversation IDs, report capability state, implement web streaming variants, and reject unsupported operations instead of simulating success.
Android capability and Prompt API execution
packages/android/locanara/*, libraries/*/android/*
Capability snapshots, Prompt API download coordination, cancellation recovery, typed feature execution, error-code mapping, and privacy-safe logging are centralized and covered by tests.
Apple model package integrity
packages/apple/Sources/ModelManager/*, packages/apple/Sources/Engine/*, packages/apple/Tests/*
Model downloads now support multi-asset packages, checksum and metadata verification, prepared bridge commits, cancellation tokens, package cleanup, safe storage paths, parameterized SQL, and lifecycle/concurrency tests.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested labels: 📖 documentation, :earth_americas: web, 💨 ci

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR does not add the requested Pipeline DSL guide, README examples, or pipeline demo screens for issue #14. Add the Pipeline DSL guide, README examples, and Apple/Android pipeline demo screens required by issue #14.
Out of Scope Changes check ⚠️ Warning Most changes harden lifecycle, logging, SQL, CI, and knowledge docs, which are unrelated to the Pipeline DSL documentation issue. Split the lifecycle, logging, SQL, CI, and knowledge-guidance updates into separate PRs or link the issues that request them.
Docstring Coverage ⚠️ Warning Docstring coverage is 30.21% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the two main themes: on-device AI lifecycle hardening and updated agent guidance.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/stabilize-ondevice-ai-lifecycle

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the Locanara on-device AI framework across Apple, Android, Web, and wrapper platforms to align with the ML Kit GenAI Prompt API, clean up sensitive logging, and introduce the review-self Codex skill. Key improvements include adding streaming variants to the Expo and React Native wrappers, implementing robust Android capability probing, and removing simulated model management. The reviewer identified critical issues regarding missing conversationId session tracking in the React Native Android wrapper, a potential correctness bug in the Web module's delta stream parsing, and an opportunity to use Kotlin's idiomatic Mutex.withLock for cleaner synchronization in Locanara.kt.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread libraries/expo-ondevice-ai/src/ExpoOndeviceAiModule.web.ts
Comment thread packages/android/locanara/src/main/kotlin/com/locanara/Locanara.kt
@hyochan
hyochan marked this pull request as ready for review July 11, 2026 18:06
@hyochan

hyochan commented Jul 11, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 11, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.claude/knowledge/apple-intelligence.md (1)

21-73: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix the stale FoundationModels signatures in .claude/knowledge/apple-intelligence.md

  • session.prewarm() should be prewarm(promptPrefix:).
  • session.append() isn’t a LanguageModelSession API; session history is managed through Transcript.
  • includeSchemaInPrompt belongs on respond(..., includeSchemaInPrompt:options:), not GenerationOptions.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/knowledge/apple-intelligence.md around lines 21 - 73, Update the
FoundationModels API references in the LanguageModelSession and
GenerationOptions sections: replace session.prewarm() with
prewarm(promptPrefix:), remove session.append() and describe Transcript-based
history management, and move includeSchemaInPrompt from GenerationOptions to the
respond(..., includeSchemaInPrompt:options:) signature. Preserve the remaining
documented APIs.
🧹 Nitpick comments (6)
packages/apple/Sources/ModelManager/ModelDownloader.swift (1)

360-377: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

cancelDownload hardcodes a 2-asset naming convention instead of using the package's actual asset list.

idsToCancel = [modelId, "\(modelId)-mmproj"] assumes every package has exactly a main model and an -mmproj companion. downloadModel itself iterates modelInfo.packageAssets generically, so a future 3rd asset (or any asset not following the -mmproj suffix convention) won't have its in-flight URLSessionDownloadTask actively cancelled here.

Correctness is still preserved for such a case — didFinishDownloadingTo independently checks cancelledPackages.contains(info.packageModelId) and rejects/cleans up the transfer regardless — but cancellation would no longer be prompt: the outer stream would keep waiting on the un-cancelled asset's transfer to finish naturally before terminating.

Since the current registry only defines main+mmproj packages, this is a latent gap rather than an active bug, but worth generalizing before a 3-asset package is added.

♻️ Sketch: track and cancel actual per-package asset ids
-    /// Cancel a download in progress
-    ///
-    /// Also cancels the companion mmproj download for multimodal models.
-    /// - Parameter modelId: Model identifier
-    public func cancelDownload(_ modelId: String) {
-        queue.sync {
-            self.cancelledPackages.insert(modelId)
-
-            // Cancel both the main model and its mmproj companion (if any)
-            let idsToCancel = [modelId, "\(modelId)-mmproj"]
-            for id in idsToCancel {
+    /// Cancel a download in progress, including any in-flight package assets.
+    /// - Parameter modelId: Model identifier
+    public func cancelDownload(_ modelId: String) {
+        queue.sync {
+            self.cancelledPackages.insert(modelId)
+
+            // Cancel every currently active task belonging to this package,
+            // rather than assuming a fixed main+mmproj naming convention.
+            let idsToCancel = self.activeTasks.keys.filter {
+                self.taskInfo[$0]?.packageModelId == modelId
+            }
+            for id in idsToCancel {
                 guard let task = activeTasks[id] else { continue }
                 downloadResults[id] = false
                 task.cancel()
                 activeTasks.removeValue(forKey: id)
                 taskInfo.removeValue(forKey: id)
                 progressContinuations.removeValue(forKey: id)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/apple/Sources/ModelManager/ModelDownloader.swift` around lines 360 -
377, Update cancelDownload to derive the assets to cancel from the package’s
actual packageAssets list rather than hardcoding modelId and the “-mmproj”
suffix. Reuse the same package metadata and asset-ID construction used by
downloadModel, then apply the existing task cancellation and cleanup logic to
every asset ID, including future non-mmproj assets.
packages/android/locanara/src/main/kotlin/com/locanara/engine/ExecuTorchEngine.kt (1)

76-79: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redacted logs still leak e.message via the thrown exception.

Across generate(), generateStreaming(), cancel(), unload(), and create(), catch blocks were changed to log generic messages instead of ${e.message}. But in the two places that rethrow (Line 129, Line 199, Line 323-324), the original e.message is still interpolated straight into the new LocanaraException/exception message, so it isn't actually removed from what downstream callers can see/log — only from this file's own Log.e call. If the goal is avoiding leakage of native exception content, the mitigation is incomplete; if native exception messages aren't considered sensitive, the redaction elsewhere (Lines 76-79, 105-106, 151-152, 173, 212-213, 229-230, 320) trades away useful crash diagnostics for no real privacy benefit.

Consider retaining at least e::class.simpleName (or a short, non-content-bearing summary) in these logs for observability, since these are native-module/state errors rather than prompt/output content.

Also applies to: 105-106, 122-129, 151-152, 173-173, 195-198, 212-213, 229-230, 320-320

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/android/locanara/src/main/kotlin/com/locanara/engine/ExecuTorchEngine.kt`
around lines 76 - 79, Update the exception handling in generate(),
generateStreaming(), cancel(), unload(), and create() consistently: retain a
short non-content-bearing diagnostic such as e::class.simpleName in error logs
and in rethrown exception messages, while removing direct e.message
interpolation. Apply the same redaction and observability behavior to the listed
catch blocks, preserving their existing control flow and exception types.

Source: Coding guidelines

libraries/expo-ondevice-ai/src/ExpoOndeviceAiModule.web.ts (2)

604-648: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract duplicated caching and mapping logic into shared helpers.

translateStreaming (lines 614–627) duplicates the translator cache lookup, LRU eviction, and creation logic from translate (lines 532–545). Similarly, rewriteStreaming (lines 658–675) duplicates the toneMap and lengthMap objects from rewrite (lines 709–720). Extracting these into shared helpers would eliminate ~27 lines of copy-paste and prevent the two copies from diverging.

♻️ Proposed refactor: shared helpers
+// Module-level constants for rewrite option mapping
+const REWRITE_TONE_MAP: Record<string, 'more-casual' | 'more-formal' | 'as-is'> = {
+  FRIENDLY: 'more-casual',
+  PROFESSIONAL: 'more-formal',
+  ELABORATE: 'as-is',
+  SHORTEN: 'as-is',
+  EMOJIFY: 'more-casual',
+  REPHRASE: 'as-is',
+};
+
+const REWRITE_LENGTH_MAP: Record<string, 'shorter' | 'as-is' | 'longer'> = {
+  ELABORATE: 'longer',
+  SHORTEN: 'shorter',
+};
+
+// Shared translator cache helper
+async function getOrCreateTranslator(
+  sourceLanguage: string,
+  targetLanguage: string,
+): Promise<ChromeTranslator> {
+  const key = `${sourceLanguage}-${targetLanguage}`;
+  if (!cachedTranslators.has(key)) {
+    if (cachedTranslators.size >= MAX_CACHED_TRANSLATORS) {
+      const oldestKey = cachedTranslators.keys().next().value!;
+      cachedTranslators.get(oldestKey)?.destroy();
+      cachedTranslators.delete(oldestKey);
+    }
+    const Translator = getTranslatorAPI();
+    if (!Translator) throw new Error('Translator API not available in this browser');
+    cachedTranslators.set(
+      key,
+      await Translator.create({ sourceLanguage, targetLanguage }),
+    );
+  }
+  return cachedTranslators.get(key)!;
+}

Then update both translate and translateStreaming to use getOrCreateTranslator, and both rewrite and rewriteStreaming to use the module-level maps.

Also applies to: 650-693

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libraries/expo-ondevice-ai/src/ExpoOndeviceAiModule.web.ts` around lines 604
- 648, Extract the duplicated translator cache lookup, LRU eviction, and
creation logic from translate and translateStreaming into a shared
getOrCreateTranslator helper, then update both methods to use it. Move the
duplicated toneMap and lengthMap definitions from rewrite and rewriteStreaming
to module-level shared maps, and update both methods to reference those maps
without changing existing translation or rewrite behavior.

176-191: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Emit a final stream event on error to prevent hanging listeners.

If consumeTextStream throws mid-stream (e.g., Chrome API failure), the caller rejects but no isFinal: true event is emitted. Listeners waiting for stream completion will hang. Consider wrapping consumeTextStream calls with error handling that emits a terminal event.

♻️ Proposed fix: error-aware consumeTextStream wrapper
 async function consumeTextStream(
   eventName: string,
   stream: AsyncIterable<string>,
 ): Promise<string> {
   let accumulated = '';
   for await (const chunk of stream) {
     const text = typeof chunk === 'string' ? chunk : String(chunk);
     accumulated += text;
     emitEvent(eventName, {delta: text, accumulated, isFinal: false});
   }
   return accumulated;
 }
+
+async function consumeTextStreamSafe(
+  eventName: string,
+  stream: AsyncIterable<string>,
+): Promise<string> {
+  try {
+    return await consumeTextStream(eventName, stream);
+  } catch (error) {
+    emitEvent(eventName, {delta: '', accumulated: '', isFinal: true});
+    throw error;
+  }
+}

Then replace consumeTextStream calls in chatStream, summarizeStreaming, translateStreaming, and rewriteStreaming with consumeTextStreamSafe.

Also applies to: 505-511, 587-596, 634-642, 680-688

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libraries/expo-ondevice-ai/src/ExpoOndeviceAiModule.web.ts` around lines 176
- 191, Update consumeTextStream to emit a terminal event with isFinal: true when
stream iteration fails, while preserving the existing error propagation. Apply
this error-aware handling to the consumeTextStream call sites in chatStream,
summarizeStreaming, translateStreaming, and rewriteStreaming so listeners always
receive completion even when the Chrome API fails.
packages/apple/Sources/Personalization/FeedbackCollector.swift (1)

293-302: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Wrap deleteProfile in a transaction for atomicity.

deleteProfile performs two DELETEs (feedback, then profile) without a transaction. If the first succeeds and the second fails, feedback rows are orphaned with no parent profile. The refactored activateProfile in this PR already uses BEGIN/COMMIT/ROLLBACK, and the Android deleteProfile uses db.beginTransaction(). The same clearFeedback pattern (lines 519-525) has the same gap, though its consequence is less severe (stale timestamp vs. orphaned data).

🔒 Suggested fix for `deleteProfile`
 public func deleteProfile(_ profileId: String) throws {
     guard isInitialized else { throw FeedbackError.notInitialized }

-    // Delete feedback first (due to foreign key)
-    try execute("DELETE FROM feedback WHERE profile_id = ?;", bindings: [.text(profileId)])
-
-    // Delete profile
-    try execute("DELETE FROM profiles WHERE id = ?;", bindings: [.text(profileId)])
+    try execute("BEGIN TRANSACTION;")
+    do {
+        try execute("DELETE FROM feedback WHERE profile_id = ?;", bindings: [.text(profileId)])
+        try execute("DELETE FROM profiles WHERE id = ?;", bindings: [.text(profileId)])
+        try execute("COMMIT;")
+    } catch {
+        try? execute("ROLLBACK;")
+        throw error
+    }

     logger.info("Deleted feedback profile")
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/apple/Sources/Personalization/FeedbackCollector.swift` around lines
293 - 302, Update deleteProfile to execute both DELETE statements within an
explicit transaction, matching the BEGIN/COMMIT/ROLLBACK pattern used by
activateProfile: begin before deleting feedback, commit only after deleting the
profile, and roll back before propagating any failure. Preserve the existing
initialization guard and deletion order.
libraries/expo-ondevice-ai/src/index.ts (1)

138-171: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the shared streaming subscription helper.
chatStream, summarizeStreaming, translateStreaming, and rewriteStreaming all repeat the same subscribe → strip onChunk → native call → setTimeout(0) flush → cleanup flow. A small helper would remove the duplicated ExpoOndeviceAiModule as unknown as ... cast and keep the flush behavior in one place.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libraries/expo-ondevice-ai/src/index.ts` around lines 138 - 171, Extract the
repeated streaming lifecycle from chatStream, summarizeStreaming,
translateStreaming, and rewriteStreaming into a shared helper that accepts the
native operation and options, subscribes to onChunk through the typed
EventEmitter interface, strips onChunk before invocation, flushes queued events
with setTimeout(0), and removes the subscription in finally. Update each
streaming method to use this helper and eliminate the duplicated
ExpoOndeviceAiModule cast and cleanup logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@AGENTS.md`:
- Around line 593-599: Remove the entire claude-mem-context block, including its
machine-local session metadata, from the tracked AGENTS.md policy file. Keep the
file limited to repository-wide instructions and do not add generated or local
memory content.

In `@knowledge/_claude-context/context.md`:
- Around line 695-707: Update the source wording in the corresponding
knowledge/internal architecture files to use “Nitro-generated bridges” and
“on-device,” then rerun the knowledge compiler to regenerate context.md. Do not
edit the generated knowledge/_claude-context/context.md output directly.

In
`@libraries/expo-ondevice-ai/android/src/main/java/expo/modules/ondeviceai/ExpoOndeviceAiHelper.kt`:
- Around line 100-109: Wire ProofreadOptions.inputType through the complete
proofread path: update the Nitro spec, React Native TypeScript wrapper, and both
native implementations to pass and honor the value using the existing
proofreadInputType mapping. Ensure VOICE selects ProofreadInputType.VOICE and
the default remains KEYBOARD; alternatively remove inputType consistently from
all public wrapper APIs and native/spec definitions.

In
`@libraries/expo-ondevice-ai/android/src/main/java/expo/modules/ondeviceai/ExpoOndeviceAiModule.kt`:
- Around line 423-451: The chatStream flow must retain the generated
conversation ID from the streamed chunks. Update the collect block around
locanara.chatStream to capture the latest chunk.conversationId, then use that
captured ID in the promise.resolve result instead of parameters.conversationId,
while preserving the existing event payload.
- Around line 221-231: Update the AsyncFunctions getAvailableModels,
getDownloadedModels, and getLoadedModel to reject their promises with the same
ERR_UNSUPPORTED error used by downloadModel, loadModel, and deleteModel, rather
than resolving with empty collections or null. Preserve the existing
unsupported-error format and behavior used by those neighboring methods.

In
`@libraries/react-native-ondevice-ai/android/src/main/java/com/margelo/nitro/ondeviceai/HybridOndeviceAi.kt`:
- Around line 427-459: Update HybridOndeviceAi.chatStream so
NitroChatResult.canContinue is derived from the Locanara streaming result rather
than hardcoded to true. Use the stream’s canContinue value when available, or
capture the final chunk’s value/SDK ChatResult and fall back to false when the
stream indicates completion without continuation.

In `@libraries/react-native-ondevice-ai/src/index.ts`:
- Around line 533-545: Update the style mapping in rewriteStreaming to match
rewrite(): normalize falsy native style values to undefined before returning,
while preserving the existing RewriteOptions["outputType"] cast and all other
result fields.

In `@packages/android/locanara/src/main/kotlin/com/locanara/Locanara.kt`:
- Line 55: Add the `@Volatile` annotation to the geminiNanoInfo property, matching
the existing annotations on deviceCapability and promptApiStatus. Keep its
nullable type and initialization unchanged so reads in getGeminiNanoStatus()
observe writes from publishCapabilityLocked().

In
`@packages/android/locanara/src/main/kotlin/com/locanara/mlkit/AndroidCapability.kt`:
- Around line 204-227: Update MLKitCapabilityProbe.snapshot so
promptClient.checkStatus() uses the same failure handling as the task-feature
probes: preserve cancellation propagation, but catch other errors and map them
to PromptApiStatus.NotAvailable(...). Keep promptClient.close() in the existing
finally block and return the snapshot with the degraded prompt status.

In `@packages/apple/Sources/ModelManager/ModelRegistry.swift`:
- Around line 131-142: Update DeviceCapabilityDetector.recommendModel to derive
its recommendation from ModelRegistry rather than hardcoding gemma-2-2b-it-q4.
Ensure the selected model identifier exists in the registry and satisfies the
registered minMemoryMB requirement, including the 6000 MB Gemma model threshold.

In `@packages/apple/Sources/ModelManager/ModelStorage.swift`:
- Around line 564-593: Update listDownloadedModels to decode each directory’s
lastPathComponent back to the original model ID before passing it to
isModelDownloaded, so storageComponent is applied only once. Use
percent-decoding consistent with storageComponent, preserve valid IDs containing
special or Unicode characters, and skip entries that cannot be decoded safely.

In `@packages/apple/Tests/PersonalizationSecurityTests.swift`:
- Around line 12-15: Update the test cleanup defer block around
FeedbackCollector.shutdown so database shutdown completes before removing
dbPath. Replace the fire-and-forget Task with a synchronous close path, such as
a nonisolated FeedbackCollector method, or use structured concurrency that
awaits shutdown before FileManager.default.removeItem; ensure no cleanup task
outlives the test scope.

In `@packages/apple/Tests/RAGTests.swift`:
- Around line 33-38: Update the deletion test after
vectorStore.deleteCollection(id: adversarialID) to retrieve the adversarial
collection with getCollection(id:) and assert that the result is nil, while
preserving the existing assertions verifying the retained collection and
document remain.
- Around line 21-24: Update the test cleanup defer blocks to ensure
vectorStore.close() is awaited before removing the database file; replace the
fire-and-forget Task pattern with an async teardown or equivalent synchronous
cleanup approach, applying the same fix to both cleanup locations.

In `@scripts/agent/compile-context.ts`:
- Around line 173-217: Normalize all paths produced by path.relative() to POSIX
separators before comparison or serialization. Update readInternalKnowledge() so
actualNames and returned source values use forward slashes, and update
listExternalReferences() similarly, preserving the existing allowlist validation
and ordering.

---

Outside diff comments:
In @.claude/knowledge/apple-intelligence.md:
- Around line 21-73: Update the FoundationModels API references in the
LanguageModelSession and GenerationOptions sections: replace session.prewarm()
with prewarm(promptPrefix:), remove session.append() and describe
Transcript-based history management, and move includeSchemaInPrompt from
GenerationOptions to the respond(..., includeSchemaInPrompt:options:) signature.
Preserve the remaining documented APIs.

---

Nitpick comments:
In `@libraries/expo-ondevice-ai/src/ExpoOndeviceAiModule.web.ts`:
- Around line 604-648: Extract the duplicated translator cache lookup, LRU
eviction, and creation logic from translate and translateStreaming into a shared
getOrCreateTranslator helper, then update both methods to use it. Move the
duplicated toneMap and lengthMap definitions from rewrite and rewriteStreaming
to module-level shared maps, and update both methods to reference those maps
without changing existing translation or rewrite behavior.
- Around line 176-191: Update consumeTextStream to emit a terminal event with
isFinal: true when stream iteration fails, while preserving the existing error
propagation. Apply this error-aware handling to the consumeTextStream call sites
in chatStream, summarizeStreaming, translateStreaming, and rewriteStreaming so
listeners always receive completion even when the Chrome API fails.

In `@libraries/expo-ondevice-ai/src/index.ts`:
- Around line 138-171: Extract the repeated streaming lifecycle from chatStream,
summarizeStreaming, translateStreaming, and rewriteStreaming into a shared
helper that accepts the native operation and options, subscribes to onChunk
through the typed EventEmitter interface, strips onChunk before invocation,
flushes queued events with setTimeout(0), and removes the subscription in
finally. Update each streaming method to use this helper and eliminate the
duplicated ExpoOndeviceAiModule cast and cleanup logic.

In
`@packages/android/locanara/src/main/kotlin/com/locanara/engine/ExecuTorchEngine.kt`:
- Around line 76-79: Update the exception handling in generate(),
generateStreaming(), cancel(), unload(), and create() consistently: retain a
short non-content-bearing diagnostic such as e::class.simpleName in error logs
and in rethrown exception messages, while removing direct e.message
interpolation. Apply the same redaction and observability behavior to the listed
catch blocks, preserving their existing control flow and exception types.

In `@packages/apple/Sources/ModelManager/ModelDownloader.swift`:
- Around line 360-377: Update cancelDownload to derive the assets to cancel from
the package’s actual packageAssets list rather than hardcoding modelId and the
“-mmproj” suffix. Reuse the same package metadata and asset-ID construction used
by downloadModel, then apply the existing task cancellation and cleanup logic to
every asset ID, including future non-mmproj assets.

In `@packages/apple/Sources/Personalization/FeedbackCollector.swift`:
- Around line 293-302: Update deleteProfile to execute both DELETE statements
within an explicit transaction, matching the BEGIN/COMMIT/ROLLBACK pattern used
by activateProfile: begin before deleting feedback, commit only after deleting
the profile, and roll back before propagating any failure. Preserve the existing
initialization guard and deletion order.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0629e510-321c-428d-b30f-299663c50436

📥 Commits

Reviewing files that changed from the base of the PR and between 2ddebb8 and 9ae95dd.

⛔ Files ignored due to path filters (6)
  • bun.lock is excluded by !**/*.lock
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroChatOptions.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroChatOptions.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroChatOptions.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroChatOptions.hpp is excluded by !**/generated/**
  • scripts/agent/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (127)
  • .claude/commands/android.md
  • .claude/commands/apple.md
  • .claude/commands/audit-code.md
  • .claude/commands/commit.md
  • .claude/commands/docs.md
  • .claude/commands/gql.md
  • .claude/commands/knowledge-compile.md
  • .claude/commands/locanara.md
  • .claude/commands/resolve-issue.md
  • .claude/commands/review-pr.md
  • .claude/commands/skills-index.md
  • .claude/commands/test.md
  • .claude/commands/verify-all.md
  • .claude/guides/01-overview.md
  • .claude/guides/02-api-naming.md
  • .claude/guides/03-deprecation.md
  • .claude/guides/04-apple-package.md
  • .claude/guides/05-android-package.md
  • .claude/guides/06-gql-package.md
  • .claude/guides/07-versioning.md
  • .claude/guides/08-deployment.md
  • .claude/guides/09-expo-ondevice-ai.md
  • .claude/guides/09-platform-differences.md
  • .claude/guides/10-todo-list.md
  • .claude/guides/11-react-native-ondevice-ai.md
  • .claude/guides/12-flutter-ondevice-ai.md
  • .claude/guides/example-app-structure.md
  • .claude/knowledge/DIGEST.md
  • .claude/knowledge/apple-intelligence.md
  • .claude/knowledge/chrome-built-in-ai.md
  • .claude/knowledge/gemini-nano.md
  • .claude/knowledge/llama-cpp.md
  • .codex/skills/review-self/SKILL.md
  • .codex/skills/review-self/agents/openai.yaml
  • .github/workflows/ci-agent-context.yml
  • .github/workflows/ci-version-mirrors.yml
  • AGENTS.md
  • SKILLS_INDEX.md
  • knowledge/README.md
  • knowledge/_claude-context/context.md
  • knowledge/external/foundation-models-api.md
  • knowledge/external/gemini-nano-api.md
  • knowledge/external/localllmclient-api.md
  • knowledge/internal/01-naming-conventions.md
  • knowledge/internal/02-architecture.md
  • knowledge/internal/03-coding-style.md
  • knowledge/internal/04-api-design.md
  • knowledge/internal/05-git-deployment.md
  • libraries/expo-ondevice-ai/android/build.gradle
  • libraries/expo-ondevice-ai/android/src/main/java/expo/modules/ondeviceai/ExpoOndeviceAiHelper.kt
  • libraries/expo-ondevice-ai/android/src/main/java/expo/modules/ondeviceai/ExpoOndeviceAiModule.kt
  • libraries/expo-ondevice-ai/android/src/main/java/expo/modules/ondeviceai/ExpoOndeviceAiSerialization.kt
  • libraries/expo-ondevice-ai/plugin/src/withOndeviceAi.ts
  • libraries/expo-ondevice-ai/src/ExpoOndeviceAiModule.web.ts
  • libraries/expo-ondevice-ai/src/__mocks__/expo-modules-core.js
  • libraries/expo-ondevice-ai/src/__tests__/index.test.ts
  • libraries/expo-ondevice-ai/src/__tests__/web-module.test.ts
  • libraries/expo-ondevice-ai/src/index.ts
  • libraries/flutter_ondevice_ai/example/ios/LocanaraLlamaBridge/Sources/LlamaCppBridgeEngine.swift
  • libraries/react-native-ondevice-ai/README.md
  • libraries/react-native-ondevice-ai/android/gradle.properties
  • libraries/react-native-ondevice-ai/android/src/main/java/com/margelo/nitro/ondeviceai/HybridOndeviceAi.kt
  • libraries/react-native-ondevice-ai/android/src/main/java/com/margelo/nitro/ondeviceai/OndeviceAiHelper.kt
  • libraries/react-native-ondevice-ai/android/src/test/java/com/margelo/nitro/ondeviceai/OndeviceAiHelperTest.kt
  • libraries/react-native-ondevice-ai/ios/HybridOndeviceAi.swift
  • libraries/react-native-ondevice-ai/ios/OndeviceAiHelper.swift
  • libraries/react-native-ondevice-ai/src/__tests__/index.test.ts
  • libraries/react-native-ondevice-ai/src/index.ts
  • libraries/react-native-ondevice-ai/src/specs/OndeviceAi.nitro.ts
  • llms-full.txt
  • llms.txt
  • package.json
  • packages/android/locanara/src/main/kotlin/com/locanara/Locanara.kt
  • packages/android/locanara/src/main/kotlin/com/locanara/builtin/ChatChain.kt
  • packages/android/locanara/src/main/kotlin/com/locanara/builtin/ClassifyChain.kt
  • packages/android/locanara/src/main/kotlin/com/locanara/builtin/ExtractChain.kt
  • packages/android/locanara/src/main/kotlin/com/locanara/builtin/ProofreadChain.kt
  • packages/android/locanara/src/main/kotlin/com/locanara/builtin/RewriteChain.kt
  • packages/android/locanara/src/main/kotlin/com/locanara/builtin/SummarizeChain.kt
  • packages/android/locanara/src/main/kotlin/com/locanara/builtin/TranslateChain.kt
  • packages/android/locanara/src/main/kotlin/com/locanara/engine/ExecuTorchEngine.kt
  • packages/android/locanara/src/main/kotlin/com/locanara/mlkit/AndroidCapability.kt
  • packages/android/locanara/src/main/kotlin/com/locanara/mlkit/MLKitClients.kt
  • packages/android/locanara/src/main/kotlin/com/locanara/mlkit/MLKitPromptClient.kt
  • packages/android/locanara/src/main/kotlin/com/locanara/personalization/FeedbackCollector.kt
  • packages/android/locanara/src/main/kotlin/com/locanara/personalization/PersonalizationManager.kt
  • packages/android/locanara/src/main/kotlin/com/locanara/rag/RAGManager.kt
  • packages/android/locanara/src/main/kotlin/com/locanara/rag/RAGQueryEngine.kt
  • packages/android/locanara/src/main/kotlin/com/locanara/rag/VectorStore.kt
  • packages/android/locanara/src/test/kotlin/com/locanara/AndroidCapabilityTests.kt
  • packages/android/locanara/src/test/kotlin/com/locanara/ConversationIdTests.kt
  • packages/android/locanara/src/test/kotlin/com/locanara/ErrorHandlingTests.kt
  • packages/apple/Example/LocanaraExample/components/pages/FrameworkShowcase/PipelineDemo.swift
  • packages/apple/Sources/BuiltIn/ChatChain.swift
  • packages/apple/Sources/BuiltIn/ClassifyChain.swift
  • packages/apple/Sources/BuiltIn/ExtractChain.swift
  • packages/apple/Sources/BuiltIn/ProofreadChain.swift
  • packages/apple/Sources/BuiltIn/RewriteChain.swift
  • packages/apple/Sources/BuiltIn/SummarizeChain.swift
  • packages/apple/Sources/BuiltIn/TranslateChain.swift
  • packages/apple/Sources/Engine/DeviceCapabilityDetector.swift
  • packages/apple/Sources/Engine/EngineTypes.swift
  • packages/apple/Sources/Engine/LlamaCppBridge.swift
  • packages/apple/Sources/Engine/LlamaCppEngine.swift
  • packages/apple/Sources/Engine/LocalModelInferenceProvider.swift
  • packages/apple/Sources/ModelManager/ModelDownloader.swift
  • packages/apple/Sources/ModelManager/ModelManager.swift
  • packages/apple/Sources/ModelManager/ModelRegistry.swift
  • packages/apple/Sources/ModelManager/ModelStorage.swift
  • packages/apple/Sources/Personalization/FeedbackCollector.swift
  • packages/apple/Sources/Personalization/PromptOptimizer.swift
  • packages/apple/Sources/RAG/EmbeddingEngine.swift
  • packages/apple/Sources/RAG/RAGCollectionManager.swift
  • packages/apple/Sources/RAG/RAGQueryEngine.swift
  • packages/apple/Sources/RAG/VectorStore.swift
  • packages/apple/Tests/ModelPackageIntegrityTests.swift
  • packages/apple/Tests/PersonalizationSecurityTests.swift
  • packages/apple/Tests/RAGTests.swift
  • packages/site/package.json
  • packages/site/public/llms-full.txt
  • packages/site/public/llms.txt
  • packages/web/src/Locanara.ts
  • packages/web/tests/Locanara.test.ts
  • scripts/agent/compile-context.test.ts
  • scripts/agent/compile-context.ts
  • scripts/agent/lint-markdown.ts
  • scripts/agent/package.json
💤 Files with no reviewable changes (16)
  • packages/android/locanara/src/main/kotlin/com/locanara/builtin/ExtractChain.kt
  • packages/android/locanara/src/main/kotlin/com/locanara/builtin/SummarizeChain.kt
  • packages/apple/Sources/BuiltIn/RewriteChain.swift
  • packages/apple/Sources/BuiltIn/SummarizeChain.swift
  • packages/android/locanara/src/main/kotlin/com/locanara/builtin/RewriteChain.kt
  • packages/android/locanara/src/main/kotlin/com/locanara/builtin/ProofreadChain.kt
  • packages/android/locanara/src/main/kotlin/com/locanara/builtin/TranslateChain.kt
  • packages/android/locanara/src/main/kotlin/com/locanara/builtin/ClassifyChain.kt
  • packages/apple/Sources/BuiltIn/TranslateChain.swift
  • packages/apple/Sources/BuiltIn/ChatChain.swift
  • packages/android/locanara/src/main/kotlin/com/locanara/builtin/ChatChain.kt
  • packages/apple/Sources/RAG/RAGCollectionManager.swift
  • packages/apple/Sources/BuiltIn/ExtractChain.swift
  • packages/apple/Sources/BuiltIn/ProofreadChain.swift
  • packages/apple/Sources/Engine/LocalModelInferenceProvider.swift
  • packages/apple/Sources/BuiltIn/ClassifyChain.swift

Comment thread AGENTS.md Outdated
Comment thread knowledge/_claude-context/context.md Outdated
Comment thread packages/apple/Sources/ModelManager/ModelStorage.swift
Comment thread packages/apple/Tests/PersonalizationSecurityTests.swift Outdated
Comment thread packages/apple/Tests/RAGTests.swift Outdated
Comment thread packages/apple/Tests/RAGTests.swift
Comment thread scripts/agent/compile-context.ts
@hyochan

hyochan commented Jul 11, 2026

Copy link
Copy Markdown
Member Author

Addressed the remaining review-body findings in 0bb361e.

  • Reconciled FoundationModels guidance with the installed Xcode 26.6 SDK interface.
  • Generalized Apple package-asset cancellation and made profile/feedback deletion transactional.
  • Sanitized ExecuTorch logs and public failures while preserving cancellation and native status semantics.
  • Consolidated Expo streaming subscription handling, translator creation, and rewrite mappings.
  • Added Web terminal events for stream factory/iteration failures without replacing the original error.
  • Made the Web translator cache single-flight, active-lease-safe, bounded, and reset-abortable.
  • Added regression coverage for Apple rollback/cancellation, Android privacy/status/cancellation, and Expo stream/cache lifecycle.

Verified locally: Apple 177 tests and example build; Android 102 debug + 102 release tests, build, lint, and example assemble; Expo 48 tests, typecheck, lint (0 errors), build, and Prettier; agent-context checks 9 tests and Markdown lint.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
libraries/react-native-ondevice-ai/ios/HybridOndeviceAi.swift (1)

268-305: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

chatStream should return the real canContinue value instead of hardcoding true. This diverges from chat() and can tell callers a conversation is still open when it isn't.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libraries/react-native-ondevice-ai/ios/HybridOndeviceAi.swift` around lines
268 - 305, The chatStream method currently hardcodes canContinue to true in its
NitroChatResult. Update chatStream to obtain and return the same real
canContinue value used by chat(), while preserving the existing streaming and
final-result behavior.
♻️ Duplicate comments (1)
libraries/react-native-ondevice-ai/android/src/main/java/com/margelo/nitro/ondeviceai/HybridOndeviceAi.kt (1)

436-468: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

canContinue remains hardcoded to true — previously flagged, still unresolved.

Line 466 still returns canContinue = true unconditionally in chatStream, matching the exact pattern raised in an earlier review round on these same lines. The non-streaming chat() (line 303) correctly derives canContinue from result.canContinue; the streaming path should do the same once a completion signal is available from locanara.chatStream(...).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@libraries/react-native-ondevice-ai/android/src/main/java/com/margelo/nitro/ondeviceai/HybridOndeviceAi.kt`
around lines 436 - 468, The streaming chatStream implementation still hardcodes
NitroChatResult.canContinue to true. Update chatStream and the Locanara stream
result handling to capture the completion canContinue value from
locanara.chatStream(...), then return that value in the final NitroChatResult
while preserving the existing accumulated message and conversationId behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@libraries/expo-ondevice-ai/android/src/main/java/expo/modules/ondeviceai/ExpoOndeviceAiModule.kt`:
- Around line 459-465: Update the chatStream response construction in the Kotlin
bridge so canContinue reflects the actual Locanara stream completion state
instead of always being true. Reuse the existing stream-state value or
completion result available in chatStream, while preserving the message and
conversationId fields.

In `@libraries/expo-ondevice-ai/src/__tests__/web-module.test.ts`:
- Around line 271-272: Remove the duplicated async factory callback declarations
in the create mocks at the visible location and the corresponding locations near
lines 319, 366, and 462. Keep one valid async options callback per factory,
ensuring each declaration is properly closed so the test file parses.

---

Outside diff comments:
In `@libraries/react-native-ondevice-ai/ios/HybridOndeviceAi.swift`:
- Around line 268-305: The chatStream method currently hardcodes canContinue to
true in its NitroChatResult. Update chatStream to obtain and return the same
real canContinue value used by chat(), while preserving the existing streaming
and final-result behavior.

---

Duplicate comments:
In
`@libraries/react-native-ondevice-ai/android/src/main/java/com/margelo/nitro/ondeviceai/HybridOndeviceAi.kt`:
- Around line 436-468: The streaming chatStream implementation still hardcodes
NitroChatResult.canContinue to true. Update chatStream and the Locanara stream
result handling to capture the completion canContinue value from
locanara.chatStream(...), then return that value in the final NitroChatResult
while preserving the existing accumulated message and conversationId behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 999eeae8-802f-415a-8d68-1be500b1170e

📥 Commits

Reviewing files that changed from the base of the PR and between 9ae95dd and 0bb361e.

⛔ Files ignored due to path filters (16)
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JHybridOndeviceAiSpec.cpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JHybridOndeviceAiSpec.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroProofreadInputType.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroProofreadOptions.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/HybridOndeviceAiSpec.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroProofreadInputType.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroProofreadOptions.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/NitroOndeviceAi-Swift-Cxx-Umbrella.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/c++/HybridOndeviceAiSpecSwift.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/HybridOndeviceAiSpec.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/HybridOndeviceAiSpec_cxx.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroProofreadInputType.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroProofreadOptions.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/HybridOndeviceAiSpec.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroProofreadInputType.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroProofreadOptions.hpp is excluded by !**/generated/**
📒 Files selected for processing (34)
  • .claude/knowledge/apple-intelligence.md
  • AGENTS.md
  • knowledge/_claude-context/context.md
  • knowledge/internal/01-naming-conventions.md
  • knowledge/internal/02-architecture.md
  • libraries/expo-ondevice-ai/android/src/main/java/expo/modules/ondeviceai/ExpoOndeviceAiModule.kt
  • libraries/expo-ondevice-ai/src/ExpoOndeviceAiModule.web.ts
  • libraries/expo-ondevice-ai/src/__mocks__/expo-modules-core.js
  • libraries/expo-ondevice-ai/src/__tests__/index.test.ts
  • libraries/expo-ondevice-ai/src/__tests__/web-module.test.ts
  • libraries/expo-ondevice-ai/src/index.ts
  • libraries/react-native-ondevice-ai/android/src/main/java/com/margelo/nitro/ondeviceai/HybridOndeviceAi.kt
  • libraries/react-native-ondevice-ai/android/src/main/java/com/margelo/nitro/ondeviceai/OndeviceAiHelper.kt
  • libraries/react-native-ondevice-ai/android/src/test/java/com/margelo/nitro/ondeviceai/OndeviceAiHelperTest.kt
  • libraries/react-native-ondevice-ai/ios/HybridOndeviceAi.swift
  • libraries/react-native-ondevice-ai/ios/OndeviceAiHelper.swift
  • libraries/react-native-ondevice-ai/src/__tests__/index.test.ts
  • libraries/react-native-ondevice-ai/src/index.ts
  • libraries/react-native-ondevice-ai/src/specs/OndeviceAi.nitro.ts
  • packages/android/locanara/src/main/kotlin/com/locanara/Locanara.kt
  • packages/android/locanara/src/main/kotlin/com/locanara/engine/ExecuTorchEngine.kt
  • packages/android/locanara/src/main/kotlin/com/locanara/mlkit/AndroidCapability.kt
  • packages/android/locanara/src/test/kotlin/com/locanara/AndroidCapabilityTests.kt
  • packages/android/locanara/src/test/kotlin/com/locanara/engine/ExecuTorchEnginePrivacyTests.kt
  • packages/apple/Sources/Engine/DeviceCapabilityDetector.swift
  • packages/apple/Sources/ModelManager/ModelDownloader.swift
  • packages/apple/Sources/ModelManager/ModelStorage.swift
  • packages/apple/Sources/Personalization/FeedbackCollector.swift
  • packages/apple/Tests/EngineIntegrationTests.swift
  • packages/apple/Tests/ModelPackageIntegrityTests.swift
  • packages/apple/Tests/PersonalizationSecurityTests.swift
  • packages/apple/Tests/RAGTests.swift
  • scripts/agent/compile-context.test.ts
  • scripts/agent/compile-context.ts
💤 Files with no reviewable changes (1)
  • AGENTS.md
✅ Files skipped from review due to trivial changes (2)
  • knowledge/internal/01-naming-conventions.md
  • knowledge/internal/02-architecture.md
🚧 Files skipped from review as they are similar to previous changes (14)
  • libraries/expo-ondevice-ai/src/mocks/expo-modules-core.js
  • libraries/react-native-ondevice-ai/ios/OndeviceAiHelper.swift
  • packages/apple/Tests/RAGTests.swift
  • libraries/react-native-ondevice-ai/android/src/test/java/com/margelo/nitro/ondeviceai/OndeviceAiHelperTest.kt
  • scripts/agent/compile-context.test.ts
  • libraries/react-native-ondevice-ai/android/src/main/java/com/margelo/nitro/ondeviceai/OndeviceAiHelper.kt
  • packages/android/locanara/src/main/kotlin/com/locanara/mlkit/AndroidCapability.kt
  • packages/apple/Sources/Personalization/FeedbackCollector.swift
  • libraries/expo-ondevice-ai/src/index.ts
  • libraries/expo-ondevice-ai/src/ExpoOndeviceAiModule.web.ts
  • packages/apple/Sources/ModelManager/ModelStorage.swift
  • scripts/agent/compile-context.ts
  • knowledge/_claude-context/context.md
  • packages/android/locanara/src/main/kotlin/com/locanara/Locanara.kt

Comment thread libraries/expo-ondevice-ai/src/__tests__/web-module.test.ts
@hyochan

hyochan commented Jul 11, 2026

Copy link
Copy Markdown
Member Author

Follow-up review disposition: the chatStream canContinue observations for Expo Android and React Native Android/iOS are false positives under the current core contract. Every successful Apple and Android ChatResult path returns canContinue=true, while ChatStreamChunk has no continuation field; isFinal only marks completion of the current response. Inferring false from isFinal would incorrectly end every normal conversation, and re-running chat would duplicate inference and memory effects. Dynamic continuation semantics should be introduced later as a source-first GraphQL/core/all-wrapper contract change. The alleged duplicate async factories in the Expo Web test are also absent on the current head; Prettier, TypeScript checking, and the focused suite pass (19/19). The two inline threads now contain the detailed evidence and are resolved.

@hyochan
hyochan merged commit cdd2329 into main Jul 11, 2026
17 checks passed
@hyochan
hyochan deleted the codex/stabilize-ondevice-ai-lifecycle branch July 11, 2026 21:19
@coderabbitai coderabbitai Bot mentioned this pull request Aug 7, 2026
10 of 11 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs: document Pipeline DSL with cross-platform code examples

1 participant