arcane-os 0.56.0 → 0.58.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 (75) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +6 -2
  3. package/browser-runtime/ai/core-local.mjs +306 -0
  4. package/browser-runtime/ai/core-onnx.mjs +96 -0
  5. package/browser-runtime/ai/onnx-tensors.mjs +85 -0
  6. package/browser-runtime/core/client.mjs +151 -16
  7. package/docs/architecture.md +5 -2
  8. package/docs/platform-targets.md +30 -13
  9. package/docs/reference/README.md +38 -3
  10. package/docs/reference/availability-and-normalization.md +9 -6
  11. package/docs/reference/cli.md +29 -9
  12. package/docs/reference/core-client.md +80 -0
  13. package/docs/reference/core-native-packaging.md +304 -12
  14. package/docs/reference/core-preferences.md +49 -0
  15. package/docs/reference/diarization.md +206 -0
  16. package/docs/reference/git-text-snapshot.md +104 -0
  17. package/docs/reference/inventory/package-api.json +330 -22
  18. package/docs/reference/local-ai.md +331 -0
  19. package/docs/reference/protocols.md +97 -20
  20. package/docs/reference/pwa.md +5 -2
  21. package/docs/reference/runtime-modules.md +6 -5
  22. package/docs/reference/sdk-api.md +611 -63
  23. package/package.json +15 -3
  24. package/runtime/arcane/modules/LocalAIReadiness.js +3 -1
  25. package/schemas/arcane-app.schema.json +55 -1
  26. package/schemas/arcane-package.schema.json +44 -0
  27. package/schemas/native-build-plan.schema.json +27 -1
  28. package/schemas/target-adapter.schema.json +2 -0
  29. package/src/app-descriptor.mjs +18 -2
  30. package/src/cli/main.mjs +20 -4
  31. package/src/constants.mjs +2 -0
  32. package/src/core/host.mjs +23 -0
  33. package/src/core/hosts/macos/ArcaneCoreProcess.h +85 -0
  34. package/src/core/hosts/macos/ArcaneCoreProcess.m +357 -0
  35. package/src/core/hosts/macos/ArcaneHost.h +86 -0
  36. package/src/core/hosts/macos/ArcaneHost.m +455 -0
  37. package/src/core/hosts/macos/ArcaneLauncher.m +432 -0
  38. package/src/core/hosts/windows/ArcaneCoreProcess.cs +3 -3
  39. package/src/core/hosts/windows/ArcaneHost.cs +695 -0
  40. package/src/core/hosts/windows/ArcaneHost.manifest +22 -0
  41. package/src/core/hosts/windows/ArcaneLauncher.cs +239 -0
  42. package/src/core/services/local-ai.mjs +621 -0
  43. package/src/core/services/packaged-web.mjs +89 -0
  44. package/src/core/services/preferences.mjs +201 -0
  45. package/src/core-classic-source.mjs +2 -2
  46. package/src/dev-server.mjs +9 -5
  47. package/src/diarization/build.mjs +35 -0
  48. package/src/diarization/index.mjs +432 -0
  49. package/src/diarization/native/CMakeLists.txt +9 -0
  50. package/src/diarization/native/main.cpp +269 -0
  51. package/src/diarization/service.mjs +129 -0
  52. package/src/git-text-snapshot.mjs +265 -0
  53. package/src/import-map.mjs +2 -0
  54. package/src/index.mjs +1 -0
  55. package/src/local-ai/archive.mjs +52 -0
  56. package/src/local-ai/config.mjs +62 -0
  57. package/src/local-ai/dev.mjs +390 -0
  58. package/src/local-ai/discover.mjs +71 -0
  59. package/src/local-ai/http.mjs +106 -0
  60. package/src/local-ai/install.mjs +459 -0
  61. package/src/local-ai/onnx-worker.mjs +85 -0
  62. package/src/local-ai/onnx.mjs +368 -0
  63. package/src/local-ai/server.mjs +262 -0
  64. package/src/native/macos-provider.mjs +291 -0
  65. package/src/native/portable-layout.mjs +37 -4
  66. package/src/native/portable-provider.mjs +44 -8
  67. package/src/native/windows-provider.mjs +262 -0
  68. package/src/native-plan.mjs +9 -1
  69. package/src/native-provider-loader.mjs +10 -2
  70. package/src/packager/core.mjs +6 -2
  71. package/src/process.mjs +184 -20
  72. package/src/scaffold.mjs +1 -1
  73. package/src/targets/index.mjs +11 -1
  74. package/src/templates/workspace-template.mjs +6 -5
  75. package/src/toolchain.mjs +33 -5
