arcane-os 0.1.0-dev.5

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 (248) hide show
  1. package/CHANGELOG.md +154 -0
  2. package/COMMERCIAL-LICENSE.md +13 -0
  3. package/LICENSE +661 -0
  4. package/NOTICE +70 -0
  5. package/README.md +448 -0
  6. package/bin/arcane-test.mjs +741 -0
  7. package/bin/arcane.mjs +5 -0
  8. package/docs/architecture.md +234 -0
  9. package/docs/compatibility.md +36 -0
  10. package/docs/event-manager.md +166 -0
  11. package/docs/platform-targets.md +108 -0
  12. package/docs/publishing.md +203 -0
  13. package/docs/reference/README.md +117 -0
  14. package/docs/reference/arcane-ollama.md +288 -0
  15. package/docs/reference/availability-and-normalization.md +123 -0
  16. package/docs/reference/behavioral-testing.md +86 -0
  17. package/docs/reference/cli.md +569 -0
  18. package/docs/reference/core/README.md +62 -0
  19. package/docs/reference/core/arcane-ai-contracts.md +873 -0
  20. package/docs/reference/core/arcane-api.md +601 -0
  21. package/docs/reference/core/arcane-entities.md +65 -0
  22. package/docs/reference/core/arcane-events.md +134 -0
  23. package/docs/reference/core/ollama-module.md +181 -0
  24. package/docs/reference/core/reference/arcane-api/ai-and-ollama.md +1909 -0
  25. package/docs/reference/core/reference/arcane-api/applications-terminal-capabilities.md +1057 -0
  26. package/docs/reference/core/reference/arcane-api/core-and-events.md +320 -0
  27. package/docs/reference/core/reference/arcane-api/filesystem-storage-preferences-appearance.md +610 -0
  28. package/docs/reference/core/reference/arcane-api/namespaces.md +1157 -0
  29. package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +1423 -0
  30. package/docs/reference/core/reference/arcane-api/session-provisioning-diagnostics-development.md +315 -0
  31. package/docs/reference/event-manager.md +957 -0
  32. package/docs/reference/inventory/package-api.json +2632 -0
  33. package/docs/reference/inventory/runtime-components.json +934 -0
  34. package/docs/reference/inventory/runtime-entities.json +26 -0
  35. package/docs/reference/inventory/runtime-modules.json +1249 -0
  36. package/docs/reference/protocols.md +242 -0
  37. package/docs/reference/runtime-components.md +1098 -0
  38. package/docs/reference/runtime-entities.md +303 -0
  39. package/docs/reference/runtime-modules.md +2010 -0
  40. package/docs/reference/sdk-api.md +4901 -0
  41. package/docs/roadmap.md +79 -0
  42. package/docs/work-amplification.md +124 -0
  43. package/node_modules/event-pubsub/CHANGELOG.md +55 -0
  44. package/node_modules/event-pubsub/MIGRATION.md +70 -0
  45. package/node_modules/event-pubsub/README.md +363 -0
  46. package/node_modules/event-pubsub/SECURITY.md +37 -0
  47. package/node_modules/event-pubsub/index.js +141 -0
  48. package/node_modules/event-pubsub/licence +21 -0
  49. package/node_modules/event-pubsub/package.json +59 -0
  50. package/node_modules/strong-type/README.md +408 -0
  51. package/node_modules/strong-type/assets/strong-type-header.png +0 -0
  52. package/node_modules/strong-type/index.js +1151 -0
  53. package/node_modules/strong-type/licence +21 -0
  54. package/node_modules/strong-type/node.js +125 -0
  55. package/node_modules/strong-type/package.json +61 -0
  56. package/package.json +95 -0
  57. package/runtime/ARCANE_RUNTIME_RELEASE.json +791 -0
  58. package/runtime/arcane/components/app-bar.html +468 -0
  59. package/runtime/arcane/components/assistant-panel.html +715 -0
  60. package/runtime/arcane/components/calculator.html +8 -0
  61. package/runtime/arcane/components/chart.html +655 -0
  62. package/runtime/arcane/components/chat.html +1225 -0
  63. package/runtime/arcane/components/conversation-view.html +13 -0
  64. package/runtime/arcane/components/dashboard-config.html +341 -0
  65. package/runtime/arcane/components/data-maintenance.html +112 -0
  66. package/runtime/arcane/components/data-view.html +92 -0
  67. package/runtime/arcane/components/directory-picker.html +197 -0
  68. package/runtime/arcane/components/document-inspector.html +252 -0
  69. package/runtime/arcane/components/file-drop.html +264 -0
  70. package/runtime/arcane/components/file-inspector.html +293 -0
  71. package/runtime/arcane/components/file-manager.html +1715 -0
  72. package/runtime/arcane/components/header.html +142 -0
  73. package/runtime/arcane/components/integration-settings.html +14 -0
  74. package/runtime/arcane/components/local-ai-status.html +360 -0
  75. package/runtime/arcane/components/markdown-document.html +1048 -0
  76. package/runtime/arcane/components/markdown-editor.html +360 -0
  77. package/runtime/arcane/components/media-embed.html +8 -0
  78. package/runtime/arcane/components/modal.html +402 -0
  79. package/runtime/arcane/components/output-panel.html +259 -0
  80. package/runtime/arcane/components/preferences-form.html +135 -0
  81. package/runtime/arcane/components/record-timeline.html +105 -0
  82. package/runtime/arcane/components/relationship-board.html +116 -0
  83. package/runtime/arcane/components/screen-capture.html +8 -0
  84. package/runtime/arcane/components/source-code-viewer.html +441 -0
  85. package/runtime/arcane/components/source-explanation.html +124 -0
  86. package/runtime/arcane/components/speech.html +365 -0
  87. package/runtime/arcane/components/summary-strip.html +177 -0
  88. package/runtime/arcane/components/table.html +77 -0
  89. package/runtime/arcane/components/task-progress.html +282 -0
  90. package/runtime/arcane/components/terminal-workspace.html +65 -0
  91. package/runtime/arcane/components/theme-editor.html +41 -0
  92. package/runtime/arcane/components/theme-switcher.html +46 -0
  93. package/runtime/arcane/components/unified-inbox.html +20 -0
  94. package/runtime/arcane/components/voice-transcription.html +476 -0
  95. package/runtime/arcane/components/weather-widget.html +8 -0
  96. package/runtime/arcane/components/web-navigator.html +239 -0
  97. package/runtime/arcane/css/communications.css +1 -0
  98. package/runtime/arcane/css/dashboard-config.css +45 -0
  99. package/runtime/arcane/css/document-site.css +981 -0
  100. package/runtime/arcane/css/layout.css +438 -0
  101. package/runtime/arcane/css/primitives.css +321 -0
  102. package/runtime/arcane/css/theme.css +112 -0
  103. package/runtime/arcane/css/utility-workspace.css +1 -0
  104. package/runtime/arcane/entities/ApiModelRecord.js +20 -0
  105. package/runtime/arcane/entities/Calculation.js +13 -0
  106. package/runtime/arcane/entities/Chat.js +581 -0
  107. package/runtime/arcane/entities/CommunicationMessage.js +29 -0
  108. package/runtime/arcane/entities/CommunicationThread.js +21 -0
  109. package/runtime/arcane/entities/Document.js +10 -0
  110. package/runtime/arcane/entities/File.js +143 -0
  111. package/runtime/arcane/entities/Image.js +135 -0
  112. package/runtime/arcane/entities/IntentEnvelope.js +834 -0
  113. package/runtime/arcane/entities/Preference.js +83 -0
  114. package/runtime/arcane/entities/TWiNPolicyDecision.js +1092 -0
  115. package/runtime/arcane/entities/TerminalSession.js +46 -0
  116. package/runtime/arcane/entities/Theme.js +107 -0
  117. package/runtime/arcane/entities/User.js +1046 -0
  118. package/runtime/arcane/entities/Weather.js +23 -0
  119. package/runtime/arcane/img/arcane-os-everywhere.png +0 -0
  120. package/runtime/arcane/img/arrow-left.png +0 -0
  121. package/runtime/arcane/img/arrow-right.png +0 -0
  122. package/runtime/arcane/img/doc.svg +5 -0
  123. package/runtime/arcane/img/folder.svg +4 -0
  124. package/runtime/arcane/img/image.svg +5 -0
  125. package/runtime/arcane/img/refresh.png +0 -0
  126. package/runtime/arcane/img/send.svg +9 -0
  127. package/runtime/arcane/img/trash.svg +5 -0
  128. package/runtime/arcane/img/upload.svg +5 -0
  129. package/runtime/arcane/modules/AI.js +2048 -0
  130. package/runtime/arcane/modules/AIPreferenceRuntime.js +32 -0
  131. package/runtime/arcane/modules/AIPreferenceTuple.js +92 -0
  132. package/runtime/arcane/modules/AIResponseLength.js +42 -0
  133. package/runtime/arcane/modules/AIResponseURLPolicy.js +626 -0
  134. package/runtime/arcane/modules/AnsiText.js +53 -0
  135. package/runtime/arcane/modules/ApiModelDatabase.js +25 -0
  136. package/runtime/arcane/modules/AppDataScope.js +245 -0
  137. package/runtime/arcane/modules/AppearancePreferences.js +28 -0
  138. package/runtime/arcane/modules/ArcaneCommunicationBridge.js +22 -0
  139. package/runtime/arcane/modules/ArcaneNavigationPolicy.js +135 -0
  140. package/runtime/arcane/modules/ArcaneNetworkPolicy.js +255 -0
  141. package/runtime/arcane/modules/AsyncBoundary.js +161 -0
  142. package/runtime/arcane/modules/BrowserTestSuite.js +326 -0
  143. package/runtime/arcane/modules/CalculatorEngine.js +20 -0
  144. package/runtime/arcane/modules/CaseEvidenceIndexer.js +134 -0
  145. package/runtime/arcane/modules/ChartLibrary.js +35 -0
  146. package/runtime/arcane/modules/ChatRecords.js +13 -0
  147. package/runtime/arcane/modules/CommunicationAppController.js +43 -0
  148. package/runtime/arcane/modules/CommunicationHub.js +16 -0
  149. package/runtime/arcane/modules/CommunicationPreferences.js +13 -0
  150. package/runtime/arcane/modules/CommunicationProviderRegistry.js +16 -0
  151. package/runtime/arcane/modules/ComponentContracts.js +586 -0
  152. package/runtime/arcane/modules/ConfiguredAIChatSession.js +288 -0
  153. package/runtime/arcane/modules/ConversationActionItems.js +488 -0
  154. package/runtime/arcane/modules/ConversationClosingReport.js +274 -0
  155. package/runtime/arcane/modules/ConversationTimebox.js +527 -0
  156. package/runtime/arcane/modules/CoreLocalModelCatalog.js +255 -0
  157. package/runtime/arcane/modules/DBLS.js +171 -0
  158. package/runtime/arcane/modules/DBOPFS.js +1154 -0
  159. package/runtime/arcane/modules/DBOPFSWorker.js +116 -0
  160. package/runtime/arcane/modules/DataMaintenance.js +91 -0
  161. package/runtime/arcane/modules/DevelopmentWorkspace.js +74 -0
  162. package/runtime/arcane/modules/DirectoryPicker.js +109 -0
  163. package/runtime/arcane/modules/DocumentNavigation.js +223 -0
  164. package/runtime/arcane/modules/Errors.js +1025 -0
  165. package/runtime/arcane/modules/GifEncoder.js +29 -0
  166. package/runtime/arcane/modules/HTMLImport.js +114 -0
  167. package/runtime/arcane/modules/InMemoryCommunicationProvider.js +14 -0
  168. package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +275 -0
  169. package/runtime/arcane/modules/LocalAIReadiness.js +870 -0
  170. package/runtime/arcane/modules/LocalAIReadinessController.js +156 -0
  171. package/runtime/arcane/modules/MD.js +111 -0
  172. package/runtime/arcane/modules/Mail.js +352 -0
  173. package/runtime/arcane/modules/MailTransport.mjs +180 -0
  174. package/runtime/arcane/modules/Marked.min.js +71 -0
  175. package/runtime/arcane/modules/MemoryRecords.js +44 -0
  176. package/runtime/arcane/modules/MessageAdvisory.js +38 -0
  177. package/runtime/arcane/modules/ModelDefinition.js +189 -0
  178. package/runtime/arcane/modules/Ollama.js +74 -0
  179. package/runtime/arcane/modules/OllamaModelIdentifier.js +21 -0
  180. package/runtime/arcane/modules/OllamaSettings.js +24 -0
  181. package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +14 -0
  182. package/runtime/arcane/modules/PreferenceStore.js +109 -0
  183. package/runtime/arcane/modules/QRCode.min.js +1 -0
  184. package/runtime/arcane/modules/Questionnaire.js +61 -0
  185. package/runtime/arcane/modules/RecordLinkIndex.js +24 -0
  186. package/runtime/arcane/modules/RecordPassageIndex.js +222 -0
  187. package/runtime/arcane/modules/RecordReviewStore.js +94 -0
  188. package/runtime/arcane/modules/RevocableProjectionLedger.js +1623 -0
  189. package/runtime/arcane/modules/RiskSignalAnalyzer.js +37 -0
  190. package/runtime/arcane/modules/ScamRiskPolicy.js +62 -0
  191. package/runtime/arcane/modules/ScopedOPFSCache.js +183 -0
  192. package/runtime/arcane/modules/ScreenCapture.js +20 -0
  193. package/runtime/arcane/modules/SpeechPlayback.js +581 -0
  194. package/runtime/arcane/modules/StaticDocumentCatalog.js +1248 -0
  195. package/runtime/arcane/modules/SystemAppearance.js +20 -0
  196. package/runtime/arcane/modules/SystemPlatformPresentation.js +51 -0
  197. package/runtime/arcane/modules/SystemToolRegistry.js +28 -0
  198. package/runtime/arcane/modules/TerminalClient.js +52 -0
  199. package/runtime/arcane/modules/TerminalCommandRegistry.js +50 -0
  200. package/runtime/arcane/modules/ThemeBootstrap.js +26 -0
  201. package/runtime/arcane/modules/ThemeManager.js +131 -0
  202. package/runtime/arcane/modules/TimeGuard.js +149 -0
  203. package/runtime/arcane/modules/ToolCallRouter.js +83 -0
  204. package/runtime/arcane/modules/WaitForComponent.js +102 -0
  205. package/runtime/arcane/modules/YouTubeMedia.js +16 -0
  206. package/runtime/arcane/modules/uPlot.LICENSE.txt +21 -0
  207. package/runtime/arcane/modules/uPlot.iife.min.js +2 -0
  208. package/runtime/arcane/modules/uPlot.min.css +1 -0
  209. package/runtime/arcane/security/arcane-network-policy.json +6 -0
  210. package/runtime/strong-type/index.js +352 -0
  211. package/runtime/strong-type/licence +21 -0
  212. package/runtime/strong-type/package.json +45 -0
  213. package/schemas/arcane-app-bundle.schema.json +125 -0
  214. package/schemas/arcane-app.schema.json +329 -0
  215. package/schemas/arcane-lock.schema.json +86 -0
  216. package/schemas/arcane-package.schema.json +224 -0
  217. package/schemas/cli-event.schema.json +122 -0
  218. package/schemas/event-stack.schema.json +152 -0
  219. package/schemas/native-build-plan.schema.json +176 -0
  220. package/schemas/target-adapter.schema.json +117 -0
  221. package/src/app-descriptor.mjs +500 -0
  222. package/src/cli/main.mjs +561 -0
  223. package/src/constants.mjs +31 -0
  224. package/src/dev-server.mjs +718 -0
  225. package/src/doctor.mjs +315 -0
  226. package/src/dom-event-instrumentation.mjs +594 -0
  227. package/src/errors.mjs +75 -0
  228. package/src/event-manager.mjs +1342 -0
  229. package/src/event-queue.mjs +138 -0
  230. package/src/events.mjs +219 -0
  231. package/src/index.mjs +177 -0
  232. package/src/integrated-provider-loader.mjs +432 -0
  233. package/src/native-plan.mjs +698 -0
  234. package/src/native-provider-loader.mjs +1126 -0
  235. package/src/packager/core.mjs +2691 -0
  236. package/src/process.mjs +353 -0
  237. package/src/release-bundle.mjs +2523 -0
  238. package/src/repository.mjs +90 -0
  239. package/src/runtime.mjs +452 -0
  240. package/src/scaffold.mjs +380 -0
  241. package/src/targets/index.mjs +436 -0
  242. package/src/templates/assets/app-icon.png +0 -0
  243. package/src/templates/workspace-template.mjs +388 -0
  244. package/src/testing-loader.mjs +9 -0
  245. package/src/testing.mjs +427 -0
  246. package/src/toolchain.mjs +1335 -0
  247. package/src/update-check.mjs +307 -0
  248. package/src/workspace.mjs +449 -0
