arcane-os 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/NOTICE +5 -3
- package/README.md +73 -24
- package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +67 -18
- package/browser-runtime/ai/ARCANE_AI_BROWSER_WASM_COMPONENTS.json +16 -5
- package/browser-runtime/ai/browser-kokoro-worker.mjs +3 -0
- package/browser-runtime/ai/browser-speech-artifacts.mjs +1108 -0
- package/browser-runtime/ai/browser-speech-providers.mjs +475 -0
- package/browser-runtime/ai/browser-speech.mjs +9 -0
- package/browser-runtime/ai/browser-wasm-llm-provider.mjs +1537 -167
- package/browser-runtime/ai/browser-wasm.mjs +46 -1
- package/browser-runtime/ai/browser-whisper-worker.mjs +3 -0
- package/browser-runtime/ai/browser-wllama-runtime.mjs +677 -132
- package/browser-runtime/ai/model-controller.mjs +138 -12
- package/browser-runtime/ai/speech-worker-client.mjs +207 -0
- package/browser-runtime/ai/speech-worker-runtime.mjs +516 -0
- package/browser-runtime/ai/wllama/index.mjs +389 -0
- package/docs/architecture.md +132 -22
- package/docs/reference/README.md +1 -1
- package/docs/reference/ai/browser-wasm.md +101 -42
- package/docs/reference/availability-and-normalization.md +19 -5
- package/docs/reference/behavioral-testing.md +18 -5
- package/docs/reference/cli.md +2 -2
- package/docs/reference/inventory/package-api.json +14 -14
- package/docs/reference/protocols.md +4 -4
- package/docs/reference/sdk-api.md +68 -38
- package/docs/work-amplification.md +8 -4
- package/package.json +7 -3
- package/runtime/ARCANE_RUNTIME_RELEASE.json +50 -20
- package/runtime/arcane/components/chat.html +280 -62
- package/runtime/arcane/components/speech.html +1113 -265
- package/runtime/arcane/entities/Chat.js +246 -43
- package/runtime/arcane/modules/AI.js +713 -162
- package/runtime/arcane/modules/AIProviderRuntime.js +2289 -0
- package/runtime/arcane/modules/AIRuntimeState.js +872 -0
- package/runtime/arcane/modules/ConfiguredAIChatSession.js +293 -27
- package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +682 -0
- package/runtime/arcane/modules/DocumentLexicalSearch.js +292 -0
- package/runtime/arcane/modules/PersistentAIChatSession.js +268 -0
- package/runtime/arcane/modules/StaticDocumentCatalog.js +25 -206
- package/schemas/arcane-lock.schema.json +6 -4
- package/src/cli/main.mjs +14 -2
- package/src/constants.mjs +1 -1
- package/src/dev-server.mjs +244 -13
- package/src/import-map.mjs +59 -1
- package/src/packager/core.mjs +2 -2
- package/src/runtime.mjs +14 -4
- package/src/sdk-browser-runtime.mjs +28 -75
- package/src/templates/workspace-template.mjs +4 -4
- package/src/toolchain.mjs +3 -0
- package/src/workspace-runtime.mjs +1 -1
- package/src/workspace.mjs +1 -1
package/src/dev-server.mjs
CHANGED
|
@@ -17,6 +17,7 @@ import {verifyRuntime} from './runtime.mjs';
|
|
|
17
17
|
import {verifySdkBrowserRuntime} from './sdk-browser-runtime.mjs';
|
|
18
18
|
import {resolveWorkspace} from './workspace.mjs';
|
|
19
19
|
import {createEventQueue} from './event-queue.mjs';
|
|
20
|
+
import {SDK_NAME,SDK_VERSION} from './constants.mjs';
|
|
20
21
|
|
|
21
22
|
const MIME_TYPES=new Map([
|
|
22
23
|
['.css','text/css; charset=utf-8'],
|
|
@@ -44,6 +45,17 @@ const SESSION_COOKIE='Arcane-Dev-Session';
|
|
|
44
45
|
const PRIVATE_SOURCE_SEGMENTS=new Set([
|
|
45
46
|
'arcane-app.json','arcane-package.json','test','tests','scripts','node_modules','dist','local'
|
|
46
47
|
]);
|
|
48
|
+
const SDK_RUNTIME_SOURCE_PROTOCOL='arcane-sdk-runtime-source/1';
|
|
49
|
+
const SDK_RUNTIME_SOURCE_ARCANE_ROOTS=new Set([
|
|
50
|
+
'components','css','entities','img','modules','security'
|
|
51
|
+
]);
|
|
52
|
+
const SDK_RUNTIME_SOURCE_PRIVATE_SEGMENTS=new Set([
|
|
53
|
+
'node_modules','.git','.hg','.svn','cvs'
|
|
54
|
+
]);
|
|
55
|
+
const SDK_RUNTIME_SOURCE_PRIVATE_MANIFESTS=new Set([
|
|
56
|
+
'arcane-app.json','arcane-package.json','arcane.lock.json',
|
|
57
|
+
'arcane_app_release.json','arcane_runtime_release.json','arcane_sdk_browser_release.json'
|
|
58
|
+
]);
|
|
47
59
|
|
|
48
60
|
// This server is a loopback-only development host, not a production policy
|
|
49
61
|
// boundary. The bundled runtime currently needs inline component execution,
|
|
@@ -139,6 +151,173 @@ function inventoryKey(value){
|
|
|
139
151
|
return process.platform==='win32'?value.toLowerCase():value;
|
|
140
152
|
}
|
|
141
153
|
|
|
154
|
+
function canonicalLocationKey(value){
|
|
155
|
+
return inventoryKey(path.resolve(value));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function pathIsWithin(root,candidate){
|
|
159
|
+
const relative=path.relative(root,candidate);
|
|
160
|
+
return relative===''||(!path.isAbsolute(relative)&&relative!=='..'
|
|
161
|
+
&&!relative.startsWith(`..${path.sep}`));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function pathsOverlap(left,right){
|
|
165
|
+
return pathIsWithin(left,right)||pathIsWithin(right,left);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function canonicalRealDirectory(requested,label){
|
|
169
|
+
let info;
|
|
170
|
+
try{
|
|
171
|
+
info=await lstat(requested);
|
|
172
|
+
}catch{
|
|
173
|
+
fail(`${label} must be an existing real directory.`,'ARCANE_DEV_RUNTIME_SOURCE_INVALID');
|
|
174
|
+
}
|
|
175
|
+
if(info.isSymbolicLink()||!info.isDirectory()){
|
|
176
|
+
fail(`${label} must be an existing real directory.`,'ARCANE_DEV_RUNTIME_SOURCE_INVALID');
|
|
177
|
+
}
|
|
178
|
+
let canonical;
|
|
179
|
+
try{
|
|
180
|
+
canonical=await realpath(requested);
|
|
181
|
+
}catch{
|
|
182
|
+
fail(`${label} could not be resolved as a real directory.`,'ARCANE_DEV_RUNTIME_SOURCE_INVALID');
|
|
183
|
+
}
|
|
184
|
+
if(canonicalLocationKey(canonical)!==canonicalLocationKey(requested)){
|
|
185
|
+
fail(`${label} must not contain a symlink or reparse-point escape.`,'ARCANE_DEV_RUNTIME_SOURCE_INVALID');
|
|
186
|
+
}
|
|
187
|
+
return canonical;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function sdkRuntimeSourcePathAllowed(relative,{arcaneRoot=false}={}){
|
|
191
|
+
if(relative.length===0)return false;
|
|
192
|
+
const normalized=relative.map(function normalizeRuntimeSourceSegment(segment){
|
|
193
|
+
return segment.normalize('NFC').toLowerCase();
|
|
194
|
+
});
|
|
195
|
+
if(normalized.some(function runtimeSourceSegmentIsPrivate(segment){
|
|
196
|
+
return segment.startsWith('.')||SDK_RUNTIME_SOURCE_PRIVATE_SEGMENTS.has(segment)
|
|
197
|
+
||SDK_RUNTIME_SOURCE_PRIVATE_MANIFESTS.has(segment);
|
|
198
|
+
}))return false;
|
|
199
|
+
return !arcaneRoot||SDK_RUNTIME_SOURCE_ARCANE_ROOTS.has(normalized[0]);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function sdkArcaneSourcePathAllowed(relative){
|
|
203
|
+
return sdkRuntimeSourcePathAllowed(relative,{arcaneRoot:true});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function sdkDependencySourcePathAllowed(relative){
|
|
207
|
+
return sdkRuntimeSourcePathAllowed(relative);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function sdkBrowserSourcePathAllowed(relative){
|
|
211
|
+
return sdkRuntimeSourcePathAllowed(relative);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function emitRuntimeSourceEvent(onEvent,event){
|
|
215
|
+
if(typeof onEvent==='function')await onEvent(event);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function verifySdkRuntimeSourceRoot(sourceRoot,workspaceRoot,appId,{signal,onEvent}={}){
|
|
219
|
+
throwIfAborted(signal);
|
|
220
|
+
if(typeof sourceRoot!=='string'||!sourceRoot.trim()){
|
|
221
|
+
fail('sdkRuntimeSourceRoot must name an Arcane SDK directory.',
|
|
222
|
+
'ARCANE_DEV_RUNTIME_SOURCE_INVALID');
|
|
223
|
+
}
|
|
224
|
+
const requestedRoot=path.resolve(sourceRoot);
|
|
225
|
+
await emitRuntimeSourceEvent(onEvent,{
|
|
226
|
+
type:'runtime.source.mount.started',
|
|
227
|
+
appId,
|
|
228
|
+
requestedRoot,
|
|
229
|
+
target:'browser'
|
|
230
|
+
});
|
|
231
|
+
const canonicalRoot=await canonicalRealDirectory(requestedRoot,'SDK runtime source root');
|
|
232
|
+
const canonicalWorkspaceRoot=await realpath(workspaceRoot);
|
|
233
|
+
if(pathsOverlap(canonicalRoot,canonicalWorkspaceRoot)){
|
|
234
|
+
fail('SDK runtime source root must not overlap the application workspace.',
|
|
235
|
+
'ARCANE_DEV_RUNTIME_SOURCE_INVALID');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
let packageFile;
|
|
239
|
+
try{
|
|
240
|
+
packageFile=await openSafeFile(canonicalRoot,['package.json']);
|
|
241
|
+
}catch{
|
|
242
|
+
fail('SDK runtime source package.json must be a readable bounded real file.',
|
|
243
|
+
'ARCANE_DEV_RUNTIME_SOURCE_INVALID');
|
|
244
|
+
}
|
|
245
|
+
if(!packageFile){
|
|
246
|
+
fail('SDK runtime source root must contain a real package.json.',
|
|
247
|
+
'ARCANE_DEV_RUNTIME_SOURCE_INVALID');
|
|
248
|
+
}
|
|
249
|
+
let packageDocument;
|
|
250
|
+
try{
|
|
251
|
+
packageDocument=JSON.parse(packageFile.bytes.toString('utf8'));
|
|
252
|
+
}catch{
|
|
253
|
+
fail('SDK runtime source package.json must be valid JSON.',
|
|
254
|
+
'ARCANE_DEV_RUNTIME_SOURCE_INVALID');
|
|
255
|
+
}
|
|
256
|
+
if(!packageDocument||Array.isArray(packageDocument)||packageDocument.name!==SDK_NAME){
|
|
257
|
+
fail(`SDK runtime source package name must be exactly ${SDK_NAME}.`,
|
|
258
|
+
'ARCANE_DEV_RUNTIME_SOURCE_INVALID');
|
|
259
|
+
}
|
|
260
|
+
if(packageDocument.version!==SDK_VERSION){
|
|
261
|
+
fail(
|
|
262
|
+
`SDK runtime source version must exactly match the executing SDK (${SDK_VERSION}).`,
|
|
263
|
+
'ARCANE_DEV_RUNTIME_VERSION_MISMATCH'
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const roots=[
|
|
268
|
+
{
|
|
269
|
+
path:'runtime/arcane',
|
|
270
|
+
prefix:['arcane'],
|
|
271
|
+
allow:sdkArcaneSourcePathAllowed
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
path:'runtime/strong-type',
|
|
275
|
+
prefix:['arcane','dependencies','strong-type'],
|
|
276
|
+
allow:sdkDependencySourcePathAllowed
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
path:'browser-runtime',
|
|
280
|
+
prefix:['arcane','sdk'],
|
|
281
|
+
allow:sdkBrowserSourcePathAllowed
|
|
282
|
+
}
|
|
283
|
+
];
|
|
284
|
+
const mappings=[];
|
|
285
|
+
for(let index=0;index<roots.length;index+=1){
|
|
286
|
+
throwIfAborted(signal);
|
|
287
|
+
const root=roots[index];
|
|
288
|
+
const requested=path.join(canonicalRoot,...root.path.split('/'));
|
|
289
|
+
const canonical=await canonicalRealDirectory(requested,`SDK runtime source ${root.path}`);
|
|
290
|
+
if(!pathIsWithin(canonicalRoot,canonical)){
|
|
291
|
+
fail(`SDK runtime source ${root.path} must remain inside the SDK root.`,
|
|
292
|
+
'ARCANE_DEV_RUNTIME_SOURCE_INVALID');
|
|
293
|
+
}
|
|
294
|
+
mappings.push({prefix:root.prefix,root:canonical,allow:root.allow});
|
|
295
|
+
await emitRuntimeSourceEvent(onEvent,{
|
|
296
|
+
type:'runtime.source.mount.progress',
|
|
297
|
+
current:index+1,
|
|
298
|
+
total:roots.length,
|
|
299
|
+
path:root.path
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
const runtime=Object.freeze({
|
|
303
|
+
mode:'sdk-source',
|
|
304
|
+
protocol:SDK_RUNTIME_SOURCE_PROTOCOL,
|
|
305
|
+
sdkVersion:SDK_VERSION,
|
|
306
|
+
mutable:true,
|
|
307
|
+
distributionAuthority:false,
|
|
308
|
+
sourceRoot:canonicalRoot
|
|
309
|
+
});
|
|
310
|
+
await emitRuntimeSourceEvent(onEvent,{
|
|
311
|
+
type:'runtime.source.mount.ready',
|
|
312
|
+
appId,
|
|
313
|
+
canonicalRoot,
|
|
314
|
+
sdkVersion:SDK_VERSION,
|
|
315
|
+
protocol:SDK_RUNTIME_SOURCE_PROTOCOL,
|
|
316
|
+
routeCount:mappings.length
|
|
317
|
+
});
|
|
318
|
+
return {mappings,runtime};
|
|
319
|
+
}
|
|
320
|
+
|
|
142
321
|
function identityMap(identities,prefix=''){
|
|
143
322
|
const normalizedPrefix=prefix?`${prefix}/`:'';
|
|
144
323
|
const result=new Map();
|
|
@@ -346,8 +525,34 @@ function sharedPathAllowed(relative,route){
|
|
|
346
525
|
});
|
|
347
526
|
}
|
|
348
527
|
|
|
349
|
-
async function sourceRoutes(workspaceRoot,appId,{
|
|
528
|
+
async function sourceRoutes(workspaceRoot,appId,{
|
|
529
|
+
workspaceRuntimeReceipt,
|
|
530
|
+
sdkRuntimeSourceRoot,
|
|
531
|
+
signal,
|
|
532
|
+
onEvent
|
|
533
|
+
}){
|
|
350
534
|
const resolved=await resolveWorkspace({workspaceRoot,appId});
|
|
535
|
+
const appMapping={
|
|
536
|
+
prefix:['apps',resolved.appId],
|
|
537
|
+
root:resolved.appRoot,
|
|
538
|
+
allow:relative=>sourcePathAllowed(relative,resolved.app.manifest)
|
|
539
|
+
};
|
|
540
|
+
if(sdkRuntimeSourceRoot!==undefined){
|
|
541
|
+
const sdkSource=await verifySdkRuntimeSourceRoot(
|
|
542
|
+
sdkRuntimeSourceRoot,
|
|
543
|
+
resolved.workspaceRoot,
|
|
544
|
+
resolved.appId,
|
|
545
|
+
{signal,onEvent}
|
|
546
|
+
);
|
|
547
|
+
return {
|
|
548
|
+
workspaceRoot:resolved.workspaceRoot,
|
|
549
|
+
workspaceMode:resolved.config.workspaceMode,
|
|
550
|
+
appId:resolved.appId,
|
|
551
|
+
startPath:`/apps/${resolved.appId}/${resolved.app.manifest.entry}`,
|
|
552
|
+
runtime:sdkSource.runtime,
|
|
553
|
+
mappings:[appMapping,...sdkSource.mappings]
|
|
554
|
+
};
|
|
555
|
+
}
|
|
351
556
|
if(resolved.config.workspaceMode==='integrated'){
|
|
352
557
|
return {
|
|
353
558
|
workspaceRoot:resolved.workspaceRoot,
|
|
@@ -355,11 +560,7 @@ async function sourceRoutes(workspaceRoot,appId,{workspaceRuntimeReceipt,signal}
|
|
|
355
560
|
appId:resolved.appId,
|
|
356
561
|
startPath:`/apps/${resolved.appId}/${resolved.app.manifest.entry}`,
|
|
357
562
|
mappings:[
|
|
358
|
-
|
|
359
|
-
prefix:['apps',resolved.appId],
|
|
360
|
-
root:resolved.appRoot,
|
|
361
|
-
allow:relative=>sourcePathAllowed(relative,resolved.app.manifest)
|
|
362
|
-
},
|
|
563
|
+
appMapping,
|
|
363
564
|
...resolved.config.sharedPayloads['browser-runtime'].map(route=>({
|
|
364
565
|
prefix:route.destination.split('/'),
|
|
365
566
|
root:path.join(resolved.workspaceRoot,...route.source.split('/')),
|
|
@@ -411,11 +612,7 @@ async function sourceRoutes(workspaceRoot,appId,{workspaceRuntimeReceipt,signal}
|
|
|
411
612
|
appId:resolved.appId,
|
|
412
613
|
startPath:`/apps/${resolved.appId}/${resolved.app.manifest.entry}`,
|
|
413
614
|
mappings:[
|
|
414
|
-
|
|
415
|
-
prefix:['apps',resolved.appId],
|
|
416
|
-
root:resolved.appRoot,
|
|
417
|
-
allow:relative=>sourcePathAllowed(relative,resolved.app.manifest)
|
|
418
|
-
},
|
|
615
|
+
appMapping,
|
|
419
616
|
{
|
|
420
617
|
prefix:['arcane'],
|
|
421
618
|
root:runtimeRoot,
|
|
@@ -506,18 +703,33 @@ async function startOwnedDevServer({
|
|
|
506
703
|
port=0,
|
|
507
704
|
signal,
|
|
508
705
|
workspaceRuntimeReceipt,
|
|
706
|
+
sdkRuntimeSourceRoot,
|
|
509
707
|
releaseReceipt
|
|
510
708
|
}={},events,releaseSignal){
|
|
511
709
|
throwIfAborted(signal);
|
|
512
710
|
if(mode!=='source'&&mode!=='packaged')fail(`Unsupported server mode: ${String(mode)}.`,'ARCANE_USAGE');
|
|
711
|
+
if(sdkRuntimeSourceRoot!==undefined&&mode!=='source'){
|
|
712
|
+
fail('sdkRuntimeSourceRoot is supported only in source development mode.','ARCANE_USAGE');
|
|
713
|
+
}
|
|
513
714
|
if(host!=='127.0.0.1'&&host!=='::1'){
|
|
514
715
|
fail('Development server host must be a numeric loopback address (127.0.0.1 or ::1).','ARCANE_POLICY_DENIED');
|
|
515
716
|
}
|
|
516
717
|
if(!Number.isInteger(port)||port<0||port>65535)fail('port must be an integer from 0 through 65535.','ARCANE_USAGE');
|
|
517
|
-
|
|
718
|
+
const requestedRuntimeMode=mode==='source'&&sdkRuntimeSourceRoot!==undefined
|
|
719
|
+
?'sdk-source'
|
|
720
|
+
:null;
|
|
721
|
+
await events.send({
|
|
722
|
+
type:'server.starting',
|
|
723
|
+
mode,
|
|
724
|
+
host,
|
|
725
|
+
port,
|
|
726
|
+
appId,
|
|
727
|
+
...(requestedRuntimeMode?{runtimeMode:requestedRuntimeMode}:{})
|
|
728
|
+
});
|
|
518
729
|
const routeSet=mode==='source'
|
|
519
730
|
?await sourceRoutes(workspaceRoot,appId,{
|
|
520
731
|
workspaceRuntimeReceipt,
|
|
732
|
+
sdkRuntimeSourceRoot,
|
|
521
733
|
signal,
|
|
522
734
|
onEvent:event=>events.send(event)
|
|
523
735
|
})
|
|
@@ -647,6 +859,17 @@ async function startOwnedDevServer({
|
|
|
647
859
|
while(requestTasks.size>0){
|
|
648
860
|
await Promise.allSettled([...requestTasks]);
|
|
649
861
|
}
|
|
862
|
+
if(routeSet.runtime?.mode==='sdk-source'){
|
|
863
|
+
await events.send({
|
|
864
|
+
type:'runtime.source.mount.stopped',
|
|
865
|
+
appId:routeSet.appId,
|
|
866
|
+
reason:events.error||operationalError
|
|
867
|
+
?'failed'
|
|
868
|
+
:signal?.aborted
|
|
869
|
+
?'cancelled'
|
|
870
|
+
:'closed'
|
|
871
|
+
});
|
|
872
|
+
}
|
|
650
873
|
await events.send({
|
|
651
874
|
type:'server.stopped',
|
|
652
875
|
host:address.address,
|
|
@@ -695,6 +918,10 @@ async function startOwnedDevServer({
|
|
|
695
918
|
mode,
|
|
696
919
|
workspaceRoot:routeSet.workspaceRoot,
|
|
697
920
|
appId:routeSet.appId,
|
|
921
|
+
...(routeSet.runtime?{
|
|
922
|
+
runtimeMode:routeSet.runtime.mode,
|
|
923
|
+
runtime:routeSet.runtime
|
|
924
|
+
}:{}),
|
|
698
925
|
host:address.address,
|
|
699
926
|
port:address.port,
|
|
700
927
|
origin,
|
|
@@ -711,7 +938,11 @@ async function startOwnedDevServer({
|
|
|
711
938
|
host:result.host,
|
|
712
939
|
port:result.port,
|
|
713
940
|
url,
|
|
714
|
-
appId:result.appId
|
|
941
|
+
appId:result.appId,
|
|
942
|
+
...(routeSet.runtime?{
|
|
943
|
+
runtimeMode:routeSet.runtime.mode,
|
|
944
|
+
runtime:routeSet.runtime
|
|
945
|
+
}:{})
|
|
715
946
|
});
|
|
716
947
|
throwIfAborted(signal);
|
|
717
948
|
}catch(error){
|
package/src/import-map.mjs
CHANGED
|
@@ -14,15 +14,26 @@ export const MANAGED_IMPORT_MAP_ATTRIBUTE='data-arcane-import-map';
|
|
|
14
14
|
const JAVASCRIPT_EXTENSION=/\.(?:js|mjs)$/u;
|
|
15
15
|
const NODE_ONLY_MODULE='modules/CaseEvidenceIndexer.js';
|
|
16
16
|
const RUNTIME_STRONG_TYPE_IMPORT='../../node_modules/strong-type/index.js';
|
|
17
|
+
const PERSISTENT_CHAT_IMPORT='#arcane/persistent-ai-chat-session';
|
|
18
|
+
const PERSISTENT_CHAT_MODULE='modules/PersistentAIChatSession.js';
|
|
17
19
|
const SDK_BROWSER_ENTRY='sdk/event-manager.mjs';
|
|
18
20
|
const SDK_BROWSER_AI_ENTRY='sdk/ai/browser-wasm.mjs';
|
|
21
|
+
const SDK_BROWSER_SPEECH_ENTRY='sdk/ai/browser-speech.mjs';
|
|
22
|
+
const SDK_BROWSER_SPEECH_WORKER_RUNTIME='sdk/ai/speech-worker-runtime.mjs';
|
|
19
23
|
const SDK_BROWSER_FILES=Object.freeze([
|
|
20
24
|
'sdk/ai/ARCANE_AI_BROWSER_WASM_COMPONENTS.json',
|
|
25
|
+
'sdk/ai/browser-kokoro-worker.mjs',
|
|
26
|
+
'sdk/ai/browser-speech-artifacts.mjs',
|
|
27
|
+
'sdk/ai/browser-speech-providers.mjs',
|
|
28
|
+
SDK_BROWSER_SPEECH_ENTRY,
|
|
21
29
|
'sdk/ai/browser-wasm-llm-provider.mjs',
|
|
22
30
|
SDK_BROWSER_AI_ENTRY,
|
|
31
|
+
'sdk/ai/browser-whisper-worker.mjs',
|
|
23
32
|
'sdk/ai/browser-wllama-runtime.mjs',
|
|
24
33
|
'sdk/ai/internal/sha256.mjs',
|
|
25
34
|
'sdk/ai/model-controller.mjs',
|
|
35
|
+
'sdk/ai/speech-worker-client.mjs',
|
|
36
|
+
SDK_BROWSER_SPEECH_WORKER_RUNTIME,
|
|
26
37
|
'sdk/ai/wllama/LICENCE',
|
|
27
38
|
'sdk/ai/wllama/index.mjs',
|
|
28
39
|
'sdk/ai/wllama/llama.cpp-LICENSE',
|
|
@@ -719,6 +730,14 @@ function nonliteralDynamic(importer,offset){
|
|
|
719
730
|
);
|
|
720
731
|
}
|
|
721
732
|
|
|
733
|
+
function isAuthorizedSpeechRuntimeImport(tokens,index,close,importer){
|
|
734
|
+
if(importer!==SDK_BROWSER_SPEECH_WORKER_RUNTIME||close!==index+5)return false;
|
|
735
|
+
const [entry,dot,moduleUrl]=tokens.slice(index+2,close);
|
|
736
|
+
return entry?.type==='identifier'&&entry.value==='entry'
|
|
737
|
+
&&dot?.value==='.'
|
|
738
|
+
&&moduleUrl?.type==='identifier'&&moduleUrl.value==='moduleUrl';
|
|
739
|
+
}
|
|
740
|
+
|
|
722
741
|
export function scanModuleImports(source,{importer='<module>'}={}){
|
|
723
742
|
if(typeof source!=='string')throw new TypeError('scanModuleImports source must be a string.');
|
|
724
743
|
const tokens=tokenize(source);
|
|
@@ -745,6 +764,11 @@ export function scanModuleImports(source,{importer='<module>'}={}){
|
|
|
745
764
|
||commas.length>2
|
|
746
765
|
||(commas.length===2
|
|
747
766
|
&&(commas[1]!==close-1||commas[1]===commas[0]+1))){
|
|
767
|
+
if(isAuthorizedSpeechRuntimeImport(tokens,index,close,importer)){
|
|
768
|
+
hasModuleSyntax=true;
|
|
769
|
+
index=close;
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
748
772
|
nonliteralDynamic(importer,current.start);
|
|
749
773
|
}
|
|
750
774
|
hasModuleSyntax=true;
|
|
@@ -811,6 +835,20 @@ function unresolved(importer,specifier,normalizedTarget,reason='is not in the sh
|
|
|
811
835
|
}
|
|
812
836
|
|
|
813
837
|
function resolveImport(importer,specifier,files){
|
|
838
|
+
if(specifier===PERSISTENT_CHAT_IMPORT){
|
|
839
|
+
if(!files.has(PERSISTENT_CHAT_MODULE)){
|
|
840
|
+
unresolved(importer,specifier,PERSISTENT_CHAT_MODULE);
|
|
841
|
+
}
|
|
842
|
+
return {target:PERSISTENT_CHAT_MODULE,persistentChat:true};
|
|
843
|
+
}
|
|
844
|
+
if(specifier.startsWith(PERSISTENT_CHAT_IMPORT)){
|
|
845
|
+
unresolved(
|
|
846
|
+
importer,
|
|
847
|
+
specifier,
|
|
848
|
+
PERSISTENT_CHAT_MODULE,
|
|
849
|
+
'does not match the exact browser import-map key'
|
|
850
|
+
);
|
|
851
|
+
}
|
|
814
852
|
const reachableSpecifier=stripQueryAndHash(specifier);
|
|
815
853
|
if(!reachableSpecifier)unresolved(importer,specifier,'<empty>');
|
|
816
854
|
if(reachableSpecifier.includes('%')){
|
|
@@ -966,9 +1004,13 @@ export async function buildImportMap({files,readFile,signal}={}){
|
|
|
966
1004
|
}
|
|
967
1005
|
const hasSdkBrowserGraph=inventory.has(SDK_BROWSER_ENTRY);
|
|
968
1006
|
const hasSdkAiGraph=inventory.has(SDK_BROWSER_AI_ENTRY);
|
|
1007
|
+
const hasSdkSpeechGraph=inventory.has(SDK_BROWSER_SPEECH_ENTRY);
|
|
969
1008
|
if(hasSdkAiGraph&&!hasSdkBrowserGraph){
|
|
970
1009
|
unresolved(SDK_BROWSER_AI_ENTRY,'<authenticated SDK browser closure>',SDK_BROWSER_ENTRY);
|
|
971
1010
|
}
|
|
1011
|
+
if(hasSdkSpeechGraph&&!hasSdkAiGraph){
|
|
1012
|
+
unresolved(SDK_BROWSER_SPEECH_ENTRY,'<authenticated SDK browser closure>',SDK_BROWSER_AI_ENTRY);
|
|
1013
|
+
}
|
|
972
1014
|
if(hasSdkBrowserGraph){
|
|
973
1015
|
for(const required of SDK_BROWSER_FILES){
|
|
974
1016
|
if(!inventory.has(required)){
|
|
@@ -1016,11 +1058,20 @@ export async function buildImportMap({files,readFile,signal}={}){
|
|
|
1016
1058
|
'arcane-os/ai/browser-wasm',
|
|
1017
1059
|
'./arcane/sdk/ai/browser-wasm.mjs'
|
|
1018
1060
|
);
|
|
1061
|
+
registerSpecifier(
|
|
1062
|
+
namedRegistry,
|
|
1063
|
+
'arcane-os/ai/browser-speech',
|
|
1064
|
+
'./arcane/sdk/ai/browser-speech.mjs'
|
|
1065
|
+
);
|
|
1019
1066
|
}
|
|
1020
1067
|
}
|
|
1021
1068
|
|
|
1022
1069
|
const sdkRoots=hasSdkBrowserGraph
|
|
1023
|
-
?[
|
|
1070
|
+
?[
|
|
1071
|
+
SDK_BROWSER_ENTRY,
|
|
1072
|
+
...(hasSdkAiGraph?[SDK_BROWSER_AI_ENTRY]:[]),
|
|
1073
|
+
...(hasSdkSpeechGraph?[SDK_BROWSER_SPEECH_ENTRY]:[]),
|
|
1074
|
+
]:[];
|
|
1024
1075
|
const queue=[...roots,...sdkRoots];
|
|
1025
1076
|
const reached=new Set();
|
|
1026
1077
|
const entities=new Set();
|
|
@@ -1036,6 +1087,13 @@ export async function buildImportMap({files,readFile,signal}={}){
|
|
|
1036
1087
|
const resolution=resolveImport(importer,imported.specifier,inventory);
|
|
1037
1088
|
if(resolution.runtimeStrongType)usesRuntimeStrongType=true;
|
|
1038
1089
|
if(resolution.eventPubSub)usesEventPubSub=true;
|
|
1090
|
+
if(resolution.persistentChat){
|
|
1091
|
+
registerSpecifier(
|
|
1092
|
+
namedRegistry,
|
|
1093
|
+
PERSISTENT_CHAT_IMPORT,
|
|
1094
|
+
'./arcane/modules/PersistentAIChatSession.js'
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
1039
1097
|
if(resolution.target.startsWith('entities/'))entities.add(resolution.target);
|
|
1040
1098
|
if(!reached.has(resolution.target))queue.push(resolution.target);
|
|
1041
1099
|
}
|
package/src/packager/core.mjs
CHANGED
|
@@ -1036,7 +1036,7 @@ function packagedRuntimeAuthorities(receipt){
|
|
|
1036
1036
|
||!Number.isSafeInteger(receipt.fileCount)||receipt.fileCount<1
|
|
1037
1037
|
||!Number.isSafeInteger(receipt.totalBytes)||receipt.totalBytes<1
|
|
1038
1038
|
||!SHA256_PATTERN.test(receipt.contentSha256??'')
|
|
1039
|
-
||!isPlainObject(arcane)||arcane.authority!=='arcane-os-
|
|
1039
|
+
||!isPlainObject(arcane)||arcane.authority!=='arcane-os-sdk'
|
|
1040
1040
|
||!SHA256_PATTERN.test(arcane.manifestSha256??'')
|
|
1041
1041
|
||!SHA256_PATTERN.test(arcane.contentSha256??'')
|
|
1042
1042
|
||!isPlainObject(arcane.source)
|
|
@@ -1158,7 +1158,7 @@ async function authenticatePackageRuntimeVerificationState(context,state,{signal
|
|
|
1158
1158
|
});
|
|
1159
1159
|
const workspaceReceipt=snapshot.workspaceRuntimeReceipt;
|
|
1160
1160
|
const expectedArcaneSource={
|
|
1161
|
-
authority:'arcane-os-
|
|
1161
|
+
authority:'arcane-os-sdk',
|
|
1162
1162
|
location:snapshot.runtimeReceipt.canonicalLocation,
|
|
1163
1163
|
manifestSha256:snapshot.runtimeReceipt.manifestSha256,
|
|
1164
1164
|
contentSha256:snapshot.runtimeReceipt.contentSha256,
|
package/src/runtime.mjs
CHANGED
|
@@ -7,6 +7,8 @@ import {SDK_VERSION} from './constants.mjs';
|
|
|
7
7
|
|
|
8
8
|
export const RUNTIME_MANIFEST_NAME='ARCANE_RUNTIME_RELEASE.json';
|
|
9
9
|
const SHA256_PATTERN=/^[a-f0-9]{64}$/;
|
|
10
|
+
const SDK_RUNTIME_REPOSITORY='https://github.com/TheWizardNexus/arcane-os-sdk.git';
|
|
11
|
+
const LEGACY_ARCANE_REPOSITORY='https://github.com/TheWizardNexus/ARCANE-OS.git';
|
|
10
12
|
const READ_ONLY_NO_FOLLOW=FS_CONSTANTS.O_RDONLY|(FS_CONSTANTS.O_NOFOLLOW??0);
|
|
11
13
|
const MAX_VERIFIED_RUNTIME_FILE_BYTES=64*1024*1024;
|
|
12
14
|
const sdkRoot=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
|
|
@@ -59,10 +61,18 @@ function validateRelease(value){
|
|
|
59
61
|
}
|
|
60
62
|
if(value.sdkVersion!==SDK_VERSION)fail('Runtime manifest sdkVersion is incompatible with this SDK.');
|
|
61
63
|
if(!value.source||typeof value.source!=='object'
|
|
62
|
-
||Object.keys(value.source).sort(compareText).join('\0')!=='
|
|
63
|
-
||value.source.
|
|
64
|
-
||
|
|
65
|
-
||value.source.
|
|
64
|
+
||Object.keys(value.source).sort(compareText).join('\0')!=='authority\0legacyProjection\0path\0protocol\0repository'
|
|
65
|
+
||value.source.authority!=='sdk-canonical'
|
|
66
|
+
||value.source.repository!==SDK_RUNTIME_REPOSITORY
|
|
67
|
+
||value.source.path!=='runtime/arcane'
|
|
68
|
+
||value.source.protocol!=='arcane/1'
|
|
69
|
+
||!value.source.legacyProjection
|
|
70
|
+
||typeof value.source.legacyProjection!=='object'
|
|
71
|
+
||Object.keys(value.source.legacyProjection).sort(compareText).join('\0')!=='bundleVersion\0commit\0repository'
|
|
72
|
+
||value.source.legacyProjection.repository!==LEGACY_ARCANE_REPOSITORY
|
|
73
|
+
||typeof value.source.legacyProjection.commit!=='string'
|
|
74
|
+
||!/^[a-f0-9]{40}$/.test(value.source.legacyProjection.commit)
|
|
75
|
+
||value.source.legacyProjection.bundleVersion!=='0.8.12'){
|
|
66
76
|
fail('Runtime manifest source identity is invalid.');
|
|
67
77
|
}
|
|
68
78
|
if(!Array.isArray(value.files)||!Number.isSafeInteger(value.fileCount)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {createHash} from 'node:crypto';
|
|
2
2
|
import {constants as FS_CONSTANTS} from 'node:fs';
|
|
3
|
-
import {lstat,open,readdir,realpath} from 'node:fs/promises';
|
|
3
|
+
import {lstat,open,readFile,readdir,realpath} from 'node:fs/promises';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import {fileURLToPath} from 'node:url';
|
|
6
6
|
import {SDK_VERSION} from './constants.mjs';
|
|
@@ -8,96 +8,49 @@ import {SDK_VERSION} from './constants.mjs';
|
|
|
8
8
|
const MANIFEST_NAME='ARCANE_SDK_BROWSER_RELEASE.json';
|
|
9
9
|
const BUILDER='arcane-sdk-browser-runtime-v1';
|
|
10
10
|
const PROTOCOL='arcane-sdk-browser-runtime/1';
|
|
11
|
-
export const SDK_BROWSER_RUNTIME_MANIFEST_SHA256=
|
|
12
|
-
'88395493b411fd5461fbb2bb065ae2b745f6d1672f796583fd248ec97f71f4f7';
|
|
13
|
-
export const SDK_BROWSER_RUNTIME_CONTENT_SHA256=
|
|
14
|
-
'5e03f45a732db51cb5a2b2193cc79ecda34501d07a9b2e82e794e5fa37d55d00';
|
|
15
11
|
const REPOSITORY='https://github.com/TheWizardNexus/arcane-os-sdk.git';
|
|
16
12
|
const SHA256_PATTERN=/^[a-f0-9]{64}$/u;
|
|
17
13
|
const READ_ONLY_NO_FOLLOW=FS_CONSTANTS.O_RDONLY|(FS_CONSTANTS.O_NOFOLLOW??0);
|
|
18
14
|
const MAX_VERIFIED_FILE_BYTES=64*1024*1024;
|
|
19
15
|
const packageRoot=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
|
|
20
16
|
const defaultRoot=path.join(packageRoot,'browser-runtime');
|
|
17
|
+
const canonicalManifestBytes=await readFile(path.join(defaultRoot,MANIFEST_NAME));
|
|
18
|
+
const canonicalRelease=JSON.parse(canonicalManifestBytes.toString('utf8'));
|
|
19
|
+
if(
|
|
20
|
+
canonicalRelease?.schemaVersion!==1
|
|
21
|
+
||canonicalRelease.builder!==BUILDER
|
|
22
|
+
||canonicalRelease.sdkVersion!==SDK_VERSION
|
|
23
|
+
||!SHA256_PATTERN.test(canonicalRelease.contentSha256)
|
|
24
|
+
){
|
|
25
|
+
throw new Error('The canonical SDK browser runtime receipt is invalid.');
|
|
26
|
+
}
|
|
27
|
+
export const SDK_BROWSER_RUNTIME_MANIFEST_SHA256=
|
|
28
|
+
createHash('sha256').update(canonicalManifestBytes).digest('hex');
|
|
29
|
+
export const SDK_BROWSER_RUNTIME_CONTENT_SHA256=canonicalRelease.contentSha256;
|
|
21
30
|
const issuedReceipts=new WeakSet();
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
runtimePolicy:{
|
|
29
|
-
modelAuthorities:'caller-supplied-exact-bytes-and-sha256',
|
|
30
|
-
modelWeightsPacked:false,
|
|
31
|
-
remoteModelHelpers:false,
|
|
32
|
-
compatibilityRuntime:false,
|
|
33
|
-
toolCalls:'structural-only-never-executed'
|
|
34
|
-
},
|
|
35
|
-
components:[
|
|
36
|
-
{
|
|
37
|
-
name:'@wllama/wllama',
|
|
38
|
-
version:'3.6.0',
|
|
39
|
-
licenseSpdx:'MIT',
|
|
40
|
-
sourceRepository:'https://github.com/ngxson/wllama.git',
|
|
41
|
-
sourceRevision:'f16050d8d51a00602c6a2a6b8ac9c09f490eea7f',
|
|
42
|
-
npm:{
|
|
43
|
-
resolved:'https://registry.npmjs.org/@wllama/wllama/-/wllama-3.6.0.tgz',
|
|
44
|
-
integrity:'sha512-NN3ZBXqaaUwGXTQubkNvsCaLPjN2XVa0bVS40OYCE8zquYmRc2W3oHYEgwvuSWWDB8aUqTLyMioySCXNkcnD1w==',
|
|
45
|
-
tarballBytes:5671369,
|
|
46
|
-
tarballSha256:'137c35ceccb4911a9b0ce9b427889f75991654ec6a6d1dd8fabd879b14b07a1b'
|
|
47
|
-
},
|
|
48
|
-
files:[
|
|
49
|
-
{
|
|
50
|
-
role:'runtime-module',
|
|
51
|
-
path:'ai/wllama/index.mjs',
|
|
52
|
-
sourcePath:'node_modules/@wllama/wllama/esm/index.js',
|
|
53
|
-
bytes:373519,
|
|
54
|
-
sha256:'4637e42d636010493a9b274fbbe70bfd8120365da726b1d9e589d85ca84a00d6'
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
role:'runtime-wasm',
|
|
58
|
-
path:'ai/wllama/wllama.wasm',
|
|
59
|
-
sourcePath:'node_modules/@wllama/wllama/esm/wasm/wllama.wasm',
|
|
60
|
-
bytes:8524865,
|
|
61
|
-
sha256:'95c6ff9ef2a03ff2c63bc91db132f0126a0bd0456b272cd8ae2e0f592fb059f6'
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
role:'license',
|
|
65
|
-
path:'ai/wllama/LICENCE',
|
|
66
|
-
sourcePath:'node_modules/@wllama/wllama/LICENCE',
|
|
67
|
-
bytes:1071,
|
|
68
|
-
sha256:'5866e3bd7e3cbd3f7c8bea6efd8a1e7fa7cc8de68c30f428aff7c6584a0fb720'
|
|
69
|
-
}
|
|
70
|
-
]
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
name:'llama.cpp',
|
|
74
|
-
version:'b10454',
|
|
75
|
-
licenseSpdx:'MIT',
|
|
76
|
-
sourceRepository:'https://github.com/ggerganov/llama.cpp.git',
|
|
77
|
-
sourceRevision:'4df29be4f4c3673f428170fda944a5b19f743bb8',
|
|
78
|
-
embeddedBy:'@wllama/wllama@3.6.0',
|
|
79
|
-
files:[
|
|
80
|
-
{
|
|
81
|
-
role:'license',
|
|
82
|
-
path:'ai/wllama/llama.cpp-LICENSE',
|
|
83
|
-
sourceUrl:'https://raw.githubusercontent.com/ggerganov/llama.cpp/4df29be4f4c3673f428170fda944a5b19f743bb8/LICENSE',
|
|
84
|
-
bytes:1078,
|
|
85
|
-
sha256:'94f29bbed6a22c35b992c5c6ebf0e7c92f13b836b90f36f461c9cf2f0f1d010d'
|
|
86
|
-
}
|
|
87
|
-
]
|
|
88
|
-
}
|
|
89
|
-
]
|
|
90
|
-
});
|
|
31
|
+
const canonicalAiComponentsBytes=await readFile(
|
|
32
|
+
path.join(defaultRoot,'ai','ARCANE_AI_BROWSER_WASM_COMPONENTS.json')
|
|
33
|
+
);
|
|
34
|
+
const expectedAiComponents=Object.freeze(
|
|
35
|
+
JSON.parse(canonicalAiComponentsBytes.toString('utf8'))
|
|
36
|
+
);
|
|
91
37
|
|
|
92
38
|
const expectedFiles=Object.freeze([
|
|
93
39
|
['ai/ARCANE_AI_BROWSER_WASM_COMPONENTS.json','browser-runtime/ai/ARCANE_AI_BROWSER_WASM_COMPONENTS.json','sdk-source-identity'],
|
|
40
|
+
['ai/browser-kokoro-worker.mjs','browser-runtime/ai/browser-kokoro-worker.mjs','sdk-source-identity'],
|
|
41
|
+
['ai/browser-speech-artifacts.mjs','browser-runtime/ai/browser-speech-artifacts.mjs','sdk-source-identity'],
|
|
42
|
+
['ai/browser-speech-providers.mjs','browser-runtime/ai/browser-speech-providers.mjs','sdk-source-identity'],
|
|
43
|
+
['ai/browser-speech.mjs','browser-runtime/ai/browser-speech.mjs','sdk-source-identity'],
|
|
94
44
|
['ai/browser-wasm-llm-provider.mjs','browser-runtime/ai/browser-wasm-llm-provider.mjs','sdk-source-identity'],
|
|
95
45
|
['ai/browser-wasm.mjs','browser-runtime/ai/browser-wasm.mjs','sdk-source-identity'],
|
|
46
|
+
['ai/browser-whisper-worker.mjs','browser-runtime/ai/browser-whisper-worker.mjs','sdk-source-identity'],
|
|
96
47
|
['ai/browser-wllama-runtime.mjs','browser-runtime/ai/browser-wllama-runtime.mjs','sdk-source-identity'],
|
|
97
48
|
['ai/internal/sha256.mjs','browser-runtime/ai/internal/sha256.mjs','sdk-source-identity'],
|
|
98
49
|
['ai/model-controller.mjs','browser-runtime/ai/model-controller.mjs','sdk-source-identity'],
|
|
50
|
+
['ai/speech-worker-client.mjs','browser-runtime/ai/speech-worker-client.mjs','sdk-source-identity'],
|
|
51
|
+
['ai/speech-worker-runtime.mjs','browser-runtime/ai/speech-worker-runtime.mjs','sdk-source-identity'],
|
|
99
52
|
['ai/wllama/LICENCE','node_modules/@wllama/wllama/LICENCE','vendor-package-identity'],
|
|
100
|
-
['ai/wllama/index.mjs','
|
|
53
|
+
['ai/wllama/index.mjs','browser-runtime/ai/wllama/index.mjs','deterministic-derived-vendor'],
|
|
101
54
|
['ai/wllama/llama.cpp-LICENSE','browser-runtime/ai/wllama/llama.cpp-LICENSE','vendor-source-identity'],
|
|
102
55
|
['ai/wllama/wllama.wasm','node_modules/@wllama/wllama/esm/wasm/wllama.wasm','vendor-package-identity'],
|
|
103
56
|
['dependencies/event-pubsub/index.js','node_modules/event-pubsub/index.js','vendor-package-identity'],
|
|
@@ -47,7 +47,7 @@ export function workspaceTemplate({
|
|
|
47
47
|
const name=displayName||`Arcane ${titleCase(appId)}`;
|
|
48
48
|
const packageName=`arcane-${appId}`;
|
|
49
49
|
const runtimeContentSha256=runtimeRelease?.contentSha256;
|
|
50
|
-
const upstreamCommit=runtimeRelease?.source?.commit;
|
|
50
|
+
const upstreamCommit=runtimeRelease?.source?.legacyProjection?.commit;
|
|
51
51
|
const browserManifestSha256=sdkBrowserRuntimeRelease?.manifestSha256;
|
|
52
52
|
const browserContentSha256=sdkBrowserRuntimeRelease?.contentSha256;
|
|
53
53
|
const browserSource=sdkBrowserRuntimeRelease?.source;
|
|
@@ -153,15 +153,15 @@ appropriate.
|
|
|
153
153
|
files.set('AGENTS.md',`# ${name} development instructions
|
|
154
154
|
|
|
155
155
|
- Use plain JavaScript, HTML, and CSS; do not introduce TypeScript or TSX.
|
|
156
|
-
- Keep reusable mechanisms in Arcane
|
|
156
|
+
- Keep reusable portable mechanisms in the Arcane SDK and app-specific behavior under \`apps/${appId}/\`.
|
|
157
157
|
- Keep \`arcane/css/theme.css\` before app styles and import \`arcane/ThemeBootstrap\` before app code runs.
|
|
158
158
|
- Use \`rgb(...)\` or \`rgba(...)\` for new CSS colors.
|
|
159
159
|
- Build one named app and one explicit target at a time. Native targets may be unavailable until their adapters are installed.
|
|
160
|
-
- Run
|
|
160
|
+
- Run tests and checks only when the user explicitly selects verification or a distribution artifact requires it.
|
|
161
161
|
`);
|
|
162
162
|
files.set('README.md',`# ${name}
|
|
163
163
|
|
|
164
|
-
This repository contains the Arcane
|
|
164
|
+
This repository contains the portable Arcane application \`${appId}\`. It embeds immutable SDK runtime bytes for distribution and has no runtime dependency on an Arcane OS source checkout.
|
|
165
165
|
|
|
166
166
|
## Start
|
|
167
167
|
|