arcane-os 0.55.0 → 0.57.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 (56) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +5 -3
  3. package/browser-runtime/ai/core-local.mjs +306 -0
  4. package/browser-runtime/core/client.mjs +75 -14
  5. package/browser-runtime/core/contracts.mjs +2 -1
  6. package/docs/consumer-updates.md +82 -0
  7. package/docs/publishing.md +41 -18
  8. package/docs/reference/README.md +31 -1
  9. package/docs/reference/availability-and-normalization.md +11 -3
  10. package/docs/reference/cli.md +9 -1
  11. package/docs/reference/core-client.md +45 -2
  12. package/docs/reference/core-native-packaging.md +195 -11
  13. package/docs/reference/core-runtime.md +10 -0
  14. package/docs/reference/inventory/package-api.json +160 -20
  15. package/docs/reference/local-ai.md +197 -0
  16. package/docs/reference/protocols.md +91 -14
  17. package/docs/reference/pwa.md +17 -0
  18. package/docs/reference/runtime-modules.md +6 -5
  19. package/docs/reference/sdk-api.md +165 -26
  20. package/package.json +5 -1
  21. package/runtime/arcane/modules/LocalAIReadiness.js +3 -1
  22. package/schemas/arcane-app.schema.json +64 -0
  23. package/schemas/arcane-package.schema.json +54 -0
  24. package/src/app-descriptor.mjs +35 -2
  25. package/src/app-layout.mjs +6 -2
  26. package/src/cli/main.mjs +8 -2
  27. package/src/core/host.mjs +23 -0
  28. package/src/core/hosts/arcane-core-loader.cjs +2 -0
  29. package/src/core/hosts/linux/arcane_core_process.c +568 -0
  30. package/src/core/hosts/linux/arcane_core_process.h +127 -0
  31. package/src/core/hosts/macos/ArcaneCoreProcess.h +85 -0
  32. package/src/core/hosts/macos/ArcaneCoreProcess.m +357 -0
  33. package/src/core/hosts/sea-launcher.cjs +17 -0
  34. package/src/core/hosts/windows/ArcaneCoreProcess.cs +416 -0
  35. package/src/core/hosts/windows/ArcaneHost.cs +695 -0
  36. package/src/core/hosts/windows/ArcaneHost.manifest +22 -0
  37. package/src/core/hosts/windows/ArcaneLauncher.cs +239 -0
  38. package/src/core/runtime.mjs +15 -3
  39. package/src/core/services/local-ai.mjs +543 -0
  40. package/src/core-classic-source.mjs +2 -2
  41. package/src/dev-server.mjs +7 -0
  42. package/src/import-map.mjs +1 -0
  43. package/src/local-ai/archive.mjs +52 -0
  44. package/src/local-ai/config.mjs +62 -0
  45. package/src/local-ai/dev.mjs +390 -0
  46. package/src/local-ai/discover.mjs +71 -0
  47. package/src/local-ai/http.mjs +106 -0
  48. package/src/local-ai/install.mjs +344 -0
  49. package/src/local-ai/server.mjs +262 -0
  50. package/src/native/portable-layout.mjs +29 -4
  51. package/src/native/portable-provider.mjs +39 -8
  52. package/src/native/windows-provider.mjs +262 -0
  53. package/src/native-provider-loader.mjs +8 -2
  54. package/src/packager/core.mjs +89 -15
  55. package/src/process.mjs +74 -12
  56. package/src/toolchain.mjs +38 -8
@@ -48,6 +48,16 @@
48
48
  "pwa": { "$ref": "#/$defs/pwa" },
49
49
  "include": { "$ref": "#/$defs/nonemptyPathList" },
50
50
  "exclude": { "$ref": "#/$defs/pathList" },
