arcane-os 0.64.0 → 0.66.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.
Files changed (50) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +1 -1
  3. package/browser-runtime/ai/browser-decisions.mjs +3 -2
  4. package/browser-runtime/ai/decision-runtime.mjs +14 -24
  5. package/browser-runtime/core/client.mjs +62 -5
  6. package/docs/guides/native-whisper-openvino.md +334 -0
  7. package/docs/publishing.md +39 -0
  8. package/docs/reference/README.md +11 -1
  9. package/docs/reference/ai/browser-decisions.md +7 -4
  10. package/docs/reference/arcane-ollama.md +1 -1
  11. package/docs/reference/codex-app-server.md +269 -0
  12. package/docs/reference/core-client.md +60 -2
  13. package/docs/reference/core-development.md +157 -0
  14. package/docs/reference/core-native-packaging.md +13 -0
  15. package/docs/reference/core-repositories.md +232 -4
  16. package/docs/reference/git-text-snapshot.md +15 -1
  17. package/docs/reference/inventory/package-api.json +174 -6
  18. package/docs/reference/inventory/runtime-modules.json +8 -6
  19. package/docs/reference/local-ai.md +80 -3
  20. package/docs/reference/local-image-generation.md +5 -0
  21. package/docs/reference/local-image-sdxl.md +1 -1
  22. package/docs/reference/local-whisper.md +7 -1
  23. package/docs/reference/mcp-stdio.md +263 -0
  24. package/docs/reference/native-speech.md +6 -0
  25. package/docs/reference/runtime-components.md +18 -1
  26. package/docs/reference/runtime-modules.md +10 -2
  27. package/docs/reference/sdk-api.md +346 -4
  28. package/package.json +6 -2
  29. package/runtime/arcane/modules/AI.js +156 -13
  30. package/runtime/arcane/modules/Ollama.js +1 -1
  31. package/runtime/arcane/modules/OllamaModelIdentifier.js +13 -0
  32. package/schemas/arcane-app.schema.json +8 -1
  33. package/schemas/arcane-package.schema.json +8 -1
  34. package/src/codex/app-server.mjs +845 -0
  35. package/src/core/development.mjs +457 -0
  36. package/src/core/repositories.mjs +178 -9
  37. package/src/core/services/local-ai.mjs +80 -10
  38. package/src/dev-server.mjs +3 -2
  39. package/src/git-identity.mjs +132 -0
  40. package/src/git-text-snapshot.mjs +5 -3
  41. package/src/index.mjs +1 -0
  42. package/src/local-ai/dev.mjs +80 -426
  43. package/src/local-ai/whisper/config.mjs +31 -3
  44. package/src/local-ai/whisper/index.mjs +62 -5
  45. package/src/local-ai/whisper/install.mjs +72 -14
  46. package/src/local-ai/whisper/native/main.cpp +33 -2
  47. package/src/local-ai/whisper/openvino-build.mjs +138 -0
  48. package/src/mcp/stdio-server.mjs +579 -0
  49. package/src/native/portable-layout.mjs +3 -1
  50. package/src/toolchain.mjs +19 -8
@@ -15,7 +15,7 @@ import {
15
15
  AI_PROVIDER_PROTOCOL,
16
16
  getAIProviderRuntime
17
17
  } from './AIProviderRuntime.js';
18
- import {normalizeOllamaModelIdentifier} from './OllamaModelIdentifier.js';
18
+ import {normalizeOllamaModelIdentifier,sameOllamaModelIdentifier} from './OllamaModelIdentifier.js';
19
19
  import {arcaneLogging} from 'arcane-os/logging';
