arcane-os 0.29.0 → 0.30.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.
@@ -47,6 +47,7 @@ const SDK_BROWSER_SELF_IMPORTS=new Map([
47
47
  ['arcane-os/speech-text','sdk/speech-text.mjs'],
48
48
  ['arcane-os/ai/browser-wasm',SDK_BROWSER_AI_ENTRY],
49
49
  ['arcane-os/ai/tool-text-stream','sdk/ai/tool-text-stream.mjs'],
50
+ ['arcane-os/ai/twin-cloud','sdk/ai/twin-cloud.mjs'],
50
51
  ['arcane-os/ai/browser-speech',SDK_BROWSER_SPEECH_ENTRY]
51
52
  ]);
52
53
  function fail(message,code='ARCANE_IMPORT_MAP_INVALID'){
@@ -847,7 +848,7 @@ function assetUrlVersionEdits(value,version){
847
848
  let decodedKey=key;
848
849
  try{decodedKey=decodeURIComponent(key.replaceAll('+',' '));}
849
850
  catch{decodedKey=key;}
850
- const remove=decodedKey==='v'||(decodedKey==='arcaneVersion'&&(clean||versionFound));
851
+ const remove=decodedKey==='arcaneVersion'&&(clean||versionFound);
851
852
  parameters.push({start:offset,end:offset+parameter.length,remove});
852
853
  if(decodedKey==='arcaneVersion'&&!versionFound&&!clean){
853
854
  versionFound=true;
@@ -859,10 +860,10 @@ function assetUrlVersionEdits(value,version){
859
860
  }
860
861
  offset+=parameter.length+1;
861
862
  }
862
- if(clean&&!parameters.some(function hasRemainingField(parameter){
863
- return !parameter.remove&&parameter.end>parameter.start;
863
+ if(clean&&parameters.every(function removesSdkField(parameter){
864
+ return parameter.remove;
864
865
  }))return [{start:queryStart,end:address.length,value:''}];
865
- // Remove adjacent obsolete fields together, including only their separator.
866
+ // Remove adjacent SDK version fields together, including only their separator.
866
867
  // Other field spelling and source-level escapes remain untouched.
867
868
  for(let index=0;index<parameters.length;index+=1){
868
869
  if(!parameters[index].remove)continue;
@@ -874,13 +875,10 @@ function assetUrlVersionEdits(value,version){
874
875
  value:''
875
876
  });
876
877
  }
877
- const lastRetained=parameters.findLast(function retainedParameter(parameter){
878
- return !parameter.remove;
879
- });
880
878
  if(!clean&&!versionFound)edits.push({
881
879
  start:address.length,
882
880
  end:address.length,
883
- value:`${lastRetained&&lastRetained.end>lastRetained.start?'&':''}arcaneVersion=${versionValue}`
881
+ value:`&arcaneVersion=${versionValue}`
884
882
  });
885
883
  return edits;
886
884
  }
@@ -93,7 +93,7 @@ function splitUstarPath(archivePath){
93
93
  return {name,prefix};
94
94
  }
95
95
  }
96
- fail(`Archive path cannot be represented by ustar: ${normalized}.`);
96
+ return null;
97
97
  }
98
98
 
99
99
  function writeTextField(header,offset,length,value,label){
@@ -112,8 +112,10 @@ function writeOctalField(header,offset,length,value,label,{trailingSpace=false}=
112
112
  header.write(rendered,offset,length,'ascii');
113
113
  }
114
114
 
115
- export function createCanonicalUstarHeader(archivePath,size){
116
- const {name,prefix}=splitUstarPath(archivePath);
115
+ function createTarHeader(archivePath,size,type){
116
+ const fields=splitUstarPath(archivePath);
117
+ if(!fields)fail(`Archive path cannot be represented by ustar: ${archivePath}.`);
118
+ const {name,prefix}=fields;
117
119
  const header=Buffer.alloc(TAR_BLOCK_SIZE);
118
120
  writeTextField(header,0,100,name,'ustar name');
119
121
  writeOctalField(header,100,8,ARCHIVE_MODE,'ustar mode');
@@ -122,7 +124,7 @@ export function createCanonicalUstarHeader(archivePath,size){
122
124
  writeOctalField(header,124,12,size,'ustar size');
123
125
  writeOctalField(header,136,12,0,'ustar mtime');
124
126
  header.fill(0x20,148,156);
125
- header[156]=0x30;
127
+ header[156]=type;
126
128
  header.write('ustar\0',257,6,'ascii');
127
129
  header.write('00',263,2,'ascii');
128
130
  writeTextField(header,265,32,'root','ustar owner');
@@ -134,13 +136,45 @@ export function createCanonicalUstarHeader(archivePath,size){
134
136
  return header;
135
137
  }
136
138
 
137
- function tarEntry(archivePath,content){
138
- const header=createCanonicalUstarHeader(archivePath,content.length);
139
+ export function createCanonicalUstarHeader(archivePath,size){
140
+ return createTarHeader(archivePath,size,0x30);
141
+ }
142
+
143
+ function tarRecord(archivePath,content,type=0x30){
144
+ const header=createTarHeader(archivePath,content.length,type);
139
145
  const remainder=content.length%TAR_BLOCK_SIZE;
140
146
  const padding=remainder===0?Buffer.alloc(0):Buffer.alloc(TAR_BLOCK_SIZE-remainder);
141
147
  return Buffer.concat([header,content,padding]);
142
148
  }
143
149
 
150
+ function paxPathRecord(archivePath) {
151
+ const value = Buffer.from(` path=${archivePath}\n`, 'utf8');
152
+ // POSIX PAX framing includes the decimal prefix itself in the record length.
153
+ let length = value.length + 1;
154
+ while(String(length).length + value.length !== length) {
155
+ length = String(length).length + value.length;
156
+ }
157
+ const prefix = Buffer.from(String(length), 'ascii');
158
+ return Buffer.concat(
159
+ [prefix, value]
160
+ );
161
+ }
162
+
163
+ function tarEntry(archivePath, content) {
164
+ const fields = splitUstarPath(archivePath);
165
+ if(fields && !/[^\u0000-\u007f]/u.test(archivePath)) {
166
+ return tarRecord(archivePath, content);
167
+ }
168
+ // The following regular header is only a placeholder; PAX owns the complete path.
169
+ const extendedPath = paxPathRecord(archivePath);
170
+ return Buffer.concat(
171
+ [
172
+ tarRecord('PaxHeaders/entry', extendedPath, 0x78),
173
+ tarRecord('PaxPayload/entry', content)
174
+ ]
175
+ );
176
+ }
177
+
144
178
  async function realDirectory(location,label){
145
179
  const requested=path.resolve(location);
146
180
  let info;
@@ -308,9 +342,53 @@ function readOctalField(header,offset,length,label){
308
342
  return value;
309
343
  }
310
344
 
345
+ function readPaxPath(content) {
346
+ let offset = 0;
347
+ let archivePath;
348
+ while(offset < content.length) {
349
+ const separator = content.indexOf(0x20, offset);
350
+ if(separator < 0) {
351
+ fail('Bundle contains malformed PAX record framing.');
352
+ }
353
+ const lengthText = content.subarray(offset, separator).toString('latin1');
354
+ if(!/^[1-9][0-9]*$/u.test(lengthText)) {
355
+ fail('Bundle contains malformed PAX record framing.');
356
+ }
357
+ const length = Number(lengthText);
358
+ const end = offset + length;
359
+ if(!is.safeInteger(length) || end > content.length || end <= separator + 1
360
+ || content[end - 1] !== 0x0a) {
361
+ fail('Bundle contains an incomplete or malformed PAX record.');
362
+ }
363
+ const assignment = content.indexOf(0x3d, separator + 1);
364
+ if(assignment <= separator + 1 || assignment >= end - 1) {
365
+ fail('Bundle contains a malformed PAX assignment.');
366
+ }
367
+ const keyword = content.subarray(separator + 1, assignment).toString('utf8');
368
+ if(keyword !== 'path') {
369
+ fail(`Bundle contains an unsupported PAX field: ${keyword}.`);
370
+ }
371
+ try {
372
+ const decoder = new TextDecoder(
373
+ 'utf-8',
374
+ {fatal: true, ignoreBOM: true}
375
+ );
376
+ archivePath = decoder.decode(content.subarray(assignment + 1, end - 1));
377
+ } catch (error) {
378
+ fail(`Bundle PAX path is not valid UTF-8: ${error.message}.`);
379
+ }
380
+ offset = end;
381
+ }
382
+ if(archivePath === undefined) {
383
+ fail('Bundle PAX header is missing its path.');
384
+ }
385
+ return archivePath;
386
+ }
387
+
311
388
  function readTarEntries(archive){
312
389
  const entries=new Map();
313
390
  let offset=0;
391
+ let pendingPath;
314
392
  while(offset+TAR_BLOCK_SIZE<=archive.length){
315
393
  const header=archive.subarray(offset,offset+TAR_BLOCK_SIZE);
316
394
  if(header.every(value=>value===0))break;
@@ -321,21 +399,31 @@ function readTarEntries(archive){
321
399
  let actualChecksum=0;
322
400
  for(const value of checksumHeader)actualChecksum+=value;
323
401
  if(actualChecksum!==expectedChecksum)fail('Bundle contains a malformed ustar header.');
324
- if(header[156]!==0&&header[156]!==0x30)fail('Bundle contains a non-file archive entry.');
402
+ const type=header[156];
403
+ if(type!==0&&type!==0x30&&type!==0x78)fail('Bundle contains a non-file archive entry.');
325
404
  const name=readStringField(header,0,100);
326
405
  const prefix=readStringField(header,345,155);
327
- const archivePath=validateAppBundlePath(prefix?`${prefix}/${name}`:name,'archive path');
328
- if(entries.has(pathKey(archivePath)))fail(`Bundle contains a duplicate path: ${archivePath}.`);
406
+ const headerPath=prefix?`${prefix}/${name}`:name;
329
407
  const size=readOctalField(header,124,12,'ustar size');
330
408
  const contentStart=offset+TAR_BLOCK_SIZE;
331
409
  const contentEnd=contentStart+size;
332
- if(contentEnd>archive.length)fail(`Bundle entry is incomplete: ${archivePath}.`);
410
+ if(contentEnd>archive.length)fail(`Bundle entry is incomplete: ${pendingPath??headerPath}.`);
411
+ const content=archive.subarray(contentStart,contentEnd);
412
+ offset=contentStart+Math.ceil(size/TAR_BLOCK_SIZE)*TAR_BLOCK_SIZE;
413
+ if(type===0x78){
414
+ if(pendingPath!==undefined)fail('Bundle PAX path is missing its following file.');
415
+ pendingPath=readPaxPath(content);
416
+ continue;
417
+ }
418
+ const archivePath=validateAppBundlePath(pendingPath??headerPath,'archive path');
419
+ pendingPath=undefined;
420
+ if(entries.has(pathKey(archivePath)))fail(`Bundle contains a duplicate path: ${archivePath}.`);
333
421
  entries.set(pathKey(archivePath),{
334
422
  path:archivePath,
335
- content:Buffer.from(archive.subarray(contentStart,contentEnd))
423
+ content:Buffer.from(content)
336
424
  });
337
- offset=contentStart+Math.ceil(size/TAR_BLOCK_SIZE)*TAR_BLOCK_SIZE;
338
425
  }
426
+ if(pendingPath!==undefined)fail('Bundle PAX path is missing its following file.');
339
427
  return entries;
340
428
  }
341
429