noteconnection 1.8.0 → 1.9.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 (195) hide show
  1. package/README.md +257 -1803
  2. package/dist/src/agent_workspace.contract.parity.test.js +105 -421
  3. package/dist/src/agent_workspace.frontend.test.js +410 -11
  4. package/dist/src/android.knowledgebase.picker.contract.test.js +102 -0
  5. package/dist/src/appimage.patchelf.test.js +113 -0
  6. package/dist/src/appimage.portability.test.js +156 -0
  7. package/dist/src/backend/FileLoader.js +18 -4
  8. package/dist/src/backend/FileLoader.test.js +125 -0
  9. package/dist/src/backend/GraphBuilder.identity.test.js +86 -0
  10. package/dist/src/backend/GraphBuilder.js +66 -18
  11. package/dist/src/backend/GraphBuilder.matching.test.js +127 -0
  12. package/dist/src/backend/GraphMetrics.js +0 -3
  13. package/dist/src/backend/ResourceIdentity.js +155 -0
  14. package/dist/src/backend/ResourceIdentity.test.js +113 -0
  15. package/dist/src/backend/algorithms/CycleDetection.js +0 -1
  16. package/dist/src/backend/algorithms/HybridEngine.js +0 -3
  17. package/dist/src/backend/algorithms/LayoutEngine.js +0 -1
  18. package/dist/src/backend/algorithms/StatisticalAnalyzer.js +0 -4
  19. package/dist/src/backend/algorithms/TopologicalSort.js +0 -2
  20. package/dist/src/backend/build_dag.js +0 -1
  21. package/dist/src/capacitor.bridge.serialization.contract.test.js +3 -0
  22. package/dist/src/capacitor.runtime.contract.test.js +11 -2
  23. package/dist/src/core/Graph.js +138 -42
  24. package/dist/src/core/Graph.test.js +74 -0
  25. package/dist/src/core/NoteConnection.js +2 -2
  26. package/dist/src/core/PathBridge.js +265 -2
  27. package/dist/src/core/ResourceReference.js +17 -0
  28. package/dist/src/desktop.godot.pack.test.js +142 -0
  29. package/dist/src/desktop.runtime.graph.test.js +66 -0
  30. package/dist/src/export/WorkspaceExportBundle.js +44 -0
  31. package/dist/src/export/WorkspaceExportBundle.test.js +61 -0
  32. package/dist/src/foundation.release.evidence.contract.test.js +127 -9
  33. package/dist/src/foundation.sqlite.runtime.contract.test.js +6 -0
  34. package/dist/src/frontend/README.md +257 -1803
  35. package/dist/src/frontend/agent_workspace.js +665 -7
  36. package/dist/src/frontend/index.html +31 -2
  37. package/dist/src/frontend/knowledge_projection_contract.js +230 -0
  38. package/dist/src/frontend/knowledge_projection_store.js +214 -0
  39. package/dist/src/frontend/libs/path_core.js +158 -42
  40. package/dist/src/frontend/locales/en.json +32 -0
  41. package/dist/src/frontend/locales/zh.json +32 -0
  42. package/dist/src/frontend/mobile_budget_runtime.js +33 -0
  43. package/dist/src/frontend/mobile_exact_analyzer.js +393 -0
  44. package/dist/src/frontend/mobile_identity_contract.js +221 -0
  45. package/dist/src/frontend/mobile_semantic_comparator.js +264 -0
  46. package/dist/src/frontend/path.html +6 -2
  47. package/dist/src/frontend/simulationWorker.js +1 -1
  48. package/dist/src/frontend/source_manager.js +110 -4
  49. package/dist/src/frontend/storage_provider.js +661 -71
  50. package/dist/src/frontend/styles.css +107 -13
  51. package/dist/src/frontend/workspace_panes.js +111 -7
  52. package/dist/src/frontend.runtime.assets.contract.test.js +81 -0
  53. package/dist/src/graph.answer.operator.contract.test.js +72 -0
  54. package/dist/src/index.js +6 -2
  55. package/dist/src/index.knowledge-root.test.js +118 -0
  56. package/dist/src/indexing/IndexLifecycle.js +22 -0
  57. package/dist/src/indexing/IndexLifecycle.test.js +24 -0
  58. package/dist/src/knowledge.api.contract.test.js +4 -2
  59. package/dist/src/knowledge_projection.contract.test.js +134 -0
  60. package/dist/src/knowledge_projection_store.test.js +200 -0
  61. package/dist/src/learning/AnswerQualityEvaluation.js +153 -0
  62. package/dist/src/learning/AnswerQualityEvaluation.test.js +118 -0
  63. package/dist/src/learning/KnowledgeLearningPlatform.js +4687 -3892
  64. package/dist/src/learning/KnowledgeLearningPlatform.persistence.test.js +418 -0
  65. package/dist/src/learning/KnowledgeLearningPlatform.test.js +243 -6
  66. package/dist/src/learning/KnowledgeWorkspaceConversationRegression.js +25 -1
  67. package/dist/src/learning/KnowledgeWorkspaceConversationRegression.test.js +13 -5
  68. package/dist/src/learning/MeasurementEvidenceConflicts.test.js +78 -0
  69. package/dist/src/learning/PublicAnswerContract.test.js +85 -0
  70. package/dist/src/learning/PublicEvidenceSelection.test.js +94 -0
  71. package/dist/src/learning/PublicMeasurementDisclosure.test.js +54 -0
  72. package/dist/src/learning/agentConversationExecution.js +211 -0
  73. package/dist/src/learning/agentConversationExecution.test.js +209 -0
  74. package/dist/src/learning/agentConversationSerialization.js +266 -0
  75. package/dist/src/learning/agentConversationSerialization.test.js +165 -0
  76. package/dist/src/learning/agentResponseBudget.js +179 -0
  77. package/dist/src/learning/agentResponseBudget.test.js +97 -0
  78. package/dist/src/learning/agentWorkspaceCapabilityContracts.js +65 -0
  79. package/dist/src/learning/answerReleaseReview.js +1191 -250
  80. package/dist/src/learning/answerReleaseReview.test.js +1293 -7
  81. package/dist/src/learning/answerTaskPlan.js +473 -0
  82. package/dist/src/learning/conversationComposer.js +627 -386
  83. package/dist/src/learning/conversationComposer.test.js +1403 -0
  84. package/dist/src/learning/evidenceContextAssembler.js +312 -120
  85. package/dist/src/learning/graphAnswerCoverage.js +37 -0
  86. package/dist/src/learning/graphAnswerCoverage.test.js +93 -0
  87. package/dist/src/learning/graphAnswerCoverageCalibration.js +59 -0
  88. package/dist/src/learning/graphAnswerCoverageCalibration.test.js +46 -0
  89. package/dist/src/learning/graphAnswerFacts.js +94 -0
  90. package/dist/src/learning/graphAnswerFacts.test.js +39 -0
  91. package/dist/src/learning/graphAnswerPlan.js +908 -0
  92. package/dist/src/learning/graphAnswerPlan.test.js +907 -0
  93. package/dist/src/learning/graphAnswerQualityCalibration.js +255 -0
  94. package/dist/src/learning/graphAnswerQualityCalibration.test.js +72 -0
  95. package/dist/src/learning/graphAnswerQualityPolicy.js +30 -0
  96. package/dist/src/learning/graphClaimMatcher.js +104 -0
  97. package/dist/src/learning/graphConditionedContext.js +109 -0
  98. package/dist/src/learning/graphConditionedContext.test.js +99 -0
  99. package/dist/src/learning/graphContextAssembler.js +3 -13
  100. package/dist/src/learning/graphContextAssembler.test.js +2 -2
  101. package/dist/src/learning/graphExpansionPolicy.js +22 -0
  102. package/dist/src/learning/graphExpansionPolicy.test.js +33 -0
  103. package/dist/src/learning/identityMetadata.test.js +26 -0
  104. package/dist/src/learning/index.js +1 -0
  105. package/dist/src/learning/mobileAnswerProjection.js +104 -0
  106. package/dist/src/learning/mobileAnswerProjection.test.js +102 -0
  107. package/dist/src/learning/queryBackend.js +34 -10
  108. package/dist/src/learning/queryBackend.test.js +21 -0
  109. package/dist/src/learning/ragContextPack.js +115 -16
  110. package/dist/src/learning/ragContextPack.test.js +151 -0
  111. package/dist/src/learning/ragEvidenceQuality.js +96 -0
  112. package/dist/src/learning/ragEvidenceQuality.test.js +29 -0
  113. package/dist/src/learning/ragPublicText.js +67 -4
  114. package/dist/src/learning/ragPublicText.test.js +57 -0
  115. package/dist/src/learning/ragSufficiencyJudge.js +11 -1
  116. package/dist/src/learning/ragSufficiencyProviderJudge.js +16 -26
  117. package/dist/src/learning/ragSufficiencyProviderJudge.test.js +51 -0
  118. package/dist/src/learning/requestNormalization.js +106 -0
  119. package/dist/src/learning/requestNormalization.test.js +101 -0
  120. package/dist/src/learning/store.js +116 -12
  121. package/dist/src/learning/store.test.js +299 -0
  122. package/dist/src/learning/vectorAccelerationAdapter.js +22 -6
  123. package/dist/src/learning/vectorAccelerationAdapter.test.js +30 -0
  124. package/dist/src/learning/workspaceHydration.js +168 -0
  125. package/dist/src/learning/workspaceHydration.test.js +30 -0
  126. package/dist/src/license.policy.contract.test.js +9 -4
  127. package/dist/src/markdown/MarkdownGateway.js +4 -0
  128. package/dist/src/markdown/MarkdownGateway.test.js +59 -0
  129. package/dist/src/middleware/SseResponseWriter.js +122 -0
  130. package/dist/src/middleware/SseResponseWriter.test.js +54 -0
  131. package/dist/src/middleware/auth.js +28 -7
  132. package/dist/src/middleware/auth.test.js +33 -0
  133. package/dist/src/middleware/index.js +2 -1
  134. package/dist/src/mobile.android.evidence.contract.test.js +146 -0
  135. package/dist/src/mobile.android.signing.contract.test.js +66 -0
  136. package/dist/src/mobile.artifact.contract.test.js +144 -0
  137. package/dist/src/mobile.budget.contract.test.js +90 -0
  138. package/dist/src/mobile.canonical-id.readiness.test.js +54 -0
  139. package/dist/src/mobile.graph.parity.contract.test.js +207 -0
  140. package/dist/src/mobile.identity.contract.test.js +66 -0
  141. package/dist/src/mobile.identity.corpus.manifest.test.js +72 -0
  142. package/dist/src/mobile.identity.corpus.test.js +40 -0
  143. package/dist/src/mobile.native.recovery.contract.test.js +64 -0
  144. package/dist/src/mobile.pipeline.test.js +11 -1
  145. package/dist/src/mobile.profile.contract.test.js +225 -0
  146. package/dist/src/mobile.semantic.parity.test.js +92 -0
  147. package/dist/src/mobile_exact_analyzer.test.js +113 -0
  148. package/dist/src/notemd/ContentGenerator.js +4 -9
  149. package/dist/src/notemd/FileProcessor.js +4 -9
  150. package/dist/src/notemd/NotemdService.js +12 -17
  151. package/dist/src/notemd/Translator.js +4 -9
  152. package/dist/src/notemd/progressReporter.js +9 -0
  153. package/dist/src/notemd.server.integration.test.js +119 -4
  154. package/dist/src/notemd.workflow.pipeline.test.js +1 -1
  155. package/dist/src/pathbridge.handshake.contract.test.js +29 -0
  156. package/dist/src/pathbridge.host-adapter.contract.test.js +133 -0
  157. package/dist/src/pathcore.bundle.contract.test.js +8 -0
  158. package/dist/src/platform/ExportProfile.js +28 -0
  159. package/dist/src/platform/MobileBudget.js +11 -0
  160. package/dist/src/platform/PlatformCapabilities.js +14 -0
  161. package/dist/src/platform/PlatformCapabilities.test.js +6 -0
  162. package/dist/src/query_backend.external_http.integration.test.js +2 -2
  163. package/dist/src/release.godot.mirror.contract.test.js +28 -4
  164. package/dist/src/resources/ResourceRegistry.js +40 -0
  165. package/dist/src/resources/ResourceRegistry.test.js +36 -0
  166. package/dist/src/routes/index.js +0 -4
  167. package/dist/src/routes/knowledge.js +46 -33
  168. package/dist/src/routes/knowledgePayload.js +185 -0
  169. package/dist/src/routes/knowledgePayload.test.js +53 -0
  170. package/dist/src/routes/markdown.js +8 -0
  171. package/dist/src/routes/notemd.js +990 -234
  172. package/dist/src/routes/registry.contract.test.js +5 -2
  173. package/dist/src/routes/registry.shadow.contract.test.js +22 -0
  174. package/dist/src/routes/render.js +14 -51
  175. package/dist/src/runtime.capabilities.test.js +18 -2
  176. package/dist/src/server.js +566 -1090
  177. package/dist/src/server.migration.test.js +417 -15
  178. package/dist/src/sidecar.build.lock.contract.test.js +201 -0
  179. package/dist/src/sidecar.freshness.contract.test.js +184 -0
  180. package/dist/src/sidecar.replacement.boundary.contract.test.js +6 -3
  181. package/dist/src/sidecar.supply.readiness.contract.test.js +11 -0
  182. package/dist/src/simulation.worker.offline.test.js +104 -0
  183. package/dist/src/storage.provider.capacitor.content.contract.test.js +4 -1
  184. package/dist/src/storage.provider.contract.test.js +152 -1
  185. package/dist/src/tauri.frontend.build.contract.test.js +8 -0
  186. package/dist/src/tauri.window.evidence.execution.test.js +68 -0
  187. package/dist/src/utils/RuntimePaths.js +6 -0
  188. package/dist/src/utils/RuntimePaths.test.js +27 -0
  189. package/dist/src/windows.installer.build.test.js +109 -0
  190. package/dist/src/windows.installer.payload.test.js +76 -0
  191. package/dist/src/windows.installer.runtime.test.js +69 -0
  192. package/dist/src/workspace/WorkspaceRegistry.js +13 -0
  193. package/package.json +32 -10
  194. package/dist/src/routes/data.js +0 -267
  195. package/dist/src/routes/diagnostics.js +0 -51