@@ -0,0 +1,1046 @@
1
+ import Is from '../../node_modules/strong-type/index.js';
2
+ import DBLS from '../modules/DBLS.js';
3
+
4
+ /**
5
+ * DBOPFS Module
6
+ *
7
+ * This import initializes the DBOPFS singleton.
8
+ *
9
+ * The module attaches itself to the global scope:
10
+ *
11
+ * window.dbopfs
12
+ *
13
+ * Engineers can access persistence anywhere via:
14
+ *
15
+ * dbopfs.get(...)
16
+ * dbopfs.set(...)
17
+ *
18
+ * The import must remain even if unused because it registers the singleton.
19
+ */
20
+ import DBOPFS from '../modules/DBOPFS.js';
21
+
22
+ function createDefaultDashboard(){
23
+ return { charts:{} };
24
+ }
25
+
26
+ function normalizeDashboard(dashboard={}){
27
+ const source=dashboard&&typeof dashboard==='object'&&!Array.isArray(dashboard)
28
+ &&dashboard.charts&&typeof dashboard.charts==='object'&&!Array.isArray(dashboard.charts)
29
+ ? dashboard.charts
30
+ : {};
31
+ const charts={};
32
+
33
+ for(const [key,value] of Object.entries(source)){
34
+ if(typeof value==='boolean'){
35
+ charts[key]=value;
36
+ }
37
+ }
38
+
39
+ return { charts };
40
+ }
41
+
42
+ const is = new Is(false);
43
+
44
+ /**
45
+ * Email validation pattern
46
+ */
47
+ const EMAIL_REGEX =
48
+ /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
49
+
50
+
51
+ /**
52
+ * Canonical schema for a UserEntity record.
53
+ *
54
+ * @typedef {Object} UserEntityData
55
+ * @property {string|number} username
56
+ * @property {string} email
57
+ * @property {string|number} phone
58
+ * @property {string} license_key
59
+ * @property {string} subscription_key
60
+ * @property {number} current_time
61
+ * @property {number} last_successful_time
62
+ * @property {string|number} contact_1
63
+ * @property {string|number} contact_2
64
+ * @property {string|number} contact_3
65
+ * @property {string|number} contact_4
66
+ * @property {string|number} contact_5
67
+ * @property {string|number} contact_6
68
+ * @property {string} AI_personality
69
+ * @property {string} religion
70
+ * @property {string} AI_voice
71
+ * @property {number} AI_speed
72
+ * @property {string} ai_verbosity
73
+ * @property {boolean} initialSpeechMuted
74
+ * @property {boolean} conversationClosingReportEnabled
75
+ * @property {boolean} conversationActionItemsEnabled
76
+ * @property {number} firstBootUp
77
+ * @property {boolean} questionnaireShown
78
+ * @property {string|number} skin
79
+ * @property {boolean} preferrsLocal
80
+ * @property {boolean} developer
81
+ * @property {array} preferredModels
82
+ * @property {{charts:Object<string,boolean>}} dashboard
83
+ */
84
+
85
+
86
+ class UserEntity {
87
+
88
+ /** @type {string} */
89
+ #tableName = 'users';
90
+
91
+ /** @type {string} */
92
+ fileName = 'users.json';
93
+
94
+ /**
95
+ * Controls automatic persistence
96
+ *
97
+ * @type {boolean}
98
+ */
99
+ persist = true;
100
+
101
+ ready = false;
102
+
103
+ #loadPromise = null;
104
+
105
+ #explicitUpdateQueue = Promise.resolve();
106
+
107
+ #schema = Object.freeze(
108
+ [
109
+ 'username',
110
+ 'email',
111
+ 'phone',
112
+ 'language',
113
+ 'license_key',
114
+ 'subscription_key',
115
+ 'current_time',
116
+ 'last_successful_time',
117
+
118
+ 'contact_1',
119
+ 'contact_2',
120
+ 'contact_3',
121
+ 'contact_4',
122
+ 'contact_5',
123
+ 'contact_6',
124
+
125
+ 'AI_personality',
126
+ 'religion',
127
+ 'AI_voice',
128
+ 'AI_speed',
129
+ 'ai_verbosity',
130
+ 'initialSpeechMuted',
131
+ 'conversationClosingReportEnabled',
132
+ 'conversationActionItemsEnabled',
133
+ 'firstBootUp',
134
+ 'questionnaireShown',
135
+ 'skin',
136
+ 'developer',
137
+ 'prefersLocal',
138
+ 'preferredModels',
139
+ 'dashboard'
140
+ ]
141
+ );
142
+
143
+
144
+
145
+ #username = '';
146
+ #email = '';
147
+ #phone = '';
148
+ #license_key = '';
149
+ #subscription_key = '';
150
+ #current_time = 0;
151
+ #last_successful_time = 0;
152
+ #language = '';
153
+
154
+ #contact_1 = '';
155
+ #contact_2 = '';
156
+ #contact_3 = '';
157
+ #contact_4 = '';
158
+ #contact_5 = '';
159
+ #contact_6 = '';
160
+
161
+ #AI_personality = '';
162
+ #religion = '';
163
+ #AI_voice = '';
164
+ #AI_speed = 1.0;
165
+ #ai_verbosity = 'medium';
166
+ #initialSpeechMuted = true;
167
+ #conversationClosingReportEnabled = true;
168
+ #conversationActionItemsEnabled = true;
169
+ #firstBootUp = 0;
170
+ #questionnaireShown = false;
171
+ #skin = 'default';
172
+ #developer = false;
173
+ #prefersLocal = false;
174
+ #preferredModels = [];
175
+ #dashboard = createDefaultDashboard();
176
+
177
+
178
+ /**
179
+ * Create a new UserEntity
180
+ *
181
+ * @param {string} fileName
182
+ */
183
+ constructor(
184
+ fileName = ''
185
+ ){
186
+ if(window.user){
187
+ return window.user;
188
+ }
189
+
190
+ if(!is.string(fileName) || !fileName){
191
+ fileName=this.fileName;
192
+ }
193
+
194
+ this.fileName = fileName;
195
+
196
+ window.addEventListener(
197
+ 'dbopfs-ready',
198
+ this.load.bind(this)
199
+ );
200
+
201
+ if(window.dbopfs?.ready){
202
+ this.load();
203
+ }
204
+
205
+ return this;
206
+ }
207
+
208
+
209
+ /** @returns {string|number} */
210
+ get username(){
211
+ return this.#username;
212
+ }
213
+
214
+ /** @param {string|number} v */
215
+ set username(v){
216
+ if(!is.union(v,'string','number')){
217
+ throw new Error('username must be string or number');
218
+ }
219
+
220
+ this.#username = v;
221
+
222
+ this.#persist();
223
+ }
224
+
225
+
226
+
227
+ /** @returns {string} */
228
+ get email(){
229
+ return this.#email;
230
+ }
231
+
232
+ /** @param {string} v */
233
+ set email(v){
234
+ if(!is.string(v)){
235
+ throw new Error('email must be string');
236
+ }
237
+
238
+ if(v.length>0 && !EMAIL_REGEX.test(v)){
239
+ //throw new Error('email must be a valid email address');
240
+ }
241
+
242
+ this.#email = v;
243
+
244
+ this.#persist();
245
+ }
246
+
247
+
248
+
249
+ /** @returns {string|number} */
250
+ get phone(){
251
+ return this.#phone;
252
+ }
253
+
254
+ /** @param {string|number} v */
255
+ set phone(v){
256
+ if(!is.union(v,'string','number')){
257
+ throw new Error('phone must be string or number');
258
+ }
259
+
260
+ this.#phone = v;
261
+
262
+ this.#persist();
263
+ }
264
+
265
+
266
+
267
+ /** @returns {string} */
268
+ get license_key(){
269
+ return this.#license_key;
270
+ }
271
+
272
+ /** @param {string} v */
273
+ set license_key(v){
274
+ if(!is.string(v)){
275
+ throw new Error('license_key must be string');
276
+ }
277
+
278
+ this.#license_key = v;
279
+
280
+ this.#persist();
281
+ }
282
+
283
+ /** @returns {string} */
284
+ get subscription_key(){
285
+ return this.#subscription_key;
286
+ }
287
+
288
+ /** @param {string} v */
289
+ set subscription_key(v){
290
+ if(!is.string(v)){
291
+ throw new Error('subscription_key must be string');
292
+ }
293
+
294
+ this.#subscription_key = v;
295
+
296
+ this.#persist();
297
+ }
298
+
299
+ /** @returns {number} */
300
+ get current_time(){
301
+ return this.#current_time;
302
+ }
303
+
304
+ /** @param {number} v */
305
+ set current_time(v){
306
+ if(!is.number(v)){
307
+ throw new Error('current_time must be number');
308
+ }
309
+
310
+ this.#current_time = v;
311
+
312
+ this.#persist();
313
+ }
314
+
315
+ /** @returns {number} */
316
+ get last_successful_time(){
317
+ return this.#last_successful_time;
318
+ }
319
+
320
+ /** @param {number} v */
321
+ set last_successful_time(v){
322
+ if(!is.number(v)){
323
+ throw new Error('last_successful_time must be number');
324
+ }
325
+
326
+ this.#last_successful_time = v;
327
+
328
+ this.#persist();
329
+ }
330
+
331
+ /** @returns {string} */
332
+ get language(){
333
+ return this.#language;
334
+ }
335
+
336
+ /** @param {string} v */
337
+ set language(v){
338
+ if(!is.string(v)){
339
+ throw new Error('language must be string');
340
+ }
341
+
342
+ this.#language = v;
343
+
344
+ this.#persist();
345
+ }
346
+
347
+
348
+
349
+ /** @returns {string|number} */
350
+ get contact_1(){
351
+ return this.#contact_1;
352
+ }
353
+
354
+ /** @param {string|number} v */
355
+ set contact_1(v){
356
+ if(!is.union(v,'string','number')){
357
+ throw new Error('contact_1 must be string or number');
358
+ }
359
+
360
+ this.#contact_1 = v;
361
+
362
+ this.#persist();
363
+ }
364
+
365
+
366
+
367
+ /** @returns {string|number} */
368
+ get contact_2(){
369
+ return this.#contact_2;
370
+ }
371
+
372
+ /** @param {string|number} v */
373
+ set contact_2(v){
374
+ if(!is.union(v,'string','number')){
375
+ throw new Error('contact_2 must be string or number');
376
+ }
377
+
378
+ this.#contact_2 = v;
379
+
380
+ this.#persist();
381
+ }
382
+
383
+
384
+
385
+ /** @returns {string|number} */
386
+ get contact_3(){
387
+ return this.#contact_3;
388
+ }
389
+
390
+ /** @param {string|number} v */
391
+ set contact_3(v){
392
+ if(!is.union(v,'string','number')){
393
+ throw new Error('contact_3 must be string or number');
394
+ }
395
+
396
+ this.#contact_3 = v;
397
+
398
+ this.#persist();
399
+ }
400
+
401
+
402
+
403
+ /** @returns {string|number} */
404
+ get contact_4(){
405
+ return this.#contact_4;
406
+ }
407
+
408
+ /** @param {string|number} v */
409
+ set contact_4(v){
410
+ if(!is.union(v,'string','number')){
411
+ throw new Error('contact_4 must be string or number');
412
+ }
413
+
414
+ this.#contact_4 = v;
415
+
416
+ this.#persist();
417
+ }
418
+
419
+
420
+
421
+ /** @returns {string|number} */
422
+ get contact_5(){
423
+ return this.#contact_5;
424
+ }
425
+
426
+ /** @param {string|number} v */
427
+ set contact_5(v){
428
+ if(!is.union(v,'string','number')){
429
+ throw new Error('contact_5 must be string or number');
430
+ }
431
+
432
+ this.#contact_5 = v;
433
+
434
+ this.#persist();
435
+ }
436
+
437
+
438
+
439
+ /** @returns {string|number} */
440
+ get contact_6(){
441
+ return this.#contact_6;
442
+ }
443
+
444
+ /** @param {string|number} v */
445
+ set contact_6(v){
446
+ if(!is.union(v,'string','number')){
447
+ throw new Error('contact_6 must be string or number');
448
+ }
449
+
450
+ this.#contact_6 = v;
451
+
452
+ this.#persist();
453
+ }
454
+
455
+
456
+
457
+ /** @returns {string} */
458
+ get AI_personality(){
459
+ return this.#AI_personality;
460
+ }
461
+
462
+ /** @param {string} v */
463
+ set AI_personality(v){
464
+ if(!is.string(v)){
465
+ throw new Error('AI_personality must be string');
466
+ }
467
+
468
+ if(v.length > 1000){
469
+ throw new Error('AI_personality must be less than 1000 characters');
470
+ }
471
+
472
+ this.#AI_personality = v;
473
+
474
+ this.#persist();
475
+ }
476
+
477
+
478
+
479
+ /** @returns {string} */
480
+ get religion(){
481
+ return this.#religion;
482
+ }
483
+
484
+ /** @param {string} v */
485
+ set religion(v){
486
+ if(!is.string(v)){
487
+ throw new Error('religion must be string');
488
+ }
489
+
490
+ this.#religion = v;
491
+
492
+ this.#persist();
493
+ }
494
+
495
+
496
+
497
+ /** @returns {string} */
498
+ get AI_voice(){
499
+ return this.#AI_voice;
500
+ }
501
+
502
+ /** @param {string} v */
503
+ set AI_voice(v){
504
+ if(!is.string(v)){
505
+ throw new Error('AI_voice must be string');
506
+ }
507
+
508
+ this.#AI_voice = v;
509
+
510
+ this.#persist();
511
+ }
512
+
513
+
514
+
515
+ /** @returns {number} */
516
+ get AI_speed(){
517
+ return this.#AI_speed;
518
+ }
519
+
520
+ /** @param {number} v */
521
+ set AI_speed(v){
522
+ if(!is.number(v)){
523
+ throw new Error('AI_speed must be number');
524
+ }
525
+
526
+ const MIN_SPEED=0.5;
527
+ const MAX_SPEED=2.0;
528
+
529
+ if(v<MIN_SPEED||v>MAX_SPEED){
530
+ throw new Error(`AI_speed must be between ${MIN_SPEED} and ${MAX_SPEED}`);
531
+ }
532
+
533
+ this.#AI_speed=v;
534
+
535
+ this.#persist();
536
+ }
537
+
538
+
539
+
540
+ /** @returns {string} */
541
+ get ai_verbosity(){
542
+ return this.#ai_verbosity;
543
+ }
544
+
545
+ /** @param {string} v */
546
+ set ai_verbosity(v){
547
+ if(!is.string(v)||!v.trim()){
548
+ throw new Error('ai_verbosity must be a non-empty string');
549
+ }
550
+
551
+ this.#ai_verbosity=v.trim();
552
+
553
+ this.#persist();
554
+ }
555
+
556
+
557
+
558
+ /** @returns {boolean} */
559
+ get initialSpeechMuted(){
560
+ return this.#initialSpeechMuted;
561
+ }
562
+
563
+ /** @param {boolean} v */
564
+ set initialSpeechMuted(v){
565
+ if(!is.boolean(v)){
566
+ throw new Error('initialSpeechMuted must be boolean');
567
+ }
568
+
569
+ this.#initialSpeechMuted = v;
570
+
571
+ this.#persist();
572
+ }
573
+
574
+
575
+
576
+ /** @returns {boolean} */
577
+ get conversationClosingReportEnabled(){
578
+ return this.#conversationClosingReportEnabled;
579
+ }
580
+
581
+ /** @param {boolean} v */
582
+ set conversationClosingReportEnabled(v){
583
+ if(!is.boolean(v)){
584
+ throw new Error('conversationClosingReportEnabled must be boolean');
585
+ }
586
+
587
+ this.#conversationClosingReportEnabled = v;
588
+
589
+ this.#persist();
590
+ }
591
+
592
+
593
+
594
+ /** @returns {boolean} */
595
+ get conversationActionItemsEnabled(){
596
+ return this.#conversationActionItemsEnabled;
597
+ }
598
+
599
+ /** @param {boolean} v */
600
+ set conversationActionItemsEnabled(v){
601
+ if(!is.boolean(v)){
602
+ throw new Error('conversationActionItemsEnabled must be boolean');
603
+ }
604
+
605
+ this.#conversationActionItemsEnabled = v;
606
+
607
+ this.#persist();
608
+ }
609
+
610
+
611
+
612
+ /** @returns {number} */
613
+ get firstBootUp(){
614
+ return this.#firstBootUp;
615
+ }
616
+
617
+ /** @param {number} v */
618
+ set firstBootUp(v){
619
+ if(
620
+ typeof v!=='number'
621
+ ||!Number.isFinite(v)
622
+ ||!Number.isSafeInteger(v)
623
+ ||v<0
624
+ ){
625
+ throw new Error(
626
+ 'firstBootUp must be a non-negative finite integer timestamp'
627
+ );
628
+ }
629
+
630
+ this.#firstBootUp = v;
631
+
632
+ this.#persist();
633
+ }
634
+
635
+
636
+
637
+ /** @returns {boolean} */
638
+ get questionnaireShown(){
639
+ return this.#questionnaireShown;
640
+ }
641
+
642
+ /** @param {boolean} v */
643
+ set questionnaireShown(v){
644
+ if(!is.boolean(v)){
645
+ throw new Error('questionnaireShown must be boolean');
646
+ }
647
+
648
+ this.#questionnaireShown = v;
649
+
650
+ this.#persist();
651
+ }
652
+
653
+
654
+
655
+ /** @returns {string|number} */
656
+ get skin(){
657
+ return this.#skin;
658
+ }
659
+
660
+ /** @param {string|number} v */
661
+ set skin(v){
662
+ if(!is.union(v,'string','number')){
663
+ throw new Error('skin must be string or number');
664
+ }
665
+
666
+ this.#skin = v;
667
+
668
+ this.#persist();
669
+ }
670
+
671
+
672
+ /** @returns {boolean} */
673
+ get developer(){
674
+ return this.#developer;
675
+ }
676
+
677
+ /** @param {boolean} v */
678
+ set developer(v){
679
+ if(!is.boolean(v)){
680
+ throw new Error('developer must be boolean');
681
+ }
682
+
683
+ this.#developer = v;
684
+
685
+ this.#persist();
686
+ }
687
+
688
+
689
+
690
+ /** @returns {boolean} */
691
+ get prefersLocal(){
692
+ return this.#prefersLocal;
693
+ }
694
+
695
+ /** @param {boolean} v */
696
+ set prefersLocal(v){
697
+ if(!is.boolean(v)){
698
+ throw new Error('prefersLocal must be boolean');
699
+ }
700
+
701
+ this.#prefersLocal = v;
702
+
703
+ this.#persist();
704
+ }
705
+
706
+ /** @returns {array} */
707
+ get preferredModels(){
708
+ return this.#preferredModels;
709
+ }
710
+
711
+ /** @param {array} v */
712
+ set preferredModels(v){
713
+ if(!is.array(v)){
714
+ throw new Error('preferredModels must be an array');
715
+ }
716
+
717
+ this.#preferredModels = v;
718
+
719
+ this.#persist();
720
+ }
721
+
722
+ /** @returns {{charts:Object<string,boolean>}} */
723
+ get dashboard(){
724
+ return {
725
+ charts:{...this.#dashboard.charts}
726
+ };
727
+ }
728
+
729
+ /** @param {{charts:Object<string,boolean>}} v */
730
+ set dashboard(v){
731
+ if(!v||typeof v!=='object'||Array.isArray(v)){
732
+ throw new Error('dashboard must be an object');
733
+ }
734
+
735
+ this.#dashboard=normalizeDashboard(v);
736
+
737
+ this.#persist();
738
+ }
739
+
740
+ /**
741
+ * Explicit schema representation
742
+ *
743
+ * @returns {UserEntityData}
744
+ */
745
+ get explicit(){
746
+ const data={};
747
+
748
+ for(let i=0;i<this.#schema.length;i++){
749
+ const key=this.#schema[i];
750
+ data[key]=this[key];
751
+ }
752
+
753
+ return data;
754
+ }
755
+
756
+
757
+
758
+ /**
759
+ * Update entity fields from object or JSON
760
+ *
761
+ * @param {UserEntityData|string|Object} src
762
+ */
763
+ set explicit(src){
764
+ if(!src){
765
+ src=this.explicit;
766
+ }
767
+
768
+ if(!is.union(src,'object','string')){
769
+ throw new Error('UserEntity.explicit setter expects object or JSON string');
770
+ }
771
+
772
+ if(is.string(src)){
773
+ src = JSON.parse(src);
774
+ }
775
+
776
+ if(!is.object(src)){
777
+ throw new Error('UserEntity.explicit parsed src must be object');
778
+ }
779
+
780
+ const persist=this.persist;
781
+ this.persist=false;
782
+
783
+ for(let i=0;i<this.#schema.length;i++){
784
+ const key=this.#schema[i];
785
+
786
+ if(is.undefined(src[key])){
787
+ continue;
788
+ }
789
+
790
+ try {
791
+ this[key] = src[key];
792
+ } catch(e){
793
+ console.warn(`UserEntity.explicit setter skipping invalid field ${key}: ${e.message}`);
794
+ }
795
+ }
796
+
797
+ this.persist=persist;
798
+ this.#persist();
799
+ }
800
+
801
+ /**
802
+ * Applies and durably saves an explicit profile update as one serialized
803
+ * operation. A failed write restores the prior in-memory values.
804
+ *
805
+ * @param {UserEntityData|string|Object} src
806
+ * @returns {Promise<UserEntityData>}
807
+ */
808
+ updateExplicit(src){
809
+ const operation=this.#explicitUpdateQueue.then(()=>{
810
+ const applyUpdate=async()=>{
811
+ const prior=this.explicit;
812
+ const persist=this.persist;
813
+ let baseline=prior;
814
+
815
+ if(persist){
816
+ const durable=await dbopfs.get(
817
+ this.#tableName,
818
+ this.fileName,
819
+ true
820
+ );
821
+ if(durable){
822
+ baseline=durable;
823
+ }
824
+ }
825
+
826
+ this.persist=false;
827
+ try{
828
+ this.explicit=baseline;
829
+ this.explicit=src;
830
+ }finally{
831
+ this.persist=persist;
832
+ }
833
+
834
+ if(!persist){
835
+ return this.explicit;
836
+ }
837
+
838
+ try{
839
+ await this.save();
840
+ return this.explicit;
841
+ }catch(error){
842
+ this.persist=false;
843
+ try{
844
+ this.explicit=baseline;
845
+ }finally{
846
+ this.persist=persist;
847
+ }
848
+ throw error;
849
+ }
850
+ };
851
+ return this.#withExplicitLock(applyUpdate);
852
+ });
853
+
854
+ this.#explicitUpdateQueue=operation.catch(()=>false);
855
+ return operation;
856
+ }
857
+
858
+ withFreshExplicit(operation){
859
+ if(typeof operation!=='function'){
860
+ throw new TypeError('A fresh UserEntity operation is required.');
861
+ }
862
+ const result=this.#explicitUpdateQueue.then(()=>
863
+ this.#withExplicitLock(async()=>{
864
+ const durable=await dbopfs.get(
865
+ this.#tableName,
866
+ this.fileName,
867
+ true
868
+ );
869
+ if(durable){
870
+ const persist=this.persist;
871
+ this.persist=false;
872
+ try{
873
+ this.explicit=durable;
874
+ }finally{
875
+ this.persist=persist;
876
+ }
877
+ }
878
+
879
+ return operation(this.explicit);
880
+ })
881
+ );
882
+
883
+ this.#explicitUpdateQueue=result.catch(()=>false);
884
+ return result;
885
+ }
886
+
887
+ #withExplicitLock(operation){
888
+ const locks=globalThis.navigator?.locks;
889
+
890
+ if(typeof locks?.request==='function'){
891
+ return locks.request(
892
+ `user-entity:${this.fileName}`,
893
+ operation
894
+ );
895
+ }
896
+ if(typeof globalThis.window==='object'){
897
+ const error=new Error(
898
+ 'This browser cannot safely coordinate profile changes across tabs.'
899
+ );
900
+ error.code='USER_ENTITY_LOCK_UNAVAILABLE';
901
+ return Promise.reject(error);
902
+ }
903
+
904
+ return operation();
905
+ }
906
+
907
+ /**
908
+ * Load entity from OPFS
909
+ *
910
+ * Reads serialized entity data using DBOPFS
911
+ * this is async so your code can be faster
912
+ * only await if you need to ensure data is loaded before proceeding
913
+ */
914
+ async load(){
915
+ if(this.ready){
916
+ return this.explicit;
917
+ }
918
+
919
+ if(this.#loadPromise){
920
+ return this.#loadPromise;
921
+ }
922
+
923
+ this.#loadPromise=this.#load();
924
+
925
+ try{
926
+ return await this.#loadPromise;
927
+ }finally{
928
+ this.#loadPromise=null;
929
+ }
930
+ }
931
+
932
+ /**
933
+ * Force-reads the durable profile so an already-open tab observes changes
934
+ * made by another tab before making consent-sensitive decisions.
935
+ *
936
+ * @returns {Promise<UserEntityData>}
937
+ */
938
+ async refresh(){
939
+ await this.#explicitUpdateQueue;
940
+ const user=await dbopfs.get(
941
+ this.#tableName,
942
+ this.fileName,
943
+ true
944
+ );
945
+
946
+ if(user){
947
+ const persist=this.persist;
948
+ this.persist=false;
949
+ try{
950
+ this.explicit=user;
951
+ }finally{
952
+ this.persist=persist;
953
+ }
954
+ }
955
+
956
+ return this.explicit;
957
+ }
958
+
959
+ async #load(){
960
+ const user=await dbopfs.get(
961
+ this.#tableName,
962
+ this.fileName
963
+ );
964
+
965
+ if(user){
966
+ const persist=this.persist;
967
+ this.persist=false;
968
+
969
+ try{
970
+ this.explicit=user;
971
+ }finally{
972
+ this.persist=persist;
973
+ }
974
+ }
975
+
976
+ this.ready=true;
977
+
978
+ window.dispatchEvent(
979
+ new CustomEvent(
980
+ 'user-entity-loaded',
981
+ {
982
+ detail:{
983
+ user:this
984
+ }
985
+ }
986
+ )
987
+ );
988
+
989
+ return this.explicit;
990
+ }
991
+
992
+ /**
993
+ * Persist entity to OPFS
994
+ *
995
+ * Writes serialized entity data using DBOPFS
996
+ * this is async so your code can run faster
997
+ * only await if you need to ensure data is saved before proceeding
998
+ */
999
+ async save(){
1000
+ return dbopfs.set(
1001
+ this.#tableName,
1002
+ this.fileName,
1003
+ JSON.stringify(this.explicit)
1004
+ );
1005
+ }
1006
+
1007
+ /**
1008
+ * Serialize entity to JSON
1009
+ *
1010
+ * @returns {string}
1011
+ */
1012
+ toJSON(){
1013
+ return JSON.stringify(
1014
+ this.explicit
1015
+ );
1016
+ }
1017
+
1018
+ /**
1019
+ * Internal persistence trigger
1020
+ */
1021
+ #persist(){
1022
+ if(this.persist){
1023
+ return this.save();
1024
+ }
1025
+
1026
+ return true;
1027
+ }
1028
+ }
1029
+
1030
+ window.addEventListener(
1031
+ 'dbopfs-ready',
1032
+ initSingletonUserEntity
1033
+ );
1034
+
1035
+ if(window.dbopfs?.ready){
1036
+ initSingletonUserEntity();
1037
+ }
1038
+
1039
+ function initSingletonUserEntity(){
1040
+ if(!window.user){
1041
+ window.user = new UserEntity();
1042
+ }
1043
+ }
1044
+
1045
+
1046
+ export default UserEntity;