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
|
import { arcaneLogging } from 'arcane-os/logging';
|
|
2
5
|
export const MAIL_OUTBOX_PROTOCOL='arcane-mail-outbox/1';
|
|
3
6
|
export const MAIL_OUTBOX_TABLE='mail_outbox';
|
|
@@ -30,7 +33,7 @@ function coded(error,code){
|
|
|
30
33
|
|
|
31
34
|
function reportMailOutboxObserverError(error){
|
|
32
35
|
try{
|
|
33
|
-
if(
|
|
36
|
+
if(is.function(globalThis.reportError)){
|
|
34
37
|
globalThis.reportError(error);
|
|
35
38
|
return;
|
|
36
39
|
}
|
|
@@ -45,14 +48,14 @@ function fail(message,code='MAIL_OUTBOX_INVALID',ErrorType=Error,cause){
|
|
|
45
48
|
|
|
46
49
|
function isPlainRecord(value){
|
|
47
50
|
return Boolean(value)
|
|
48
|
-
&&
|
|
49
|
-
&&!
|
|
51
|
+
&&is.object(value)
|
|
52
|
+
&&!is.array(value)
|
|
50
53
|
&&Object.getPrototypeOf(value)===Object.prototype;
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
function timestamp(value,label,{nullable=false}={}){
|
|
54
57
|
if(nullable&&value===null) return null;
|
|
55
|
-
if(!
|
|
58
|
+
if(!is.safeInteger(value)||value<0){
|
|
56
59
|
fail(`${label} must be a nonnegative epoch-millisecond integer.`,'MAIL_OUTBOX_RECORD_INVALID');
|
|
57
60
|
}
|
|
58
61
|
return value;
|
|
@@ -61,31 +64,31 @@ function timestamp(value,label,{nullable=false}={}){
|
|
|
61
64
|
function clockMilliseconds(clock){
|
|
62
65
|
const value=clock();
|
|
63
66
|
const milliseconds=value instanceof Date?value.getTime():Number(value);
|
|
64
|
-
if(!
|
|
67
|
+
if(!is.safeInteger(milliseconds)||milliseconds<0){
|
|
65
68
|
fail('Mail outbox clock returned an invalid time.','MAIL_OUTBOX_CLOCK_INVALID');
|
|
66
69
|
}
|
|
67
70
|
return milliseconds;
|
|
68
71
|
}
|
|
69
72
|
|
|
70
73
|
function safeString(value,pattern){
|
|
71
|
-
return
|
|
74
|
+
return is.string(value)&&pattern.test(value)?value:null;
|
|
72
75
|
}
|
|
73
76
|
|
|
74
77
|
function safeStatusCode(value){
|
|
75
|
-
return
|
|
78
|
+
return is.safeInteger(value)&&value>=100&&value<=599?value:null;
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
function retryAfterSeconds(value){
|
|
79
82
|
if(value===undefined||value===null) return null;
|
|
80
83
|
const seconds=Number(value);
|
|
81
|
-
if(!
|
|
84
|
+
if(!is.safeInteger(seconds)||seconds<0){
|
|
82
85
|
return null;
|
|
83
86
|
}
|
|
84
87
|
return seconds;
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
function parseSerializedReport(value){
|
|
88
|
-
if(
|
|
91
|
+
if(!is.string(value)||!value){
|
|
89
92
|
fail('Mail outbox record contains no serialized report.','MAIL_OUTBOX_RECORD_INVALID');
|
|
90
93
|
}
|
|
91
94
|
let parsed;
|
|
@@ -115,7 +118,7 @@ function serializeReport(report){
|
|
|
115
118
|
}catch(error){
|
|
116
119
|
fail('Mail outbox report must be JSON serializable.','MAIL_OUTBOX_REPORT_INVALID',TypeError,error);
|
|
117
120
|
}
|
|
118
|
-
if(
|
|
121
|
+
if(!is.string(serialized)||!serialized){
|
|
119
122
|
fail('Mail outbox report must be JSON serializable.','MAIL_OUTBOX_REPORT_INVALID',TypeError);
|
|
120
123
|
}
|
|
121
124
|
return serialized;
|
|
@@ -124,13 +127,13 @@ function serializeReport(report){
|
|
|
124
127
|
function quarantineSnapshot(value){
|
|
125
128
|
try{
|
|
126
129
|
const serialized=JSON.stringify(value);
|
|
127
|
-
if(
|
|
130
|
+
if(is.string(serialized))return serialized;
|
|
128
131
|
}catch{}
|
|
129
132
|
return null;
|
|
130
133
|
}
|
|
131
134
|
|
|
132
135
|
function reportKey(value){
|
|
133
|
-
if(
|
|
136
|
+
if(!is.string(value)||!REPORT_KEY_PATTERN.test(value)){
|
|
134
137
|
fail(
|
|
135
138
|
'Mail outbox reportKey must contain safe characters.',
|
|
136
139
|
'MAIL_OUTBOX_REPORT_KEY_INVALID',
|
|
@@ -145,7 +148,7 @@ function recordFileName(value){
|
|
|
145
148
|
}
|
|
146
149
|
|
|
147
150
|
function reportKeyFromFileName(value){
|
|
148
|
-
if(
|
|
151
|
+
if(!is.string(value)||!value.endsWith(FILE_SUFFIX)){
|
|
149
152
|
fail('Mail outbox contains an invalid record filename.','MAIL_OUTBOX_RECORD_INVALID');
|
|
150
153
|
}
|
|
151
154
|
return reportKey(value.slice(0,-FILE_SUFFIX.length));
|
|
@@ -158,7 +161,7 @@ function invalidRecordMetadata(fileName,error){
|
|
|
158
161
|
repairable=true;
|
|
159
162
|
}catch{}
|
|
160
163
|
return {
|
|
161
|
-
fileName:
|
|
164
|
+
fileName:is.string(fileName)?fileName:'',
|
|
162
165
|
code:safeString(error?.code,SAFE_CODE_PATTERN)||'MAIL_OUTBOX_RECORD_UNREADABLE',
|
|
163
166
|
repairable
|
|
164
167
|
};
|
|
@@ -174,7 +177,7 @@ function normalizedFailure(value){
|
|
|
174
177
|
fail('Mail outbox failure record is invalid.','MAIL_OUTBOX_RECORD_INVALID');
|
|
175
178
|
}
|
|
176
179
|
const code=safeString(value.code,SAFE_CODE_PATTERN);
|
|
177
|
-
if(!code
|
|
180
|
+
if(!code||!is.boolean(value.retryable)||!is.boolean(value.uncertain)){
|
|
178
181
|
fail('Mail outbox failure record is invalid.','MAIL_OUTBOX_RECORD_INVALID');
|
|
179
182
|
}
|
|
180
183
|
return {
|
|
@@ -209,8 +212,8 @@ function normalizedResult(value,{invalidCode='MAIL_OUTBOX_RECORD_INVALID'}={}){
|
|
|
209
212
|
acceptanceAuthority,
|
|
210
213
|
requestId:safeString(value.requestId,SAFE_ID_PATTERN),
|
|
211
214
|
providerId:safeString(value.providerId,SAFE_ID_PATTERN),
|
|
212
|
-
providerStatus:
|
|
213
|
-
||
|
|
215
|
+
providerStatus:is.string(value.providerStatus)
|
|
216
|
+
||is.safeInteger(value.providerStatus)
|
|
214
217
|
?value.providerStatus
|
|
215
218
|
:null,
|
|
216
219
|
providerCode:safeString(value.providerCode,SAFE_CODE_PATTERN),
|
|
@@ -303,7 +306,7 @@ function normalizedRecord(value,expectedReportKey=null){
|
|
|
303
306
|
if(!STATE_SET.has(value.state)){
|
|
304
307
|
fail('Mail outbox record contains an invalid state.','MAIL_OUTBOX_RECORD_INVALID');
|
|
305
308
|
}
|
|
306
|
-
if(!
|
|
309
|
+
if(!is.safeInteger(value.attempts)||value.attempts<0){
|
|
307
310
|
fail('Mail outbox record contains an invalid attempt count.','MAIL_OUTBOX_RECORD_INVALID');
|
|
308
311
|
}
|
|
309
312
|
const record={
|
|
@@ -432,12 +435,12 @@ function deliveryFailure(error){
|
|
|
432
435
|
const milliseconds=Number(error?.retryAfterMs);
|
|
433
436
|
const seconds=retryAfterSeconds(
|
|
434
437
|
error?.retryAfterSeconds??(
|
|
435
|
-
|
|
438
|
+
is.finite(milliseconds)&&milliseconds>=0
|
|
436
439
|
?Math.ceil(milliseconds/1000)
|
|
437
440
|
:null
|
|
438
441
|
)
|
|
439
442
|
);
|
|
440
|
-
const errorRecord=error&&
|
|
443
|
+
const errorRecord=error&&is.object(error)&&!is.array(error)
|
|
441
444
|
?{...error}
|
|
442
445
|
:{};
|
|
443
446
|
return {
|
|
@@ -445,9 +448,9 @@ function deliveryFailure(error){
|
|
|
445
448
|
result:null,
|
|
446
449
|
failure:{
|
|
447
450
|
...errorRecord,
|
|
448
|
-
name:
|
|
449
|
-
message:
|
|
450
|
-
...(
|
|
451
|
+
name:is.string(error?.name)?error.name:'Error',
|
|
452
|
+
message:is.string(error?.message)?error.message:String(error??''),
|
|
453
|
+
...(is.string(error?.stack)?{stack:error.stack}:{}),
|
|
451
454
|
code:safeString(error?.code,SAFE_CODE_PATTERN)||'MAIL_DELIVERY_FAILED',
|
|
452
455
|
statusCode:safeStatusCode(error?.statusCode),
|
|
453
456
|
retryable,
|
|
@@ -484,10 +487,10 @@ function abortedDeliveryError(error,signal){
|
|
|
484
487
|
|
|
485
488
|
function validateSignal(signal){
|
|
486
489
|
if(signal===null||signal===undefined) return null;
|
|
487
|
-
if(
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
490
|
+
if(!is.object(signal)
|
|
491
|
+
||!is.boolean(signal.aborted)
|
|
492
|
+
||!is.function(signal.addEventListener)
|
|
493
|
+
||!is.function(signal.removeEventListener)){
|
|
491
494
|
fail('Mail outbox signal must be an AbortSignal.','MAIL_OUTBOX_INVALID',TypeError);
|
|
492
495
|
}
|
|
493
496
|
return signal;
|
|
@@ -522,7 +525,7 @@ function waitForOutboxOperation(operation,signal){
|
|
|
522
525
|
}
|
|
523
526
|
|
|
524
527
|
function drainReason(value){
|
|
525
|
-
if(
|
|
528
|
+
if(!is.string(value)||!/^[a-z][a-z0-9_-]*$/.test(value)){
|
|
526
529
|
fail('Mail outbox drain reason is invalid.','MAIL_OUTBOX_INVALID',TypeError);
|
|
527
530
|
}
|
|
528
531
|
return value;
|
|
@@ -583,16 +586,16 @@ class MailOutbox{
|
|
|
583
586
|
return globalThis.navigator?.onLine!==false;
|
|
584
587
|
},
|
|
585
588
|
lockManager=undefined,
|
|
586
|
-
onlineTarget=
|
|
589
|
+
onlineTarget=is.function(globalThis.addEventListener)?globalThis:null,
|
|
587
590
|
onRecordCommitted=null,
|
|
588
591
|
quarantineTable=QUARANTINE_TABLE,
|
|
589
592
|
table=MAIL_OUTBOX_TABLE
|
|
590
593
|
}={}){
|
|
591
|
-
if(!storage
|
|
594
|
+
if(!storage||!is.object(storage)){
|
|
592
595
|
fail('Mail outbox storage is required.','MAIL_OUTBOX_STORAGE_UNAVAILABLE',TypeError);
|
|
593
596
|
}
|
|
594
597
|
for(const method of ['get','set','getAllKeys']){
|
|
595
|
-
if(
|
|
598
|
+
if(!is.function(storage[method])){
|
|
596
599
|
fail(
|
|
597
600
|
`Mail outbox storage must provide ${method}().`,
|
|
598
601
|
'MAIL_OUTBOX_STORAGE_UNAVAILABLE',
|
|
@@ -603,36 +606,36 @@ class MailOutbox{
|
|
|
603
606
|
const resolvedLockManager=lockManager
|
|
604
607
|
??storage.lockManager
|
|
605
608
|
??globalThis.navigator?.locks;
|
|
606
|
-
if(!resolvedLockManager
|
|
609
|
+
if(!resolvedLockManager||!is.function(resolvedLockManager.request)){
|
|
607
610
|
fail(
|
|
608
611
|
'Mail outbox requires a Web Locks compatible lock manager.',
|
|
609
612
|
'MAIL_OUTBOX_LOCK_UNAVAILABLE',
|
|
610
613
|
TypeError
|
|
611
614
|
);
|
|
612
615
|
}
|
|
613
|
-
if(
|
|
616
|
+
if(!is.function(deliver)){
|
|
614
617
|
fail('Mail outbox deliver must be a function.','MAIL_OUTBOX_INVALID',TypeError);
|
|
615
618
|
}
|
|
616
|
-
if(
|
|
619
|
+
if(!is.function(clock)||!is.function(isOnline)){
|
|
617
620
|
fail('Mail outbox clock and isOnline hooks must be functions.','MAIL_OUTBOX_INVALID',TypeError);
|
|
618
621
|
}
|
|
619
622
|
if(onlineTarget!==null&&(
|
|
620
|
-
|
|
621
|
-
|
|
623
|
+
!is.function(onlineTarget?.addEventListener)
|
|
624
|
+
||!is.function(onlineTarget?.removeEventListener)
|
|
622
625
|
)){
|
|
623
626
|
fail('Mail outbox onlineTarget must be an EventTarget.','MAIL_OUTBOX_INVALID',TypeError);
|
|
624
627
|
}
|
|
625
|
-
if(onRecordCommitted!==null
|
|
628
|
+
if(onRecordCommitted!==null&&!is.function(onRecordCommitted)){
|
|
626
629
|
fail(
|
|
627
630
|
'Mail outbox onRecordCommitted must be a function.',
|
|
628
631
|
'MAIL_OUTBOX_INVALID',
|
|
629
632
|
TypeError
|
|
630
633
|
);
|
|
631
634
|
}
|
|
632
|
-
if(
|
|
635
|
+
if(!is.string(table)||!TABLE_PATTERN.test(table)){
|
|
633
636
|
fail('Mail outbox table name is invalid.','MAIL_OUTBOX_INVALID',TypeError);
|
|
634
637
|
}
|
|
635
|
-
if(
|
|
638
|
+
if(!is.string(quarantineTable)||!TABLE_PATTERN.test(quarantineTable)
|
|
636
639
|
||quarantineTable===table){
|
|
637
640
|
fail('Mail outbox quarantine table name is invalid.','MAIL_OUTBOX_INVALID',TypeError);
|
|
638
641
|
}
|
|
@@ -656,7 +659,7 @@ class MailOutbox{
|
|
|
656
659
|
|
|
657
660
|
async #ready(){
|
|
658
661
|
try{
|
|
659
|
-
if(this.#storage.readyPromise&&
|
|
662
|
+
if(this.#storage.readyPromise&&is.function(this.#storage.readyPromise.then)){
|
|
660
663
|
await this.#storage.readyPromise;
|
|
661
664
|
}
|
|
662
665
|
}catch(error){
|
|
@@ -684,7 +687,7 @@ class MailOutbox{
|
|
|
684
687
|
if(!this.#onRecordCommitted) return;
|
|
685
688
|
try{
|
|
686
689
|
const result=this.#onRecordCommitted(record);
|
|
687
|
-
if(result&&
|
|
690
|
+
if(result&&is.function(result.then)){
|
|
688
691
|
result.catch(reportMailOutboxObserverError);
|
|
689
692
|
}
|
|
690
693
|
}catch(error){
|
|
@@ -773,7 +776,7 @@ class MailOutbox{
|
|
|
773
776
|
}catch(error){
|
|
774
777
|
fail('Mail outbox could not list durable storage.','MAIL_OUTBOX_STORAGE_FAILED',Error,error);
|
|
775
778
|
}
|
|
776
|
-
if(!
|
|
779
|
+
if(!is.array(keys)){
|
|
777
780
|
fail('Mail outbox storage returned an invalid key list.','MAIL_OUTBOX_STORAGE_FAILED');
|
|
778
781
|
}
|
|
779
782
|
return [...keys];
|
|
@@ -807,7 +810,7 @@ class MailOutbox{
|
|
|
807
810
|
}
|
|
808
811
|
|
|
809
812
|
#assertInvalidTarget(fileName,inventory){
|
|
810
|
-
if(
|
|
813
|
+
if(!is.string(fileName)||!fileName){
|
|
811
814
|
fail('Mail outbox invalid-record filename is required.','MAIL_OUTBOX_INVALID',TypeError);
|
|
812
815
|
}
|
|
813
816
|
const target=inventory.invalidRecords.find(
|
|
@@ -848,7 +851,7 @@ class MailOutbox{
|
|
|
848
851
|
}
|
|
849
852
|
|
|
850
853
|
#assertDeleteAvailable(){
|
|
851
|
-
if(
|
|
854
|
+
if(!is.function(this.#storage.delete)){
|
|
852
855
|
fail(
|
|
853
856
|
'Mail outbox storage does not support invalid-record deletion.',
|
|
854
857
|
'MAIL_OUTBOX_DELETE_UNAVAILABLE'
|
|
@@ -1026,7 +1029,7 @@ class MailOutbox{
|
|
|
1026
1029
|
}
|
|
1027
1030
|
|
|
1028
1031
|
async enqueue({report,reportKey:key}={}, {attempt=true,signal=null}={}){
|
|
1029
|
-
if(
|
|
1032
|
+
if(!is.boolean(attempt)){
|
|
1030
1033
|
fail('Mail outbox attempt must be boolean.','MAIL_OUTBOX_INVALID',TypeError);
|
|
1031
1034
|
}
|
|
1032
1035
|
const resolvedSignal=validateSignal(signal);
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
const REPORT_KEY_PATTERN=/^[a-zA-Z0-9._:-]+$/;
|
|
2
5
|
const REQUEST_ID_PATTERN=/^[a-zA-Z0-9-]+$/;
|
|
3
6
|
const PROVIDER_ID_PATTERN=/^[a-zA-Z0-9._:-]+$/;
|
|
@@ -18,16 +21,16 @@ export class MailTransportError extends Error {
|
|
|
18
21
|
this.code=code;
|
|
19
22
|
this.details=details;
|
|
20
23
|
this.retryable=Boolean(retryable);
|
|
21
|
-
this.retryAfterMs=
|
|
24
|
+
this.retryAfterMs=is.safeInteger(retryAfterMs)&&retryAfterMs>0
|
|
22
25
|
? retryAfterMs
|
|
23
26
|
: 0;
|
|
24
|
-
this.statusCode=
|
|
27
|
+
this.statusCode=is.safeInteger(statusCode)?statusCode:0;
|
|
25
28
|
this.uncertain=Boolean(uncertain);
|
|
26
29
|
}
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
export function normalizeMailEndpoint(endpoint,base=globalThis.location?.href){
|
|
30
|
-
if(
|
|
33
|
+
if(!is.string(endpoint)||!endpoint.trim()){
|
|
31
34
|
throw new Error('Mail endpoint is required');
|
|
32
35
|
}
|
|
33
36
|
let url;
|
|
@@ -47,7 +50,7 @@ export function normalizeMailEndpoint(endpoint,base=globalThis.location?.href){
|
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
export function serializeMailReport(report){
|
|
50
|
-
if(!report
|
|
53
|
+
if(!report||!is.object(report)||is.array(report)){
|
|
51
54
|
throw new Error('Mail report must be a JSON object');
|
|
52
55
|
}
|
|
53
56
|
try{
|
|
@@ -62,7 +65,7 @@ export function serializeMailReport(report){
|
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
function validateSerializedReport(serializedReport){
|
|
65
|
-
if(
|
|
68
|
+
if(!is.string(serializedReport)||!serializedReport){
|
|
66
69
|
throw new Error('Serialized mail report is required');
|
|
67
70
|
}
|
|
68
71
|
let parsed;
|
|
@@ -71,7 +74,7 @@ function validateSerializedReport(serializedReport){
|
|
|
71
74
|
}catch{
|
|
72
75
|
throw new Error('Serialized mail report must contain valid JSON');
|
|
73
76
|
}
|
|
74
|
-
if(!parsed
|
|
77
|
+
if(!parsed||!is.object(parsed)||is.array(parsed)){
|
|
75
78
|
throw new Error('Serialized mail report must contain a JSON object');
|
|
76
79
|
}
|
|
77
80
|
return serializedReport;
|
|
@@ -80,14 +83,14 @@ function validateSerializedReport(serializedReport){
|
|
|
80
83
|
function parseJsonObject(value){
|
|
81
84
|
try{
|
|
82
85
|
const parsed=JSON.parse(value);
|
|
83
|
-
return parsed&&
|
|
86
|
+
return parsed&&is.object(parsed)&&!is.array(parsed)?parsed:null;
|
|
84
87
|
}catch{
|
|
85
88
|
return null;
|
|
86
89
|
}
|
|
87
90
|
}
|
|
88
91
|
|
|
89
92
|
function parseRetryAfter(value,now=Date.now()){
|
|
90
|
-
if(
|
|
93
|
+
if(!is.string(value)||!value.trim()){
|
|
91
94
|
return 0;
|
|
92
95
|
}
|
|
93
96
|
const trimmed=value.trim();
|
|
@@ -95,7 +98,7 @@ function parseRetryAfter(value,now=Date.now()){
|
|
|
95
98
|
return Math.max(0,Math.ceil(Number(trimmed)*1000));
|
|
96
99
|
}
|
|
97
100
|
const timestamp=Date.parse(trimmed);
|
|
98
|
-
return
|
|
101
|
+
return is.finite(timestamp)
|
|
99
102
|
? Math.max(0,timestamp-now)
|
|
100
103
|
: 0;
|
|
101
104
|
}
|
|
@@ -110,19 +113,19 @@ function invalidSuccessResponse(response,responseText){
|
|
|
110
113
|
function parseDeliveryResponse(response,responseText){
|
|
111
114
|
const body=parseJsonObject(responseText);
|
|
112
115
|
if(!body
|
|
113
|
-
||
|
|
116
|
+
|| !is.string(body.requestId)||!REQUEST_ID_PATTERN.test(body.requestId)
|
|
114
117
|
|| !Object.hasOwn(RESPONSE_CONTRACT,body.status)
|
|
115
118
|
|| RESPONSE_CONTRACT[body.status]!==response.status) {
|
|
116
119
|
throw invalidSuccessResponse(response,responseText);
|
|
117
120
|
}
|
|
118
121
|
for(const field of ['accepted','rejected']){
|
|
119
122
|
if(body[field]!==undefined
|
|
120
|
-
&& (!
|
|
123
|
+
&& (!is.safeInteger(body[field])||body[field]<0)) {
|
|
121
124
|
throw invalidSuccessResponse(response,responseText);
|
|
122
125
|
}
|
|
123
126
|
}
|
|
124
127
|
if(body.providerId!==undefined
|
|
125
|
-
&& (
|
|
128
|
+
&& (!is.string(body.providerId)||!PROVIDER_ID_PATTERN.test(body.providerId))){
|
|
126
129
|
throw invalidSuccessResponse(response,responseText);
|
|
127
130
|
}
|
|
128
131
|
|
|
@@ -135,7 +138,7 @@ function parseDeliveryResponse(response,responseText){
|
|
|
135
138
|
status:body.status,
|
|
136
139
|
statusCode:response.status,
|
|
137
140
|
...(body.providerId?{providerId:body.providerId}:{}),
|
|
138
|
-
retryAfterMs:
|
|
141
|
+
retryAfterMs:is.safeInteger(body.retryAfterMs)&&body.retryAfterMs>0
|
|
139
142
|
? body.retryAfterMs
|
|
140
143
|
: parseRetryAfter(response.headers?.get?.('retry-after')),
|
|
141
144
|
};
|
|
@@ -143,14 +146,14 @@ function parseDeliveryResponse(response,responseText){
|
|
|
143
146
|
|
|
144
147
|
function parseRejection(response,responseText){
|
|
145
148
|
const body=parseJsonObject(responseText);
|
|
146
|
-
const source=body?.error&&
|
|
149
|
+
const source=body?.error&&is.object(body.error)&&!is.array(body.error)
|
|
147
150
|
? body.error
|
|
148
151
|
: body;
|
|
149
152
|
const rawCode=source?.code;
|
|
150
|
-
const code=
|
|
153
|
+
const code=is.string(rawCode)&&ERROR_CODE_PATTERN.test(rawCode)
|
|
151
154
|
? rawCode
|
|
152
155
|
: `MAIL_HTTP_${String(response.status)}`;
|
|
153
|
-
let retryable=
|
|
156
|
+
let retryable=is.boolean(source?.retryable)
|
|
154
157
|
? source.retryable
|
|
155
158
|
: RETRYABLE_STATUS_CODES.has(response.status);
|
|
156
159
|
if(code==='invalid_idempotent_request'||NON_RETRYABLE_RATE_CODES.has(code)){
|
|
@@ -158,8 +161,8 @@ function parseRejection(response,responseText){
|
|
|
158
161
|
}else if(code==='concurrent_idempotent_requests'){
|
|
159
162
|
retryable=true;
|
|
160
163
|
}
|
|
161
|
-
const uncertain=
|
|
162
|
-
const bodyRetryAfter=
|
|
164
|
+
const uncertain=is.boolean(source?.uncertain)?source.uncertain:false;
|
|
165
|
+
const bodyRetryAfter=is.safeInteger(source?.retryAfterMs)&&source.retryAfterMs>0
|
|
163
166
|
? source.retryAfterMs
|
|
164
167
|
: 0;
|
|
165
168
|
return new MailTransportError(`Mail server rejected the request (${response.status})`,{
|
|
@@ -173,8 +176,8 @@ function parseRejection(response,responseText){
|
|
|
173
176
|
}
|
|
174
177
|
|
|
175
178
|
async function readResponseText(response){
|
|
176
|
-
if(!response.body
|
|
177
|
-
if(
|
|
179
|
+
if(!response.body||!is.function(response.body.getReader)){
|
|
180
|
+
if(!is.function(response.text)){
|
|
178
181
|
throw new MailTransportError('Mail server returned an unreadable response',{
|
|
179
182
|
code:'MAIL_UNREADABLE_RESPONSE',statusCode:response.status,uncertain:true,
|
|
180
183
|
});
|
|
@@ -225,21 +228,21 @@ export async function sendMailReport({
|
|
|
225
228
|
serializedReport,
|
|
226
229
|
signal,
|
|
227
230
|
}){
|
|
228
|
-
if(
|
|
231
|
+
if(!is.function(fetchImpl)){
|
|
229
232
|
throw new MailTransportError('Mail transport is unavailable',{code:'MAIL_UNAVAILABLE'});
|
|
230
233
|
}
|
|
231
234
|
const resolvedEndpoint=normalizeMailEndpoint(endpoint);
|
|
232
|
-
if(
|
|
235
|
+
if(!is.string(appName)||!/^[a-z0-9](?:[a-z0-9-]{0,62})$/.test(appName)){
|
|
233
236
|
throw new Error('Mail application identity is invalid');
|
|
234
237
|
}
|
|
235
|
-
if(
|
|
238
|
+
if(!is.string(reportKey)||!REPORT_KEY_PATTERN.test(reportKey)){
|
|
236
239
|
throw new Error('Mail report key must contain safe characters');
|
|
237
240
|
}
|
|
238
241
|
if(requestTimeout!==null&&requestTimeout!==undefined
|
|
239
|
-
&&(!
|
|
242
|
+
&&(!is.safeInteger(requestTimeout)||requestTimeout<1)){
|
|
240
243
|
throw new Error('Mail request timeout must be a positive integer');
|
|
241
244
|
}
|
|
242
|
-
if(appKey!==undefined&&appKey!==null
|
|
245
|
+
if(appKey!==undefined&&appKey!==null&&!is.string(appKey)){
|
|
243
246
|
throw new Error('Mail application key must be a string');
|
|
244
247
|
}
|
|
245
248
|
if(signal!==undefined&&!(signal instanceof AbortSignal)){
|
|
@@ -252,7 +255,7 @@ export async function sendMailReport({
|
|
|
252
255
|
'Idempotency-Key':reportKey,
|
|
253
256
|
'X-Mail-App':appName,
|
|
254
257
|
};
|
|
255
|
-
if(
|
|
258
|
+
if(is.string(appKey)&&appKey){
|
|
256
259
|
headers['X-Mail-Key']=appKey;
|
|
257
260
|
}
|
|
258
261
|
|
|
@@ -1,50 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
// Speech-only filtering; single markers and all other text remain literal.
|
|
4
|
-
class MarkdownSpeech {
|
|
5
|
-
#pendingMarker='';
|
|
6
|
-
#repeated=false;
|
|
7
|
-
|
|
8
|
-
append(text='',end=false){
|
|
9
|
-
if(typeof text!=='string'){
|
|
10
|
-
throw new TypeError('Markdown speech input must be text.');
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
let narration='';
|
|
14
|
-
|
|
15
|
-
for(const character of text){
|
|
16
|
-
if(character===this.#pendingMarker){
|
|
17
|
-
this.#repeated=true;
|
|
18
|
-
continue;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if(this.#pendingMarker&&!this.#repeated){
|
|
22
|
-
narration+=this.#pendingMarker;
|
|
23
|
-
}
|
|
24
|
-
this.reset();
|
|
25
|
-
|
|
26
|
-
if(FORMATTING_MARKERS.has(character)){
|
|
27
|
-
// Wait for the next character to distinguish one mark from a run.
|
|
28
|
-
this.#pendingMarker=character;
|
|
29
|
-
}else{
|
|
30
|
-
narration+=character;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if(end){
|
|
35
|
-
if(this.#pendingMarker&&!this.#repeated){
|
|
36
|
-
narration+=this.#pendingMarker;
|
|
37
|
-
}
|
|
38
|
-
this.reset();
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return narration;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
reset(){
|
|
45
|
-
this.#pendingMarker='';
|
|
46
|
-
this.#repeated=false;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export {MarkdownSpeech};
|
|
1
|
+
export {MarkdownSpeech} from 'arcane-os/speech-text';
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
function normalizeMemoryContent(content=''){
|
|
2
|
-
if(
|
|
5
|
+
if(!is.string(content)){
|
|
3
6
|
return '';
|
|
4
7
|
}
|
|
5
8
|
|
|
@@ -9,7 +12,7 @@ function normalizeMemoryContent(content=''){
|
|
|
9
12
|
try{
|
|
10
13
|
const parsed=JSON.parse(normalized);
|
|
11
14
|
|
|
12
|
-
if(
|
|
15
|
+
if(!is.string(parsed)){
|
|
13
16
|
break;
|
|
14
17
|
}
|
|
15
18
|
|
|
@@ -23,14 +26,14 @@ function normalizeMemoryContent(content=''){
|
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
function hasMemoryContent(memory={}){
|
|
26
|
-
const records=
|
|
29
|
+
const records=is.array(memory)
|
|
27
30
|
?memory
|
|
28
31
|
:[memory];
|
|
29
32
|
|
|
30
33
|
return records.some(
|
|
31
34
|
record=>Boolean(
|
|
32
35
|
normalizeMemoryContent(
|
|
33
|
-
|
|
36
|
+
is.string(record)
|
|
34
37
|
?record
|
|
35
38
|
:record?.memory
|
|
36
39
|
)
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
function completeText(value){return String(value??'');}
|
|
2
5
|
|
|
3
6
|
export function normalizeContentAdvisory(value){
|
|
4
|
-
if(!value
|
|
7
|
+
if(!value||!is.object(value))return null;
|
|
5
8
|
return {
|
|
6
9
|
level:['critical','high','caution','low','unavailable'].includes(value.level)?value.level:'caution',
|
|
7
10
|
title:completeText(value.title??'Content advisory'),
|
|
@@ -27,10 +30,10 @@ export function unavailableMessageInspection(messages){
|
|
|
27
30
|
|
|
28
31
|
export async function inspectMessageRecords(messages,inspector,{prepare}={}){
|
|
29
32
|
const advisories=new Map();let failures=0;
|
|
30
|
-
if(
|
|
33
|
+
if(!is.function(inspector))return {advisories,failures};
|
|
31
34
|
const records=Array.from(messages||[]);
|
|
32
35
|
let context;
|
|
33
|
-
try{context=
|
|
36
|
+
try{context=is.function(prepare)?await prepare(records):undefined;}
|
|
34
37
|
catch{return unavailableMessageInspection(records);}
|
|
35
38
|
for(const message of records){
|
|
36
39
|
try{const advisory=normalizeContentAdvisory(await inspector(message,context));if(advisory){if(advisory.level==='unavailable')failures+=1;advisories.set(message,advisory);}}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
const MODEL_REFERENCE=/^[A-Za-z0-9][A-Za-z0-9._/-]*(?::[A-Za-z0-9][A-Za-z0-9._-]*)?$/;
|
|
2
5
|
const PARAMETER_NAME=/^[a-z][a-z0-9_]*$/;
|
|
3
6
|
|
|
@@ -8,13 +11,13 @@ function fail(message){
|
|
|
8
11
|
}
|
|
9
12
|
|
|
10
13
|
async function completeResponseText(response){
|
|
11
|
-
if(
|
|
14
|
+
if(!is.function(response?.text)){
|
|
12
15
|
fail('The model definition response is not readable.');
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
const text=await response.text();
|
|
16
19
|
|
|
17
|
-
if(
|
|
20
|
+
if(!is.string(text)){
|
|
18
21
|
fail('The model definition response is not readable text.');
|
|
19
22
|
}
|
|
20
23
|
|
|
@@ -27,7 +30,7 @@ async function completeResponseText(response){
|
|
|
27
30
|
* are rejected so browser prompts cannot silently drift from the definition.
|
|
28
31
|
*/
|
|
29
32
|
export function parseModelDefinition(source){
|
|
30
|
-
if(
|
|
33
|
+
if(!is.string(source)
|
|
31
34
|
||!source
|
|
32
35
|
||source.includes('\0')
|
|
33
36
|
){
|
|
@@ -85,7 +88,7 @@ export function parseModelDefinition(source){
|
|
|
85
88
|
export async function loadModelDefinitionSystemPrompt(url,{
|
|
86
89
|
fetchImpl=globalThis.fetch
|
|
87
90
|
}={}){
|
|
88
|
-
if(
|
|
91
|
+
if(!is.function(fetchImpl)){
|
|
89
92
|
fail('A browser fetch implementation is required.');
|
|
90
93
|
}
|
|
91
94
|
|