@@ -1137,6 +1137,30 @@ function createWorkspaceHtml() {
1137
1137
  <select id="agent-workspace-scope-select"></select>
1138
1138
  <div id="agent-workspace-scope-summary"></div>
1139
1139
  </div>
1140
+ <div class="agent-answer-language-control">
1141
+ <label for="agent-workspace-answer-language-select">Answer language</label>
1142
+ <select id="agent-workspace-answer-language-select">
1143
+ <option value="en">English</option>
1144
+ <option value="zh">中文</option>
1145
+ </select>
1146
+ <div id="agent-workspace-answer-language-summary"></div>
1147
+ </div>
1148
+ <div class="agent-response-mode-control">
1149
+ <label for="agent-workspace-response-mode-select">Answer detail</label>
1150
+ <select id="agent-workspace-response-mode-select">
1151
+ <option value="slim">Slim</option>
1152
+ <option value="full">Full report</option>
1153
+ </select>
1154
+ <div id="agent-workspace-response-mode-summary"></div>
1155
+ </div>
1156
+ <div class="agent-response-budget-control" data-agent-desktop-only="true">
1157
+ <label for="agent-workspace-response-budget-select">Full report budget</label>
1158
+ <select id="agent-workspace-response-budget-select">
1159
+ <option value="adaptive">Adaptive</option>
1160
+ <option value="unbounded">No product cap</option>
1161
+ </select>
1162
+ <div id="agent-workspace-response-budget-summary"></div>
1163
+ </div>
1140
1164
  <input id="agent-workspace-user-id" value="path_user_default" />
1141
1165
  <div id="agent-workspace-chat-messages"></div>
1142
1166
  <div id="agent-workspace-knowledge-points"></div>
@@ -1188,6 +1212,7 @@ function createBaseSandbox(dom) {
1188
1212
  const sandbox = {
1189
1213
  window: dom.window,
1190
1214
  document: dom.window.document,
1215
+ localStorage: dom.window.localStorage,
1191
1216
  console: {
1192
1217
  log: jest.fn(),
1193
1218
  warn: jest.fn(),
@@ -4157,7 +4182,6 @@ describe('workspace panes controller', () => {
4157
4182
  const toolbarRule = styles.match(/\.agent-workspace-drawer-toolbar\s*\{[^}]*\}/)?.[0] || '';
4158
4183
  const workspaceScopeRule = styles.match(/\.agent-scope-control--workspace\s*\{[^}]*\}/)?.[0] || '';
4159
4184
  const scopeStyle = styles.match(/\.agent-scope-control\s*\{[^}]*\}/)?.[0] || '';
4160
- const chatPaneRule = styles.match(/\.agent-chat-pane\s*\{[^}]*\}/)?.[0] || '';
4161
4185
  const knowledgePointsRule = styles.match(/\.agent-knowledge-points\s*\{[^}]*\}/)?.[0] || '';
4162
4186
  const knowledgeCardHeaderRule = styles.match(/\.agent-knowledge-card-header\s*\{[^}]*\}/)?.[0] || '';
