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
@@ -37,12 +37,317 @@
37
37
  }
38
38
 
39
39
  const ACTIVE_SOURCE_TARGET_STORAGE_KEY = 'nc_last_target';
40
+ const ANSWER_LANGUAGE_STORAGE_KEY = 'nc_agent_answer_language';
41
+ const RESPONSE_MODE_STORAGE_KEY = 'nc_agent_response_mode';
42
+ const RESPONSE_BUDGET_MODE_STORAGE_KEY = 'nc_agent_response_budget_mode';
40
43
  const ACTIVE_SOURCE_TARGET_EVENT = 'noteconnection:active-target-changed';
41
44
  const AGENT_CONVERSATION_ENDPOINT = '/api/knowledge/conversation';
42
45
  const GRAPH_FOCUS_DIAGNOSTICS_EVENT = 'noteconnection:agent-graph-focus-diagnostics';
43
46
  const GRAPH_FOCUS_DIAGNOSTICS_ENDPOINT = '/api/knowledge/session/graph-focus-diagnostics';
44
47
  let lastGraphFocusDiagnosticsPersistenceKey = '';
45
48
 
49
+ function isMobileNativeRuntime() {
50
+ const caps = window && window.__NC_RUNTIME_CAPS && typeof window.__NC_RUNTIME_CAPS === 'object'
51
+ ? window.__NC_RUNTIME_CAPS
52
+ : {};
53
+ const platform = String(caps.platform || '').trim().toLowerCase();
54
+ if (platform.startsWith('capacitor-') || platform === 'android' || platform === 'ios') {
55
+ return true;
56
+ }
57
+ if (window && window.Capacitor && typeof window.Capacitor.isNativePlatform === 'function') {
58
+ try {
59
+ return window.Capacitor.isNativePlatform() === true;
60
+ } catch (_error) {
61
+ return false;
62
+ }
63
+ }
64
+ return false;
65
+ }
66
+
67
+ function trimMobileText(value, limit) {
68
+ const normalized = String(value || '').replace(/\s+/gu, ' ').trim();
69
+ return normalized.length <= limit
70
+ ? normalized
71
+ : `${normalized.slice(0, Math.max(0, limit - 1)).trim()}…`;
72
+ }
73
+
74
+ function normalizeMobileMarkdown(value) {
75
+ return String(value || '').replace(/\r\n?/gu, '\n').trim();
76
+ }
77
+
78
+ function countMobileDisplayMathDelimiters(value) {
79
+ return (String(value || '').match(/(?<!\\)\$\$/gu) || []).length;
80
+ }
81
+
82
+ function trimMobileMarkdownText(value, limit) {
83
+ const normalized = normalizeMobileMarkdown(value);
84
+ if (normalized.length <= limit) {
85
+ return normalized;
86
+ }
87
+ const suffix = '…';
88
+ let candidate = normalized.slice(0, Math.max(0, limit - suffix.length)).trimEnd();
89
+ if (countMobileDisplayMathDelimiters(candidate) % 2 !== 0) {
90
+ const openingIndex = candidate.lastIndexOf('$$');
91
+ candidate = openingIndex >= 0 ? candidate.slice(0, openingIndex).trimEnd() : '';
92
+ }
93
+ return `${candidate}${suffix}`.trim();
94
+ }
95
+
96
+ function normalizeMobileContent(source) {
97
+ return normalizeMobileMarkdown(source)
98
+ .replace(/\$\$\s*```(?:mermaid|graph|diagram)[\s\S]*?(?:```|$)/giu, '\n')
99
+ .replace(/\r\n?/gu, '\n')
100
+ .replace(/```(?:mermaid|graph|diagram)[\s\S]*?(?:```|$)/giu, '\n')
101
+ .trim();
102
+ }
103
+
104
+ function formatMobileDisplayMath(source) {
105
+ return normalizeMobileContent(source)
106
+ .replace(/(?<!\\)\$\$([\s\S]*?)(?<!\\)\$\$/gu, (_match, expression) => {
107
+ const body = String(expression || '').trim();
108
+ return body ? `\n\n$$\n${body}\n$$\n\n` : '';
109
+ })
110
+ .replace(/[ \t]+\n/gu, '\n')
111
+ .replace(/\n{3,}/gu, '\n\n')
112
+ .trim();
113
+ }
114
+
115
+ function extractMobileQueryTerms(message) {
116
+ const source = String(message || '').trim();
117
+ const candidates = [];
118
+ const append = (value) => {
119
+ const term = String(value || '').replace(/^[\s??!!,,。::;;]+|[\s??!!,,。::;;]+$/gu, '').trim();
120
+ if (term.length >= 2 && !candidates.includes(term)) {
121
+ candidates.push(term);
122
+ }
123
+ };
124
+ const cjkTerms = source.match(/[\u3400-\u9fff]{2,}/gu) || [];
125
+ cjkTerms.forEach((term) => {
126
+ const subject = term
127
+ .replace(/^(?:什么是|什么叫|何为|请问|请解释(?:一下)?|解释(?:一下)?|介绍(?:一下)?|关于|我想知道)/u, '')
128
+ .replace(/(?:的定义|是什么|是怎样|相关知识点|知识点)$/u, '')
129
+ .trim();
130
+ append(subject);
131
+ const predicateSubject = subject.match(/(?:是|为|叫)([\u3400-\u9fff]{2,})$/u);
132
+ append(predicateSubject && predicateSubject[1]);
133
+ append(term);
134
+ });
135
+ const englishTerms = source
136
+ .replace(/\b(?:what|is|are|the|a|an|which|knowledge|points?|should|i|learn|study|define|explain|please)\b/giu, ' ')
137
+ .match(/[A-Za-z][A-Za-z0-9_-]*(?:\s+[A-Za-z][A-Za-z0-9_-]*){0,3}/gu) || [];
138
+ englishTerms.forEach(append);
139
+ append(source);
140
+ return candidates.slice(0, 8);
141
+ }
142
+
143
+ function takeBoundedMobileAnswerLines(lines, maxLines) {
144
+ const boundedLines = [];
145
+ let displayMathOpen = false;
146
+ const hardLimit = Math.max(maxLines + 12, maxLines);
147
+ for (const line of lines) {
148
+ if (boundedLines.length >= maxLines && !displayMathOpen) {
149
+ break;
150
+ }
151
+ boundedLines.push(line);
152
+ if (countMobileDisplayMathDelimiters(line) % 2 !== 0) {
153
+ displayMathOpen = !displayMathOpen;
154
+ }
155
+ if (boundedLines.length >= hardLimit) {
156
+ break;
157
+ }
158
+ }
159
+ if (displayMathOpen) {
160
+ let lastMathLine = -1;
161
+ for (let index = boundedLines.length - 1; index >= 0; index -= 1) {
162
+ if (countMobileDisplayMathDelimiters(boundedLines[index]) % 2 !== 0) {
163
+ lastMathLine = index;
164
+ break;
165
+ }
166
+ }
167
+ if (lastMathLine >= 0) {
168
+ boundedLines.splice(lastMathLine);
169
+ }
170
+ }
171
+ return boundedLines;
172
+ }
173
+
174
+ function selectMobileAnswerSection(content, subject) {
175
+ const source = normalizeMobileContent(content);
176
+ if (!source) {
177
+ return '';
178
+ }
179
+ const subjectKey = String(subject || '').replace(/[^\p{L}\p{N}]+/gu, '').toLowerCase();
180
+ const lines = source.split('\n');
181
+ const sections = [];
182
+ let current = [];
183
+ lines.forEach((line) => {
184
+ if (/^\s*#{1,6}\s+/u.test(line) && current.length > 0) {
185
+ sections.push(current);
186
+ current = [];
187
+ }
188
+ current.push(line);
189
+ });
190
+ if (current.length > 0) {
191
+ sections.push(current);
192
+ }
193
+ const selected = sections.find((section) => {
194
+ const text = section.join(' ');
195
+ const key = text.replace(/[^\p{L}\p{N}]+/gu, '').toLowerCase();
196
+ return subjectKey && key.includes(subjectKey);
197
+ }) || sections[0] || [];
198
+ const usefulLines = selected
199
+ .filter((line) => !/^\s*#{1,6}\s+/u.test(line))
200
+ .map((line) => line.trim())
201
+ .filter(Boolean)
202
+ .filter((line) => !/(?:常见用例|应用场景|关键技术规格|技术规格|比较数学模型|相关技术与比较|参考文献|参考资料|bibliograph|reference|technical specification|comparison model|common use|use case|application(?:s)?|performance metric|related technolog)/iu.test(line));
203
+ return formatMobileDisplayMath(takeBoundedMobileAnswerLines(usefulLines, 6).join('\n\n'));
204
+ }
205
+
206
+ function isMobilePrerequisiteNeighbor(neighbor) {
207
+ return String(neighbor && neighbor.direction || '').trim().toLowerCase() === 'incoming'
208
+ && /prerequisite|precondition|foundation|先修|前置|基础|sequence/u.test(
209
+ String(neighbor && neighbor.edgeType || '')
210
+ );
211
+ }
212
+
213
+ function isMobileApplicationNeighbor(neighbor) {
214
+ return String(neighbor && neighbor.direction || '').trim().toLowerCase() === 'outgoing'
215
+ && /application|use|performance|应用|用例|性能/u.test(
216
+ String(neighbor && neighbor.edgeType || '')
217
+ );
218
+ }
219
+
220
+ function buildMobileLearningRoute(primary, matches) {
221
+ if (Array.isArray(primary && primary.learningRoute) && primary.learningRoute.length > 0) {
222
+ return primary.learningRoute
223
+ .slice(0, 6)
224
+ .map((entry, index) => ({
225
+ nodeId: trimMobileText(entry.nodeId || entry.id, 160),
226
+ title: trimMobileText(entry.title || entry.label || entry.nodeId || entry.id, 160),
227
+ role: String(entry.role || 'mechanism').trim() || 'mechanism',
228
+ order: Math.max(1, Math.floor(Number(entry.order) || index + 1)),
229
+ }))
230
+ .filter((entry) => Boolean(entry.nodeId && entry.title));
231
+ }
232
+ const entries = [];
233
+ const seen = new Set();
234
+ const append = (match, role) => {
235
+ const nodeId = String(match && (match.id || match.nodeId) || '').trim();
236
+ const title = String(match && (match.label || match.title || match.id) || '').trim();
237
+ if (!nodeId || !title || seen.has(nodeId)) {
238
+ return;
239
+ }
240
+ seen.add(nodeId);
241
+ entries.push({ nodeId, title, role });
242
+ };
243
+ const neighbors = Array.isArray(primary && primary.neighbors) ? primary.neighbors : [];
244
+ neighbors
245
+ .filter(isMobilePrerequisiteNeighbor)
246
+ .sort((left, right) => String(left.label || left.id || '').localeCompare(String(right.label || right.id || '')))
247
+ .forEach((neighbor) => append(neighbor, 'prerequisite'));
248
+ append(primary, 'core');
249
+ neighbors
250
+ .filter((neighbor) => !isMobilePrerequisiteNeighbor(neighbor))
251
+ .sort((left, right) => {
252
+ const leftApplication = isMobileApplicationNeighbor(left) ? 1 : 0;
253
+ const rightApplication = isMobileApplicationNeighbor(right) ? 1 : 0;
254
+ return leftApplication - rightApplication
255
+ || String(left.label || left.id || '').localeCompare(String(right.label || right.id || ''));
256
+ })
257
+ .forEach((neighbor) => append(neighbor, isMobileApplicationNeighbor(neighbor) ? 'application' : 'mechanism'));
258
+ (Array.isArray(matches) ? matches : []).forEach((match) => append(match, 'mechanism'));
259
+ return entries.slice(0, 6).map((entry, index) => ({
260
+ nodeId: trimMobileText(entry.nodeId, 160),
261
+ title: trimMobileText(entry.title, 160),
262
+ role: entry.role,
263
+ order: index + 1,
264
+ }));
265
+ }
266
+
267
+ async function requestMobileLocalConversation(requestPayload) {
268
+ const storage = window && window.NoteConnectionStorage;
269
+ if (!storage || typeof storage.createProvider !== 'function') {
270
+ throw new Error('mobile_local_storage_unavailable');
271
+ }
272
+ const runtimeCaps = window.__NC_RUNTIME_CAPS || {};
273
+ const provider = storage.createProvider({ runtimeCaps });
274
+ if (!provider || typeof provider.queryKnowledgeBaseExact !== 'function') {
275
+ throw new Error('mobile_exact_query_unavailable');
276
+ }
277
+ const matchesById = new Map();
278
+ for (const term of extractMobileQueryTerms(requestPayload.message)) {
279
+ const result = await provider.queryKnowledgeBaseExact({
280
+ query: term,
281
+ maxMatches: Math.min(8, Math.max(1, Number(requestPayload.topK) || 4)),
282
+ maxNeighborsPerMatch: 8,
283
+ edgeKinds: ['explicit', 'runtime'],
284
+ });
285
+ (Array.isArray(result && result.matches) ? result.matches : []).forEach((match) => {
286
+ if (match && match.id && !matchesById.has(match.id)) {
287
+ matchesById.set(match.id, match);
288
+ }
289
+ });
290
+ if (matchesById.size >= 8) {
291
+ break;
292
+ }
293
+ }
294
+ const matches = Array.from(matchesById.values()).slice(0, 8);
295
+ if (matches.length <= 0) {
296
+ throw new Error('mobile_exact_query_no_match');
297
+ }
298
+ const primary = matches[0];
299
+ const primaryTitle = String(primary.label || primary.id || '').trim();
300
+ let sourceContent = '';
301
+ if (primary.sourceUri && typeof provider.readContent === 'function') {
302
+ try {
303
+ sourceContent = await provider.readContent(primary.sourceUri);
304
+ } catch (_error) {
305
+ sourceContent = '';
306
+ }
307
+ }
308
+ const useChinese = /[\u3400-\u9fff]/u.test(`${requestPayload.message || ''}${primaryTitle}`);
309
+ const directAnswer = selectMobileAnswerSection(sourceContent, primaryTitle)
310
+ || (useChinese
311
+ ? `本地知识图谱已定位到“${primaryTitle}”,但无法读取对应正文。`
312
+ : `${primaryTitle} was located in the local knowledge projection, but its source text is unavailable.`);
313
+ const boundedDirectAnswer = trimMobileMarkdownText(formatMobileDisplayMath(directAnswer), 2400);
314
+ const route = buildMobileLearningRoute(primary, matches);
315
+ const citations = matches.slice(0, 4).map((match) => ({
316
+ citationId: `mobile:${match.id}`,
317
+ title: trimMobileText(match.label || match.id, 160),
318
+ sourcePath: trimMobileText(match.sourceUri || '', 320),
319
+ })).filter((citation) => citation.sourcePath);
320
+ return {
321
+ userId: requestPayload.userId,
322
+ sessionId: requestPayload.sessionId,
323
+ assistantMessage: boundedDirectAnswer,
324
+ answer: boundedDirectAnswer,
325
+ message: boundedDirectAnswer,
326
+ responseProfile: 'mobile_compact',
327
+ mobileProjection: {
328
+ schemaVersion: 1,
329
+ primarySubject: trimMobileText(primaryTitle, 160),
330
+ directAnswer: boundedDirectAnswer,
331
+ route,
332
+ citations,
333
+ },
334
+ assistantBlocks: [],
335
+ knowledgePoints: [],
336
+ citations: [],
337
+ recalledMemories: [],
338
+ memoryActions: [],
339
+ summary: {
340
+ generatedAt: new Date().toISOString(),
341
+ topK: Number(requestPayload.topK) || 4,
342
+ returnedKnowledgePoints: route.length,
343
+ returnedCitations: citations.length,
344
+ recalledMemoryCount: 0,
345
+ appliedMemoryCount: 0,
346
+ queryEvidenceCoverageRatioPct: sourceContent ? 100 : 0,
347
+ },
348
+ };
349
+ }
350
+
46
351
  function normalizeActiveSourceTarget(value) {
47
352
  const normalized = String(value || '').trim();
48
353
  return normalized || 'ALL_FOLDERS';
@@ -265,6 +570,225 @@
265
570
  return 'en';
266
571
  }
267
572
 
573
+ function normalizeAnswerLanguage(value) {
574
+ const normalized = String(value || '').trim().toLowerCase().split('-')[0];
575
+ return normalized === 'zh' ? 'zh' : 'en';
576
+ }
577
+
578
+ function getAnswerLanguage() {
579
+ const selector = getElement('agent-workspace-answer-language-select');
580
+ const selected = selector && typeof selector.value === 'string' ? selector.value : '';
581
+ if (selected === 'en' || selected === 'zh') {
582
+ return selected;
583
+ }
584
+ if (typeof localStorage !== 'undefined') {
585
+ const stored = String(localStorage.getItem(ANSWER_LANGUAGE_STORAGE_KEY) || '').trim();
586
+ if (stored === 'en' || stored === 'zh') {
587
+ return stored;
588
+ }
589
+ }
590
+ return normalizeAnswerLanguage(getActiveLanguage());
591
+ }
592
+
593
+ function renderAnswerLanguageSelector() {
594
+ const selector = getElement('agent-workspace-answer-language-select');
595
+ if (!selector) {
596
+ return;
597
+ }
598
+ const stored = typeof localStorage !== 'undefined'
599
+ ? String(localStorage.getItem(ANSWER_LANGUAGE_STORAGE_KEY) || '').trim()
600
+ : '';
601
+ const targetLanguage = stored === 'en' || stored === 'zh'
602
+ ? stored
603
+ : normalizeAnswerLanguage(getActiveLanguage());
604
+ selector.value = targetLanguage;
605
+ updateAnswerLanguageSummary(targetLanguage);
606
+ }
607
+
608
+ function updateAnswerLanguageSummary(language) {
609
+ const summary = getElement('agent-workspace-answer-language-summary');
610
+ if (!summary) {
611
+ return;
612
+ }
613
+ summary.textContent = language === 'zh'
614
+ ? translate('agentWorkspace.answerLanguage.summaryChinese', 'Chinese answer')
615
+ : translate('agentWorkspace.answerLanguage.summaryEnglish', 'English answer');
616
+ }
617
+
618
+ function bindAnswerLanguageSelector() {
619
+ const selector = getElement('agent-workspace-answer-language-select');
620
+ if (!selector || selector.getAttribute('data-agent-answer-language-bound') === 'true') {
621
+ return;
622
+ }
623
+ selector.setAttribute('data-agent-answer-language-bound', 'true');
624
+ selector.addEventListener('change', function () {
625
+ const language = normalizeAnswerLanguage(selector.value);
626
+ selector.value = language;
627
+ if (typeof localStorage !== 'undefined') {
628
+ localStorage.setItem(ANSWER_LANGUAGE_STORAGE_KEY, language);
629
+ }
630
+ updateAnswerLanguageSummary(language);
631
+ });
632
+ if (window.i18n && typeof window.i18n.onLanguageChange === 'function') {
633
+ window.i18n.onLanguageChange(function () {
634
+ const hasExplicitChoice = typeof localStorage !== 'undefined'
635
+ && ['en', 'zh'].includes(String(localStorage.getItem(ANSWER_LANGUAGE_STORAGE_KEY) || '').trim());
636
+ if (!hasExplicitChoice) {
637
+ renderAnswerLanguageSelector();
638
+ }
639
+ });
640
+ }
641
+ }
642
+
643
+ function normalizeResponseMode(value) {
644
+ return String(value || '').trim().toLowerCase() === 'full' ? 'full' : 'slim';
645
+ }
646
+
647
+ function getResponseMode() {
648
+ const selector = getElement('agent-workspace-response-mode-select');
649
+ const selected = selector && typeof selector.value === 'string' ? selector.value : '';
650
+ if (selected === 'slim' || selected === 'full') {
651
+ return selected;
652
+ }
653
+ if (typeof localStorage !== 'undefined') {
654
+ const stored = normalizeResponseMode(localStorage.getItem(RESPONSE_MODE_STORAGE_KEY));
655
+ if (stored) {
656
+ return stored;
657
+ }
658
+ }
659
+ return 'slim';
660
+ }
661
+
662
+ function updateResponseModeSummary(mode) {
663
+ const summary = getElement('agent-workspace-response-mode-summary');
664
+ if (!summary) {
665
+ return;
666
+ }
667
+ summary.textContent = mode === 'full'
668
+ ? translate('agentWorkspace.responseMode.summaryFull', 'Complete evidence-bounded technical report')
669
+ : translate('agentWorkspace.responseMode.summarySlim', 'Short grounded answer');
670
+ }
671
+
672
+ function renderResponseModeSelector() {
673
+ const selector = getElement('agent-workspace-response-mode-select');
674
+ if (!selector) {
675
+ return;
676
+ }
677
+ const stored = typeof localStorage !== 'undefined'
678
+ ? normalizeResponseMode(localStorage.getItem(RESPONSE_MODE_STORAGE_KEY))
679
+ : 'slim';
680
+ selector.value = stored;
681
+ updateResponseModeSummary(stored);
682
+ }
683
+
684
+ function bindResponseModeSelector() {
685
+ const selector = getElement('agent-workspace-response-mode-select');
686
+ if (!selector || selector.getAttribute('data-agent-response-mode-bound') === 'true') {
687
+ return;
688
+ }
689
+ selector.setAttribute('data-agent-response-mode-bound', 'true');
690
+ selector.addEventListener('change', function () {
691
+ const mode = normalizeResponseMode(selector.value);
692
+ selector.value = mode;
693
+ if (typeof localStorage !== 'undefined') {
694
+ localStorage.setItem(RESPONSE_MODE_STORAGE_KEY, mode);
695
+ }
696
+ updateResponseModeSummary(mode);
697
+ });
698
+ if (window.i18n && typeof window.i18n.onLanguageChange === 'function') {
699
+ window.i18n.onLanguageChange(function () {
700
+ updateResponseModeSummary(getResponseMode());
701
+ });
702
+ }
703
+ }
704
+
705
+ function normalizeResponseBudgetMode(value) {
706
+ const normalized = String(value || '').trim().toLowerCase().replace(/[-\s]+/gu, '_');
707
+ return ['unbounded', 'no_cap', 'nocap', 'no_limit', 'nolimit', 'unlimited'].includes(normalized)
708
+ ? 'unbounded'
709
+ : 'adaptive';
710
+ }
711
+
712
+ function getResponseBudgetMode() {
713
+ const selector = getElement('agent-workspace-response-budget-select');
714
+ const selected = selector && typeof selector.value === 'string' ? selector.value : '';
715
+ if (selected === 'adaptive' || selected === 'unbounded') {
716
+ return selected;
717
+ }
718
+ if (typeof localStorage !== 'undefined') {
719
+ return normalizeResponseBudgetMode(localStorage.getItem(RESPONSE_BUDGET_MODE_STORAGE_KEY));
720
+ }
721
+ return 'adaptive';
722
+ }
723
+
724
+ function getResponseBudgetCapability() {
725
+ if (isMobileNativeRuntime()) {
726
+ return undefined;
727
+ }
728
+ const navigatorObject = window && window.navigator ? window.navigator : null;
729
+ const deviceMemory = Number(navigatorObject && navigatorObject.deviceMemory);
730
+ const hardwareConcurrency = Number(navigatorObject && navigatorObject.hardwareConcurrency);
731
+ const memoryClass = Number.isFinite(deviceMemory) && deviceMemory > 0
732
+ ? (deviceMemory >= 8 ? 'high' : deviceMemory >= 4 ? 'standard' : 'low')
733
+ : 'standard';
734
+ const workload = memoryClass === 'high'
735
+ && Number.isFinite(hardwareConcurrency)
736
+ && hardwareConcurrency >= 8
737
+ ? 'max'
738
+ : getResponseMode() === 'full'
739
+ ? 'large'
740
+ : 'normal';
741
+ return { memoryClass, workload };
742
+ }
743
+
744
+ function updateResponseBudgetSummary(mode) {
745
+ const summary = getElement('agent-workspace-response-budget-summary');
746
+ if (!summary) {
747
+ return;
748
+ }
749
+ summary.textContent = mode === 'unbounded'
750
+ ? translate('agentWorkspace.responseBudget.summaryUnbounded', 'Product cap disabled; runtime safety limits remain')
751
+ : translate('agentWorkspace.responseBudget.summaryAdaptive', 'Host-adaptive report budget');
752
+ }
753
+
754
+ function renderResponseBudgetSelector() {
755
+ const selector = getElement('agent-workspace-response-budget-select');
756
+ const control = selector && selector.closest('[data-agent-desktop-only]');
757
+ if (!selector) {
758
+ return;
759
+ }
760
+ const mobile = isMobileNativeRuntime();
761
+ if (control) {
762
+ control.hidden = mobile;
763
+ }
764
+ const stored = typeof localStorage !== 'undefined'
765
+ ? normalizeResponseBudgetMode(localStorage.getItem(RESPONSE_BUDGET_MODE_STORAGE_KEY))
766
+ : 'adaptive';
767
+ selector.value = stored;
768
+ updateResponseBudgetSummary(stored);
769
+ }
770
+
771
+ function bindResponseBudgetSelector() {
772
+ const selector = getElement('agent-workspace-response-budget-select');
773
+ if (!selector || selector.getAttribute('data-agent-response-budget-bound') === 'true') {
774
+ return;
775
+ }
776
+ selector.setAttribute('data-agent-response-budget-bound', 'true');
777
+ selector.addEventListener('change', function () {
778
+ const mode = normalizeResponseBudgetMode(selector.value);
779
+ selector.value = mode;
780
+ if (typeof localStorage !== 'undefined') {
781
+ localStorage.setItem(RESPONSE_BUDGET_MODE_STORAGE_KEY, mode);
782
+ }
783
+ updateResponseBudgetSummary(mode);
784
+ });
785
+ if (window.i18n && typeof window.i18n.onLanguageChange === 'function') {
786
+ window.i18n.onLanguageChange(function () {
787
+ updateResponseBudgetSummary(getResponseBudgetMode());
788
+ });
789
+ }
790
+ }
791
+
268
792
  function formatTemplate(template, params) {
269
793
  return String(template || '').replace(/\{(\w+)\}/g, function (_match, name) {
270
794
  return Object.prototype.hasOwnProperty.call(params || {}, name)
@@ -974,7 +1498,16 @@
974
1498
  }
975
1499
 
976
1500
  function buildConversationGroundingPayload(result) {
977
- const citationCount = Array.isArray(result && result.citations) ? result.citations.length : 0;
1501
+ const mobileProjection = result && result.mobileProjection && typeof result.mobileProjection === 'object'
1502
+ ? result.mobileProjection
1503
+ : null;
1504
+ const responseCitations = Array.isArray(result && result.citations) ? result.citations : [];
1505
+ const projectedCitations = Array.isArray(mobileProjection && mobileProjection.citations)
1506
+ ? mobileProjection.citations
1507
+ : [];
1508
+ const citationCount = responseCitations.length > 0
1509
+ ? responseCitations.length
1510
+ : projectedCitations.length;
978
1511
  const memoryCount = Array.isArray(result && result.recalledMemories) ? result.recalledMemories.length : 0;
979
1512
  const memoryActionCount = Array.isArray(result && result.memoryActions) ? result.memoryActions.length : 0;
980
1513
  const trace = result && result.trace && typeof result.trace === 'object' ? result.trace : null;
@@ -994,6 +1527,15 @@
994
1527
  const ragFailureClassifications = Array.isArray(trace && trace.ragFailureClassifications)
995
1528
  ? trace.ragFailureClassifications.filter((classification) => classification && typeof classification === 'object')
996
1529
  : [];
1530
+ const graphAnswerPlan = trace && trace.graphAnswerPlan && typeof trace.graphAnswerPlan === 'object'
1531
+ ? trace.graphAnswerPlan
1532
+ : null;
1533
+ const graphAnswerCoverage = trace && trace.graphAnswerCoverage && typeof trace.graphAnswerCoverage === 'object'
1534
+ ? trace.graphAnswerCoverage
1535
+ : null;
1536
+ const graphExpansion = trace && trace.graphExpansion && typeof trace.graphExpansion === 'object'
1537
+ ? trace.graphExpansion
1538
+ : null;
997
1539
  if (
998
1540
  citationCount <= 0
999
1541
  && memoryCount <= 0
@@ -1004,6 +1546,9 @@
1004
1546
  && !ragContextPack
1005
1547
  && !ragSufficiencyReview
1006
1548
  && !ragRecovery
1549
+ && !graphAnswerPlan
1550
+ && !graphAnswerCoverage
1551
+ && !graphExpansion
1007
1552
  && ragFailureClassifications.length <= 0
1008
1553
  ) {
1009
1554
  return null;
@@ -1032,6 +1577,9 @@
1032
1577
  ragSufficiencyReview,
1033
1578
  ragRecovery,
1034
1579
  ragFailureClassifications,
1580
+ graphAnswerPlan,
1581
+ graphAnswerCoverage,
1582
+ graphExpansion,
1035
1583
  };
1036
1584
  }
1037
1585
 
@@ -1302,6 +1850,73 @@
1302
1850
  });
1303
1851
  }