20
20
  import {
21
21
  fetchHTTPResponse,
@@ -1371,11 +1371,115 @@ class AI {
1371
1371
  const localOnly=providerId==='OLLAMA';
1372
1372
  let state='unloaded';
1373
1373
  let busy=false;
1374
+ let nativeWatch=null;
1375
+
1376
+ function nativeOllamaReady(){
1377
+ if(!nativeWatch
1378
+ ||nativeWatch.ollama!==runtime.#nativeOllama()
1379
+ ||nativeWatch.modelId!==runtime.model
1380
+ ||nativeWatch.error){
1381
+ return false;
1382
+ }
1383
+ const snapshot=nativeWatch.snapshot;
1384
+ return snapshot?.available===true
1385
+ &&(snapshot.models??[]).some(function selectedResident(model){
1386
+ return sameOllamaModelIdentifier(model.id??model.model??model.name,nativeWatch.modelId)
1387
+ &&model.loaded===true;
1388
+ });
1389
+ }
1390
+
1391
+ function stopNativeOllamaWatch(){
1392
+ const previous=nativeWatch;
1393
+ nativeWatch=null;
1394
+ previous?.unsubscribe?.();
1395
+ return previous;
1396
+ }
1397
+
1398
+ function observeNativeOllama(watch,snapshot,error=null){
1399
+ if(nativeWatch!==watch){
1400
+ return;
1401
+ }
1402
+ watch.snapshot=snapshot;
1403
+ watch.error=error;
1404
+ if(state==='ready'&&!nativeOllamaReady()){
1405
+ state='unloaded';
1406
+ runtime.#retainBuiltInLLMReadiness(
1407
+ runtime.#reconcileBuiltInLLMReadiness()
1408
+ );
1409
+ }
1410
+ }
1411
+
1412
+ async function inspectNativeOllama(watch,signal){
1413
+ if(signal?.aborted){
1414
+ throw normalizeAIRequestAbort(signal.reason);
1415
+ }
1416
+ const revision=watch.revision;
1417
+ try{
1418
+ const running=await watch.ollama.running({signal});
1419
+ if(signal?.aborted){
1420
+ throw normalizeAIRequestAbort(signal.reason);
1421
+ }
1422
+ if(nativeWatch===watch&&watch.revision===revision){
1423
+ observeNativeOllama(watch,{
1424
+ available:true,
1425
+ models:(running.models??[]).map(function residentModel(model){
1426
+ return {...model,loaded:true};
1427
+ })
1428
+ });
1429
+ }
1430
+ }catch(error){
1431
+ if(!signal?.aborted&&watch.revision===revision){
1432
+ observeNativeOllama(watch,null,error);
1433
+ }
1434
+ throw error;
1435
+ }
1436
+ if(nativeWatch!==watch||!nativeOllamaReady()){
1437
+ throw watch.error??aiProviderError(
1438
+ 'The selected Ollama model is not loaded.',
1439
+ 'ARCANE_AI_ROLE_NOT_READY'
1440
+ );
1441
+ }
1442
+ }
1443
+
1444
+ async function loadNativeOllama(context){
1445
+ stopNativeOllamaWatch();
1446
+ const ollama=runtime.#nativeOllama();
1447
+ if(!is.function(ollama?.generate)||!is.function(ollama?.running)){
1448
+ throw aiProviderError(
1449
+ 'The native Ollama API does not expose model loading and resident status.',
1450
+ 'AI_NATIVE_LOCAL_REQUIRED'
1451
+ );
1452
+ }
1453
+ const watch={
1454
+ ollama,modelId:context.selection.modelId,
1455
+ snapshot:null,error:null,revision:0,unsubscribe:null
1456
+ };
1457
+ nativeWatch=watch;
1458
+ if(is.function(globalThis.Arcane?.events?.on)){
1459
+ watch.unsubscribe=globalThis.Arcane.events.on(
1460
+ 'localai.state',
1461
+ function observeNativeOllamaState(snapshot){
1462
+ watch.revision+=1;
1463
+ const engine=snapshot.ollama??snapshot.runtimes?.find(function ollamaEngine(record){
1464
+ return record.id==='ollama';
1465
+ });
1466
+ observeNativeOllama(watch,engine??null,engine?.error??null);
1467
+ }
1468
+ );
1469
+ }
1470
+ // Empty generation is Ollama's public preload operation. Residency
1471
+ // is established separately; a successful response alone is insufficient.
1472
+ await ollama.generate(
1473
+ {model:watch.modelId,prompt:'',stream:false},
1474
+ {signal:context.signal}
1475
+ );
1476
+ await inspectNativeOllama(watch,context.signal);
1477
+ }
1374
1478
 
1375
1479
  function statusBuiltInLLMProvider(){
1376
1480
  if(state==='ready'
1377
- &&!busy
1378
- &&!runtime.#builtInLLMCapability(providerId)){
1481
+ &&((localOnly&&!nativeOllamaReady())
1482
+ ||(!busy&&!runtime.#builtInLLMCapability(providerId)))){
1379
1483
  state='unloaded';
1380
1484
  }
1381
1485
  return completeValue({
@@ -1423,7 +1527,7 @@ class AI {
1423
1527
  return runtime.#builtInLLMInspection(providerId,selection);
1424
1528
  },
1425
1529
  status:statusBuiltInLLMProvider,
1426
- load:function loadBuiltInLLMProvider(context={}){
1530
+ load:async function loadBuiltInLLMProvider(context={}){
1427
1531
  if(context.signal?.aborted){
1428
1532
  throw normalizeAIRequestAbort(context.signal.reason);
1429
1533
  }
@@ -1447,7 +1551,7 @@ class AI {
1447
1551
  const inspection=assertBuiltInLLMSelection(context.selection);
1448
1552
  state='loading';
1449
1553
  context.progress({
1450
- phase:'capability',
1554
+ phase:localOnly?'loading':'capability',
1451
1555
  completed:0,
1452
1556
  total:1,
1453
1557
  unit:'items',
@@ -1457,9 +1561,18 @@ class AI {
1457
1561
  state='unloaded';
1458
1562
  throw normalizeAIRequestAbort(context.signal.reason);
1459
1563
  }
1564
+ if(localOnly){
1565
+ try{
1566
+ await loadNativeOllama(context);
1567
+ }catch(error){
1568
+ state='unloaded';
1569
+ stopNativeOllamaWatch();
1570
+ throw error;
1571
+ }
1572
+ }
1460
1573
  state='ready';
1461
1574
  context.progress({
1462
- phase:'capability',
1575
+ phase:localOnly?'loading':'capability',
1463
1576
  completed:1,
1464
1577
  total:1,
1465
1578
  unit:'items',
@@ -1489,24 +1602,39 @@ class AI {
1489
1602
  );
1490
1603
  }
1491
1604
  busy=true;
1605
+ const watch=nativeWatch;
1492
1606
  if(context.operation==='chat'){
1493
- return Promise.resolve(
1494
- runtime.#requestBuiltInLLMChat(
1607
+ return (async function requestLoadedBuiltInLLM(){
1608
+ if(localOnly){
1609
+ await inspectNativeOllama(watch,context.signal);
1610
+ }
1611
+ const result=await runtime.#requestBuiltInLLMChat(
1495
1612
  context.payload,
1496
1613
  context.signal,
1497
1614
  controls.onRetry
1498
- )
1499
- ).finally(releaseBuiltInLLMRequest);
1615
+ );
1616
+ if(localOnly){
1617
+ await inspectNativeOllama(watch,context.signal);
1618
+ }
1619
+ return result;
1620
+ })().finally(releaseBuiltInLLMRequest);
1500
1621
  }
1501
1622
  if(context.operation==='stream'){
1502
1623
  const handle=createBuiltInAIStreamBridge(
1503
- function executeBuiltInLLMProviderStream(bridge){
1504
- return runtime.#requestBuiltInLLMStream(
1624
+ async function executeBuiltInLLMProviderStream(bridge){
1625
+ if(localOnly){
1626
+ await inspectNativeOllama(watch,bridge.signal);
1627
+ }
1628
+ const result=await runtime.#requestBuiltInLLMStream(
1505
1629
  context.payload,
1506
1630
  bridge,
1507
1631
  controls.observeToolText,
1508
1632
  controls.onRetry
1509
1633
  );
1634
+ if(localOnly){
1635
+ await inspectNativeOllama(watch,bridge.signal);
1636
+ }
1637
+ return result;
1510
1638
  },
1511
1639
  context.signal
1512
1640
  );
@@ -1522,18 +1650,27 @@ class AI {
1522
1650
  'ARCANE_AI_PROVIDER_RUNTIME_INVALID'
1523
1651
  );
1524
1652
  },
1525
- unload:function unloadBuiltInLLMProvider(context={}){
1653
+ unload:async function unloadBuiltInLLMProvider(context={}){
1526
1654
  if(context.signal?.aborted){
1527
1655
  throw normalizeAIRequestAbort(context.signal.reason);
1528
1656
  }
1657
+ const watch=stopNativeOllamaWatch();
1658
+ const release=localOnly&&state==='ready';
1529
1659
  state='unloaded';
1530
1660
  busy=false;
1661
+ if(release&&watch){
1662
+ await watch.ollama.generate(
1663
+ {model:watch.modelId,prompt:'',keep_alive:0,stream:false},
1664
+ {signal:context.signal}
1665
+ );
1666
+ }
1531
1667
  return statusBuiltInLLMProvider();
1532
1668
  },
1533
1669
  dispose:function disposeBuiltInLLMProvider(context={}){
1534
1670
  if(context.signal?.aborted){
1535
1671
  throw normalizeAIRequestAbort(context.signal.reason);
1536
1672
  }
1673
+ stopNativeOllamaWatch();
1537
1674
  state='disposed';
1538
1675
  busy=false;
1539
1676
  return statusBuiltInLLMProvider();
@@ -2110,6 +2247,12 @@ class AI {
2110
2247
  return Promise.resolve(this.#providerRuntime.status('llm'));
2111
2248
  }
2112
2249
  const status=this.#providerRuntime.status('llm');
2250
+ const provider=this.#builtInLLMProviders.get(selection.providerId)?.provider;
2251
+ if(selection.providerId==='OLLAMA'
2252
+ &&provider?.status().loaded!==true
2253
+ &&(status.loaded===true||status.busy===true)){
2254
+ return this.#providerRuntime.unload('llm');
2255
+ }
2113
2256
  if(this.#builtInLLMCapability(selection.providerId)){
2114
2257
  if(status.state==='ready'&&status.loaded===true){
2115
2258
  return Promise.resolve(status);
@@ -40,7 +40,7 @@ export class Ollama{
40
40
  version(){ return api().version() }
41
41
  models(){ return api().models() }
42
42
  list(){ return api().models() }
43
- running(){ return api().running() }
43
+ running(options){ return api().running(options) }
44
44
  show(model,options){ return api().show(model,options) }
45
45
  generate(request,options){ return api().generate(request,options) }
46
46
  chat(request,options){ return api().chat(request,options) }
@@ -22,3 +22,16 @@ export function normalizeOllamaModelIdentifier(value){
22
22
  export function isOllamaModelIdentifier(value){
23
23
  return normalizeOllamaModelIdentifier(value)!==null;
24
24
  }
25
+
26
+ /** Compare Ollama names with its default host, namespace and tag; never rewrite a request. */
27
+ export function sameOllamaModelIdentifier(left,right){
28
+ if(!is.string(left)||!is.string(right))return false;
29
+ function identity(value){
30
+ const parts=value.replace(/^[A-Za-z][A-Za-z0-9+.-]*:\/\//,'').split('/');
31
+ const model=parts.pop();
32
+ const namespace=parts.pop()??'library';
33
+ const host=parts.join('/')||'registry.ollama.ai';
34
+ return `${host}/${namespace}/${model.includes(':')?model:`${model}:latest`}`.toLowerCase();
35
+ }
36
+ return identity(left)===identity(right);
37
+ }
@@ -303,6 +303,7 @@
303
303
  "id": { "const": "whisper.cpp" },
304
304
  "version": { "type": "string", "default": "1.9.4" },
305
305
  "backend": { "enum": ["auto", "cuda", "metal", "cpu"], "default": "auto" },
306
+ "encoder": { "const": "openvino-npu", "description": "Explicit Intel NPU encoder. Omit for ordinary GPU/CPU encoding. The precompiled Windows x64 runtime supports CUDA-first CPU recovery and requires a compatible installed Intel NPU driver." },
306
307
  "helperRoot": { "type": "string", "description": "Complete helper directory copied into the native runtime, including its app-local libraries. Defaults to the directory containing helperExecutable." },
307
308
  "helperExecutable": { "type": "string" },
308
309
  "helperUrl": { "type": "string" },
@@ -318,6 +319,7 @@
318
319
  "required": ["backend"],
319
320
  "properties": {
320
321
  "backend": { "enum": ["cuda", "metal", "cpu"] },
322
+ "encoder": { "const": "openvino-npu" },
321
323
  "root": { "type": "string" },
322
324
  "url": { "type": "string" },
323
325
  "libraryDirectory": { "type": "string" }
@@ -337,7 +339,12 @@
337
339
  "id": { "type": "string" },
338
340
  "path": { "type": "string" },
339
341
  "url": { "type": "string" },
340
- "filename": { "type": "string" }
342
+ "filename": { "type": "string" },
343
+ "archiveUrl": { "type": "string", "description": "Complete model archive containing the declared model members." },
344
+ "encoderFilename": { "type": "string", "description": "Matching OpenVINO encoder XML filename in archiveUrl." },
345
+ "encoderDataFilename": { "type": "string", "description": "Matching OpenVINO encoder BIN filename in archiveUrl." },
346
+ "encoderPath": { "type": "string", "description": "Matching local OpenVINO encoder XML file." },
347
+ "encoderDataPath": { "type": "string", "description": "Matching local OpenVINO encoder BIN file." }
341
348
  }
342
349
  }
343
350
  ]
@@ -197,6 +197,7 @@
197
197
  "id": { "const": "whisper.cpp" },
198
198
  "version": { "type": "string", "default": "1.9.4" },
199
199
  "backend": { "enum": ["auto", "cuda", "metal", "cpu"], "default": "auto" },
200
+ "encoder": { "const": "openvino-npu", "description": "Explicit Intel NPU encoder. Omit for ordinary GPU/CPU encoding. The precompiled Windows x64 runtime supports CUDA-first CPU recovery and requires a compatible installed Intel NPU driver." },
200
201
  "helperRoot": { "type": "string", "description": "Complete helper directory copied into the native runtime, including its app-local libraries. Defaults to the directory containing helperExecutable." },
201
202
  "helperExecutable": { "type": "string" },
202
203
  "helperUrl": { "type": "string" },
@@ -212,6 +213,7 @@
212
213
  "required": ["backend"],
213
214
  "properties": {
214
215
  "backend": { "enum": ["cuda", "metal", "cpu"] },
216
+ "encoder": { "const": "openvino-npu" },
215
217
  "root": { "type": "string" },
216
218
  "url": { "type": "string" },
217
219
  "libraryDirectory": { "type": "string" }
@@ -231,7 +233,12 @@
231
233
  "id": { "type": "string" },
232
234
  "path": { "type": "string" },
233
235
  "url": { "type": "string" },
234
- "filename": { "type": "string" }
236
+ "filename": { "type": "string" },
237
+ "archiveUrl": { "type": "string", "description": "Complete model archive containing the declared model members." },
238
+ "encoderFilename": { "type": "string", "description": "Matching OpenVINO encoder XML filename in archiveUrl." },
239
+ "encoderDataFilename": { "type": "string", "description": "Matching OpenVINO encoder BIN filename in archiveUrl." },
240
+ "encoderPath": { "type": "string", "description": "Matching local OpenVINO encoder XML file." },
241
+ "encoderDataPath": { "type": "string", "description": "Matching local OpenVINO encoder BIN file." }
235
242
  }
236
243
  }
237
244
  ]