4163
4187
  const knowledgeMenuButtonRule = styles.match(/\.agent-knowledge-menu-button\s*\{[^}]*\}/)?.[0] || '';
@@ -4165,8 +4189,6 @@ describe('workspace panes controller', () => {
4165
4189
  expect(toolbarRule).toContain('grid-template-columns: minmax(180px, 1fr) auto');
4166
4190
  expect(workspaceScopeRule).toContain('grid-template-columns: minmax(94px, auto) minmax(0, 1fr)');
4167
4191
  expect(scopeStyle).not.toContain('position: sticky');
4168
- expect(chatPaneRule).toContain('display: grid');
4169
- expect(chatPaneRule).toContain('grid-template-rows:');
4170
4192
  expect(knowledgePointsRule).toContain('overflow-y: auto');
4171
4193
  expect(knowledgeCardHeaderRule).toContain('grid-template-columns: minmax(0, 1fr) 44px');
4172
4194
  expect(knowledgeMenuButtonRule).toContain('width: 44px');
@@ -4290,12 +4312,8 @@ describe('workspace panes controller', () => {
4290
4312
  expect(chatPaneRule).toContain('overflow-x: hidden');
4291
4313
  expect(chatPaneRule).toContain('overflow-y: auto');
4292
4314
  expect(chatPaneRule).not.toContain('overflow: hidden');
4293
- expect(chatPaneRule).toContain('display: grid');
4294
- expect(chatPaneRule).toContain('grid-template-rows:');
4295
- expect(chatPaneRule).toContain('minmax(128px, 0.62fr)');
4296
- expect(chatPaneRule).toContain('max-height: calc(100vh - 142px)');
4315
+ // The browser verifier measures readable transcript height with optional controls present.
4297
4316
  expect(chatMessagesRule).toContain('overflow-y: auto');
4298
- expect(chatMessagesRule).toContain('min-height: 0');
4299
4317
  expect(knowledgePointsRule).toContain('overflow-y: auto');
4300
4318
  expect(knowledgePointsRule).toContain('min-height: 0');
4301
4319
  expect(knowledgePointsRule).toContain('scrollbar-gutter: stable');
@@ -6112,6 +6130,289 @@ describe('agent workspace learning-path integration', () => {
6112
6130
  corpusId: 'waterglass',
6113
6131
  sourcePathPrefixes: ['Knowledge_Base/waterglass'],
6114
6132
  });
6133
+ expect(requestBody.answerLanguage).toBe('en');
6134
+ });
6135
+ test('uses the software language by default and preserves an explicit answer-language override in requests', async () => {
6136
+ const { document, window, fetchMock, } = loadAgentWorkspaceHarness({ withI18n: true });
6137
+ if (!fetchMock) {
6138
+ throw new Error('expected fetch mock');
6139
+ }
6140
+ const completedTurn = (turnId) => createSseResponse([
6141
+ {
6142
+ event: 'turn_completed',
6143
+ payload: {
6144
+ type: 'turn_completed',
6145
+ turnId,
6146
+ emittedAt: '2026-08-24T00:00:00.000Z',
6147
+ result: {
6148
+ assistantMessage: 'scoped response',
6149
+ citations: [],
6150
+ recalledMemories: [],
6151
+ memoryActions: [],
6152
+ knowledgePoints: [],
6153
+ summary: {
6154
+ generatedAt: '2026-08-24T00:00:00.000Z',
6155
+ topK: 6,
6156
+ returnedKnowledgePoints: 0,
6157
+ returnedCitations: 0,
6158
+ recalledMemoryCount: 0,
6159
+ queryEvidenceCoverageRatioPct: 0,
6160
+ },
6161
+ },
6162
+ },
6163
+ },
6164
+ ]);
6165
+ fetchMock.mockImplementationOnce(async () => completedTurn('turn_answer_language_default'));
6166
+ fetchMock.mockImplementationOnce(async () => completedTurn('turn_answer_language_override'));
6167
+ const selector = document.getElementById('agent-workspace-answer-language-select');
6168
+ await window.i18n.setLanguage('zh');
6169
+ expect(selector.value).toBe('zh');
6170
+ const input = document.getElementById('agent-workspace-chat-input');
6171
+ input.value = 'what is water glass?';
6172
+ await window.NoteConnectionAgentWorkspace.sendConversation();
6173
+ expect(JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body || '{}')).answerLanguage).toBe('zh');
6174
+ selector.value = 'en';
6175
+ selector.dispatchEvent(new window.Event('change', { bubbles: true }));
6176
+ expect(window.localStorage.getItem('nc_agent_answer_language')).toBe('en');
6177
+ await window.i18n.setLanguage('zh');
6178
+ expect(selector.value).toBe('en');
6179
+ input.value = 'what is water glass?';
6180
+ await window.NoteConnectionAgentWorkspace.sendConversation();
6181
+ expect(JSON.parse(String(fetchMock.mock.calls[1]?.[1]?.body || '{}')).answerLanguage).toBe('en');
6182
+ });
6183
+ test('persists slim/full answer detail and sends the selected mode', async () => {
6184
+ const { document, window, fetchMock } = loadAgentWorkspaceHarness({ withI18n: true });
6185
+ if (!fetchMock) {
6186
+ throw new Error('expected fetch mock');
6187
+ }
6188
+ const response = () => createSseResponse([
6189
+ {
6190
+ event: 'turn_completed',
6191
+ payload: {
6192
+ type: 'turn_completed',
6193
+ turnId: 'turn_response_mode',
6194
+ emittedAt: '2026-09-03T00:00:00.000Z',
6195
+ result: {
6196
+ responseMode: 'full',
6197
+ assistantMessage: 'full report',
6198
+ answer: 'full report',
6199
+ citations: [],
6200
+ recalledMemories: [],
6201
+ memoryActions: [],
6202
+ knowledgePoints: [],
6203
+ summary: {
6204
+ generatedAt: '2026-09-03T00:00:00.000Z',
6205
+ topK: 6,
6206
+ returnedKnowledgePoints: 0,
6207
+ returnedCitations: 0,
6208
+ recalledMemoryCount: 0,
6209
+ queryEvidenceCoverageRatioPct: 0,
6210
+ },
6211
+ },
6212
+ },
6213
+ },
6214
+ ]);
6215
+ fetchMock.mockImplementationOnce(async () => response());
6216
+ const selector = document.getElementById('agent-workspace-response-mode-select');
6217
+ expect(selector.value).toBe('slim');
6218
+ selector.value = 'full';
6219
+ selector.dispatchEvent(new window.Event('change', { bubbles: true }));
6220
+ expect(window.localStorage.getItem('nc_agent_response_mode')).toBe('full');
6221
+ expect(String(document.getElementById('agent-workspace-response-mode-summary')?.textContent || '')).toContain('Complete');
6222
+ const input = document.getElementById('agent-workspace-chat-input');
6223
+ input.value = 'what is water glass?';
6224
+ await window.NoteConnectionAgentWorkspace.sendConversation();
6225
+ expect(JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body || '{}')).responseMode).toBe('full');
6226
+ selector.value = 'slim';
6227
+ selector.dispatchEvent(new window.Event('change', { bubbles: true }));
6228
+ input.value = 'what is water glass?';
6229
+ await window.NoteConnectionAgentWorkspace.sendConversation();
6230
+ expect(JSON.parse(String(fetchMock.mock.calls[1]?.[1]?.body || '{}')).responseMode).toBe('slim');
6231
+ });
6232
+ test('persists adaptive/unbounded full-report budget and sends it only on desktop', async () => {
6233
+ const { document, window, fetchMock } = loadAgentWorkspaceHarness({ withI18n: true });
6234
+ if (!fetchMock) {
6235
+ throw new Error('expected fetch mock');
6236
+ }
6237
+ const response = () => createSseResponse([{
6238
+ event: 'turn_completed',
6239
+ payload: {
6240
+ type: 'turn_completed',
6241
+ turnId: 'turn_response_budget_ui',
6242
+ emittedAt: '2026-09-03T00:00:00.000Z',
6243
+ result: {
6244
+ responseMode: 'full',
6245
+ responseBudget: { mode: 'unbounded', tier: 'unbounded', productCapDisabled: true },
6246
+ assistantMessage: 'full report',
6247
+ answer: 'full report',
6248
+ citations: [],
6249
+ recalledMemories: [],
6250
+ memoryActions: [],
6251
+ knowledgePoints: [],
6252
+ summary: {
6253
+ generatedAt: '2026-09-03T00:00:00.000Z',
6254
+ topK: 6,
6255
+ returnedKnowledgePoints: 0,
6256
+ returnedCitations: 0,
6257
+ recalledMemoryCount: 0,
6258
+ queryEvidenceCoverageRatioPct: 0,
6259
+ },
6260
+ },
6261
+ },
6262
+ }]);
6263
+ fetchMock.mockImplementationOnce(async () => response());
6264
+ Object.defineProperty(window.navigator, 'deviceMemory', { configurable: true, value: 8 });
6265
+ Object.defineProperty(window.navigator, 'hardwareConcurrency', { configurable: true, value: 8 });
6266
+ const budgetSelector = document.getElementById('agent-workspace-response-budget-select');
6267
+ expect(budgetSelector.value).toBe('adaptive');
6268
+ budgetSelector.value = 'unbounded';
6269
+ budgetSelector.dispatchEvent(new window.Event('change', { bubbles: true }));
6270
+ expect(window.localStorage.getItem('nc_agent_response_budget_mode')).toBe('unbounded');
6271
+ const input = document.getElementById('agent-workspace-chat-input');
6272
+ input.value = 'what is water glass?';
6273
+ await window.NoteConnectionAgentWorkspace.sendConversation();
6274
+ const desktopRequest = JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body || '{}'));
6275
+ expect(desktopRequest.responseBudgetMode).toBe('unbounded');
6276
+ expect(desktopRequest.responseBudgetCapability).toEqual({ memoryClass: 'high', workload: 'max' });
6277
+ window.__NC_RUNTIME_CAPS = { platform: 'capacitor-android', supports_sidecar: false };
6278
+ fetchMock.mockImplementationOnce(async () => response());
6279
+ window.NoteConnectionAgentWorkspace.init();
6280
+ const control = budgetSelector.closest('[data-agent-desktop-only]');
6281
+ expect(control.hidden).toBe(true);
6282
+ input.value = 'what is water glass?';
6283
+ await window.NoteConnectionAgentWorkspace.sendConversation();
6284
+ const mobileRequest = JSON.parse(String(fetchMock.mock.calls[1]?.[1]?.body || '{}'));
6285
+ expect(mobileRequest.responseBudgetMode).toBeUndefined();
6286
+ expect(mobileRequest.responseProfile).toBe('mobile_compact');
6287
+ });
6288
+ test('uses the bounded local exact path on native mobile and exposes only the compact projection', async () => {
6289
+ const { document, window, fetchMock, } = loadAgentWorkspaceHarness({ withI18n: true });
6290
+ if (!fetchMock) {
6291
+ throw new Error('expected fetch mock');
6292
+ }
6293
+ window.__NC_RUNTIME_CAPS = {
6294
+ platform: 'capacitor-android',
6295
+ supports_sidecar: false,
6296
+ };
6297
+ const matches = [
6298
+ { id: 'amorphous_ice', label: '非晶冰', sourceUri: 'Knowledge_Base/waterglass/amorphous-ice.md' },
6299
+ { id: 'water_structure', label: '水分子与氢键网络', sourceUri: 'Knowledge_Base/waterglass/amorphous-ice.md' },
6300
+ { id: 'radial_distribution', label: '径向分布函数 g(r)', sourceUri: 'Knowledge_Base/waterglass/amorphous-ice.md' },
6301
+ ];
6302
+ const queriedTerms = [];
6303
+ const queryKnowledgeBaseExact = jest.fn(async ({ query }) => {
6304
+ queriedTerms.push(query);
6305
+ return { matches: query === '非晶冰' ? matches : [] };
6306
+ });
6307
+ const readContent = jest.fn(async () => [
6308
+ '# 非晶冰',
6309
+ '',
6310
+ '非晶冰是水的一种缺乏长程有序的固态形式。',
6311
+ '',
6312
+ '## 常见用例与性能指标',
6313
+ '该章节不应进入移动端直接回答。',
6314
+ ].join('\n'));
6315
+ window.NoteConnectionStorage = {
6316
+ createProvider: () => ({ queryKnowledgeBaseExact, readContent }),
6317
+ };
6318
+ const input = document.getElementById('agent-workspace-chat-input');
6319
+ input.value = '什么是非晶冰?我应该通过哪些知识点学习?';
6320
+ await window.NoteConnectionAgentWorkspace.sendConversation();
6321
+ expect(queryKnowledgeBaseExact).toHaveBeenCalled();
6322
+ expect(queriedTerms).toContain('非晶冰');
6323
+ expect(readContent).toHaveBeenCalledWith('Knowledge_Base/waterglass/amorphous-ice.md');
6324
+ expect(fetchMock).not.toHaveBeenCalled();
6325
+ const result = window.__NC_LAST_AGENT_CONVERSATION_RESULT;
6326
+ expect(result).toEqual(expect.objectContaining({
6327
+ responseProfile: 'mobile_compact',
6328
+ mobileProjection: expect.objectContaining({
6329
+ primarySubject: '非晶冰',
6330
+ directAnswer: '非晶冰是水的一种缺乏长程有序的固态形式。',
6331
+ route: expect.arrayContaining([
6332
+ expect.objectContaining({ title: '非晶冰', role: 'core' }),
6333
+ expect.objectContaining({ title: '水分子与氢键网络' }),
6334
+ ]),
6335
+ }),
6336
+ }));
6337
+ expect(result.knowledgePoints).toEqual([]);
6338
+ expect(result.citations).toEqual([]);
6339
+ expect(result.trace).toBeUndefined();
6340
+ expect(JSON.stringify(result)).not.toContain('该章节不应进入移动端直接回答');
6341
+ const renderedAssistant = Array.from(document.querySelectorAll('.agent-chat-message-assistant')).map((node) => String(node.textContent || '')).join('\n');
6342
+ expect(renderedAssistant).toContain('建议学习路径');
6343
+ expect(renderedAssistant).toContain('水分子与氢键网络');
6344
+ });
6345
+ test('falls back to the bounded remote projection when the native exact index has no match', async () => {
6346
+ const { document, window, fetchMock, } = loadAgentWorkspaceHarness({ withI18n: true });
6347
+ if (!fetchMock) {
6348
+ throw new Error('expected fetch mock');
6349
+ }
6350
+ window.__NC_RUNTIME_CAPS = {
6351
+ platform: 'capacitor-android',
6352
+ supports_sidecar: false,
6353
+ };
6354
+ const queryKnowledgeBaseExact = jest.fn(async () => ({ matches: [] }));
6355
+ window.NoteConnectionStorage = {
6356
+ createProvider: () => ({ queryKnowledgeBaseExact }),
6357
+ };
6358
+ fetchMock.mockImplementationOnce(async (_url, init) => {
6359
+ const request = JSON.parse(String(init?.body || '{}'));
6360
+ expect(request.responseProfile).toBe('mobile_compact');
6361
+ return createSseResponse([
6362
+ {
6363
+ event: 'turn_completed',
6364
+ payload: {
6365
+ type: 'turn_completed',
6366
+ turnId: 'turn_remote_mobile_projection',
6367
+ emittedAt: '2026-08-01T00:00:00.000Z',
6368
+ result: {
6369
+ responseProfile: 'mobile_compact',
6370
+ assistantMessage: '非晶冰是缺乏长程有序的水的固态形式。',
6371
+ answer: '非晶冰是缺乏长程有序的水的固态形式。',
6372
+ mobileProjection: {
6373
+ schemaVersion: 1,
6374
+ primarySubject: '非晶冰',
6375
+ directAnswer: '非晶冰是缺乏长程有序的水的固态形式。',
6376
+ route: [
6377
+ { nodeId: 'water', title: '水分子与氢键网络', role: 'prerequisite', order: 1 },
6378
+ { nodeId: 'ice', title: '非晶冰', role: 'core', order: 2 },
6379
+ ],
6380
+ citations: [],
6381
+ },
6382
+ assistantBlocks: [],
6383
+ knowledgePoints: [],
6384
+ citations: [],
6385
+ recalledMemories: [],
6386
+ memoryActions: [],
6387
+ summary: {
6388
+ generatedAt: '2026-08-01T00:00:00.000Z',
6389
+ topK: 6,
6390
+ returnedKnowledgePoints: 2,
6391
+ returnedCitations: 0,
6392
+ recalledMemoryCount: 0,
6393
+ appliedMemoryCount: 0,
6394
+ queryEvidenceCoverageRatioPct: 100,
6395
+ },
6396
+ },
6397
+ },
6398
+ },
6399
+ ]);
6400
+ });
6401
+ const input = document.getElementById('agent-workspace-chat-input');
6402
+ input.value = '什么是非晶冰?';
6403
+ await window.NoteConnectionAgentWorkspace.sendConversation();
6404
+ expect(queryKnowledgeBaseExact).toHaveBeenCalled();
6405
+ expect(fetchMock).toHaveBeenCalledTimes(1);
6406
+ expect(String(fetchMock.mock.calls[0]?.[1]?.headers?.Accept || '')).toBe('text/event-stream');
6407
+ const result = window.__NC_LAST_AGENT_CONVERSATION_RESULT;
6408
+ expect(result).toEqual(expect.objectContaining({
6409
+ responseProfile: 'mobile_compact',
6410
+ mobileProjection: expect.objectContaining({ primarySubject: '非晶冰' }),
6411
+ }));
6412
+ expect(result.trace).toBeUndefined();
6413
+ const renderedAssistant = Array.from(document.querySelectorAll('.agent-chat-message-assistant')).map((node) => String(node.textContent || '')).join('\n');
6414
+ expect(renderedAssistant).toContain('建议学习路径');
6415
+ expect(renderedAssistant).toContain('非晶冰');
6115
6416
  });
