arcane-os 0.5.0 → 0.5.2
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 +13 -0
- package/README.md +11 -8
- package/bin/arcane-test.mjs +22 -10
- package/package.json +1 -1
- package/runtime/arcane/modules/AI.js +15 -6
- package/src/application-tests.mjs +7 -0
- package/src/import-map.mjs +49 -0
- package/src/toolchain.mjs +11 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.2
|
|
4
|
+
|
|
5
|
+
- Forwarded each application's existing managed browser import map into
|
|
6
|
+
isolated `arcane test` and `arcane check` files so valid `arcane/*` imports
|
|
7
|
+
resolve from the selected workspace runtime while integrated-legacy apps
|
|
8
|
+
retain their established no-map path.
|
|
9
|
+
|
|
10
|
+
## 0.5.1
|
|
11
|
+
|
|
12
|
+
- Separated the TWiN Cloud LLM access key from the legacy OpenAI speech
|
|
13
|
+
credential so neither provider's key is sent to the other's endpoint while
|
|
14
|
+
retaining the established `ai.license` alias for TWiN Cloud chat.
|
|
15
|
+
|
|
3
16
|
## 0.5.0
|
|
4
17
|
|
|
5
18
|
- Added bounded parallel Browser-WASM model downloads across split GGUF
|
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ version-locked SDK runtime, while an integrated Arcane checkout uses its live
|
|
|
19
19
|
`arcane/` runtime. Both profiles preserve the same app URLs, theme, packaging,
|
|
20
20
|
event, cancellation, and browser run contracts.
|
|
21
21
|
|
|
22
|
-
This checkout defines the `0.5.
|
|
22
|
+
This checkout defines the `0.5.2` SDK contract. Applications pin one exact npm
|
|
23
23
|
version and lockfile; registry state is deliberately not baked into application
|
|
24
24
|
artifacts.
|
|
25
25
|
|
|
@@ -88,7 +88,10 @@ OpenAI-compatible chat-completion requests to
|
|
|
88
88
|
`globalThis.arcane.config.twinCloud.accessKey`. The established `ai.license`
|
|
89
89
|
property and internal `OPENAI` route identifier remain compatibility aliases;
|
|
90
90
|
applications should present the service and credential as **TWiN Cloud** and
|
|
91
|
-
**TWiN access key**.
|
|
91
|
+
**TWiN access key**. The TWiN key is used only for remote LLM chat. Existing
|
|
92
|
+
OpenAI speech routes remain separate and read only their legacy
|
|
93
|
+
`globalThis.arcane.config.openAI.apiKey`; the SDK never sends a TWiN key to
|
|
94
|
+
those speech endpoints.
|
|
92
95
|
|
|
93
96
|
## Browser-local AI
|
|
94
97
|
|
|
@@ -162,7 +165,7 @@ uses the same controller for automatic memory extraction.
|
|
|
162
165
|
Create a new repository-shaped Arcane application with the exact stable SDK:
|
|
163
166
|
|
|
164
167
|
```bash
|
|
165
|
-
npx arcane-os@0.5.
|
|
168
|
+
npx arcane-os@0.5.2 new my-app --path ./my-app --target portable --git
|
|
166
169
|
cd my-app
|
|
167
170
|
npm install
|
|
168
171
|
npm run check
|
|
@@ -173,7 +176,7 @@ To enroll an existing repository, install the exact SDK and initialize only
|
|
|
173
176
|
missing Arcane files:
|
|
174
177
|
|
|
175
178
|
```bash
|
|
176
|
-
npm install --save-dev --save-exact arcane-os@0.5.
|
|
179
|
+
npm install --save-dev --save-exact arcane-os@0.5.2
|
|
177
180
|
npm exec -- arcane init my-app --target portable
|
|
178
181
|
```
|
|
179
182
|
|
|
@@ -189,7 +192,7 @@ npm exec -- arcane-os targets
|
|
|
189
192
|
No global SDK install or standalone Arcane CLI is required. The application
|
|
190
193
|
repository's exact npm dependency and lockfile own the CLI and toolchain version.
|
|
191
194
|
|
|
192
|
-
Use `npx arcane-os@0.5.
|
|
195
|
+
Use `npx arcane-os@0.5.2` for the initial bootstrap because it names this npm
|
|
193
196
|
package explicitly; bare `npx arcane` outside an installed project could resolve
|
|
194
197
|
a different package. Both installed commands invoke the same headless toolchain.
|
|
195
198
|
Project-local npm scripts use the SDK pinned by that app's `package-lock.json`,
|
|
@@ -210,7 +213,7 @@ node ./bin/arcane.mjs new local-app --path ../local-app --target portable --git
|
|
|
210
213
|
|
|
211
214
|
# From the generated app repository
|
|
212
215
|
cd ../local-app
|
|
213
|
-
npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.5.
|
|
216
|
+
npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.5.2.tgz
|
|
214
217
|
npm run check
|
|
215
218
|
npm ci
|
|
216
219
|
```
|
|
@@ -220,7 +223,7 @@ same location. The lockfile retains the selected package dependency while
|
|
|
220
223
|
Arcane uses the installed package name and version. Local directory `file:` dependencies are not
|
|
221
224
|
accepted because npm may install them as links; use a packed `.tgz`. A GitHub
|
|
222
225
|
runner also needs that tarball at the locked path. After publication, replace
|
|
223
|
-
the local declaration with the exact `arcane-os@0.5.
|
|
226
|
+
the local declaration with the exact `arcane-os@0.5.2` registry package and
|
|
224
227
|
commit the regenerated lock.
|
|
225
228
|
|
|
226
229
|
Generated repositories use `npm ci --ignore-scripts` in CI. Run dependency
|
|
@@ -358,7 +361,7 @@ package installation, or assertions.
|
|
|
358
361
|
|
|
359
362
|
## Current target support
|
|
360
363
|
|
|
361
|
-
Version `0.5.
|
|
364
|
+
Version `0.5.2` exposes one browser target and five explicitly paired
|
|
362
365
|
native development targets: a non-runnable portable directory, a
|
|
363
366
|
Windows x64 unsigned-local-test EXE bundle, Linux x64 and Linux ARM64
|
|
364
367
|
unsigned-local-test DEBs, and an Android development-signed APK. The
|
package/bin/arcane-test.mjs
CHANGED
|
@@ -81,18 +81,30 @@ async function readManagedImportMapContext(source,signal,testFile){
|
|
|
81
81
|
:context.boundary==='test'
|
|
82
82
|
?testCodeRoot
|
|
83
83
|
:path.join(applicationRoot,'dist');
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
84
|
+
const workspaceRoot=path.resolve(process.cwd());
|
|
85
|
+
const requestedBases=context.boundary==='source'&&pathInside(workspaceRoot,applicationRoot)
|
|
86
|
+
?[expectedBase,workspaceRoot]
|
|
87
|
+
:[expectedBase];
|
|
88
|
+
let canonicalBase=null;
|
|
89
|
+
for(const requestedBase of requestedBases){
|
|
90
|
+
let baseInfo;
|
|
91
|
+
let candidate;
|
|
92
|
+
try{
|
|
93
|
+
baseInfo=await lstat(requestedBase);
|
|
94
|
+
candidate=await realpath(requestedBase);
|
|
95
|
+
}catch{
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if(!baseInfo.isSymbolicLink()&&baseInfo.isDirectory()
|
|
99
|
+
&&samePath(requestedBase,candidate)&&samePath(suppliedBase,candidate)){
|
|
100
|
+
canonicalBase=candidate;
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
91
103
|
}
|
|
92
|
-
if(
|
|
93
|
-
||!samePath(expectedBase,canonicalBase)||!samePath(suppliedBase,canonicalBase)){
|
|
104
|
+
if(canonicalBase===null){
|
|
94
105
|
importMapFailure(
|
|
95
|
-
`Managed import-map base URL must select this application's physical ${context.boundary} directory
|
|
106
|
+
`Managed import-map base URL must select this application's physical ${context.boundary} directory`
|
|
107
|
+
+(context.boundary==='source'?' or its physical workspace source directory.':'.')
|
|
96
108
|
);
|
|
97
109
|
}
|
|
98
110
|
throwIfCancelled(signal);
|
package/package.json
CHANGED
|
@@ -969,16 +969,21 @@ class AI {
|
|
|
969
969
|
return {
|
|
970
970
|
OPENAI: {
|
|
971
971
|
'Content-Type': 'application/json',
|
|
972
|
-
'Authorization': `Bearer ${this.
|
|
972
|
+
'Authorization': `Bearer ${this.twinKey}`
|
|
973
973
|
}
|
|
974
974
|
};
|
|
975
975
|
}
|
|
976
976
|
|
|
977
|
+
get #legacyOpenAISpeechKey(){
|
|
978
|
+
const value=globalThis.arcane?.config?.openAI?.apiKey;
|
|
979
|
+
return typeof value==='string'?value:'';
|
|
980
|
+
}
|
|
981
|
+
|
|
977
982
|
get #ttsHeaders(){
|
|
978
983
|
return {
|
|
979
984
|
OPENAI: {
|
|
980
985
|
'Content-Type': 'application/json',
|
|
981
|
-
'Authorization': `Bearer ${this
|
|
986
|
+
'Authorization': `Bearer ${this.#legacyOpenAISpeechKey}`
|
|
982
987
|
},
|
|
983
988
|
LOCAL_SPEACH: {
|
|
984
989
|
'Content-Type': 'application/json',
|
|
@@ -989,7 +994,7 @@ class AI {
|
|
|
989
994
|
get #sttHeaders(){
|
|
990
995
|
return {
|
|
991
996
|
OPENAI: {
|
|
992
|
-
'Authorization': `Bearer ${this
|
|
997
|
+
'Authorization': `Bearer ${this.#legacyOpenAISpeechKey}`,
|
|
993
998
|
},
|
|
994
999
|
LOCAL_SPEACH: {}
|
|
995
1000
|
};
|
|
@@ -1160,7 +1165,6 @@ class AI {
|
|
|
1160
1165
|
get twinKey(){
|
|
1161
1166
|
return this.#license
|
|
1162
1167
|
||globalThis.arcane?.config?.twinCloud?.accessKey
|
|
1163
|
-
||globalThis.arcane?.config?.openAI?.apiKey
|
|
1164
1168
|
||'';
|
|
1165
1169
|
}
|
|
1166
1170
|
|
|
@@ -1442,7 +1446,8 @@ class AI {
|
|
|
1442
1446
|
return false;
|
|
1443
1447
|
}
|
|
1444
1448
|
if(providerId==='OPENAI'){
|
|
1445
|
-
return Boolean(this
|
|
1449
|
+
return Boolean(this.#legacyOpenAISpeechKey)
|
|
1450
|
+
&&typeof globalThis.fetch==='function';
|
|
1446
1451
|
}
|
|
1447
1452
|
if(providerId==='LOCAL_SPEACH'){
|
|
1448
1453
|
return Boolean(this.#nativeSpeech(service,role));
|
|
@@ -1870,7 +1875,11 @@ class AI {
|
|
|
1870
1875
|
throw error;
|
|
1871
1876
|
}
|
|
1872
1877
|
|
|
1873
|
-
if(service==='OPENAI'&&
|
|
1878
|
+
if(service==='OPENAI'&&(
|
|
1879
|
+
role==='llm'
|
|
1880
|
+
?Boolean(this.twinKey)
|
|
1881
|
+
:Boolean(this.#legacyOpenAISpeechKey)
|
|
1882
|
+
)){
|
|
1874
1883
|
return true;
|
|
1875
1884
|
}
|
|
1876
1885
|
|
|
@@ -5,6 +5,7 @@ import {ArcaneError,ERROR_CODES,throwIfAborted} from './errors.mjs';
|
|
|
5
5
|
import {runProcess} from './process.mjs';
|
|
6
6
|
|
|
7
7
|
const TEST_RUNNER_PATH=fileURLToPath(new URL('../bin/arcane-test.mjs',import.meta.url));
|
|
8
|
+
const MANAGED_IMPORT_MAP_ENV='ARCANE_TEST_IMPORT_MAP_CONTEXT';
|
|
8
9
|
|
|
9
10
|
function normalizedTestOutput(value){
|
|
10
11
|
return typeof value==='string'?value:'';
|
|
@@ -129,6 +130,7 @@ export async function runApplicationTests({
|
|
|
129
130
|
workspaceMode='packager',
|
|
130
131
|
appId,
|
|
131
132
|
appRoot,
|
|
133
|
+
managedImportMapContext=null,
|
|
132
134
|
signal,
|
|
133
135
|
onEvent
|
|
134
136
|
}={}){
|
|
@@ -160,6 +162,11 @@ export async function runApplicationTests({
|
|
|
160
162
|
[TEST_RUNNER_PATH,testFile],
|
|
161
163
|
{
|
|
162
164
|
cwd:workspaceRoot,
|
|
165
|
+
env:{
|
|
166
|
+
[MANAGED_IMPORT_MAP_ENV]:managedImportMapContext===null
|
|
167
|
+
?''
|
|
168
|
+
:JSON.stringify(managedImportMapContext)
|
|
169
|
+
},
|
|
163
170
|
signal,
|
|
164
171
|
onEvent,
|
|
165
172
|
allowNonzero:true
|
package/src/import-map.mjs
CHANGED
|
@@ -1602,6 +1602,55 @@ export async function createApplicationTestImportMapContext({
|
|
|
1602
1602
|
};
|
|
1603
1603
|
}
|
|
1604
1604
|
|
|
1605
|
+
export async function readApplicationTestImportMapContext({
|
|
1606
|
+
workspaceRoot,
|
|
1607
|
+
applicationRoot,
|
|
1608
|
+
signal
|
|
1609
|
+
}={}){
|
|
1610
|
+
if(typeof workspaceRoot!=='string'||workspaceRoot.trim()===''){
|
|
1611
|
+
throw new TypeError('workspaceRoot must be a nonempty string.');
|
|
1612
|
+
}
|
|
1613
|
+
if(typeof applicationRoot!=='string'||applicationRoot.trim()===''){
|
|
1614
|
+
throw new TypeError('applicationRoot must be a nonempty string.');
|
|
1615
|
+
}
|
|
1616
|
+
throwIfAborted(signal);
|
|
1617
|
+
const resolvedWorkspaceRoot=path.resolve(workspaceRoot);
|
|
1618
|
+
const resolvedApplicationRoot=path.resolve(applicationRoot);
|
|
1619
|
+
await physicalDirectory(resolvedWorkspaceRoot,resolvedApplicationRoot);
|
|
1620
|
+
const artifactPath=path.join(
|
|
1621
|
+
resolvedApplicationRoot,
|
|
1622
|
+
...IMPORT_MAP_RELATIVE_PATH.split('/')
|
|
1623
|
+
);
|
|
1624
|
+
let source;
|
|
1625
|
+
try{
|
|
1626
|
+
source=await readPhysicalTextFile(
|
|
1627
|
+
resolvedApplicationRoot,
|
|
1628
|
+
artifactPath,
|
|
1629
|
+
'Application test import-map artifact'
|
|
1630
|
+
);
|
|
1631
|
+
}catch(error){
|
|
1632
|
+
if(error?.code==='ENOENT')return null;
|
|
1633
|
+
throw error;
|
|
1634
|
+
}
|
|
1635
|
+
throwIfAborted(signal);
|
|
1636
|
+
let document;
|
|
1637
|
+
try{document=JSON.parse(source);}
|
|
1638
|
+
catch(error){
|
|
1639
|
+
fail(`Application test import-map artifact is not valid JSON: ${error.message}`);
|
|
1640
|
+
}
|
|
1641
|
+
if(document===null||typeof document!=='object'||Array.isArray(document)
|
|
1642
|
+
||document.imports===null||typeof document.imports!=='object'
|
|
1643
|
+
||Array.isArray(document.imports)){
|
|
1644
|
+
fail('Application test import-map artifact must contain an imports object.');
|
|
1645
|
+
}
|
|
1646
|
+
return createApplicationTestImportMapContext({
|
|
1647
|
+
applicationRoot:resolvedWorkspaceRoot,
|
|
1648
|
+
boundary:'source',
|
|
1649
|
+
imports:document.imports,
|
|
1650
|
+
signal
|
|
1651
|
+
});
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1605
1654
|
async function generateImportMapUnlocked({
|
|
1606
1655
|
workspaceRoot,
|
|
1607
1656
|
appId,
|
package/src/toolchain.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from './workspace.mjs';
|
|
11
11
|
import {loadArcaneIntegratedProvider} from './integrated-provider-loader.mjs';
|
|
12
12
|
import {startDevServer} from './dev-server.mjs';
|
|
13
|
-
import {generateImportMap} from './import-map.mjs';
|
|
13
|
+
import {generateImportMap,readApplicationTestImportMapContext} from './import-map.mjs';
|
|
14
14
|
import {withWorkspaceOperationLock} from './workspace-operation-lock.mjs';
|
|
15
15
|
import {
|
|
16
16
|
inspectApp as inspectPackagedApp,
|
|
@@ -389,11 +389,21 @@ export async function doctorApplication(options={}){
|
|
|
389
389
|
|
|
390
390
|
async function runPreparedApplicationTests(prepared,options={}){
|
|
391
391
|
throwIfAborted(options.signal);
|
|
392
|
+
const integratedLegacy=prepared.workspaceMode==='integrated'
|
|
393
|
+
&&prepared.validation.config.browserRuntimeLayout==='integrated-legacy';
|
|
394
|
+
const managedImportMapContext=integratedLegacy
|
|
395
|
+
?null
|
|
396
|
+
:await readApplicationTestImportMapContext({
|
|
397
|
+
workspaceRoot:prepared.workspaceRoot,
|
|
398
|
+
applicationRoot:prepared.appRoot,
|
|
399
|
+
signal:options.signal
|
|
400
|
+
});
|
|
392
401
|
return runApplicationTests({
|
|
393
402
|
workspaceRoot:prepared.workspaceRoot,
|
|
394
403
|
workspaceMode:prepared.workspaceMode,
|
|
395
404
|
appId:prepared.appId,
|
|
396
405
|
appRoot:prepared.appRoot,
|
|
406
|
+
managedImportMapContext,
|
|
397
407
|
signal:options.signal,
|
|
398
408
|
onEvent:options.onEvent
|
|
399
409
|
});
|