1304
1852
 
1853
+ function formatMobileRouteRole(role, useChinese) {
1854
+ const normalized = String(role || '').trim().toLowerCase();
1855
+ if (!useChinese) {
1856
+ return normalized || 'related';
1857
+ }
1858
+ return {
1859
+ prerequisite: '前置',
1860
+ core: '核心',
1861
+ mechanism: '机制',
1862
+ application: '应用',
1863
+ }[normalized] || '相关';
1864
+ }
1865
+
1866
+ function buildMobileConversationMessage(result) {
1867
+ if (!result || typeof result !== 'object' || result.responseProfile !== 'mobile_compact') {
1868
+ return '';
1869
+ }
1870
+ const projection = result.mobileProjection && typeof result.mobileProjection === 'object'
1871
+ ? result.mobileProjection
1872
+ : {};
1873
+ const directAnswer = normalizeMobileMarkdown(
1874
+ projection.directAnswer || result.answer || result.assistantMessage || result.message || ''
1875
+ );
1876
+ const route = Array.isArray(projection.route)
1877
+ ? projection.route.filter((node) => node && typeof node === 'object')
1878
+ : [];
1879
+ if (route.length <= 0) {
1880
+ return directAnswer;
1881
+ }
1882
+ const useChinese = /[\u3400-\u9fff]/u.test(
1883
+ `${projection.primarySubject || ''}${directAnswer}`
1884
+ );
1885
+ const heading = useChinese ? '### 建议学习路径' : '### Suggested learning path';
1886
+ const items = route.map((node, index) => {
1887
+ const order = Math.max(1, Math.floor(Number(node.order) || index + 1));
1888
+ const title = trimMobileText(node.title || node.nodeId, 160);
1889
+ const role = formatMobileRouteRole(node.role, useChinese);
1890
+ if (!title) {
1891
+ return '';
1892
+ }
1893
+ return useChinese
1894
+ ? `${order}. **${title}**(${role})`
1895
+ : `${order}. **${title}** (${role})`;
1896
+ }).filter(Boolean);
1897
+ return [directAnswer, heading, items.join('\n')].filter(Boolean).join('\n\n');
1898
+ }
1899
+
1900
+ function buildMobileStructuredAnswerBlock(result) {
1901
+ const directAnswer = buildMobileConversationMessage(result);
1902
+ if (!directAnswer) {
1903
+ return null;
1904
+ }
1905
+ const projection = result && result.mobileProjection && typeof result.mobileProjection === 'object'
1906
+ ? result.mobileProjection
1907
+ : {};
1908
+ const useChinese = /[\u3400-\u9fff]/u.test(`${projection.primarySubject || ''}${directAnswer}`);
1909
+ return {
1910
+ blockId: 'mobile_projection_answer',
1911
+ type: 'structured_answer',
1912
+ title: useChinese ? '可信回答' : 'Grounded Answer',
1913
+ directAnswer,
1914
+ knowledgePointCount: 0,
1915
+ citationCount: Array.isArray(projection.citations) ? projection.citations.length : 0,
1916
+ recalledMemoryCount: 0,
1917
+ };
1918
+ }
1919
+
1305
1920
  function appendAssistantConversationResult(result) {
1306
1921
  const controller = getController();
1307
1922
  if (window && typeof window === 'object') {
@@ -1310,11 +1925,22 @@
1310
1925
  const assistantBlocks = Array.isArray(result && result.assistantBlocks)
1311
1926
  ? result.assistantBlocks.filter((block) => block && typeof block === 'object')
1312
1927
  : [];
1313
- const visibleAssistantBlocks = assistantBlocks.filter((block) => {
1314
- const type = String(block && block.type || '').trim();
1315
- return type === 'structured_answer' || type === 'main_markdown' || type === 'html_artifact';
1316
- });
1317
- const fallbackMessage = String(
1928
+ // The conversation surface has one public release boundary: the
1929
+ // structured answer. Other block types are durable metadata or
1930
+ // capability artifacts and must stay outside the chat DOM; allowing
1931
+ // them through here reintroduces the Answer Context/Evidence/HTML
1932
+ // leakage that the backend additive contract intentionally permits.
1933
+ const structuredAnswerBlock = assistantBlocks.find((block) => (
1934
+ String(block && block.type || '').trim() === 'structured_answer'
1935
+ ));
1936
+ const mobileStructuredAnswerBlock = !structuredAnswerBlock
1937
+ ? buildMobileStructuredAnswerBlock(result)
1938
+ : null;
1939
+ const visibleAssistantBlocks = structuredAnswerBlock
1940
+ ? [structuredAnswerBlock]
1941
+ : (mobileStructuredAnswerBlock ? [mobileStructuredAnswerBlock] : []);
1942
+ const mobileFallbackMessage = buildMobileConversationMessage(result);
1943
+ const fallbackMessage = mobileFallbackMessage || String(
1318
1944
  result && (
1319
1945
  result.assistantMessage
1320
1946
  || result.answer
@@ -5089,7 +5715,14 @@
5089
5715
  sessionId: getOrCreateConversationSessionId(userId),
5090
5716
  activeTarget: requestContext.activeTarget,
5091
5717
  message,
5718
+ answerLanguage: getAnswerLanguage(),
5719
+ // Native mobile always consumes the bounded projection; a
5720
+ // desktop full-report preference must not inflate the APK path.
5721
+ responseMode: isMobileNativeRuntime() ? 'slim' : getResponseMode(),
5722
+ ...(isMobileNativeRuntime() ? {} : { responseBudgetMode: getResponseBudgetMode() }),
5723
+ ...(isMobileNativeRuntime() ? {} : { responseBudgetCapability: getResponseBudgetCapability() }),
5092
5724
  topK: 6,
5725
+ ...(isMobileNativeRuntime() ? { responseProfile: 'mobile_compact' } : {}),
5093
5726
  memoryNamespace: 'conversation',
5094
5727
  scope: requestContext.scope,
5095
5728
  };
@@ -5099,7 +5732,26 @@
5099
5732
  transport: 'SSE',
5100
5733
  activeTarget: requestContext.activeTarget,
5101
5734
  });
5102
- const conversationCall = await requestConversationWithStreamingFallback(requestPayload);
5735
+ let conversationCall;
5736
+ if (isMobileNativeRuntime()) {
5737
+ const mobileStartedAt = Date.now();
5738
+ try {
5739
+ const result = await requestMobileLocalConversation(requestPayload);
5740
+ conversationCall = {
5741
+ result,
5742
+ transport: 'Mobile local exact',
5743
+ latencyMs: Date.now() - mobileStartedAt,
5744
+ };
5745
+ } catch (localError) {
5746
+ // Local exact analysis is the preferred low-memory path. A
5747
+ // remote bounded projection remains an additive fallback when
5748
+ // the projection is missing or the local index cannot load.
5749
+ console.warn('[AgentWorkspace] Mobile local analysis unavailable; using bounded remote fallback.', localError);
5750
+ conversationCall = await requestConversationWithStreamingFallback(requestPayload);
5751
+ }
5752
+ } else {
5753
+ conversationCall = await requestConversationWithStreamingFallback(requestPayload);
5754
+ }
5103
5755
  const result = conversationCall && typeof conversationCall === 'object' && conversationCall.result
5104
5756
  ? conversationCall.result
5105
5757
  : conversationCall;
@@ -5249,8 +5901,14 @@
5249
5901
  controller.init();
5250
5902
  bindWorkspaceDrawerChrome();
5251
5903
  bindWorkspaceScopeSelector();
5904
+ bindAnswerLanguageSelector();
5905
+ bindResponseModeSelector();
5906
+ bindResponseBudgetSelector();
5252
5907
  observeGlobalScopeOptions();
5253
5908
  renderWorkspaceScopeSelector();
5909
+ renderAnswerLanguageSelector();
5910
+ renderResponseModeSelector();
5911
+ renderResponseBudgetSelector();
5254
5912
  updateConversationApiStatus({
5255
5913
  state: 'idle',
5256
5914
  endpoint: AGENT_CONVERSATION_ENDPOINT,