@@ -0,0 +1,262 @@
1
+ import {createWriteStream} from 'node:fs';
2
+ import {mkdir, mkdtemp, readFile, readdir, realpath, rename, rm, stat, writeFile} from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import {Readable} from 'node:stream';
5
+ import {pipeline} from 'node:stream/promises';
6
+ import Is from 'strong-type';
7
+ import {ArcaneError, ERROR_CODES, throwIfAborted} from '../errors.mjs';
8
+ import {runProcess} from '../process.mjs';
9
+ import {createPortableNativeProvider} from './portable-provider.mjs';
10
+ import {writeOutput} from './portable-layout.mjs';
11
+
12
+ const is = new Is(false);
13
+ const PROTOCOL = 'arcane-native-builder/1';
14
+ const MANIFEST = 'arcane-native.json';
15
+ const HOST_ASSET = 'arcane-native-windows-x64.tar.gz';
16
+ const HOST_FILES = [
17
+ 'Arcane.exe',
18
+ 'Arcane.exe.config',
19
+ 'Microsoft.Web.WebView2.Core.dll',
20
+ 'Microsoft.Web.WebView2.WinForms.dll',
21
+ 'WebView2Loader.dll',
22
+ 'runtime/ArcaneCore.exe',
23
+ 'runtime/arcane-core-loader.cjs',
24
+ 'runtime/NODE-LICENSE',
25
+ 'WEBVIEW2-LICENSE',
26
+ 'LICENSE',
27
+ 'COMMERCIAL-LICENSE.md',
28
+ 'NOTICE'
29
+ ];
30
+
31
+ function windowsTarget(value) {
32
+ if (value?.target !== 'windows-x64' || value.platform !== 'windows'
33
+ || value.architecture !== 'x64' || value.format !== 'exe') {
34
+ throw new ArcaneError(ERROR_CODES.targetUnavailable, 'The Windows provider assembles windows-x64 executables.');
35
+ }
36
+ return {...value};
37
+ }
38
+
39
+ function portableRequest(target) {
40
+ return {...target, target: 'portable', format: 'portable'};
41
+ }
42
+
43
+ async function emit(onEvent, event) {
44
+ if (is.function(onEvent)) await onEvent(event);
45
+ }
46
+
47
+ async function hostFilesMissing(directory) {
48
+ const missing = [];
49
+ for (const relative of HOST_FILES) {
50
+ try {
51
+ if (!(await stat(path.join(directory, relative))).isFile()) missing.push(relative);
52
+ } catch (error) {
53
+ if (error.code !== 'ENOENT') throw error;
54
+ missing.push(relative);
55
+ }
56
+ }
57
+ return missing;
58
+ }
59
+
60
+ async function requireHostDirectory(directory) {
61
+ const resolved = await realpath(directory);
62
+ const missing = await hostFilesMissing(resolved);
63
+ if (missing.length) {
64
+ throw new ArcaneError(ERROR_CODES.prerequisiteMissing, 'The selected Windows host assets are incomplete.', {
65
+ details: {hostDirectory: resolved, missing}
66
+ });
67
+ }
68
+ return resolved;
69
+ }
70
+
71
+ async function removeOwnedDirectory(directory, error) {
72
+ try { await rm(directory, {recursive: true, force: true}); }
73
+ catch (cleanupError) {
74
+ throw new AggregateError([error, cleanupError], `Windows preparation failed; incomplete output remains at ${directory}.`);
75
+ }
76
+ throw error;
77
+ }
78
+
79
+ async function releasedHostDirectory(outputRoot, version, {signal, onEvent}) {
80
+ const cache = path.join(outputRoot, '.arcane-native-hosts', version);
81
+ const destination = path.join(cache, 'windows-x64');
82
+ try { return await requireHostDirectory(destination); }
83
+ catch (error) { if (error.code !== 'ENOENT') throw error; }
84
+ throwIfAborted(signal);
85
+ await mkdir(cache, {recursive: true});
86
+ const temporary = await mkdtemp(path.join(cache, 'preparing-windows-x64-'));
87
+ const archive = path.join(temporary, HOST_ASSET);
88
+ const extracted = path.join(temporary, 'host');
89
+ const url = `https://github.com/TheWizardNexus/arcane-os-sdk/releases/download/${version}/${HOST_ASSET}`;
90
+ try {
91
+ await emit(onEvent, {type: 'native.host.download.started', target: 'windows-x64', version, url});
92
+ throwIfAborted(signal);
93
+ const response = await fetch(url, {signal});
94
+ if (!response.ok) {
95
+ throw new ArcaneError(ERROR_CODES.prerequisiteMissing, `The SDK Windows host download returned HTTP ${response.status}.`, {
96
+ details: {url, status: response.status, response: await response.text()}
97
+ });
98
+ }
99
+ if (!response.body) throw new ArcaneError(ERROR_CODES.operationFailed, 'The SDK Windows host download returned no content.', {details: {url}});
100
+ await pipeline(Readable.fromWeb(response.body), createWriteStream(archive, {flags: 'wx'}), {signal});
101
+ throwIfAborted(signal);
102
+ await mkdir(extracted);
103
+ await runProcess('tar', ['-xzf', archive, '-C', extracted], {cwd: temporary, signal, onEvent});
104
+ await requireHostDirectory(extracted);
105
+ throwIfAborted(signal);
106
+ try { await rename(extracted, destination); }
107
+ catch (error) {
108
+ // Another preparation may have finished this exact version while
109
+ // this download was in flight. Preserve its complete asset tree.
110
+ if (!['EEXIST', 'ENOTEMPTY', 'EPERM'].includes(error.code)) throw error;
111
+ try { await requireHostDirectory(destination); }
112
+ catch { throw error; }
113
+ }
114
+ await rm(temporary, {recursive: true, force: true});
115
+ await emit(onEvent, {type: 'native.host.download.completed', target: 'windows-x64', version, hostDirectory: destination});
116
+ throwIfAborted(signal);
117
+ return destination;
118
+ } catch (error) {
119
+ return removeOwnedDirectory(temporary, error);
120
+ }
121
+ }
122
+
123
+ function contains(root, candidate) {
124
+ const relative = path.relative(root, candidate);
125
+ return relative === '' || (!path.isAbsolute(relative) && relative !== '..' && !relative.startsWith(`..${path.sep}`));
126
+ }
127
+
128
+ async function copyHostAssets(source, root, files, signal, relative = '', ancestors = new Set()) {
129
+ throwIfAborted(signal);
130
+ const canonical = await realpath(source);
131
+ const output = await realpath(root);
132
+ if (contains(canonical, output) || contains(output, canonical) || ancestors.has(canonical)) {
133
+ throw new ArcaneError(ERROR_CODES.usage, 'Windows host assets overlap their output or contain a directory cycle.', {
134
+ details: {source, outputRoot: root}
135
+ });
136
+ }
137
+ const parents = new Set([...ancestors, canonical]);
138
+ for (const entry of await readdir(source, {withFileTypes: true})) {
139
+ throwIfAborted(signal);
140
+ const selected = path.join(source, entry.name);
141
+ const destination = relative ? `${relative}/${entry.name}` : entry.name;
142
+ const information = await stat(selected);
143
+ if (information.isDirectory()) {
144
+ await mkdir(path.join(root, destination), {recursive: true});
145
+ await copyHostAssets(selected, root, files, signal, destination, parents);
146
+ } else if (information.isFile()) {
147
+ await writeOutput(root, destination, await readFile(selected, {signal}), files, signal);
148
+ } else {
149
+ throw new ArcaneError(ERROR_CODES.operationFailed, `Cannot copy Windows host asset ${selected}.`);
150
+ }
151
+ }
152
+ }
153
+
154
+ /** Compose app-owned services with the SDK's precompiled Windows host. */
155
+ export function createWindowsNativeProvider({services, hostDirectory} = {}) {
156
+ const portable = createPortableNativeProvider(services === undefined ? {} : {services});
157
+ return {
158
+ protocol: PROTOCOL,
159
+ async describe() {
160
+ return {
161
+ protocol: PROTOCOL,
162
+ id: 'arcane-sdk-windows-x64',
163
+ targets: ['windows-x64'],
164
+ executable: true,
165
+ serviceConfiguration: 'explicit release module factories'
166
+ };
167
+ },
168
+ async doctor({targetRequest, signal, onEvent} = {}) {
169
+ throwIfAborted(signal);
170
+ const target = windowsTarget(targetRequest);
171
+ const core = await portable.doctor({targetRequest: portableRequest(target), signal});
172
+ const missing = hostDirectory === undefined ? [] : await hostFilesMissing(path.resolve(hostDirectory));
173
+ const result = {
174
+ target: 'windows-x64', executable: true, ready: core.ready && missing.length === 0,
175
+ sdkRoot: core.sdkRoot, missing: [...core.missing, ...missing],
176
+ hostDirectory: hostDirectory === undefined ? null : path.resolve(hostDirectory),
177
+ hostSource: hostDirectory === undefined ? 'sdk-release' : 'selected-directory',
178
+ downloadRequired: hostDirectory === undefined,
179
+ runtimePrerequisite: 'Microsoft Edge WebView2 Runtime',
180
+ buildPrerequisite: hostDirectory === undefined ? 'tar' : null
181
+ };
182
+ await emit(onEvent, {type: 'native.doctor.completed', ...result});
183
+ throwIfAborted(signal);
184
+ return result;
185
+ },
186
+ async prepare({targetRequest, signal, onEvent} = {}) {
187
+ throwIfAborted(signal);
188
+ const target = windowsTarget(targetRequest);
189
+ const selection = await portable.prepare({targetRequest: portableRequest(target), signal});
190
+ const selected = hostDirectory === undefined ? undefined : await requireHostDirectory(hostDirectory);
191
+ // Downloads belong to build(), which has the caller's selected
192
+ // outputRoot. Preparation never invents a machine-wide cache.
193
+ const result = {...selection, target, executable: true, ...(selected ? {hostDirectory: selected} : {})};
194
+ await emit(onEvent, {type: 'native.prepared', target: 'windows-x64', sdkVersion: selection.sdkVersion});
195
+ throwIfAborted(signal);
196
+ return result;
197
+ },
198
+ async build(request = {}) {
199
+ const {signal, onEvent} = request;
200
+ throwIfAborted(signal);
201
+ const target = windowsTarget(request.targetRequest);
202
+ const selected = hostDirectory ?? request.toolchain?.hostDirectory;
203
+ const selectedHost = selected === undefined ? null : await requireHostDirectory(selected);
204
+ const artifact = await portable.build({
205
+ ...request,
206
+ targetRequest: portableRequest(target),
207
+ async onEvent(event) {
208
+ if (event.type !== 'native.payload.completed') await emit(onEvent, {...event, target: 'windows-x64'});
209
+ }
210
+ });
211
+ const root = artifact.target.rootDir;
212
+ try {
213
+ const host = selectedHost ?? await releasedHostDirectory(path.resolve(request.outputRoot), artifact.manifest.sdk.version, {signal, onEvent});
214
+ const files = [...artifact.manifest.files];
215
+ await copyHostAssets(host, root, files, signal);
216
+ const manifest = {
217
+ ...artifact.manifest,
218
+ kind: 'arcane-windows-native',
219
+ target,
220
+ host: {
221
+ executable: 'Arcane.exe',
222
+ platform: 'windows', architecture: 'x64', runtime: 'webview2',
223
+ coreExecutable: 'runtime/ArcaneCore.exe',
224
+ coreLoader: 'runtime/arcane-core-loader.cjs'
225
+ },
226
+ files
227
+ };
228
+ await writeFile(path.join(root, MANIFEST), `${JSON.stringify(manifest, null, 2)}\n`, {signal});
229
+ throwIfAborted(signal);
230
+ await emit(onEvent, {type: 'native.payload.completed', target: 'windows-x64', appId: request.appDescriptor.id, outputRoot: root});
231
+ throwIfAborted(signal);
232
+ return {...artifact, target: {...target, rootDir: root}, manifest};
233
+ } catch (error) {
234
+ return removeOwnedDirectory(root, error);
235
+ }
236
+ },
237
+ async verify({artifact, targetRequest, signal, onEvent} = {}) {
238
+ throwIfAborted(signal);
239
+ const target = windowsTarget(targetRequest);
240
+ const result = await portable.verify({artifact, targetRequest: portableRequest(target), signal});
241
+ await requireHostDirectory(result.outputRoot);
242
+ const verified = {...result, target: 'windows-x64', executable: true};
243
+ await emit(onEvent, {type: 'native.verified', target: 'windows-x64', outputRoot: result.outputRoot});
244
+ throwIfAborted(signal);
245
+ return verified;
246
+ },
247
+ async run({artifact, targetRequest, signal, onEvent} = {}) {
248
+ throwIfAborted(signal);
249
+ windowsTarget(targetRequest);
250
+ if (process.platform !== 'win32' || process.arch !== 'x64') {
251
+ throw new ArcaneError(ERROR_CODES.nativeRunUnsupported, 'Running this Windows x64 artifact requires a Windows x64 host.');
252
+ }
253
+ const root = await requireHostDirectory(artifact?.target?.rootDir);
254
+ return runProcess(path.join(root, 'Arcane.exe'), ['--close-on-stdin-eof'], {
255
+ cwd: root, signal, onEvent, cancellationMode: 'close-input'
256
+ });
257
+ }
258
+ };
259
+ }
260
+
261
+ export const arcaneNativeBuilderProvider = createWindowsNativeProvider();
262
+ export default arcaneNativeBuilderProvider;
@@ -12,7 +12,7 @@ export const NATIVE_BUILDER_PROTOCOL='arcane-native-builder/1';
12
12
 