51
+ "nativeResources": {
52
+ "type": "object",
53
+ "additionalProperties": false,
54
+ "required": ["include"],
55
+ "description": "Additional literal application-relative paths for a selected nonbrowser target. Exclude applies only to this additional selection. Native-only files retain their complete original content and do not enter browser document or PWA processing. Omission or an empty include preserves existing selection and output behavior.",
56
+ "properties": {
57
+ "include": { "$ref": "#/$defs/pathList" },
58
+ "exclude": { "$ref": "#/$defs/pathList" }
59
+ }
60
+ },
51
61
  "shared": {
52
62
  "type": "array",
53
63
  "minItems": 1,
@@ -109,6 +119,19 @@
109
119
  "oneOf": [{ "$ref": "#/$defs/rasterIcon" }, { "type": "null" }]
110
120
  },
111
121
  "order": { "type": "integer", "minimum": 0 },
122
+ "services": {
123
+ "description": "Explicit application-owned Core factory modules from the selected native release. Factories receive authored options first and launch context second.",
124
+ "type": "array",
125
+ "items": {
126
+ "type": "object",
127
+ "required": ["module"],
128
+ "properties": {
129
+ "module": { "$ref": "#/$defs/relativePath" },
130
+ "options": {}
131
+ }
132
+ }
133
+ },
134
+ "localAI": { "$ref": "#/$defs/localAI" },
112
135
  "bundledApps": {
113
136
  "type": "array",
114
137
  "uniqueItems": true,
@@ -230,6 +253,47 @@
230
253
  }
231
254
  ],
