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,3 +1,6 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
const APPLICATION_ID_PATTERN=/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
2
5
|
const APPLICATION_ID_MAX_LENGTH=64;
|
|
3
6
|
const APP_DATA_DIRECTORY='apps';
|
|
@@ -18,7 +21,7 @@ function scopeError(code,message){
|
|
|
18
21
|
* @returns {string}
|
|
19
22
|
*/
|
|
20
23
|
export function canonicalApplicationId(value,label='applicationId'){
|
|
21
|
-
if(
|
|
24
|
+
if(!is.string(value)
|
|
22
25
|
||value.length<1
|
|
23
26
|
||value.length>APPLICATION_ID_MAX_LENGTH
|
|
24
27
|
||!APPLICATION_ID_PATTERN.test(value)){
|
|
@@ -112,13 +115,13 @@ export function resolveApplicationLocalStorageKey(logicalKey='',options={}){
|
|
|
112
115
|
}
|
|
113
116
|
|
|
114
117
|
async function nativeApplicationId(arcane=globalThis.Arcane){
|
|
115
|
-
if(
|
|
118
|
+
if(!is.function(arcane?.app?.current)){
|
|
116
119
|
return null;
|
|
117
120
|
}
|
|
118
121
|
|
|
119
122
|
const descriptor=await arcane.app.current();
|
|
120
123
|
|
|
121
|
-
if(!descriptor
|
|
124
|
+
if(!descriptor||!is.object(descriptor)){
|
|
122
125
|
throw scopeError(
|
|
123
126
|
'APP_DATA_SCOPE_INVALID',
|
|
124
127
|
'Arcane returned an invalid bound application descriptor.'
|
|
@@ -200,7 +203,7 @@ export async function openApplicationDataDirectory({
|
|
|
200
203
|
arcane=globalThis.Arcane,
|
|
201
204
|
create=true
|
|
202
205
|
}={}){
|
|
203
|
-
if(!storage
|
|
206
|
+
if(!storage||!is.function(storage.getDirectory)){
|
|
204
207
|
throw scopeError(
|
|
205
208
|
'APP_DATA_STORAGE_UNAVAILABLE',
|
|
206
209
|
'Origin Private File System storage is unavailable in this browser.'
|
|
@@ -214,7 +217,7 @@ export async function openApplicationDataDirectory({
|
|
|
214
217
|
});
|
|
215
218
|
const root=await storage.getDirectory();
|
|
216
219
|
|
|
217
|
-
if(!root
|
|
220
|
+
if(!root||!is.function(root.getDirectoryHandle)){
|
|
218
221
|
throw scopeError(
|
|
219
222
|
'APP_DATA_STORAGE_UNAVAILABLE',
|
|
220
223
|
'Arcane could not open the Origin Private File System root.'
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
import CommunicationMessage from '../entities/CommunicationMessage.js';
|
|
2
5
|
import CommunicationThread from '../entities/CommunicationThread.js';
|
|
3
6
|
|
|
@@ -8,7 +11,7 @@ export default class ArcaneCommunicationBridge{
|
|
|
8
11
|
this.id=String(id||'arcane-bridge');this.label=String(label||'Arcane communications bridge');this.channels=Array.from(channels||['other']);this.endpoint=cleanEndpoint(endpoint);this.fetchImpl=fetchImpl;
|
|
9
12
|
}
|
|
10
13
|
async request(path,options={}){
|
|
11
|
-
if(
|
|
14
|
+
if(!is.function(this.fetchImpl)) throw new Error('Network access is unavailable.');
|
|
12
15
|
const response=await this.fetchImpl(`${this.endpoint}${path}`,{...options,headers:{Accept:'application/json',...(options.body?{'Content-Type':'application/json'}:{}),...options.headers}});
|
|
13
16
|
const text=await response.text();let body={};if(text){try{body=JSON.parse(text);}catch{throw new Error('The communications bridge returned invalid JSON.');}}
|
|
14
17
|
if(!response.ok) throw new Error(body.error||`Communications bridge request failed (${response.status}).`);
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
import * as ArcaneNetworkPolicy from './ArcaneNetworkPolicy.js?v=3';
|
|
2
5
|
|
|
3
6
|
function completeText(value,fallback){
|
|
4
|
-
return
|
|
7
|
+
return is.string(value)&&value.length>0?value:fallback;
|
|
5
8
|
}
|
|
6
9
|
|
|
7
10
|
function decisionId(){
|
|
8
|
-
if(
|
|
11
|
+
if(is.function(globalThis.crypto?.randomUUID))return globalThis.crypto.randomUUID();
|
|
9
12
|
return `navigation-${Date.now().toString(36)}-${Math.random().toString(36).replace(/^0\./u,'')}`;
|
|
10
13
|
}
|
|
11
14
|
|
|
@@ -83,7 +86,7 @@ function unavailableDecision(target,error){
|
|
|
83
86
|
}
|
|
84
87
|
|
|
85
88
|
function requireNetworkMatcher(networkMatcher){
|
|
86
|
-
if(
|
|
89
|
+
if(is.function(networkMatcher))return networkMatcher;
|
|
87
90
|
const error=new Error('Arcane cannot evaluate literal-IP navigation because the canonical network matcher is unavailable.');
|
|
88
91
|
error.code='ARCANE_NETWORK_POLICY_MATCHER_UNAVAILABLE';
|
|
89
92
|
throw error;
|
|
@@ -95,7 +98,7 @@ export function createArcaneNavigationGuard({
|
|
|
95
98
|
onDecision=null,
|
|
96
99
|
networkMatcher=ArcaneNetworkPolicy.findDeniedNetworkRule
|
|
97
100
|
}={}){
|
|
98
|
-
if(
|
|
101
|
+
if(!is.boolean(secure))throw new TypeError('secure must be a boolean.');
|
|
99
102
|
return async function guardArcaneNavigation(value,context={}){
|
|
100
103
|
const target=destinationContext(value);
|
|
101
104
|
let decision;
|
|
@@ -140,7 +143,7 @@ export function createArcaneNavigationGuard({
|
|
|
140
143
|
}catch(error){
|
|
141
144
|
decision=unavailableDecision(target,error);
|
|
142
145
|
}
|
|
143
|
-
if(
|
|
146
|
+
if(is.function(onDecision)){
|
|
144
147
|
await onDecision(decision,{intent:completeText(context.intent,'embedded')});
|
|
145
148
|
}
|
|
146
149
|
return decision;
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
export const ARCANE_NETWORK_POLICY_SCHEMA_VERSION=1;
|
|
2
5
|
export const ARCANE_NETWORK_POLICY_URL=new URL('../security/arcane-network-policy.json',import.meta.url);
|
|
3
6
|
|
|
@@ -24,11 +27,11 @@ let defaultPolicyPromise=null;
|
|
|
24
27
|
const DEFAULT_POLICY_LOAD_TIMEOUT_MS=5000;
|
|
25
28
|
|
|
26
29
|
function fail(message){const error=new TypeError(`ARCANE_NETWORK_POLICY_INVALID: ${message}`);error.code='ARCANE_NETWORK_POLICY_INVALID';throw error;}
|
|
27
|
-
function isRecord(value){return Boolean(value)&&
|
|
30
|
+
function isRecord(value){return Boolean(value)&&is.object(value)&&!is.array(value);}
|
|
28
31
|
function assertOnlyKeys(value,allowed,label){for(const key of Object.keys(value)){if(!allowed.has(key))fail(`${label} contains unsupported field ${key}.`);}}
|
|
29
32
|
function boundedText(value,label,maximum,{nullable=false}={}){
|
|
30
33
|
if(nullable&&value===null)return null;
|
|
31
|
-
if(
|
|
34
|
+
if(!is.string(value)||value!==value.trim()||value.length<1||value.length>maximum||/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/.test(value))fail(`${label} must be bounded plain text.`);
|
|
32
35
|
return value;
|
|
33
36
|
}
|
|
34
37
|
function identifier(value,label){const text=boundedText(value,label,80);if(!/^[a-z0-9]+(?:[._-][a-z0-9]+)*$/.test(text))fail(`${label} must be a lowercase identifier.`);return text;}
|
|
@@ -43,7 +46,7 @@ function normalizeSource(value,label){
|
|
|
43
46
|
function isIpLiteral(value){return /^\d+(?:\.\d+){3}$/.test(value)||value.includes(':');}
|
|
44
47
|
|
|
45
48
|
export function canonicalNetworkHostname(value){
|
|
46
|
-
if(
|
|
49
|
+
if(!is.string(value))return null;
|
|
47
50
|
const raw=value.trim(),candidate=raw.endsWith('.')?raw.slice(0,-1):raw;
|
|
48
51
|
if(!candidate||candidate.endsWith('.')||candidate.includes('/')||candidate.includes('@')||candidate.includes(':'))return null;
|
|
49
52
|
try{
|
|
@@ -66,7 +69,7 @@ function normalizeDomainRule(value,index,ids){
|
|
|
66
69
|
function parseIpv4(value){
|
|
67
70
|
const parts=value.split('.');if(parts.length!==4)return null;
|
|
68
71
|
const octets=parts.map(part=>/^(?:0|[1-9]\d{0,2})$/.test(part)?Number(part):NaN);
|
|
69
|
-
if(octets.some(part=>!
|
|
72
|
+
if(octets.some(part=>!is.integer(part)||part<0||part>255))return null;
|
|
70
73
|
return {numeric:octets.reduce((total,part)=>((total<<8)>>>0)+part,0)>>>0,text:octets.join('.')};
|
|
71
74
|
}
|
|
72
75
|
function parseIpv6(value){
|
|
@@ -92,7 +95,7 @@ function formatIpv6(numeric){
|
|
|
92
95
|
return new URL(`http://[${groups.join(':')}]/`).hostname.slice(1,-1);
|
|
93
96
|
}
|
|
94
97
|
function normalizeCidr(value,label){
|
|
95
|
-
if(
|
|
98
|
+
if(!is.string(value)||value!==value.trim()||value.split('/').length!==2)fail(`${label} must be a canonical IPv4 or IPv6 CIDR.`);
|
|
96
99
|
const [address,prefixText]=value.split('/');if(!/^(?:0|[1-9]\d{0,2})$/.test(prefixText))fail(`${label} has an invalid prefix.`);
|
|
97
100
|
const prefix=Number(prefixText),ipv4=parseIpv4(address);
|
|
98
101
|
if(ipv4){if(prefix>32)fail(`${label} has an invalid IPv4 prefix.`);const mask=prefix===0?0:(0xffffffff<<(32-prefix))>>>0;const network=(ipv4.numeric&mask)>>>0;const normalized=`${[(network>>>24)&255,(network>>>16)&255,(network>>>8)&255,network&255].join('.')}/${prefix}`;if(normalized!==value)fail(`${label} must use its canonical network address.`);return normalized;}
|
|
@@ -102,11 +105,11 @@ function normalizeCidr(value,label){
|
|
|
102
105
|
}
|
|
103
106
|
function normalizePortRanges(value,label){
|
|
104
107
|
if(value===undefined)return Object.freeze([]);
|
|
105
|
-
if(!
|
|
108
|
+
if(!is.array(value)||value.length>64)fail(`${label} must be a bounded array.`);
|
|
106
109
|
let previous=0;
|
|
107
110
|
return Object.freeze(value.map((range,index)=>{
|
|
108
111
|
const itemLabel=`${label}[${index}]`;if(!isRecord(range))fail(`${itemLabel} must be an object.`);assertOnlyKeys(range,PORT_KEYS,itemLabel);
|
|
109
|
-
const from=range.from,to=range.to;if(!
|
|
112
|
+
const from=range.from,to=range.to;if(!is.integer(from)||!is.integer(to)||from<1||to>65535||from>to||from<=previous)fail(`${itemLabel} must be a sorted, non-overlapping port range.`);previous=to;
|
|
110
113
|
return Object.freeze({from,to});
|
|
111
114
|
}));
|
|
112
115
|
}
|
|
@@ -160,8 +163,8 @@ indexNormalizedPolicy(EMPTY_POLICY);
|
|
|
160
163
|
export function validateArcaneNetworkPolicy(value){
|
|
161
164
|
if(!isRecord(value))fail('policy must be an object.');assertOnlyKeys(value,POLICY_KEYS,'policy');
|
|
162
165
|
if(value.schemaVersion!==ARCANE_NETWORK_POLICY_SCHEMA_VERSION)fail('schemaVersion is unsupported.');
|
|
163
|
-
if(!
|
|
164
|
-
if(!
|
|
166
|
+
if(!is.safeInteger(value.generation)||value.generation<1)fail('generation must be a positive safe integer.');
|
|
167
|
+
if(!is.array(value.domainRules)||!is.array(value.networkRules)||value.domainRules.length>50_000||value.networkRules.length>50_000)fail('rule collections must be bounded arrays.');
|
|
165
168
|
const ids=new Set(),domainRules=value.domainRules.map((rule,index)=>normalizeDomainRule(rule,index,ids)),networkRules=value.networkRules.map((rule,index)=>normalizeNetworkRule(rule,index,ids));
|
|
166
169
|
const normalized=Object.freeze({schemaVersion:value.schemaVersion,generation:value.generation,domainRules:Object.freeze(domainRules),networkRules:Object.freeze(networkRules)});
|
|
167
170
|
NORMALIZED_POLICIES.add(normalized);
|
|
@@ -170,7 +173,7 @@ export function validateArcaneNetworkPolicy(value){
|
|
|
170
173
|
}
|
|
171
174
|
|
|
172
175
|
function hostnameFromInput(value){
|
|
173
|
-
if(
|
|
176
|
+
if(!is.string(value))return null;
|
|
174
177
|
try{if(/^[a-z][a-z0-9+.-]*:\/\//i.test(value))return canonicalNetworkHostname(new URL(value).hostname);}
|
|
175
178
|
catch{return null;}
|
|
176
179
|
return canonicalNetworkHostname(value);
|
|
@@ -192,12 +195,12 @@ function normalizeNetworkMatchContext(value){
|
|
|
192
195
|
for(const key of Object.keys(value))if(!NETWORK_MATCH_CONTEXT_KEYS.has(key))networkQueryFail(`context contains unsupported field ${key}.`);
|
|
193
196
|
const protocol=value.protocol;
|
|
194
197
|
if(protocol!==undefined&&!NETWORK_MATCH_PROTOCOLS.has(protocol))networkQueryFail('context.protocol must be tcp, udp, icmp, or icmpv6.');
|
|
195
|
-
for(const key of ['localPort','remotePort'])if(value[key]!==undefined&&(!
|
|
198
|
+
for(const key of ['localPort','remotePort'])if(value[key]!==undefined&&(!is.integer(value[key])||value[key]<1||value[key]>65535))networkQueryFail(`context.${key} must be an integer from 1 through 65535.`);
|
|
196
199
|
if((value.localPort!==undefined||value.remotePort!==undefined)&&protocol!==undefined&&protocol!=='tcp'&&protocol!=='udp')networkQueryFail('port match context requires protocol tcp or udp when protocol is supplied.');
|
|
197
200
|
return {protocol,localPort:value.localPort,remotePort:value.remotePort};
|
|
198
201
|
}
|
|
199
202
|
function parsedIpLiteral(value){
|
|
200
|
-
if(
|
|
203
|
+
if(!is.string(value))return null;
|
|
201
204
|
const trimmed=value.trim(),candidate=trimmed.startsWith('[')&&trimmed.endsWith(']')?trimmed.slice(1,-1):trimmed,ipv4=parseIpv4(candidate);if(ipv4)return {family:4,numeric:ipv4.numeric};
|
|
202
205
|
const ipv6=parseIpv6(candidate.toLowerCase());return ipv6?{family:6,numeric:ipv6.numeric}:null;
|
|
203
206
|
}
|
|
@@ -238,10 +241,10 @@ export function invalidateArcaneNetworkPolicyCache(){defaultPolicyPromise=null;}
|
|
|
238
241
|
|
|
239
242
|
export async function loadArcaneNetworkPolicy({url=ARCANE_NETWORK_POLICY_URL,fetchImpl=globalThis.fetch,refresh=false,timeoutMs=DEFAULT_POLICY_LOAD_TIMEOUT_MS}={}){
|
|
240
243
|
const isDefault=String(url)===String(ARCANE_NETWORK_POLICY_URL)&&fetchImpl===globalThis.fetch;
|
|
241
|
-
if(
|
|
242
|
-
if(!
|
|
244
|
+
if(!is.boolean(refresh))networkQueryFail('refresh must be a boolean.');
|
|
245
|
+
if(!is.safeInteger(timeoutMs)||timeoutMs<1||timeoutMs>60000)networkQueryFail('timeoutMs must be an integer from 1 through 60000.');
|
|
243
246
|
if(isDefault&&!refresh&&defaultPolicyPromise)return defaultPolicyPromise;
|
|
244
|
-
if(
|
|
247
|
+
if(!is.function(fetchImpl))fail('a fetch implementation is required to load policy.');
|
|
245
248
|
const load=(async()=>{
|
|
246
249
|
const controller=new AbortController();let timeoutId;
|
|
247
250
|
const operation=(async()=>{const response=await fetchImpl(url,{cache:'no-store',credentials:'same-origin',signal:controller.signal});if(!response?.ok)throw new Error(`ARCANE_NETWORK_POLICY_LOAD_FAILED: ${response?.status||'unavailable'}`);return validateArcaneNetworkPolicy(await response.json());})();
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
const DEFAULT_TIMEOUT_MS=10000;
|
|
2
5
|
const MAX_TIMEOUT_MS=300000;
|
|
3
6
|
|
|
@@ -11,17 +14,17 @@ function invalid(message,ErrorType=TypeError){
|
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
function isPlainRecord(value){
|
|
14
|
-
if(!value
|
|
17
|
+
if(!value||!is.object(value)||is.array(value)) return false;
|
|
15
18
|
const prototype=Object.getPrototypeOf(value);
|
|
16
19
|
return prototype===Object.prototype||prototype===null;
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
function isAbortSignal(value){
|
|
20
23
|
return Boolean(value)
|
|
21
|
-
&&
|
|
22
|
-
&&
|
|
23
|
-
&&
|
|
24
|
-
&&
|
|
24
|
+
&&is.object(value)
|
|
25
|
+
&&is.boolean(value.aborted)
|
|
26
|
+
&&is.function(value.addEventListener)
|
|
27
|
+
&&is.function(value.removeEventListener);
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
function normalizeOptions(value){
|
|
@@ -30,7 +33,7 @@ function normalizeOptions(value){
|
|
|
30
33
|
if(unknown) throw invalid(`Async boundary options contain an unsupported field: ${unknown}.`);
|
|
31
34
|
|
|
32
35
|
const timeoutMs=value.timeoutMs??DEFAULT_TIMEOUT_MS;
|
|
33
|
-
if(!
|
|
36
|
+
if(!is.safeInteger(timeoutMs)||timeoutMs<1||timeoutMs>MAX_TIMEOUT_MS){
|
|
34
37
|
throw invalid(`timeoutMs must be an integer from 1 through ${MAX_TIMEOUT_MS}.`,RangeError);
|
|
35
38
|
}
|
|
36
39
|
|
|
@@ -43,13 +46,13 @@ function normalizeOptions(value){
|
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
function normalizeOperation(value){
|
|
46
|
-
if(
|
|
49
|
+
if(is.function(value)){
|
|
47
50
|
return Object.freeze({kind:'function',value});
|
|
48
51
|
}
|
|
49
|
-
if(!value||(
|
|
52
|
+
if(!value||(!is.object(value)&&!is.function(value))){
|
|
50
53
|
throw coded(new TypeError('The asynchronous operation must be a promise or function.'),'ASYNC_BOUNDARY_INVALID_OPERATION');
|
|
51
54
|
}
|
|
52
|
-
if(
|
|
55
|
+
if(!is.function(value.then)){
|
|
53
56
|
throw coded(new TypeError('The asynchronous operation must be a promise or function.'),'ASYNC_BOUNDARY_INVALID_OPERATION');
|
|
54
57
|
}
|
|
55
58
|
return Object.freeze({kind:'promise',value});
|
|
@@ -101,7 +104,7 @@ export function runAsyncBoundary(operation,options={}){
|
|
|
101
104
|
try{
|
|
102
105
|
settings=normalizeOptions(options);
|
|
103
106
|
descriptor=normalizeOperation(operation);
|
|
104
|
-
if(
|
|
107
|
+
if(!is.function(globalThis.AbortController)){
|
|
105
108
|
throw coded(new Error('AbortController is unavailable in this environment.'),'ASYNC_BOUNDARY_UNAVAILABLE');
|
|
106
109
|
}
|
|
107
110
|
}catch(error){
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
import {createArcaneEventSource} from 'arcane-os/event-manager';
|
|
2
5
|
|
|
3
6
|
const RESULT_STATUSES=new Set(['fail','pass','skip']);
|
|
@@ -41,8 +44,8 @@ export const BROWSER_TEST_SUITE_REASONS={
|
|
|
41
44
|
|
|
42
45
|
function isPlainRecord(value){
|
|
43
46
|
return Boolean(value)
|
|
44
|
-
&&
|
|
45
|
-
&&!
|
|
47
|
+
&&is.object(value)
|
|
48
|
+
&&!is.array(value)
|
|
46
49
|
&&Object.getPrototypeOf(value)===Object.prototype;
|
|
47
50
|
}
|
|
48
51
|
|
|
@@ -56,7 +59,7 @@ function fail(message,code,ErrorType=TypeError){
|
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
function descriptorText(value,label){
|
|
59
|
-
if(
|
|
62
|
+
if(!is.string(value)) fail(`${label} must be a string.`,'BROWSER_TEST_INVALID_DESCRIPTOR');
|
|
60
63
|
if(!value.trim()) fail(`${label} cannot be empty.`,'BROWSER_TEST_INVALID_DESCRIPTOR');
|
|
61
64
|
return value;
|
|
62
65
|
}
|
|
@@ -68,14 +71,14 @@ function resultMessage(value,fallback){
|
|
|
68
71
|
}
|
|
69
72
|
|
|
70
73
|
function normalizeTests(value){
|
|
71
|
-
if(!
|
|
74
|
+
if(!is.array(value)) fail('tests must be an array.','BROWSER_TEST_INVALID_OPTIONS');
|
|
72
75
|
const seen=new Set();
|
|
73
76
|
return value.map((item,index)=>{
|
|
74
77
|
if(!isPlainRecord(item)) fail(`Test descriptor ${index+1} must be a plain object.`,'BROWSER_TEST_INVALID_DESCRIPTOR');
|
|
75
78
|
const id=descriptorText(item.id,`Test descriptor ${index+1} id`);
|
|
76
79
|
if(seen.has(id)) fail(`Test descriptors contain a duplicate id: ${id}.`,'BROWSER_TEST_CASE_COLLISION');
|
|
77
80
|
seen.add(id);
|
|
78
|
-
if(
|
|
81
|
+
if(!is.function(item.run)) fail(`Test descriptor ${index+1} run must be a function.`,'BROWSER_TEST_INVALID_DESCRIPTOR');
|
|
79
82
|
return {
|
|
80
83
|
...item,
|
|
81
84
|
id,
|
|
@@ -92,7 +95,7 @@ function defaultNow(){
|
|
|
92
95
|
function normalizeOptions(input){
|
|
93
96
|
if(!isPlainRecord(input)) fail('Browser test suite options must be a plain object.','BROWSER_TEST_INVALID_OPTIONS');
|
|
94
97
|
const now=input.now??defaultNow;
|
|
95
|
-
if(
|
|
98
|
+
if(!is.function(now)) fail('now must be a function.','BROWSER_TEST_INVALID_OPTIONS');
|
|
96
99
|
return {
|
|
97
100
|
now,
|
|
98
101
|
tests:normalizeTests(input.tests??[]),
|
|
@@ -103,23 +106,23 @@ function normalizeRunOptions(input){
|
|
|
103
106
|
if(!isPlainRecord(input)) fail('Test run options must be a plain object.','BROWSER_TEST_INVALID_OPTIONS');
|
|
104
107
|
const signal=input.signal??null;
|
|
105
108
|
if(signal!==null&&(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
109
|
+
!is.object(signal)
|
|
110
|
+
||!is.boolean(signal.aborted)
|
|
111
|
+
||!is.function(signal.addEventListener)
|
|
112
|
+
||!is.function(signal.removeEventListener)
|
|
110
113
|
)) fail('signal must be an AbortSignal.','BROWSER_TEST_INVALID_OPTIONS');
|
|
111
114
|
return {context:input.context,signal};
|
|
112
115
|
}
|
|
113
116
|
|
|
114
117
|
function elapsed(now,start){
|
|
115
118
|
const end=Number(now());
|
|
116
|
-
if(!
|
|
119
|
+
if(!is.finite(end)) fail('now() must return a finite number.','BROWSER_TEST_INVALID_CLOCK');
|
|
117
120
|
return Math.max(0,end-start);
|
|
118
121
|
}
|
|
119
122
|
|
|
120
123
|
function startTime(now){
|
|
121
124
|
const value=Number(now());
|
|
122
|
-
if(!
|
|
125
|
+
if(!is.finite(value)) fail('now() must return a finite number.','BROWSER_TEST_INVALID_CLOCK');
|
|
123
126
|
return value;
|
|
124
127
|
}
|
|
125
128
|
|
|
@@ -195,7 +198,7 @@ function outcomeFromError(error){
|
|
|
195
198
|
return {
|
|
196
199
|
status:'fail',
|
|
197
200
|
message:resultMessage(error?.message,'The check failed.'),
|
|
198
|
-
code:
|
|
201
|
+
code:is.string(error?.code)?error.code:'BROWSER_TEST_ERROR',
|
|
199
202
|
errorName:resultMessage(error?.name,'Error'),
|
|
200
203
|
error
|
|
201
204
|
};
|
|
@@ -238,9 +241,9 @@ function browserTestResultReason(result){
|
|
|
238
241
|
function browserTestPublicDetail(type,detail){
|
|
239
242
|
const result=detail?.result;
|
|
240
243
|
const test=detail?.test;
|
|
241
|
-
const testCount=
|
|
244
|
+
const testCount=is.safeInteger(detail?.total)
|
|
242
245
|
?detail.total
|
|
243
|
-
:
|
|
246
|
+
:is.safeInteger(detail?.totals?.total)
|
|
244
247
|
?detail.totals.total
|
|
245
248
|
:null;
|
|
246
249
|
if(type===BROWSER_TEST_SUITE_EVENT_TYPES.runStarted){
|
|
@@ -267,7 +270,7 @@ function browserTestPublicDetail(type,detail){
|
|
|
267
270
|
testIndex:detail.index,
|
|
268
271
|
testCount,
|
|
269
272
|
status:result.status,
|
|
270
|
-
...(
|
|
273
|
+
...(is.string(result.code)?{code:result.code}:{})
|
|
271
274
|
};
|
|
272
275
|
}
|
|
273
276
|
const reason=detail.status==='aborted'
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
import {createArcaneEventSource} from 'arcane-os/event-manager';
|
|
2
5
|
import Calculation from '../entities/Calculation.js';
|
|
3
6
|
|
|
@@ -19,7 +22,7 @@ function calculatorErrorCode(error){
|
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
function attachCalculatorErrorCode(error,code){
|
|
22
|
-
if(!error||(
|
|
25
|
+
if(!error||(!is.object(error)&&!is.function(error)))return error;
|
|
23
26
|
try{Object.defineProperty(error,'code',{configurable:true,enumerable:false,value:code,writable:true});}
|
|
24
27
|
catch{try{error.code=code;}catch{}}
|
|
25
28
|
return error;
|
|
@@ -42,7 +45,7 @@ export function evaluateExpression(input){const tokens=tokenize(input);let curso
|
|
|
42
45
|
function power(){let value=unary();if(peek().type==='^'){cursor++;value=Math.pow(value,power())}return value}
|
|
43
46
|
function product(){let value=power();while(['*','/','%'].includes(peek().type)){const operator=tokens[cursor++].type,right=power();if((operator==='/'||operator==='%')&&right===0)throw new RangeError('Division by zero is undefined.');value=operator==='*'?value*right:operator==='/'?value/right:value%right}return value}
|
|
44
47
|
function expression(){let value=product();while(['+','-'].includes(peek().type)){const operator=tokens[cursor++].type,right=product();value=operator==='+'?value+right:value-right}return value}
|
|
45
|
-
const result=expression();if(peek().type!=='end')throw new SyntaxError('Unexpected content after the expression.');if(!
|
|
48
|
+
const result=expression();if(peek().type!=='end')throw new SyntaxError('Unexpected content after the expression.');if(!is.finite(result))throw new RangeError('The calculation did not produce a finite result.');return result;
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
export default class CalculatorEngine{
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
function hasUserEntry(chat=[]){
|
|
2
|
-
const messages=
|
|
5
|
+
const messages=is.array(chat)
|
|
3
6
|
?chat
|
|
4
7
|
:chat?.messages||[];
|
|
5
8
|
|
|
@@ -9,20 +12,20 @@ function hasUserEntry(chat=[]){
|
|
|
9
12
|
}
|
|
10
13
|
|
|
11
14
|
function hasConversationEntry(chat=[]){
|
|
12
|
-
const messages=
|
|
15
|
+
const messages=is.array(chat)
|
|
13
16
|
?chat
|
|
14
17
|
:chat?.messages||[];
|
|
15
18
|
|
|
16
19
|
return hasUserEntry(messages)||messages.some(
|
|
17
20
|
message=>
|
|
18
21
|
message?.role==='assistant'
|
|
19
|
-
&&
|
|
22
|
+
&&is.string(message.content)
|
|
20
23
|
&&Boolean(message.content.trim())
|
|
21
24
|
);
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
function plainRecord(value){
|
|
25
|
-
if(!value
|
|
28
|
+
if(!value||!is.object(value)||is.array(value))return false;
|
|
26
29
|
const prototype=Object.getPrototypeOf(value);
|
|
27
30
|
return prototype===Object.prototype||prototype===null;
|
|
28
31
|
}
|
|
@@ -33,9 +36,9 @@ function coded(error,code){
|
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
function copyCompleteValue(value,seen=new Map()){
|
|
36
|
-
if(value===null
|
|
39
|
+
if(value===null||!is.object(value))return value;
|
|
37
40
|
if(seen.has(value))return seen.get(value);
|
|
38
|
-
if(
|
|
41
|
+
if(is.array(value)){
|
|
39
42
|
const result=[];
|
|
40
43
|
seen.set(value,result);
|
|
41
44
|
for(const item of value)result.push(copyCompleteValue(item,seen));
|
|
@@ -72,7 +75,7 @@ function providerMessageCopy(message){
|
|
|
72
75
|
|
|
73
76
|
function structuralToolMessage(call,label){
|
|
74
77
|
const argumentValue=call?.function?.arguments;
|
|
75
|
-
if(
|
|
78
|
+
if(!is.string(argumentValue)){
|
|
76
79
|
throw coded(
|
|
77
80
|
new TypeError(`${label}.function.arguments must be a JSON string.`),
|
|
78
81
|
'AI_CHAT_INVALID_TOOL_CALL'
|
|
@@ -93,7 +96,7 @@ function structuralToolMessage(call,label){
|
|
|
93
96
|
'AI_CHAT_INVALID_TOOL_CALL'
|
|
94
97
|
);
|
|
95
98
|
}
|
|
96
|
-
if(
|
|
99
|
+
if(!is.string(argumentsRecord.message)||!argumentsRecord.message.trim()){
|
|
97
100
|
throw coded(
|
|
98
101
|
new TypeError(`${label}.function.arguments.message must contain user-facing text.`),
|
|
99
102
|
'AI_CHAT_TOOL_MESSAGE_REQUIRED'
|
|
@@ -109,14 +112,14 @@ function activeToolProtocolState(messages){
|
|
|
109
112
|
const message=messages[index];
|
|
110
113
|
if(message?.role==='assistant'){
|
|
111
114
|
if(start>=0&&!pending.size)start=-1;
|
|
112
|
-
if(
|
|
115
|
+
if(is.array(message.tool_calls)){
|
|
113
116
|
if(!pending.size&&message.tool_calls.length)start=index;
|
|
114
117
|
for(const call of message.tool_calls){
|
|
115
|
-
if(
|
|
118
|
+
if(is.string(call?.id)&&call.id)pending.add(call.id);
|
|
116
119
|
}
|
|
117
120
|
}
|
|
118
121
|
}
|
|
119
|
-
if(message?.role==='tool'&&
|
|
122
|
+
if(message?.role==='tool'&&is.string(message.tool_call_id)){
|
|
120
123
|
pending.delete(message.tool_call_id);
|
|
121
124
|
}
|
|
122
125
|
}
|
|
@@ -136,7 +139,7 @@ function appendOrdinaryMessages(result,message,index){
|
|
|
136
139
|
result.push({role:'assistant',content:String(message.content)});
|
|
137
140
|
}
|
|
138
141
|
if(message.tool_calls===undefined)return;
|
|
139
|
-
if(!
|
|
142
|
+
if(!is.array(message.tool_calls)){
|
|
140
143
|
throw coded(
|
|
141
144
|
new TypeError(`Chat message ${index+1}.tool_calls must be an array.`),
|
|
142
145
|
'AI_CHAT_INVALID_TOOL_CALL'
|
|
@@ -154,10 +157,10 @@ function appendOrdinaryMessages(result,message,index){
|
|
|
154
157
|
return;
|
|
155
158
|
}
|
|
156
159
|
if(message.role!=='tool')return;
|
|
157
|
-
const content=
|
|
160
|
+
const content=is.string(message.tool_call_id)
|
|
158
161
|
?message.persistence_message??message.message
|
|
159
162
|
:message.content;
|
|
160
|
-
if(
|
|
163
|
+
if(is.string(content)&&content.trim()){
|
|
161
164
|
result.push({role:'assistant',content});
|
|
162
165
|
}
|
|
163
166
|
}
|
|
@@ -168,7 +171,7 @@ function appendOrdinaryMessages(result,message,index){
|
|
|
168
171
|
* Settled exchanges become complete ordinary visible conversation messages.
|
|
169
172
|
*/
|
|
170
173
|
function recurringChatMessages(chat=[],{settleCompleteToolTail=false}={}){
|
|
171
|
-
const messages=
|
|
174
|
+
const messages=is.array(chat)
|
|
172
175
|
?chat
|
|
173
176
|
:chat?.messages||[];
|
|
174
177
|
const active=activeToolProtocolState(messages);
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
import { arcaneLogging } from 'arcane-os/logging';
|
|
2
5
|
import ArcaneCommunicationBridge from './ArcaneCommunicationBridge.js';
|
|
3
6
|
import CommunicationHub from './CommunicationHub.js?v=2';
|
|
@@ -89,13 +92,13 @@ export default class CommunicationAppController{
|
|
|
89
92
|
this.services=services;
|
|
90
93
|
this.channels=channels;
|
|
91
94
|
this.labels=labels;
|
|
92
|
-
this.inspectMessage=
|
|
95
|
+
this.inspectMessage=is.function(inspectMessage)
|
|
93
96
|
?inspectMessage
|
|
94
97
|
:null;
|
|
95
|
-
this.prepareMessageInspection=
|
|
98
|
+
this.prepareMessageInspection=is.function(prepareMessageInspection)
|
|
96
99
|
?prepareMessageInspection
|
|
97
100
|
:null;
|
|
98
|
-
this.onAdvisoryAction=
|
|
101
|
+
this.onAdvisoryAction=is.function(onAdvisoryAction)
|
|
99
102
|
?onAdvisoryAction
|
|
100
103
|
:null;
|
|
101
104
|
this.refreshVersion=0;
|
|
@@ -302,7 +305,7 @@ export default class CommunicationAppController{
|
|
|
302
305
|
);
|
|
303
306
|
const providers=enabled.map(
|
|
304
307
|
function createCommunicationProvider(item){
|
|
305
|
-
return
|
|
308
|
+
return is.function(item.providerFactory)
|
|
306
309
|
?item.providerFactory(
|
|
307
310
|
{
|
|
308
311
|
service:item,
|
|
@@ -355,9 +358,9 @@ export default class CommunicationAppController{
|
|
|
355
358
|
return;
|
|
356
359
|
}
|
|
357
360
|
try{
|
|
358
|
-
if(
|
|
361
|
+
if(is.function(hub.destroy)){
|
|
359
362
|
hub.destroy();
|
|
360
|
-
}else if(
|
|
363
|
+
}else if(is.function(hub.dispose)){
|
|
361
364
|
hub.dispose();
|
|
362
365
|
}
|
|
363
366
|
}catch(error){
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
import {createArcaneEventSource} from 'arcane-os/event-manager';
|
|
2
5
|
import CommunicationMessage from '../entities/CommunicationMessage.js';
|
|
3
6
|
import CommunicationThread from '../entities/CommunicationThread.js';
|
|
@@ -90,17 +93,17 @@ function refreshFailureError(error){
|
|
|
90
93
|
|
|
91
94
|
function isAbortSignal(value){
|
|
92
95
|
return Boolean(value)
|
|
93
|
-
&&
|
|
94
|
-
&&
|
|
95
|
-
&&
|
|
96
|
-
&&
|
|
96
|
+
&&is.object(value)
|
|
97
|
+
&&is.boolean(value.aborted)
|
|
98
|
+
&&is.function(value.addEventListener)
|
|
99
|
+
&&is.function(value.removeEventListener);
|
|
97
100
|
}
|
|
98
101
|
|
|
99
102
|
function normalizeRefreshOptions(value){
|
|
100
103
|
if(value===undefined){
|
|
101
104
|
return {signal:null};
|
|
102
105
|
}
|
|
103
|
-
if(!value
|
|
106
|
+
if(!value||!is.object(value)||is.array(value)){
|
|
104
107
|
throw codedError(
|
|
105
108
|
'Communication refresh options must be a plain record.',
|
|
106
109
|
communicationHubErrorCodes.refreshOptionsInvalid,
|
|
@@ -119,7 +122,7 @@ function normalizeRefreshOptions(value){
|
|
|
119
122
|
}
|
|
120
123
|
|
|
121
124
|
function normalizeProviderThreads(provider,values){
|
|
122
|
-
if(!
|
|
125
|
+
if(!is.array(values)){
|
|
123
126
|
throw new TypeError(`Communication provider ${provider.id} must return a thread array.`);
|
|
124
127
|
}
|
|
125
128
|
return values.map(function normalizeProviderThread(value){
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
import {resolveApplicationLocalStorageKey} from './AppDataScope.js';
|
|
2
5
|
|
|
3
6
|
function adapter(){
|
|
@@ -8,6 +11,6 @@ function adapter(){
|
|
|
8
11
|
|
|
9
12
|
export default class CommunicationPreferences{
|
|
10
13
|
constructor(namespace='communications'){this.key=`arcane.communications.${String(namespace)}`;this.adapter=adapter();}
|
|
11
|
-
async load(defaults={}){const result=await this.adapter.get(this.key);return result?.found&&result.value&&
|
|
14
|
+
async load(defaults={}){const result=await this.adapter.get(this.key);return result?.found&&result.value&&is.object(result.value)?{...defaults,...result.value}:{...defaults};}
|
|
12
15
|
async save(values={}){const safe=Object.fromEntries(Object.entries(values).map(([id,value])=>[id,{enabled:Boolean(value?.enabled),endpoint:String(value?.endpoint||''),accountLabel:String(value?.accountLabel||''),status:String(value?.status||'Disconnected')} ]));await this.adapter.set(this.key,safe);return safe;}
|
|
13
16
|
}
|