arcane-os 0.5.16 → 0.5.18
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.
- package/CHANGELOG.md +44 -0
- package/README.md +42 -19
- package/bin/arcane-test.mjs +16 -13
- package/browser-runtime/ai/browser-speech-artifacts.mjs +41 -38
- package/browser-runtime/ai/browser-speech-providers.mjs +53 -46
- package/browser-runtime/ai/browser-wasm-llm-provider.mjs +127 -124
- package/browser-runtime/ai/browser-wasm.mjs +6 -3
- package/browser-runtime/ai/browser-wllama-runtime.mjs +26 -23
- package/browser-runtime/ai/model-controller.mjs +94 -91
- package/browser-runtime/ai/speech-worker-client.mjs +12 -9
- package/browser-runtime/ai/speech-worker-runtime.mjs +56 -53
- package/browser-runtime/dependencies/strong-type/index.js +207 -82
- package/browser-runtime/dependencies/strong-type/package.json +15 -6
- package/browser-runtime/dom-event-instrumentation.mjs +24 -20
- package/browser-runtime/event-manager.mjs +111 -108
- package/browser-runtime/speech-text.mjs +35 -0
- package/docs/architecture.md +1 -1
- package/docs/reference/README.md +8 -8
- package/docs/reference/ai/browser-speech.md +230 -16
- package/docs/reference/ai/twin-cloud.md +1 -1
- package/docs/reference/availability-and-normalization.md +42 -1
- package/docs/reference/cli.md +1 -1
- package/docs/reference/core/arcane-ai-contracts.md +1 -1
- package/docs/reference/inventory/package-api.json +60 -4
- package/docs/reference/inventory/runtime-components.json +2 -2
- package/docs/reference/inventory/runtime-modules.json +29 -31
- package/docs/reference/protocols.md +17 -13
- package/docs/reference/runtime-components.md +6 -5
- package/docs/reference/runtime-modules.md +212 -70
- package/docs/reference/sdk-api.md +197 -11
- package/examples/wasm-ai-demo/index.html +1 -0
- package/node_modules/event-pubsub/node_modules/strong-type/README.md +408 -0
- package/node_modules/event-pubsub/node_modules/strong-type/assets/strong-type-header.png +0 -0
- package/node_modules/event-pubsub/node_modules/strong-type/index.js +1151 -0
- package/node_modules/event-pubsub/node_modules/strong-type/licence +21 -0
- package/node_modules/event-pubsub/node_modules/strong-type/node.js +125 -0
- package/node_modules/event-pubsub/node_modules/strong-type/package.json +61 -0
- package/node_modules/strong-type/README.md +42 -24
- package/node_modules/strong-type/benchmark/core.js +324 -0
- package/node_modules/strong-type/index.js +207 -82
- package/node_modules/strong-type/package.json +14 -6
- package/package.json +4 -3
- package/runtime/arcane/components/app-bar.html +5 -2
- package/runtime/arcane/components/assistant-panel.html +5 -2
- package/runtime/arcane/components/chart.html +15 -12
- package/runtime/arcane/components/chat.html +118 -141
- package/runtime/arcane/components/dashboard-config.html +6 -3
- package/runtime/arcane/components/data-view.html +4 -1
- package/runtime/arcane/components/directory-picker.html +5 -2
- package/runtime/arcane/components/document-inspector.html +6 -3
- package/runtime/arcane/components/file-drop.html +5 -2
- package/runtime/arcane/components/file-inspector.html +9 -6
- package/runtime/arcane/components/file-manager.html +16 -13
- package/runtime/arcane/components/local-ai-status.html +5 -2
- package/runtime/arcane/components/markdown-document.html +14 -11
- package/runtime/arcane/components/markdown-editor.html +4 -1
- package/runtime/arcane/components/modal.html +4 -1
- package/runtime/arcane/components/output-panel.html +6 -3
- package/runtime/arcane/components/preferences-form.html +4 -1
- package/runtime/arcane/components/record-timeline.html +8 -5
- package/runtime/arcane/components/relationship-board.html +9 -6
- package/runtime/arcane/components/source-code-viewer.html +12 -9
- package/runtime/arcane/components/source-explanation.html +8 -5
- package/runtime/arcane/components/speech.html +24 -21
- package/runtime/arcane/components/summary-strip.html +4 -1
- package/runtime/arcane/components/task-progress.html +6 -3
- package/runtime/arcane/components/terminal-workspace.html +4 -1
- package/runtime/arcane/components/voice-transcription.html +11 -8
- package/runtime/arcane/components/web-navigator.html +5 -2
- package/runtime/arcane/entities/ApiModelRecord.js +5 -2
- package/runtime/arcane/entities/Calculation.js +5 -2
- package/runtime/arcane/entities/Chat.js +33 -33
- package/runtime/arcane/entities/CommunicationMessage.js +4 -1
- package/runtime/arcane/entities/CommunicationThread.js +4 -1
- package/runtime/arcane/entities/File.js +1 -1
- package/runtime/arcane/entities/Image.js +8 -5
- package/runtime/arcane/entities/IntentEnvelope.js +20 -17
- package/runtime/arcane/entities/Preference.js +9 -6
- package/runtime/arcane/entities/Theme.js +5 -2
- package/runtime/arcane/entities/User.js +11 -11
- package/runtime/arcane/entities/Weather.js +5 -2
- package/runtime/arcane/modules/AI.js +503 -187
- package/runtime/arcane/modules/AIPreferenceRuntime.js +5 -2
- package/runtime/arcane/modules/AIPreferenceTuple.js +9 -6
- package/runtime/arcane/modules/AIProviderRuntime.js +117 -92
- package/runtime/arcane/modules/AIResponseURLPolicy.js +148 -62
- package/runtime/arcane/modules/AIRuntimeState.js +29 -26
- package/runtime/arcane/modules/AnsiText.js +4 -1
- package/runtime/arcane/modules/ApiModelDatabase.js +22 -19
- package/runtime/arcane/modules/AppDataScope.js +8 -5
- package/runtime/arcane/modules/ArcaneCommunicationBridge.js +4 -1
- package/runtime/arcane/modules/ArcaneNavigationPolicy.js +8 -5
- package/runtime/arcane/modules/ArcaneNetworkPolicy.js +18 -15
- package/runtime/arcane/modules/AsyncBoundary.js +13 -10
- package/runtime/arcane/modules/BrowserTestSuite.js +19 -16
- package/runtime/arcane/modules/CalculatorEngine.js +5 -2
- package/runtime/arcane/modules/ChatRecords.js +18 -15
- package/runtime/arcane/modules/CommunicationAppController.js +9 -6
- package/runtime/arcane/modules/CommunicationHub.js +9 -6
- package/runtime/arcane/modules/CommunicationPreferences.js +4 -1
- package/runtime/arcane/modules/CommunicationProviderRegistry.js +4 -1
- package/runtime/arcane/modules/ComponentContracts.js +56 -53
- package/runtime/arcane/modules/ConfiguredAIChatSession.js +30 -27
- package/runtime/arcane/modules/ConversationActionItems.js +15 -12
- package/runtime/arcane/modules/ConversationClosingReport.js +11 -8
- package/runtime/arcane/modules/ConversationTimebox.js +28 -25
- package/runtime/arcane/modules/CoreLocalModelCatalog.js +17 -14
- package/runtime/arcane/modules/DBLS.js +7 -4
- package/runtime/arcane/modules/DBOPFS.js +7 -7
- package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +34 -31
- package/runtime/arcane/modules/DevelopmentWorkspace.js +4 -1
- package/runtime/arcane/modules/DirectoryPicker.js +8 -5
- package/runtime/arcane/modules/DocumentLexicalSearch.js +11 -8
- package/runtime/arcane/modules/DocumentNavigation.js +7 -4
- package/runtime/arcane/modules/Errors.js +28 -25
- package/runtime/arcane/modules/HTMLImport.js +7 -4
- package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +15 -12
- package/runtime/arcane/modules/LocalAIReadiness.js +21 -18
- package/runtime/arcane/modules/LocalAIReadinessController.js +6 -3
- package/runtime/arcane/modules/MD.js +1 -1
- package/runtime/arcane/modules/Mail.js +46 -43
- package/runtime/arcane/modules/MailOutbox.mjs +48 -45
- package/runtime/arcane/modules/MailTransport.mjs +29 -26
- package/runtime/arcane/modules/MarkdownSpeech.js +1 -50
- package/runtime/arcane/modules/MemoryRecords.js +7 -4
- package/runtime/arcane/modules/MessageAdvisory.js +6 -3
- package/runtime/arcane/modules/ModelDefinition.js +7 -4
- package/runtime/arcane/modules/Ollama.js +6 -3
- package/runtime/arcane/modules/OllamaModelIdentifier.js +4 -1
- package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +18 -15
- package/runtime/arcane/modules/PersistentAIChatSession.js +33 -30
- package/runtime/arcane/modules/PreferenceStore.js +20 -17
- package/runtime/arcane/modules/PreparedSpeech.js +485 -0
- package/runtime/arcane/modules/Questionnaire.js +6 -3
- package/runtime/arcane/modules/RecordLinkIndex.js +4 -1
- package/runtime/arcane/modules/RecordPassageIndex.js +9 -6
- package/runtime/arcane/modules/RecordReviewStore.js +12 -9
- package/runtime/arcane/modules/RiskSignalAnalyzer.js +4 -1
- package/runtime/arcane/modules/ScopedOPFSCache.js +6 -3
- package/runtime/arcane/modules/ScreenCapture.js +20 -17
- package/runtime/arcane/modules/SpeechPlayback.js +61 -28
- package/runtime/arcane/modules/StaticDocumentCatalog.js +33 -30
- package/runtime/arcane/modules/SystemAppearance.js +5 -2
- package/runtime/arcane/modules/SystemToolRegistry.js +6 -3
- package/runtime/arcane/modules/TerminalClient.js +14 -11
- package/runtime/arcane/modules/TerminalCommandRegistry.js +4 -1
- package/runtime/arcane/modules/ThemeBootstrap.js +9 -6
- package/runtime/arcane/modules/ThemeManager.js +5 -2
- package/runtime/arcane/modules/TimeGuard.js +1 -1
- package/runtime/arcane/modules/ToolCallRouter.js +11 -8
- package/runtime/arcane/modules/WaitForComponent.js +19 -16
- package/runtime/arcane/modules/YouTubeMedia.js +4 -1
- package/runtime/strong-type/index.js +1276 -352
- package/runtime/strong-type/package.json +69 -45
- package/src/app-descriptor.mjs +17 -14
- package/src/application-tests.mjs +4 -1
- package/src/cli/main.mjs +8 -5
- package/src/constants.mjs +4 -1
- package/src/dev-server.mjs +8 -5
- package/src/doctor.mjs +5 -2
- package/src/dom-event-instrumentation.mjs +24 -20
- package/src/errors.mjs +7 -3
- package/src/event-manager.mjs +111 -108
- package/src/event-queue.mjs +8 -5
- package/src/events.mjs +12 -9
- package/src/import-map.mjs +36 -27
- package/src/installed-sdk-runtime.mjs +4 -1
- package/src/integrated-provider-loader.mjs +9 -6
- package/src/mail-credentials.mjs +16 -13
- package/src/mail-server.mjs +53 -50
- package/src/mail.mjs +18 -15
- package/src/native-plan.mjs +12 -9
- package/src/native-provider-loader.mjs +7 -4
- package/src/packager/core.mjs +18 -15
- package/src/process.mjs +6 -3
- package/src/release-bundle.mjs +14 -11
- package/src/runtime.mjs +4 -1
- package/src/scaffold.mjs +9 -6
- package/src/sdk-browser-runtime.mjs +4 -1
- package/src/source-server.mjs +17 -14
- package/src/targets/index.mjs +5 -2
- package/src/templates/workspace-template.mjs +13 -6
- package/src/testing-loader.mjs +7 -4
- package/src/testing.mjs +10 -7
- package/src/toolchain.mjs +13 -10
- package/src/update-check.mjs +9 -6
- package/src/workspace-operation-lock.mjs +14 -11
- package/src/workspace-runtime.mjs +4 -1
- package/src/workspace.mjs +17 -14
- package/runtime/arcane/modules/AIResponseLength.js +0 -32
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { arcaneLogging } from 'arcane-os/logging';
|
|
2
|
-
import Is from '
|
|
2
|
+
import Is from 'strong-type';
|
|
3
3
|
import '../modules/DBOPFS.js';
|
|
4
4
|
import '../modules/AI.js';
|
|
5
5
|
import {
|
|
@@ -12,7 +12,7 @@ import {normalizeMemoryContent} from '../modules/MemoryRecords.js';
|
|
|
12
12
|
const is = new Is(false);
|
|
13
13
|
|
|
14
14
|
function plainRecord(value){
|
|
15
|
-
if(!value
|
|
15
|
+
if(!value||!is.object(value)||is.array(value))return false;
|
|
16
16
|
const prototype=Object.getPrototypeOf(value);
|
|
17
17
|
return prototype===Object.prototype||prototype===null;
|
|
18
18
|
}
|
|
@@ -23,9 +23,9 @@ function coded(error,code){
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
function copyCompleteValue(value,seen=new Map()){
|
|
26
|
-
if(value===null
|
|
26
|
+
if(value===null||!is.object(value))return value;
|
|
27
27
|
if(seen.has(value))return seen.get(value);
|
|
28
|
-
if(
|
|
28
|
+
if(is.array(value)){
|
|
29
29
|
const result=[];
|
|
30
30
|
seen.set(value,result);
|
|
31
31
|
for(const item of value)result.push(copyCompleteValue(item,seen));
|
|
@@ -45,7 +45,7 @@ function copyCompleteValue(value,seen=new Map()){
|
|
|
45
45
|
|
|
46
46
|
function copyToolCalls(value){
|
|
47
47
|
if(value===undefined) return null;
|
|
48
|
-
if(!
|
|
48
|
+
if(!is.array(value)){
|
|
49
49
|
const error=new TypeError('assistantMessage.tool_calls must be an array.');
|
|
50
50
|
error.code='AI_CHAT_INVALID_TOOL_CALL';
|
|
51
51
|
throw error;
|
|
@@ -61,12 +61,12 @@ function copyToolCalls(value){
|
|
|
61
61
|
const name=call.function.name;
|
|
62
62
|
const argumentValue=call.function.arguments;
|
|
63
63
|
if(
|
|
64
|
-
|
|
64
|
+
!is.string(id)
|
|
65
65
|
||!id.trim()
|
|
66
66
|
||ids.has(id)
|
|
67
|
-
|
|
67
|
+
||!is.string(name)
|
|
68
68
|
||!name.trim()
|
|
69
|
-
|
|
69
|
+
||!is.string(argumentValue)
|
|
70
70
|
){
|
|
71
71
|
const error=new TypeError(`assistantMessage.tool_calls[${index}] is invalid.`);
|
|
72
72
|
error.code='AI_CHAT_INVALID_TOOL_CALL';
|
|
@@ -90,7 +90,7 @@ function copyToolCalls(value){
|
|
|
90
90
|
error.code='AI_CHAT_INVALID_TOOL_CALL';
|
|
91
91
|
throw error;
|
|
92
92
|
}
|
|
93
|
-
if(
|
|
93
|
+
if(!is.string(argumentRecord.message)||!argumentRecord.message.trim()){
|
|
94
94
|
const error=new TypeError(
|
|
95
95
|
`assistantMessage.tool_calls[${index}].function.arguments.message must contain user-facing text.`
|
|
96
96
|
);
|
|
@@ -114,7 +114,7 @@ function structuralToolMessage(call){
|
|
|
114
114
|
|
|
115
115
|
function storedStructuralToolCalls(value){
|
|
116
116
|
if(value===undefined) return [];
|
|
117
|
-
if(!
|
|
117
|
+
if(!is.array(value)){
|
|
118
118
|
arcaneLogging.error('Arcane stored assistant tool calls were not an array and were not retained.');
|
|
119
119
|
return [];
|
|
120
120
|
}
|
|
@@ -133,12 +133,12 @@ function storedStructuralToolCalls(value){
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
function storedToolRecord({content,name,status,timestamp}){
|
|
136
|
-
if(
|
|
136
|
+
if(!is.string(content)||!content.trim()) return [];
|
|
137
137
|
return [{
|
|
138
138
|
role:'tool',
|
|
139
139
|
content,
|
|
140
|
-
...(
|
|
141
|
-
...(
|
|
140
|
+
...(is.string(name)&&name.trim()?{name}:{}),
|
|
141
|
+
...(is.string(status)&&status.trim()?{status}:{}),
|
|
142
142
|
...(timestamp!==undefined?{timestamp}:{}),
|
|
143
143
|
}];
|
|
144
144
|
}
|
|
@@ -151,7 +151,7 @@ function storedChatRecords(messages,{memoryOnly=false}={}){
|
|
|
151
151
|
if(memoryOnly&&message.memory_excluded===true) continue;
|
|
152
152
|
const timestamp=message.timestamp;
|
|
153
153
|
if(message.role==='user'){
|
|
154
|
-
if(
|
|
154
|
+
if(!is.string(message.content)) continue;
|
|
155
155
|
result.push({
|
|
156
156
|
role:'user',
|
|
157
157
|
content:message.content,
|
|
@@ -181,7 +181,7 @@ function storedChatRecords(messages,{memoryOnly=false}={}){
|
|
|
181
181
|
continue;
|
|
182
182
|
}
|
|
183
183
|
if(message.role==='tool'){
|
|
184
|
-
const protocolResult=
|
|
184
|
+
const protocolResult=is.string(message.tool_call_id)&&message.tool_call_id;
|
|
185
185
|
result.push(...storedToolRecord({
|
|
186
186
|
content:protocolResult?message.persistence_message:message.content,
|
|
187
187
|
name:message.persistence_name??message.name,
|
|
@@ -198,12 +198,12 @@ function retainedChatMessages(messages){
|
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
function turnMessage(value,label){
|
|
201
|
-
if(!plainRecord(value)||!['tool','user'].includes(value.role)
|
|
201
|
+
if(!plainRecord(value)||!['tool','user'].includes(value.role)||!is.string(value.content)){
|
|
202
202
|
throw new TypeError(`${label} must be a user or tool message.`);
|
|
203
203
|
}
|
|
204
204
|
if(value.role==='tool'){
|
|
205
205
|
const toolCallId=value.tool_call_id;
|
|
206
|
-
if(
|
|
206
|
+
if(!is.string(toolCallId)||!toolCallId.trim()){
|
|
207
207
|
throw coded(
|
|
208
208
|
new TypeError(`${label}.tool_call_id is invalid.`),
|
|
209
209
|
'AI_CHAT_INVALID_TOOL_MESSAGE'
|
|
@@ -277,13 +277,13 @@ function pendingToolCalls(messages){
|
|
|
277
277
|
if(toolCallId===undefined){
|
|
278
278
|
continue;
|
|
279
279
|
}
|
|
280
|
-
if(
|
|
280
|
+
if(!is.string(toolCallId)||!toolCallId.trim()){
|
|
281
281
|
throw coded(
|
|
282
282
|
new TypeError(`Chat message ${index+1} has an invalid tool_call_id.`),
|
|
283
283
|
'AI_CHAT_INCOHERENT_PERSISTENCE'
|
|
284
284
|
);
|
|
285
285
|
}
|
|
286
|
-
if(
|
|
286
|
+
if(!is.string(message.content)||!message.content.trim()){
|
|
287
287
|
throw coded(
|
|
288
288
|
new TypeError(`Chat message ${index+1} has a blank tool result.`),
|
|
289
289
|
'AI_CHAT_INCOHERENT_PERSISTENCE'
|
|
@@ -308,7 +308,7 @@ function turnMessages(requestMessage,requestMessages){
|
|
|
308
308
|
throw new TypeError('Provide exactly one of requestMessage or requestMessages.');
|
|
309
309
|
}
|
|
310
310
|
const values=hasMessages?requestMessages:[requestMessage];
|
|
311
|
-
if(!
|
|
311
|
+
if(!is.array(values)||values.length===0){
|
|
312
312
|
throw new TypeError('requestMessages must be a nonempty array.');
|
|
313
313
|
}
|
|
314
314
|
const records=values.map((value,index)=>
|
|
@@ -513,7 +513,7 @@ class ChatEntity{
|
|
|
513
513
|
* @returns {Array<*>}
|
|
514
514
|
*/
|
|
515
515
|
set messages(v){
|
|
516
|
-
if(!
|
|
516
|
+
if(!is.array(v)){
|
|
517
517
|
throw new TypeError('messages must be an array.');
|
|
518
518
|
}
|
|
519
519
|
this.#messages=v.map(message=>copyCompleteValue(message));
|
|
@@ -538,7 +538,7 @@ class ChatEntity{
|
|
|
538
538
|
* @returns {boolean}
|
|
539
539
|
*/
|
|
540
540
|
set saved(v){
|
|
541
|
-
if(
|
|
541
|
+
if(!is.boolean(v)){
|
|
542
542
|
throw new TypeError('saved must be a boolean.');
|
|
543
543
|
}
|
|
544
544
|
this.#saved=v;
|
|
@@ -646,9 +646,9 @@ class ChatEntity{
|
|
|
646
646
|
const toolCallId=id;
|
|
647
647
|
const toolName=name;
|
|
648
648
|
if(
|
|
649
|
-
|
|
649
|
+
!is.string(toolCallId)
|
|
650
650
|
||!toolCallId.trim()
|
|
651
|
-
|
|
651
|
+
||!is.string(toolName)
|
|
652
652
|
||!toolName.trim()
|
|
653
653
|
){
|
|
654
654
|
throw new TypeError('Tool exchanges require an id and name.');
|
|
@@ -663,13 +663,13 @@ class ChatEntity{
|
|
|
663
663
|
);
|
|
664
664
|
}
|
|
665
665
|
|
|
666
|
-
const serializedArguments=
|
|
666
|
+
const serializedArguments=is.string(argumentValue)
|
|
667
667
|
?argumentValue
|
|
668
668
|
:JSON.stringify(argumentValue);
|
|
669
|
-
const serializedResult=
|
|
669
|
+
const serializedResult=is.string(result)
|
|
670
670
|
?result
|
|
671
671
|
:JSON.stringify(result);
|
|
672
|
-
if(
|
|
672
|
+
if(!is.string(serializedArguments)||!is.string(serializedResult)){
|
|
673
673
|
throw new TypeError('Tool exchange arguments and results must be JSON-compatible.');
|
|
674
674
|
}
|
|
675
675
|
const toolCall=copyToolCalls([{
|
|
@@ -723,7 +723,7 @@ class ChatEntity{
|
|
|
723
723
|
if(!is.boolean(messagePersist)||!is.boolean(responsePersist)||!is.boolean(extractMemory)){
|
|
724
724
|
throw new TypeError('Turn persistence and memory options must be boolean.');
|
|
725
725
|
}
|
|
726
|
-
if(
|
|
726
|
+
if(!is.function(memoryRequest)){
|
|
727
727
|
throw new TypeError('memoryRequest must be a function.');
|
|
728
728
|
}
|
|
729
729
|
if(messagePersist!==responsePersist){
|
|
@@ -762,7 +762,7 @@ class ChatEntity{
|
|
|
762
762
|
const toolCalls=copyToolCalls(assistantMessage.tool_calls);
|
|
763
763
|
const assistantContent=assistantMessage.content??'';
|
|
764
764
|
const assistantReasoning=assistantMessage.reasoning_content;
|
|
765
|
-
if(assistantReasoning!==undefined
|
|
765
|
+
if(assistantReasoning!==undefined&&!is.string(assistantReasoning)){
|
|
766
766
|
throw new TypeError('assistantMessage.reasoning_content must be a string when provided.');
|
|
767
767
|
}
|
|
768
768
|
if(
|
|
@@ -770,7 +770,7 @@ class ChatEntity{
|
|
|
770
770
|
||(
|
|
771
771
|
!String(assistantContent)
|
|
772
772
|
&&!toolCalls?.length
|
|
773
|
-
&&!(
|
|
773
|
+
&&!(is.string(assistantReasoning)&&assistantReasoning.length)
|
|
774
774
|
)
|
|
775
775
|
){
|
|
776
776
|
throw new TypeError(
|
|
@@ -823,7 +823,7 @@ class ChatEntity{
|
|
|
823
823
|
this.fileName
|
|
824
824
|
);
|
|
825
825
|
|
|
826
|
-
if(!content||(
|
|
826
|
+
if(!content||(is.array(content)&&content.length===0)){
|
|
827
827
|
const systemMessage=this.#messages.find(message=>message.role==='system');
|
|
828
828
|
this.#messages=systemMessage?[systemMessage]:[];
|
|
829
829
|
this.#persistedMessageCount=0;
|
|
@@ -832,7 +832,7 @@ class ChatEntity{
|
|
|
832
832
|
return this.transcript;
|
|
833
833
|
}
|
|
834
834
|
|
|
835
|
-
const loadedMessages=
|
|
835
|
+
const loadedMessages=is.array(content)
|
|
836
836
|
?content.map(message=>copyCompleteValue(message))
|
|
837
837
|
:String(content)
|
|
838
838
|
.split('\n')
|
|
@@ -858,7 +858,7 @@ class ChatEntity{
|
|
|
858
858
|
}
|
|
859
859
|
|
|
860
860
|
async getMemoriesAboutUser({request=messages=>ai.fetch(messages)}={}){
|
|
861
|
-
if(
|
|
861
|
+
if(!is.function(request)){
|
|
862
862
|
throw new TypeError('Memory request must be a function.');
|
|
863
863
|
}
|
|
864
864
|
return this.#writeMemory(
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
const CHANNELS=new Set(['email','sms','mms','rcs','whatsapp','other']);
|
|
2
5
|
const DIRECTIONS=new Set(['inbound','outbound']);
|
|
3
6
|
const STATUSES=new Set(['draft','queued','sending','sent','delivered','read','failed','received']);
|
|
4
7
|
|
|
5
8
|
function required(value,label){const text=String(value??'').trim();if(!text) throw new TypeError(`${label} is required.`);return text;}
|
|
6
9
|
function strings(value){return Array.from(value||[],item=>String(item??'').trim()).filter(Boolean);}
|
|
7
|
-
function instant(value){const date=value instanceof Date?value:new Date(value||Date.now());if(
|
|
10
|
+
function instant(value){const date=value instanceof Date?value:new Date(value||Date.now());if(is.NaN(date.valueOf())) throw new TypeError('Message timestamp is invalid.');return date.toISOString();}
|
|
8
11
|
|
|
9
12
|
export default class CommunicationMessage{
|
|
10
13
|
constructor(input={}){
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
import CommunicationMessage from './CommunicationMessage.js';
|
|
2
5
|
|
|
3
6
|
function required(value,label){const text=String(value??'').trim();if(!text) throw new TypeError(`${label} is required.`);return text;}
|
|
@@ -11,7 +14,7 @@ export default class CommunicationThread{
|
|
|
11
14
|
this.participants=Object.freeze(Array.from(input.participants||[],value=>String(value??'').trim()).filter(Boolean));
|
|
12
15
|
this.preview=String(input.preview||'');
|
|
13
16
|
const date=new Date(input.updatedAt||Date.now());
|
|
14
|
-
if(
|
|
17
|
+
if(is.NaN(date.valueOf())) throw new TypeError('Thread update time is invalid.');
|
|
15
18
|
this.updatedAt=date.toISOString();
|
|
16
19
|
this.unreadCount=Math.max(0,Number.parseInt(input.unreadCount||0,10)||0);
|
|
17
20
|
this.messages=Object.freeze(Array.from(input.messages||[],value=>value instanceof CommunicationMessage?value:new CommunicationMessage({...value,threadId:value.threadId||this.id,providerId:value.providerId||this.providerId,channel:value.channel||this.channel})));
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
import FileEntity from './File.js';
|
|
2
5
|
|
|
3
6
|
/**
|
|
@@ -34,11 +37,11 @@ class ImageEntity extends FileEntity {
|
|
|
34
37
|
* @returns {boolean}
|
|
35
38
|
*/
|
|
36
39
|
static isImageFile(file) {
|
|
37
|
-
if (!file ||
|
|
40
|
+
if (!file || !is.string(file.name)) {
|
|
38
41
|
return false;
|
|
39
42
|
}
|
|
40
43
|
|
|
41
|
-
const hasImageMime =
|
|
44
|
+
const hasImageMime = is.string(file.type) && file.type.startsWith('image/');
|
|
42
45
|
const hasImageExt = /\.(png|jpe?g|svg)$/i.test(file.name);
|
|
43
46
|
|
|
44
47
|
return hasImageMime || hasImageExt;
|
|
@@ -51,12 +54,12 @@ class ImageEntity extends FileEntity {
|
|
|
51
54
|
* @returns {boolean}
|
|
52
55
|
*/
|
|
53
56
|
static isPNGFile(file) {
|
|
54
|
-
if (!file ||
|
|
57
|
+
if (!file || !is.string(file.name)) {
|
|
55
58
|
return false;
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
const lowerName = file.name.toLowerCase();
|
|
59
|
-
const hasPngMime =
|
|
62
|
+
const hasPngMime = is.string(file.type) && file.type.toLowerCase() === 'image/png';
|
|
60
63
|
const hasPngExt = lowerName.endsWith('.png');
|
|
61
64
|
|
|
62
65
|
return hasPngMime || hasPngExt;
|
|
@@ -125,7 +128,7 @@ class ImageEntity extends FileEntity {
|
|
|
125
128
|
* @returns {void}
|
|
126
129
|
*/
|
|
127
130
|
static revokeBlobURL(url = '') {
|
|
128
|
-
if (
|
|
131
|
+
if (is.string(url) && url.startsWith('blob:')) {
|
|
129
132
|
URL.revokeObjectURL(url);
|
|
130
133
|
}
|
|
131
134
|
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
const IDENTIFIER_MAXIMUM_CHARACTERS = 128;
|
|
2
5
|
|
|
3
6
|
const SCHEMA = 'arcane.intent-envelope';
|
|
@@ -60,7 +63,7 @@ function fail(code, path, message, options) {
|
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
function isReservedKey(key) {
|
|
63
|
-
return
|
|
66
|
+
return is.string(key) && RESERVED_KEYS.has(key);
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
function validateUnicode(value, path) {
|
|
@@ -86,7 +89,7 @@ function normalizedString(value, path, options = {}) {
|
|
|
86
89
|
if (nullable && value === null) {
|
|
87
90
|
return null;
|
|
88
91
|
}
|
|
89
|
-
if (
|
|
92
|
+
if (!is.string(value)) {
|
|
90
93
|
fail('INTENT_ENVELOPE_INVALID', path, 'Intent envelope field has an invalid type.');
|
|
91
94
|
}
|
|
92
95
|
validateUnicode(value, path);
|
|
@@ -102,7 +105,7 @@ function identifier(value, path, options = {}) {
|
|
|
102
105
|
if (nullable && value === null) {
|
|
103
106
|
return null;
|
|
104
107
|
}
|
|
105
|
-
if (
|
|
108
|
+
if (!is.string(value)) {
|
|
106
109
|
fail('INTENT_ENVELOPE_INVALID', path, 'Intent envelope identifier has an invalid type.');
|
|
107
110
|
}
|
|
108
111
|
validateUnicode(value, path);
|
|
@@ -113,14 +116,14 @@ function identifier(value, path, options = {}) {
|
|
|
113
116
|
}
|
|
114
117
|
|
|
115
118
|
function enumeration(value, path, values) {
|
|
116
|
-
if (
|
|
119
|
+
if (!is.string(value) || !values.has(value)) {
|
|
117
120
|
fail('INTENT_ENVELOPE_INVALID', path, 'Intent envelope enum value is invalid.');
|
|
118
121
|
}
|
|
119
122
|
return value;
|
|
120
123
|
}
|
|
121
124
|
|
|
122
125
|
function plainRecord(value, path, allowedKeys, active) {
|
|
123
|
-
if (!value ||
|
|
126
|
+
if (!value || !is.object(value) || is.array(value)) {
|
|
124
127
|
fail('INTENT_ENVELOPE_INVALID', path, 'Intent envelope field must be a plain record.');
|
|
125
128
|
}
|
|
126
129
|
const prototype = Object.getPrototypeOf(value);
|
|
@@ -139,7 +142,7 @@ function plainRecord(value, path, allowedKeys, active) {
|
|
|
139
142
|
const ownKeys = Reflect.ownKeys(value);
|
|
140
143
|
|
|
141
144
|
for (const key of ownKeys) {
|
|
142
|
-
if (
|
|
145
|
+
if (!is.string(key) || isReservedKey(key) || !allowed.has(key)) {
|
|
143
146
|
active.delete(value);
|
|
144
147
|
fail('INTENT_ENVELOPE_UNKNOWN_FIELD', path, 'Intent envelope contains an unsupported field.');
|
|
145
148
|
}
|
|
@@ -172,7 +175,7 @@ function ownValue(record, key, path, options = {}) {
|
|
|
172
175
|
}
|
|
173
176
|
|
|
174
177
|
function denseArray(value, path, active) {
|
|
175
|
-
if (!
|
|
178
|
+
if (!is.array(value) || Object.getPrototypeOf(value) !== Array.prototype) {
|
|
176
179
|
fail('INTENT_ENVELOPE_INVALID', path, 'Intent envelope field must be an array.');
|
|
177
180
|
}
|
|
178
181
|
if (active.has(value)) {
|
|
@@ -184,7 +187,7 @@ function denseArray(value, path, active) {
|
|
|
184
187
|
if (key === 'length') {
|
|
185
188
|
continue;
|
|
186
189
|
}
|
|
187
|
-
if (
|
|
190
|
+
if (!is.string(key) || isReservedKey(key) || !/^(0|[1-9]\d*)$/.test(key) || Number(key) >= value.length) {
|
|
188
191
|
active.delete(value);
|
|
189
192
|
fail('INTENT_ENVELOPE_UNKNOWN_FIELD', path, 'Intent envelope array contains an unsupported field.');
|
|
190
193
|
}
|
|
@@ -208,24 +211,24 @@ function finishArray(value, active) {
|
|
|
208
211
|
}
|
|
209
212
|
|
|
210
213
|
function normalizeTimestamp(value, path) {
|
|
211
|
-
if (
|
|
214
|
+
if (!is.string(value)) {
|
|
212
215
|
fail('INTENT_ENVELOPE_INVALID', path, 'Intent envelope timestamp is invalid.');
|
|
213
216
|
}
|
|
214
217
|
const instant = new Date(value);
|
|
215
|
-
if (
|
|
218
|
+
if (is.NaN(instant.valueOf()) || instant.toISOString() !== value) {
|
|
216
219
|
fail('INTENT_ENVELOPE_INVALID', path, 'Intent envelope timestamp is invalid.');
|
|
217
220
|
}
|
|
218
221
|
return value;
|
|
219
222
|
}
|
|
220
223
|
|
|
221
224
|
function normalizeTrustedTimestamp(value, path) {
|
|
222
|
-
if (!(
|
|
225
|
+
if (!(is.string(value) || value instanceof Date)) {
|
|
223
226
|
fail('INTENT_ENVELOPE_INVALID', path, 'Intent envelope timestamp is invalid.');
|
|
224
227
|
}
|
|
225
228
|
|
|
226
229
|
const instant = new Date(value);
|
|
227
230
|
|
|
228
|
-
if (
|
|
231
|
+
if (is.NaN(instant.valueOf())) {
|
|
229
232
|
fail('INTENT_ENVELOPE_INVALID', path, 'Intent envelope timestamp is invalid.');
|
|
230
233
|
}
|
|
231
234
|
|
|
@@ -245,7 +248,7 @@ function normalizeGoal(value, path, active) {
|
|
|
245
248
|
const version = identifier(versionValue, `${path}.version`);
|
|
246
249
|
const confidenceValue = ownValue(record, 'confidence', path);
|
|
247
250
|
if (confidenceValue !== null && (
|
|
248
|
-
|
|
251
|
+
!is.number(confidenceValue) || !is.finite(confidenceValue) || confidenceValue < 0 || confidenceValue > 1
|
|
249
252
|
)) {
|
|
250
253
|
finishRecord(record, active);
|
|
251
254
|
fail('INTENT_ENVELOPE_INVALID', `${path}.confidence`, 'Intent envelope confidence is invalid.');
|
|
@@ -256,15 +259,15 @@ function normalizeGoal(value, path, active) {
|
|
|
256
259
|
}
|
|
257
260
|
|
|
258
261
|
function normalizeScalar(value, path) {
|
|
259
|
-
if (value === null ||
|
|
260
|
-
if (
|
|
262
|
+
if (value === null || is.string(value) || is.boolean(value)) {
|
|
263
|
+
if (is.string(value)) {
|
|
261
264
|
validateUnicode(value, path);
|
|
262
265
|
}
|
|
263
266
|
|
|
264
267
|
return value;
|
|
265
268
|
}
|
|
266
269
|
|
|
267
|
-
if (
|
|
270
|
+
if (is.number(value) && is.finite(value)) {
|
|
268
271
|
return value;
|
|
269
272
|
}
|
|
270
273
|
|
|
@@ -704,7 +707,7 @@ export function createIntentEnvelope(payload, trustedContext) {
|
|
|
704
707
|
}
|
|
705
708
|
|
|
706
709
|
export function rehydrateIntentEnvelope(canonical) {
|
|
707
|
-
if (
|
|
710
|
+
if (is.string(canonical)) {
|
|
708
711
|
try {
|
|
709
712
|
const parsed = JSON.parse(canonical);
|
|
710
713
|
return buildEnvelope(parsed);
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
const KEY_PATTERN=/^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
|
|
2
5
|
const TYPES=new Set(['boolean','number','select','text']);
|
|
3
6
|
|
|
@@ -18,9 +21,9 @@ export default class Preference{
|
|
|
18
21
|
this.description=String(definition.description||'');
|
|
19
22
|
this.defaultValue=this.normalize(definition.defaultValue);
|
|
20
23
|
this.options=type==='select'?this.normalizeOptions(definition.options):[];
|
|
21
|
-
this.minimum=
|
|
22
|
-
this.maximum=
|
|
23
|
-
this.step=
|
|
24
|
+
this.minimum=is.finite(Number(definition.minimum))?Number(definition.minimum):undefined;
|
|
25
|
+
this.maximum=is.finite(Number(definition.maximum))?Number(definition.maximum):undefined;
|
|
26
|
+
this.step=is.finite(Number(definition.step))?Number(definition.step):undefined;
|
|
24
27
|
|
|
25
28
|
if(type==='select'&&!this.options.some(option=>Object.is(option.value,this.defaultValue))){
|
|
26
29
|
throw new TypeError(`Preference ${key} has a default value outside its options.`);
|
|
@@ -28,9 +31,9 @@ export default class Preference{
|
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
normalizeOptions(options=[]){
|
|
31
|
-
if(!
|
|
34
|
+
if(!is.array(options)||!options.length) throw new TypeError('Select preferences require options.');
|
|
32
35
|
return options.map(option=>{
|
|
33
|
-
const normalized=
|
|
36
|
+
const normalized=is.object(option)&&option!==null
|
|
34
37
|
?{label:String(option.label??option.value),value:this.normalize(option.value)}
|
|
35
38
|
:{label:String(option),value:this.normalize(option)};
|
|
36
39
|
return normalized;
|
|
@@ -47,7 +50,7 @@ export default class Preference{
|
|
|
47
50
|
case 'boolean': return value===true||value==='true'||value===1;
|
|
48
51
|
case 'number': {
|
|
49
52
|
const number=Number(value);
|
|
50
|
-
if(!
|
|
53
|
+
if(!is.finite(number)) throw new TypeError('Numeric preferences require finite values.');
|
|
51
54
|
return number;
|
|
52
55
|
}
|
|
53
56
|
default: return String(value);
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
const HEX_COLOR=/^#([0-9a-f]{6})$/i;
|
|
2
5
|
const RGB_COLOR=/^(rgb|rgba)\(\s*(\d{1,3})(?:\s*,\s*|\s+)(\d{1,3})(?:\s*,\s*|\s+)(\d{1,3})(?:\s*(?:,|\/)\s*(0|1|0?\.\d+))?\s*\)$/i;
|
|
3
6
|
|
|
@@ -74,7 +77,7 @@ export default class Theme{
|
|
|
74
77
|
constructor(input={}){
|
|
75
78
|
this.name=normalizeName(input.name||'My Arcane skin');
|
|
76
79
|
this.scheme=input.scheme==='dark'?'dark':'light';
|
|
77
|
-
const source=input.tokens&&
|
|
80
|
+
const source=input.tokens&&is.object(input.tokens)?input.tokens:{};
|
|
78
81
|
const defaults=this.scheme==='dark'?arcaneDarkThemeTokens:arcaneLightThemeTokens;
|
|
79
82
|
this.tokens=Object.freeze(Object.fromEntries(themeTokens.map(token=>[
|
|
80
83
|
token.key,
|
|
@@ -101,7 +104,7 @@ export default class Theme{
|
|
|
101
104
|
|
|
102
105
|
static fromJSON(value){
|
|
103
106
|
if(value instanceof Theme) return value;
|
|
104
|
-
const parsed=
|
|
107
|
+
const parsed=is.string(value)?JSON.parse(value):value;
|
|
105
108
|
return new Theme(parsed);
|
|
106
109
|
}
|
|
107
110
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { arcaneLogging } from 'arcane-os/logging';
|
|
2
|
-
import Is from '
|
|
2
|
+
import Is from 'strong-type';
|
|
3
3
|
import DBLS from '../modules/DBLS.js';
|
|
4
4
|
import {
|
|
5
5
|
arcaneEvents,
|
|
@@ -30,14 +30,14 @@ function createDefaultDashboard(){
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
function normalizeDashboard(dashboard={}){
|
|
33
|
-
const source=dashboard&&
|
|
34
|
-
&&dashboard.charts&&
|
|
33
|
+
const source=dashboard&&is.object(dashboard)&&!is.array(dashboard)
|
|
34
|
+
&&dashboard.charts&&is.object(dashboard.charts)&&!is.array(dashboard.charts)
|
|
35
35
|
? dashboard.charts
|
|
36
36
|
: {};
|
|
37
37
|
const charts={};
|
|
38
38
|
|
|
39
39
|
for(const [key,value] of Object.entries(source)){
|
|
40
|
-
if(
|
|
40
|
+
if(is.boolean(value)){
|
|
41
41
|
charts[key]=value;
|
|
42
42
|
}
|
|
43
43
|
}
|
|
@@ -646,9 +646,9 @@ class UserEntity {
|
|
|
646
646
|
/** @param {number} v */
|
|
647
647
|
set firstBootUp(v){
|
|
648
648
|
if(
|
|
649
|
-
|
|
650
|
-
||!
|
|
651
|
-
||!
|
|
649
|
+
!is.number(v)
|
|
650
|
+
||!is.finite(v)
|
|
651
|
+
||!is.safeInteger(v)
|
|
652
652
|
||v<0
|
|
653
653
|
){
|
|
654
654
|
throw new Error(
|
|
@@ -757,7 +757,7 @@ class UserEntity {
|
|
|
757
757
|
|
|
758
758
|
/** @param {{charts:Object<string,boolean>}} v */
|
|
759
759
|
set dashboard(v){
|
|
760
|
-
if(!v
|
|
760
|
+
if(!v||!is.object(v)||is.array(v)){
|
|
761
761
|
throw new Error('dashboard must be an object');
|
|
762
762
|
}
|
|
763
763
|
|
|
@@ -885,7 +885,7 @@ class UserEntity {
|
|
|
885
885
|
}
|
|
886
886
|
|
|
887
887
|
withFreshExplicit(operation){
|
|
888
|
-
if(
|
|
888
|
+
if(!is.function(operation)){
|
|
889
889
|
throw new TypeError('A fresh UserEntity operation is required.');
|
|
890
890
|
}
|
|
891
891
|
const result=this.#explicitUpdateQueue.then(()=>
|
|
@@ -916,13 +916,13 @@ class UserEntity {
|
|
|
916
916
|
#withExplicitLock(operation){
|
|
917
917
|
const locks=globalThis.navigator?.locks;
|
|
918
918
|
|
|
919
|
-
if(
|
|
919
|
+
if(is.function(locks?.request)){
|
|
920
920
|
return locks.request(
|
|
921
921
|
`user-entity:${this.fileName}`,
|
|
922
922
|
operation
|
|
923
923
|
);
|
|
924
924
|
}
|
|
925
|
-
if(
|
|
925
|
+
if(is.object(globalThis.window)){
|
|
926
926
|
const error=new Error(
|
|
927
927
|
'This browser cannot safely coordinate profile changes across tabs.'
|
|
928
928
|
);
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
4
|
+
function number(value,label,{minimum=-Infinity,maximum=Infinity}={}){const result=Number(value);if(!is.finite(result)||result<minimum||result>maximum)throw new TypeError(`${label} is invalid.`);return result;}
|
|
2
5
|
function text(value,label){const result=String(value??'');if(!result.trim())throw new TypeError(`${label} is required.`);return result;}
|
|
3
|
-
function instant(value,label){const date=new Date(value);if(
|
|
6
|
+
function instant(value,label){const date=new Date(value);if(is.NaN(date.valueOf()))throw new TypeError(`${label} is invalid.`);return date.toISOString();}
|
|
4
7
|
|
|
5
8
|
export class WeatherLocation{
|
|
6
9
|
constructor({id='',name,region='',country='',latitude,longitude,timezone='auto'}={}){this.id=String(id||`${latitude},${longitude}`);this.name=text(name,'Weather location name');this.region=String(region||'');this.country=String(country||'');this.latitude=number(latitude,'Latitude',{minimum:-90,maximum:90});this.longitude=number(longitude,'Longitude',{minimum:-180,maximum:180});this.timezone=String(timezone||'auto');}
|