232
255
  "$defs": {
256
+ "localAI": {
257
+ "type": "object",
258
+ "required": ["runtimes"],
259
+ "description": "Required Core AI runtimes. Development ensures selected runtimes locally; native builds bundle their upstream runtime distributions. Model selection remains application-owned.",
260
+ "properties": {
261
+ "runtimes": {
262
+ "type": "array",
263
+ "items": {
264
+ "oneOf": [
265
+ { "enum": ["llama.cpp", "ollama"] },
266
+ {
267
+ "type": "object",
268
+ "required": ["id"],
269
+ "properties": {
270
+ "id": { "enum": ["llama.cpp", "ollama"] },
271
+ "version": { "type": "string" },
272
+ "url": { "type": "string" }
273
+ }
274
+ }
275
+ ]
276
+ }
277
+ },
278
+ "llamaCpp": {
279
+ "type": "object",
280
+ "properties": {
281
+ "url": { "type": "string" },
282
+ "model": { "type": "string" },
283
+ "modelsDirectory": { "type": "string" },
284
+ "args": { "type": "array", "items": { "type": "string" } }
285
+ }
286
+ },
287
+ "ollama": {
288
+ "type": "object",
289
+ "properties": {
290
+ "url": { "type": "string" },
291
+ "modelsDirectory": { "type": "string" },
292
+ "args": { "type": "array", "items": { "type": "string" } }
293
+ }
294
+ }
295
+ }
296
+ },
233
297
  "pwa": {
234
298
  "type": "object",
235
299
  "additionalProperties": false,
@@ -57,6 +57,9 @@
57
57
  "localAIModelPolicy": {
58
58
  "$ref": "#/$defs/localAIModelPolicy"
59
59
  },
60
+ "localAI": {
61
+ "$ref": "#/$defs/localAI"
62
+ },
60
63
  "pwa": {
61
64
  "$ref": "#/$defs/pwa"
62
65
  },
@@ -88,6 +91,16 @@
88
91
  "exclude": {
89
92
  "$ref": "#/$defs/pathList"
90
93
  },
94
+ "nativeResources": {
95
+ "type": "object",
96
+ "additionalProperties": false,
97
+ "required": ["include"],
98
+ "description": "Additional literal application-relative paths for a selected nonbrowser target. Exclude applies only to this additional selection. Native-only files retain their complete original content and do not enter browser document or PWA processing. Omission or an empty include preserves existing selection and output behavior.",
99
+ "properties": {
100
+ "include": { "$ref": "#/$defs/pathList" },
101
+ "exclude": { "$ref": "#/$defs/pathList" }
102
+ }
103
+ },
91
104
  "shared": {
92
105
  "type": "array",
93
106
  "uniqueItems": true,
@@ -134,6 +147,47 @@
134
147
  }
135
148
  ],
136
149
  "$defs": {
150
+ "localAI": {
151
+ "type": "object",
152
+ "required": ["runtimes"],
153
+ "description": "Required Core AI runtimes. Development ensures selected runtimes locally; native builds bundle their upstream runtime distributions. Model selection remains application-owned.",
154
+ "properties": {
155
+ "runtimes": {
156
+ "type": "array",
157
+ "items": {
158
+ "oneOf": [
159
+ { "enum": ["llama.cpp", "ollama"] },
160
+ {
161
+ "type": "object",
162
+ "required": ["id"],
163
+ "properties": {
164
+ "id": { "enum": ["llama.cpp", "ollama"] },
165
+ "version": { "type": "string" },
166
+ "url": { "type": "string" }
167
+ }
168
+ }
169
+ ]
170
+ }
171
+ },
172
+ "llamaCpp": {
173
+ "type": "object",
174
+ "properties": {
175
+ "url": { "type": "string" },
176
+ "model": { "type": "string" },
177
+ "modelsDirectory": { "type": "string" },
178
+ "args": { "type": "array", "items": { "type": "string" } }
179
+ }
180
+ },
181
+ "ollama": {
182
+ "type": "object",
183
+ "properties": {
184
+ "url": { "type": "string" },
185
+ "modelsDirectory": { "type": "string" },
186
+ "args": { "type": "array", "items": { "type": "string" } }
187
+ }
188
+ }
189
+ }
190
+ },
137
191
  "pwa": {
138
192
  "type": "object",
139
193
  "additionalProperties": false,
@@ -5,6 +5,7 @@ import {readFile, writeFile} from 'node:fs/promises';
5
5
  import path from 'node:path';
6
6
  import {throwIfAborted} from './errors.mjs';
7
7
  import {normalizePwaConfig} from './pwa.mjs';
8
+ import {normalizeLocalAIConfig} from './local-ai/config.mjs';
8
9
  import {
9
10
  ARCANE_MACHINE_BUNDLE_VERSION,
10
11
  ARCANE_PROTOCOL,
@@ -111,13 +112,21 @@ function validateLocalAIModelPolicy(value,label){
111
112
 
112
113
  function validatePackage(value,appId){
113
114
  assertOnlyKeys(value,new Set([
114
- 'entry','strategy','include','exclude','shared','adapter','localAIModelPolicy','pwa','documents'
115
+ 'entry','strategy','include','exclude','shared','adapter','localAIModelPolicy','pwa','documents','nativeResources'
115
116
  ]),'descriptor.package');
116
117
  const entry=normalizeRelativePath(value.entry,'descriptor.package.entry');
117
118
  const documents=normalizeAppDocuments(value.documents,'descriptor.package.documents');
118
119
  if(!['static','adapter'].includes(value.strategy))fail('descriptor.package.strategy must be static or adapter.');
119
120
  const include=relativePaths(value.include,'descriptor.package.include',{required:true});
120
121
  const exclude=relativePaths(value.exclude??[],'descriptor.package.exclude');
122
+ let nativeResources;
123
+ if(value.nativeResources!==undefined){
124
+ assertOnlyKeys(value.nativeResources,new Set(['include','exclude']),'descriptor.package.nativeResources');
125
+ nativeResources={
126
+ include:relativePaths(value.nativeResources.include,'descriptor.package.nativeResources.include'),
127
+ exclude:relativePaths(value.nativeResources.exclude??[],'descriptor.package.nativeResources.exclude')
128
+ };
129
+ }
121
130
  if(!include.some(candidate=>entry===candidate||entry.startsWith(`${candidate}/`))){
122
131
  fail('descriptor.package.entry must be covered by descriptor.package.include.');
123
132
  }
@@ -140,6 +149,7 @@ function validatePackage(value,appId){
140
149
  strategy:value.strategy,
141
150
  include,
142
151
  exclude,
152
+ ...(nativeResources===undefined?{}:{nativeResources}),
143
153
  shared,
144
154
  ...(value.pwa===undefined?{}:{pwa:normalizePwaConfig(value.pwa)}),
145
155
  ...(adapter?{adapter}:{}),
@@ -226,7 +236,7 @@ function validateDocumentCatalog(value){
226
236
  }
227
237
 
228
238
  function validateNative(value,appId){
229
- assertOnlyKeys(value,new Set(['type','icon','order','bundledApps','documentCatalog']),'descriptor.native');
239
+ assertOnlyKeys(value,new Set(['type','icon','order','bundledApps','documentCatalog','localAI','services']),'descriptor.native');
230
240
  if(!NATIVE_TYPES.has(value.type))fail('descriptor.native.type is unsupported.');
231
241
  const icon=value.icon===null?null:normalizeRelativePath(value.icon,'descriptor.native.icon');
232
242
  if(!is.integer(value.order)||value.order<0){
@@ -236,11 +246,21 @@ function validateNative(value,appId){
236
246
  pattern:APP_ID_PATTERN
237
247
  });
238
248
  const documentCatalog=validateDocumentCatalog(value.documentCatalog);
249
+ let services;
250
+ if(value.services!==undefined){
251
+ if(!is.array(value.services))fail('descriptor.native.services must be an array.');
252
+ services=value.services.map(function nativeService(service,index){
253
+ if(!isObject(service))fail(`descriptor.native.services[${index}] must be an object.`);
254
+ return {...service,module:normalizeRelativePath(service.module,`descriptor.native.services[${index}].module`)};
255
+ });
256
+ }
239
257
  return completeValue({
240
258
  type:value.type,
241
259
  icon,
242
260
  order:value.order,
243
261
  bundledApps,
262
+ ...(services===undefined?{}:{services}),
263
+ ...(value.localAI===undefined?{}:{localAI:normalizeLocalAIConfig(value.localAI)}),
244
264
  ...(documentCatalog?{documentCatalog}:{})
245
265
  });
246
266
  }
@@ -328,9 +348,14 @@ export function projectPackageManifest(descriptor){
328
348
  mediaOrigins:[...value.security.mediaOrigins]
329
349
  }}:{}),
330
350
  ...(value.package.localAIModelPolicy?{localAIModelPolicy:value.package.localAIModelPolicy}:{}),
351
+ ...(value.native.localAI===undefined?{}:{localAI:value.native.localAI}),
331
352
  ...(value.package.pwa===undefined?{}:{pwa:value.package.pwa}),
332
353
  include:[...value.package.include],
333
354
  exclude:[...value.package.exclude],
355
+ ...(value.package.nativeResources===undefined?{}:{nativeResources:{
356
+ include:[...value.package.nativeResources.include],
357
+ exclude:[...value.package.nativeResources.exclude]
358
+ }}),
334
359
  shared:[...value.package.shared],
335
360
  ...(value.package.adapter?{adapter:value.package.adapter}:{})
336
361
  };
@@ -369,6 +394,8 @@ export function projectNativeDescriptor(descriptor,{source}={}){
369
394
  }}:{}),
370
395
  ...(value.native.bundledApps.length?{bundledApps:[...value.native.bundledApps]}:{}),
371
396
  ...(value.native.documentCatalog?{documentCatalog:value.native.documentCatalog}:{}),
397
+ ...(value.native.services===undefined?{}:{services:value.native.services}),
398
+ ...(value.native.localAI===undefined?{}:{localAI:value.native.localAI}),
372
399
  include:[...value.package.include]
373
400
  };
374
401
  }