13
13
  const NATIVE_TARGETS={
14
14
  portable:{
15
- platforms:new Set(['windows','linux']),
15
+ platforms:new Set(['windows','linux','macos']),
16
16
  architectures:new Set(['x64','arm64']),
17
17
  format:'portable',
18
18
  signingMode:'unsigned-local-test',
@@ -22,6 +22,14 @@ const NATIVE_TARGETS={
22
22
  platforms:new Set(['windows']),architectures:new Set(['x64']),format:'exe',
23
23
  signingMode:'unsigned-local-test',signingProfileId:null
24
24
  },
25
+ 'macos-arm64':{
26
+ platforms:new Set(['macos']),architectures:new Set(['arm64']),format:'app',
27
+ signingMode:'unsigned-local-test',signingProfileId:null
28
+ },
29
+ 'macos-x64':{
30
+ platforms:new Set(['macos']),architectures:new Set(['x64']),format:'app',
31
+ signingMode:'unsigned-local-test',signingProfileId:null
32
+ },
25
33
  'linux-x64':{
26
34
  platforms:new Set(['linux']),architectures:new Set(['x64']),format:'deb',
27
35
  signingMode:'unsigned-local-test',signingProfileId:null
@@ -22,6 +22,13 @@ const FIXED_NATIVE_PROVIDER_PATHS={
22
22
  'android-arm64':providerPath('android-native-provider.mjs')
23
23
  };
24
24
 
25
+ const SDK_NATIVE_PROVIDER_PATHS={
26
+ portable:'./native/portable-provider.mjs',
27
+ 'windows-x64':'./native/windows-provider.mjs',
28
+ 'macos-arm64':'./native/macos-provider.mjs',
29
+ 'macos-x64':'./native/macos-provider.mjs'
30
+ };
31
+
25
32
  export const ARCANE_NATIVE_PROVIDER_PATHS={
26
33
  portable:[...FIXED_NATIVE_PROVIDER_PATHS.portable],
27
34
  'windows-x64':[...FIXED_NATIVE_PROVIDER_PATHS['windows-x64']],
@@ -133,10 +140,11 @@ export async function loadArcaneNativeProvider(options={}){
133
140
  target,
134
141
  message:`Loading the Arcane ${String(target)} provider.`
135
142
  });
136
- const location=arcaneRoot===undefined&&target==='portable'
143
+ const sdkProvider=SDK_NATIVE_PROVIDER_PATHS[target];
144
+ const location=arcaneRoot===undefined&&sdkProvider
137
145
  ?{
138
146
  canonicalRoot:path.resolve(fileURLToPath(new URL('../',import.meta.url))),
139
- providerPath:fileURLToPath(new URL('./native/portable-provider.mjs',import.meta.url)),
147
+ providerPath:fileURLToPath(new URL(sdkProvider,import.meta.url)),
140
148
  providerSource:'sdk-package'
141
149
  }
142
150
  :await resolveProviderLocation({arcaneRoot,target,inspect,canonicalize});
@@ -14,6 +14,7 @@ import path from 'node:path';
14
14
  import {pathToFileURL} from 'node:url';
15
15
  import {appRelativeRoot,resolveAppRoot,resolvePackageOutputRoot} from '../app-layout.mjs';
16
16
  import {TARGET_IDS} from '../constants.mjs';
17
+ import {normalizeLocalAIConfig} from '../local-ai/config.mjs';
17
18
  import {readInstalledSdkLayout} from '../sdk-runtime-layout.mjs';
18
19
  import {withWorkspaceOperationLock} from '../workspace-operation-lock.mjs';
19
20
  import {materializeNativeModules} from './native-modules.mjs';
@@ -321,7 +322,7 @@ function normalizeOptionalRecord(value,label){
321
322
  export function validateAppConfig(value,appId,rootConfig,configPath=path.posix.join(appRelativeRoot(rootConfig,appId),APP_CONFIG_NAME)){
322
323
  assertOnlyKeys(value,new Set([
323
324
  'schemaVersion','id','displayName','version','entry','strategy','security',
324
- 'localAIModelPolicy','include','exclude','shared','adapter','pwa','outputDirectory','documents','nativeResources'
325
+ 'localAIModelPolicy','localAI','include','exclude','shared','adapter','pwa','outputDirectory','documents','nativeResources'
325
326
  ]),`${appId}/${APP_CONFIG_NAME}`);
326
327
  if(value.schemaVersion!==1)fail(`${appId}/${APP_CONFIG_NAME}.schemaVersion must be 1.`);
327
328
  if(!is.string(value.id)||value.id!==appId||!APP_ID_PATTERN.test(value.id)){
@@ -390,6 +391,7 @@ export function validateAppConfig(value,appId,rootConfig,configPath=path.posix.j
390
391
  value.localAIModelPolicy,
391
392
  `${appId}/${APP_CONFIG_NAME}.localAIModelPolicy`
392
393
  )}),
394
+ ...(value.localAI===undefined?{}:{localAI:normalizeLocalAIConfig(value.localAI)}),
393
395
  include,
394
396
  exclude,
395
397
  ...(nativeResources===undefined?{}:{nativeResources}),
@@ -742,6 +744,7 @@ async function inspectContext(context,{signal,records}={}){
742
744
  ...(context.config.localAIModelPolicy===undefined?{}:{
743
745
  localAIModelPolicy:copyJson(context.config.localAIModelPolicy)
744
746
  }),
747
+ ...(context.config.localAI===undefined?{}:{localAI:copyJson(context.config.localAI)}),
745
748
  ...(context.config.adapter===undefined?{}:{adapter:context.config.adapter}),
746
749
  descriptor:await optionalDescriptor(context),
747
750
  browserDocuments:documents,
@@ -871,7 +874,8 @@ function releaseManifest(context,files,pwaArtifacts,rootDocument){
871
874
  ...(context.config.security===undefined?{}:{security:copyJson(context.config.security)}),
872
875
  ...(context.config.localAIModelPolicy===undefined?{}:{
873
876
  localAIModelPolicy:copyJson(context.config.localAIModelPolicy)
874
- })
877
+ }),
878
+ ...(context.config.localAI===undefined?{}:{localAI:copyJson(context.config.localAI)})
875
879
  },
876
880
  files:[...files]
877
881
  };