6116
6417
  test('prefers SSE turn streaming for conversation and renders the completed turn payload', async () => {
6117
6418
  const { document, window, fetchMock, } = loadAgentWorkspaceHarness();
@@ -6669,6 +6970,9 @@ describe('agent workspace learning-path integration', () => {
6669
6970
  }));
6670
6971
  window.marked = {
6671
6972
  parse: jest.fn((markdown) => {
6973
+ if (markdown.includes('Scoped Answer')) {
6974
+ return '<p>Scoped Answer with inline math $E=mc^2$ and display math $$\\frac{\\partial T}{\\partial t}=\\alpha\\nabla^2 T$$.</p>';
6975
+ }
6672
6976
  if (markdown.includes('## Answer Context')) {
6673
6977
  return ('<h2>Answer Context</h2>'
6674
6978
  + '<ul><li>Relevant knowledge points: <strong>1</strong></li></ul>');
@@ -6713,7 +7017,7 @@ describe('agent workspace learning-path integration', () => {
6713
7017
  blockId: 'block_structured_answer_1',
6714
7018
  type: 'structured_answer',
6715
7019
  title: 'Grounded Answer',
6716
- directAnswer: 'Scoped Answer',
7020
+ directAnswer: 'Scoped Answer with inline math $E=mc^2$ and display math $$\\frac{\\partial T}{\\partial t}=\\alpha\\nabla^2 T$$.',
6717
7021
  overviewMarkdown: '## Answer Context\n\n- Relevant knowledge points: **1**\n- Citations returned: **1**\n- Scoped memories recalled: **0**',
6718
7022
  explanationMarkdown: '## Explanation\n\n**Blocks Citation** is the current best scoped anchor.\n\nInline math $E=mc^2$ and a diagram:\n\n```mermaid\ngraph TD;A-->B;\n```',
6719
7023
  evidenceMarkdown: '## Evidence Summary\n\n1. **Blocks Citation** (Knowledge_Base/optics/blocks.md:18)\n - Scoped snippet',
@@ -6836,6 +7140,12 @@ describe('agent workspace learning-path integration', () => {
6836
7140
  },
6837
7141
  ],
6838
7142
  },
7143
+ {
7144
+ blockId: 'block_knowledge_actions_1',
7145
+ type: 'knowledge_actions',
7146
+ title: 'Knowledge Actions',
7147
+ atomIds: ['atom_blocks_1'],
7148
+ },
6839
7149
  ],
6840
7150
  citations: [
6841
7151
  {
@@ -6905,13 +7215,45 @@ describe('agent workspace learning-path integration', () => {
6905
7215
  expect(assistantNode?.querySelector('[data-structured-answer-section="overviewMarkdown"]')).toBeNull();
6906
7216
  expect(assistantNode?.querySelector('[data-structured-answer-section="explanationMarkdown"]')).toBeNull();
6907
7217
  expect(assistantNode?.querySelector('[data-structured-answer-section="evidenceMarkdown"]')).toBeNull();
7218
+ expect(assistantNode?.querySelector('[data-structured-answer-section="nextActionsMarkdown"]')).toBeNull();
6908
7219
  expect(String(assistantNode?.textContent || '')).not.toContain('Answer Context');
6909
7220
  expect(String(assistantNode?.textContent || '')).not.toContain('Evidence Summary');
7221
+ expect(String(assistantNode?.textContent || '')).not.toContain('Explanation');
6910
7222
  expect(String(assistantNode?.textContent || '')).not.toContain('Knowledge Run');
7223
+ expect(String(assistantNode?.textContent || '')).not.toContain('Knowledge Actions');
6911
7224
  expect(String(assistantNode?.textContent || '')).not.toContain('Inspect Run');
6912
- expect(renderMathInElement).not.toHaveBeenCalled();
6913
- expect(window.mermaid.initialize).not.toHaveBeenCalled();
7225
+ expect(String(assistantNode?.querySelector('[data-structured-answer-section="directAnswer"]')?.textContent || '')).toContain('$E=mc^2$');
7226
+ expect(String(assistantNode?.querySelector('[data-structured-answer-section="directAnswer"]')?.textContent || '')).toContain('$$\\frac{\\partial T}{\\partial t}=\\alpha\\nabla^2 T$$');
7227
+ expect(renderMathInElement).toHaveBeenCalledTimes(1);
7228
+ expect(window.mermaid.initialize).toHaveBeenCalledTimes(1);
6914
7229
  expect(mermaidRender).not.toHaveBeenCalled();
7230
+ const nextActionsToggle = assistantNode?.querySelector('[data-structured-answer-next-actions-toggle]');
7231
+ expect(nextActionsToggle).not.toBeNull();
7232
+ expect(nextActionsToggle?.getAttribute('aria-expanded')).toBe('false');
7233
+ expect(nextActionsToggle?.getAttribute('aria-label')).toBe('Show next actions');
7234
+ expect(assistantNode?.querySelector('[data-structured-answer-next-actions-panel]')).toBeNull();
7235
+ nextActionsToggle?.click();
7236
+ await Promise.resolve();
7237
+ expect(nextActionsToggle?.getAttribute('aria-expanded')).toBe('true');
7238
+ expect(nextActionsToggle?.getAttribute('aria-label')).toBe('Hide next actions');
7239
+ expect(assistantNode?.querySelector('[data-structured-answer-next-actions-panel]')).not.toBeNull();
7240
+ expect(String(assistantNode?.textContent || '')).toContain('Next Actions');
7241
+ expect(renderMathInElement).toHaveBeenCalled();
7242
+ nextActionsToggle?.click();
7243
+ expect(nextActionsToggle?.getAttribute('aria-expanded')).toBe('false');
7244
+ expect(assistantNode?.querySelector('[data-structured-answer-next-actions-panel]')).toBeNull();
7245
+ const markdownRuntime = window.NoteConnectionMarkdownRuntime;
7246
+ const renderMarkdownInto = markdownRuntime.renderMarkdownInto;
7247
+ markdownRuntime.renderMarkdownInto = jest.fn((container, markdown) => {
7248
+ if (markdown.includes('## Next Actions')) {
7249
+ return Promise.reject(new Error('next actions renderer failure'));
7250
+ }
7251
+ return renderMarkdownInto(container, markdown);
7252
+ });
7253
+ nextActionsToggle?.click();
7254
+ await new Promise((resolve) => setTimeout(resolve, 0));
7255
+ expect(nextActionsToggle?.getAttribute('aria-expanded')).toBe('false');
7256
+ expect(assistantNode?.querySelector('[data-structured-answer-next-actions-panel]')).toBeNull();
6915
7257
  expect(window.NoteConnectionAgentWorkspace.getLastConversationResult()?.assistantBlocks?.[0]).toEqual(expect.objectContaining({
6916
7258
  overviewMarkdown: expect.stringContaining('## Answer Context'),
6917
7259
  evidenceMarkdown: expect.stringContaining('## Evidence Summary'),
@@ -6924,6 +7266,63 @@ describe('agent workspace learning-path integration', () => {
6924
7266
  ]),
6925
7267
  }));
6926
7268
  });
7269
+ test('keeps non-public assistant blocks out of the chat release surface', async () => {
7270
+ const { document, window, fetchMock } = loadAgentWorkspaceHarness({ withI18n: true });
7271
+ if (!fetchMock) {
7272
+ throw new Error('expected fetch mock');
7273
+ }
7274
+ fetchMock.mockImplementationOnce(async () => createSseResponse([
7275
+ {
7276
+ event: 'turn_completed',
7277
+ payload: {
7278
+ type: 'turn_completed',
7279
+ turnId: 'turn_public_projection_only',
7280
+ emittedAt: '2026-04-13T00:02:00.000Z',
7281
+ result: {
7282
+ assistantMessage: 'Grounded Answer fallback',
7283
+ answer: 'Grounded Answer fallback',
7284
+ assistantBlocks: [
7285
+ {
7286
+ blockId: 'structured_public',
7287
+ type: 'structured_answer',
7288
+ title: 'Grounded Answer',
7289
+ directAnswer: 'Public answer.',
7290
+ knowledgePointCount: 0,
7291
+ citationCount: 0,
7292
+ recalledMemoryCount: 0,
7293
+ },
7294
+ { blockId: 'metadata_markdown', type: 'main_markdown', markdown: '# Answer Context' },
7295
+ { blockId: 'metadata_html', type: 'html_artifact', title: 'Evidence Summary', html: '<p>leak</p>' },
7296
+ { blockId: 'metadata_notice', type: 'system_notice', text: 'internal notice' },
7297
+ { blockId: 'metadata_citations', type: 'citations', title: 'Citations', citations: [] },
7298
+ { blockId: 'metadata_actions', type: 'knowledge_actions', title: 'Knowledge Actions', atomIds: [] },
7299
+ ],
7300
+ knowledgePoints: [],
7301
+ citations: [],
7302
+ recalledMemories: [],
7303
+ memoryActions: [],
7304
+ summary: { generatedAt: '2026-04-13T00:02:00.000Z', topK: 6, returnedKnowledgePoints: 0, returnedCitations: 0, recalledMemoryCount: 0, appliedMemoryCount: 0, queryEvidenceCoverageRatioPct: 100 },
7305
+ trace: {},
7306
+ },
7307
+ },
7308
+ },
7309
+ ]));
7310
+ const input = document.getElementById('agent-workspace-chat-input');
7311
+ input.value = 'public projection';
7312
+ await window.NoteConnectionAgentWorkspace.sendConversation();
7313
+ const renderedMessages = document.querySelectorAll('.agent-chat-message-rendered.agent-chat-message-assistant');
7314
+ expect(renderedMessages).toHaveLength(1);
7315
+ const assistantNode = renderedMessages[0];
7316
+ expect(assistantNode.querySelectorAll('.agent-chat-structured-answer-card')).toHaveLength(1);
7317
+ expect(assistantNode.querySelectorAll('.agent-chat-render-block-main_markdown')).toHaveLength(0);
7318
+ expect(assistantNode.querySelectorAll('.agent-chat-inline-artifact')).toHaveLength(0);
7319
+ expect(assistantNode.querySelectorAll('.agent-chat-knowledge-run-card')).toHaveLength(0);
7320
+ expect(assistantNode.textContent).toContain('Public answer.');
7321
+ expect(assistantNode.textContent).not.toContain('Answer Context');
7322
+ expect(assistantNode.textContent).not.toContain('Evidence Summary');
7323
+ expect(assistantNode.textContent).not.toContain('internal notice');
7324
+ expect(assistantNode.textContent).not.toContain('Knowledge Actions');
7325
+ });
6927
7326
  test('inspects durable knowledge-run artifacts from structured conversation blocks', async () => {
6928
7327
  const { document, window, fetchMock, } = loadAgentWorkspaceHarness({ withI18n: true });
6929
7328
  if (!fetchMock) {
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const fs = __importStar(require("fs"));
37
+ const path = __importStar(require("path"));
38
+ describe('Android knowledge-base picker contract', () => {
39
+ const repoRoot = path.resolve(__dirname, '..');
40
+ const templatePath = path.join(repoRoot, 'src-tauri', 'mobile', 'android', 'KnowledgeBasePickerBridge.kt');
41
+ const patcherPath = path.join(repoRoot, 'scripts', 'apply-tauri-android-pathmode.js');
42
+ const rustPath = path.join(repoRoot, 'src-tauri', 'src', 'lib.rs');
43
+ const sourceManagerPath = path.join(repoRoot, 'src', 'frontend', 'source_manager.js');
44
+ test('uses SAF tree permissions and bounded app-local streaming import', () => {
45
+ const template = fs.readFileSync(templatePath, 'utf8');
46
+ expect(template).toContain('ACTION_OPEN_DOCUMENT_TREE');
47
+ expect(template).toContain('FLAG_GRANT_PERSISTABLE_URI_PERMISSION');
48
+ expect(template).toContain('takePersistableUriPermission');
49
+ expect(template).toContain('context.filesDir, "Knowledge_Base"');
50
+ expect(template).toContain('MAX_DOCUMENT_BYTES');
51
+ expect(template).toContain('MAX_TOTAL_BYTES');
52
+ expect(template).toContain('input.copyTo(output, 32 * 1024)');
53
+ expect(template).toContain('replaceImportedTree');
54
+ expect(template).toContain('Duplicate import path');
55
+ expect(template).toContain('knowledge_base_import_journal.v1.json');
56
+ expect(template).toContain('recoverImportTransaction(activity.applicationContext)');
57
+ expect(template).toContain('target-backed-up');
58
+ expect(template).toContain('target-activated');
59
+ expect(template).toContain('invalid_import_journal');
60
+ expect(template).toContain('output.fd.sync()');
61
+ expect(template).toContain('isSafeTransactionPath');
62
+ expect(template).toContain('isKnownJournalPhase');
63
+ expect(template).toContain('recovered_previous');
64
+ expect(template).toContain('importInFlight');
65
+ expect(template).toContain('import_in_progress');
66
+ expect(template).toContain('synchronized(importLock)');
67
+ expect(template).toContain('Retaining import backup for startup recovery');
68
+ expect(template).toContain('if (!backupRoot.exists())');
69
+ expect(template).toContain('import_recovery_pending');
70
+ expect(template).toContain('Unable to restore import backup; retaining journal for retry');
71
+ expect(template).toContain('orphan_recovery_pending');
72
+ const importFailureCatch = template.match(/Log\.e\(TAG, "Knowledge base import failed"[\s\S]*?writeResult\(resultFile, "failed", "", error\.message \?: "import_failed"\)/)?.[0];
73
+ expect(importFailureCatch).toBeDefined();
74
+ expect(importFailureCatch).not.toContain('backupRoot.deleteRecursively()');
75
+ expect(template).not.toContain('readText()');
76
+ });
77
+ test('patches picker bridge into mobile-slim without enabling Godot', () => {
78
+ const patcher = fs.readFileSync(patcherPath, 'utf8');
79
+ expect(patcher).toContain('KnowledgeBasePickerBridge.kt');
80
+ expect(patcher).toContain('patchKnowledgeBasePicker');
81
+ expect(patcher).toContain('KnowledgeBasePickerBridge.bindActivity(this)');
82
+ expect(patcher).toContain('removePathmodeAssets');
83
+ });
84
+ test('exposes additive request/poll commands and enables the Android capability', () => {
85
+ const rust = fs.readFileSync(rustPath, 'utf8');
86
+ const sourceManager = fs.readFileSync(sourceManagerPath, 'utf8');
87
+ expect(rust).toContain('request_android_knowledge_base_picker');
88
+ expect(rust).toContain('consume_android_knowledge_base_picker_result');
89
+ expect(rust).toContain('kb_import_mode: "android-saf-copy"');
90
+ expect(sourceManager).toContain("request_kb_path_change");
91
+ expect(sourceManager).toContain("poll_kb_path_change");
92
+ expect(sourceManager).toContain('Timed out waiting for Android knowledge base import.');
93
+ });
94
+ test('keeps Android slim packaging size-first and clears stale generated outputs', () => {
95
+ const runner = fs.readFileSync(path.join(repoRoot, 'scripts', 'run-tauri-android.js'), 'utf8');
96
+ expect(runner).toContain("CARGO_PROFILE_RELEASE_OPT_LEVEL || 'z'");
97
+ expect(runner).toContain('CARGO_PROFILE_RELEASE_CODEGEN_UNITS, 1');
98
+ expect(runner).toContain("CARGO_PROFILE_RELEASE_LTO || 'thin'");
99
+ expect(runner).toContain('cleanAndroidSlimOutputs');
100
+ expect(runner).toContain('!includeGodotPathmode');
101
+ });
102
+ });