@@ -391,6 +418,10 @@ function synthesizedDescriptor(packageManifest,nativeDescriptor){
391
418
  ...(packageManifest.pwa===undefined?{}:{pwa:packageManifest.pwa}),
392
419
  include:[...packageManifest.include],
393
420
  exclude:[...(packageManifest.exclude??[])],
421
+ ...(packageManifest.nativeResources===undefined?{}:{nativeResources:{
422
+ include:[...packageManifest.nativeResources.include],
423
+ exclude:[...(packageManifest.nativeResources.exclude??[])]
424
+ }}),
394
425
  shared:[...packageManifest.shared],
395
426
  ...(packageManifest.adapter?{adapter:packageManifest.adapter}:{})
396
427
  },
@@ -408,6 +439,8 @@ function synthesizedDescriptor(packageManifest,nativeDescriptor){
408
439
  icon:native.icon??null,
409
440
  order:is.integer(native.order)?native.order:100,
410
441
  bundledApps:[...(native.bundledApps??[])],
442
+ ...(native.services===undefined?{}:{services:native.services}),
443
+ ...((packageManifest.localAI??native.localAI)===undefined?{}:{localAI:packageManifest.localAI??native.localAI}),
411
444
  ...(native.documentCatalog?{documentCatalog:native.documentCatalog}:{})
412
445
  },
413
446
  requirements:{
@@ -9,8 +9,12 @@ export function resolveAppRoot(workspaceRoot, config, appId) {
9
9
  return path.resolve(workspaceRoot, appRelativeRoot(config, appId));
10
10
  }
11
11
 
12
- export function resolvePackageOutputRoot(workspaceRoot, config, app) {
13
- return path.resolve(workspaceRoot, app.outputDirectory ?? `${config.distRoot}/${app.id}`);
12
+ export function resolvePackageOutputRoot(workspaceRoot, config, app, {target='browser', outputDirectory}={}) {
13
+ const nativeSelection=target!=='browser'&&app.nativeResources?.include.length>0;
14
+ const selectedDirectory=outputDirectory??(nativeSelection
15
+ ?`${config.distRoot}/.native/${target}/${app.id}`
16
+ :app.outputDirectory??`${config.distRoot}/${app.id}`);
17
+ return path.resolve(workspaceRoot,selectedDirectory);
14
18
  }
15
19
 
16
20
  export function appBaseHref(workspaceRoot, appRoot, document = 'index.html') {
package/src/cli/main.mjs CHANGED
@@ -16,6 +16,7 @@ const VALUE_OPTIONS=new Set([
16
16
  'app',
17
17
  'apps-root',
18
18
  'sdk-runtime-source',
19
+ 'local-ai',
19
20
  'arcane-root',
20
21
  'host',
21
22
  'port',
@@ -66,7 +67,7 @@ Usage:
66
67
  ${CLI_NAME} upgrade [--workspace <directory>] [--app <id>]
67
68
  ${CLI_NAME} doctor [--workspace <directory>] [--arcane-root <directory>]
68
69
  ${CLI_NAME} import-map [--workspace <directory>] [--app <id>]
69
- ${CLI_NAME} dev [--app <id>] [--public] [--http | --https] [--cert <pem>] [--key <pem>] [--host <address>] [--port 8000] [--http-port 0] [--sdk-runtime-source <sdk-root>]
70
+ ${CLI_NAME} dev [--app <id>] [--public] [--http | --https] [--cert <pem>] [--key <pem>] [--host <address>] [--port 8000] [--http-port 0] [--sdk-runtime-source <sdk-root>] [--local-ai llama.cpp,ollama]
70
71
  ${CLI_NAME} test [--app <id>] [--scope app]
71
72
  ${CLI_NAME} test --scope shared --test-file <repo-relative.test.mjs>
72
73
  ${CLI_NAME} check [--app <id>] [--scope app] [--skip-tests]
@@ -97,6 +98,7 @@ Development:
97
98
  --host <address> Override the bind address; takes precedence over --public.
98
99
  --http-port <port> Browser dev/run HTTP redirect port; 0 selects an available port (default).
99
100
  --sdk-runtime-source <sdk-root> Dev-only live SDK checkout; omitted preserves the workspace runtime mode.
101
+ --local-ai <runtimes> Add required Core runtimes to app config and ensure them for development.
100
102
 
101
103
  Global:
102
104
  --workspace <directory> Select an external or integrated Arcane workspace.
@@ -450,6 +452,9 @@ function operationOptions(command,parsed,cwd){
450
452
  if(values['sdk-runtime-source']!==undefined&&command!=='dev'){
451
453
  usage('--sdk-runtime-source is supported only by dev.');
452
454
  }
455
+ if(values['local-ai']!==undefined&&command!=='dev'){
456
+ usage('--local-ai is supported only by dev.');
457
+ }
453
458
  if(flags.has('public')&&command!=='dev'){
454
459
  usage('--public is supported only by dev.');
455
460
  }
@@ -546,6 +551,7 @@ function operationOptions(command,parsed,cwd){
546
551
  ...browserServerOptions,
547
552
  host:values.host??(flags.has('public')?'0.0.0.0':'127.0.0.1'),
548
553
  port:readPort(values.port,8000),
554
+ ...(values['local-ai']===undefined?{}:{localAI:values['local-ai']}),
549
555
  ...(values['sdk-runtime-source']===undefined?{}:{
550
556
  sdkRuntimeSourceRoot:path.resolve(cwd,values['sdk-runtime-source'])
551
557
  })
@@ -811,7 +817,7 @@ async function pairNativeProvider(command,options,loadProvider,{signal,onEvent}=
811
817
  usage(`${command} requires one native target.`);
812
818
  }
813
819
  const targetRequest=createNativeTargetRequest(options);
814
- if(!options.arcaneRoot&&options.target!=='portable'){
820
+ if(!options.arcaneRoot&&!['portable','windows-x64'].includes(options.target)){
815
821
  usage(`${command} for target ${options.target} requires --arcane-root <directory>.`);
816
822
  }
817
823
  const loaded=await loadProvider({
package/src/core/host.mjs CHANGED
@@ -1,6 +1,29 @@
1
+ import {readFile} from 'node:fs/promises';
2
+ import Is from 'strong-type';
1
3
  import {createCoreRuntime} from './runtime.mjs';
2
4
  import {startCoreStdio} from './stdio.mjs';
3
5
 
6
+ const is = new Is(false);
7
+
8
+ /**
9
+ * Read only the explicitly supplied --arcane-launch-config filename.
10
+ * Return the complete JSON object for the service factories' second argument;
11
+ * field meanings belong to the launch owner and individual services.
12
+ */
13
+ export async function readCoreLaunchContext({argv = process.argv.slice(2)} = {}) {
14
+ const flag = argv.indexOf('--arcane-launch-config');
15
+ if (flag === -1) return {};
16
+ const filename = argv[flag + 1];
17
+ if (filename === undefined || filename === '') {
18
+ throw new TypeError('--arcane-launch-config requires a filename.');
19
+ }
20
+ const context = JSON.parse(await readFile(filename, 'utf8'));
21
+ if (context === null || !is.object(context) || is.array(context)) {
22
+ throw new TypeError('Core launch configuration must be a JSON object.');
23
+ }
24
+ return context;
25
+ }
26
+
4
27
  /** Compose app services with the shared dispatcher and draining transport. */
5
28
  export function startCoreHost({application, version, services = [], input, output, onError, signal} = {}) {
6
29
  const runtime = createCoreRuntime({application, version, services});
@@ -0,0 +1,2 @@
1
+ // This ordinary filesystem module retains Node's asynchronous ESM loader.
2
+ module.exports = import('./arcane-core.mjs');