arcane-os 0.3.5 → 0.4.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0
4
+
5
+ - Preserved complete finite provider progress values and units as
6
+ informational state, including fractional and out-of-range observations,
7
+ while keeping the visual percentage bounded and exposing ARIA range values
8
+ only when the reported range is valid.
9
+ - Preserved complete file selections, timelines, relationship graphs, source
10
+ content, message advisories, record passages, and date findings without
11
+ arbitrary count or character clipping, and surfaced malformed stored-review
12
+ data instead of silently replacing it.
13
+ - Made workspace and scam-policy hardening explicitly opt in, with ordinary
14
+ complete workspace inputs and `secure:false` scam analysis remaining fully
15
+ functional without restrictive policy admission or frozen results.
16
+ - Removed the product-owned `CaseEvidenceIndexer.js` module and
17
+ `TWiNPolicyDecision.js` entity from the shared runtime payload and generated
18
+ browser import map so application policy remains owned by each consumer.
19
+
20
+ - Removed the unused `RevocableProjectionLedger.js` runtime artifact and its
21
+ public contract. No authored SDK or Arcane OS application source consumes it,
22
+ and its mandatory capacity, character, UTF-8 byte, and node budgets,
23
+ fingerprinting, freezing, and pristine-descriptor admission conflict with the
24
+ ordinary functional baseline.
25
+
26
+ ## 0.3.6
27
+
28
+ - Replaced coherent selected-but-unregistered STT and TTS placeholders during
29
+ initial explicit browser-speech configuration while preserving rejection of
30
+ registered, busy, local-only, or internally divergent selections and keeping
31
+ the new providers unloaded until the application activates them.
32
+ - Preserved complete finite provider progress values and units as
33
+ informational, non-gating state in the reusable formatter and shared Chat
34
+ activation interface.
35
+
3
36
  ## 0.3.5
4
37
 
5
38
  - Preserved complete AI and chat runtime content across multi-choice streaming,
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.3.5` SDK contract. Applications pin one exact npm
22
+ This checkout defines the `0.4.0` 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
 
@@ -126,7 +126,7 @@ uses the same controller for automatic memory extraction.
126
126
  Create a new repository-shaped Arcane application with the exact stable SDK:
127
127
 
128
128
  ```bash
129
- npx arcane-os@0.3.5 new my-app --path ./my-app --target portable --git
129
+ npx arcane-os@0.4.0 new my-app --path ./my-app --target portable --git
130
130
  cd my-app
131
131
  npm install
132
132
  npm run check
@@ -137,7 +137,7 @@ To enroll an existing repository, install the exact SDK and initialize only
137
137
  missing Arcane files:
138
138
 
139
139
  ```bash
140
- npm install --save-dev --save-exact arcane-os@0.3.5
140
+ npm install --save-dev --save-exact arcane-os@0.4.0
141
141
  npm exec -- arcane init my-app --target portable
142
142
  ```
143
143
 
@@ -153,7 +153,7 @@ npm exec -- arcane-os targets
153
153
  No global SDK install or standalone Arcane CLI is required. The application
154
154
  repository's exact npm dependency and lockfile own the CLI and toolchain version.
155
155
 
156
- Use `npx arcane-os@0.3.5` for the initial bootstrap because it names this npm
156
+ Use `npx arcane-os@0.4.0` for the initial bootstrap because it names this npm
157
157
  package explicitly; bare `npx arcane` outside an installed project could resolve
158
158
  a different package. Both installed commands invoke the same headless toolchain.
159
159
  Project-local npm scripts use the SDK pinned by that app's `package-lock.json`,
@@ -174,7 +174,7 @@ node ./bin/arcane.mjs new local-app --path ../local-app --target portable --git
174
174
 
175
175
  # From the generated app repository
176
176
  cd ../local-app
177
- npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.3.5.tgz
177
+ npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.4.0.tgz
178
178
  npm run check
179
179
  npm ci
180
180
  ```
@@ -184,7 +184,7 @@ same location. The lockfile retains the selected package dependency while
184
184
  Arcane uses the installed package name and version. Local directory `file:` dependencies are not
185
185
  accepted because npm may install them as links; use a packed `.tgz`. A GitHub
186
186
  runner also needs that tarball at the locked path. After publication, replace
187
- the local declaration with the exact `arcane-os@0.3.5` registry package and
187
+ the local declaration with the exact `arcane-os@0.4.0` registry package and
188
188
  commit the regenerated lock.
189
189
 
190
190
  Generated repositories use `npm ci --ignore-scripts` in CI. Run dependency
@@ -322,7 +322,7 @@ package installation, or assertions.
322
322
 
323
323
  ## Current target support
324
324
 
325
- Version `0.3.5` exposes one browser target and five explicitly paired
325
+ Version `0.4.0` exposes one browser target and five explicitly paired
326
326
  native development targets: a non-runnable portable directory, a
327
327
  Windows x64 unsigned-local-test EXE bundle, Linux x64 and Linux ARM64
328
328
  unsigned-local-test DEBs, and an Android development-signed APK. The
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcane-os",
3
- "version": "0.3.5",
3
+ "version": "0.4.0",
4
4
  "description": "Arcane OS JavaScript SDK, project-local CLI, browser runtime, and repository-portable application packager.",
5
5
  "type": "module",
6
6
  "main": "./src/index.mjs",
@@ -1877,13 +1877,13 @@
1877
1877
  ){
1878
1878
  const messageId=String(id);
1879
1879
  const normalizedTotal=Number.isFinite(Number(total))
1880
- ?Math.max(0,Math.floor(Number(total)))
1880
+ ?Number(total)
1881
1881
  :0;
1882
1882
  const normalizedCurrent=Number.isFinite(Number(current))
1883
- ?Math.min(normalizedTotal,Math.max(0,Math.floor(Number(current))))
1883
+ ?Number(current)
1884
1884
  :0;
1885
1885
  const normalizedFailed=Number.isFinite(Number(failed))
1886
- ?Math.max(0,Math.floor(Number(failed)))
1886
+ ?Number(failed)
1887
1887
  :0;
1888
1888
  const message=messageId
1889
1889
  ?[...chatOutput.children].find(
@@ -1936,7 +1936,7 @@
1936
1936
  ?`${visibleStatus}${warningLabel}`
1937
1937
  :`${normalizedCurrent} of ${normalizedTotal}${warningLabel}`;
1938
1938
  const percent=normalizedTotal>0
1939
- ?normalizedCurrent/normalizedTotal*100
1939
+ ?Math.min(100,Math.max(0,normalizedCurrent/normalizedTotal*100))
1940
1940
  :0;
1941
1941
 
1942
1942
  labelElement.textContent=visibleLabel;
@@ -1949,10 +1949,18 @@
1949
1949
  progress.removeAttribute('aria-valuemin');
1950
1950
  progress.removeAttribute('aria-valuemax');
1951
1951
  progress.removeAttribute('aria-valuenow');
1952
- }else{
1952
+ }else if(
1953
+ normalizedTotal>0
1954
+ &&normalizedCurrent>=0
1955
+ &&normalizedCurrent<=normalizedTotal
1956
+ ){
1953
1957
  progress.setAttribute('aria-valuemin','0');
1954
1958
  progress.setAttribute('aria-valuemax',String(normalizedTotal));
1955
1959
  progress.setAttribute('aria-valuenow',String(normalizedCurrent));
1960
+ }else{
1961
+ progress.removeAttribute('aria-valuemin');
1962
+ progress.removeAttribute('aria-valuemax');
1963
+ progress.removeAttribute('aria-valuenow');
1956
1964
  }
1957
1965
  progress.setAttribute(
1958
1966
  'aria-valuetext',
@@ -2044,16 +2052,6 @@
2044
2052
  return configured||role?.modelId||'the selected language model';
2045
2053
  }
2046
2054
 
2047
- function byteProgressUnit(value){
2048
- const unit=String(value??'')
2049
- .trim()
2050
- .toLowerCase()
2051
- .replace(/[\s_-]+/g,'');
2052
- return unit.includes('byte')
2053
- ||unit.includes('octet')
2054
- ||/^(?:[kmgtpe]?i?b)(?:(?:\/|per)?(?:s|sec|second))?$/u.test(unit);
2055
- }
2056
-
2057
2055
  function determinateProgress(){
2058
2056
  const total=Number(role?.progress?.total);
2059
2057
  const completed=Number(role?.progress?.completed);
@@ -2061,15 +2059,14 @@
2061
2059
  ?role.progress.unit
2062
2060
  :'items';
2063
2061
  if(
2064
- byteProgressUnit(unit)
2065
- ||!Number.isFinite(total)
2062
+ !Number.isFinite(total)
2066
2063
  ||total<=0
2067
2064
  ||!Number.isFinite(completed)
2068
2065
  ){
2069
2066
  return null;
2070
2067
  }
2071
2068
  return {
2072
- completed:Math.max(0,completed),
2069
+ completed,
2073
2070
  total,
2074
2071
  unit
2075
2072
  };
@@ -2106,7 +2103,7 @@
2106
2103
  return;
2107
2104
  }
2108
2105
  progress.max=measured.total;
2109
- progress.value=Math.min(measured.completed,measured.total);
2106
+ progress.value=measured.completed;
2110
2107
  }
2111
2108
 
2112
2109
  function render(){
@@ -166,8 +166,8 @@
166
166
 
167
167
  function normalizeValue(value=''){
168
168
  const next=`${value??''}`.trim();
169
- if(next.length>4096||/[\u0000-\u001f\u007f]/.test(next)){
170
- throw new TypeError('The directory path must be bounded plain text.');
169
+ if(/[\u0000-\u001f\u007f]/.test(next)){
170
+ throw new TypeError('The directory path must be plain text.');
171
171
  }
172
172
  return next;
173
173
  }
@@ -188,18 +188,7 @@
188
188
  if(!files.length){
189
189
  return false;
190
190
  }
191
- if(options.maxFiles>0&&files.length>options.maxFiles){
192
- setError(
193
- `Select no more than ${options.maxFiles} file${options.maxFiles===1?'':'s'}.`,
194
- {
195
- code:'ARCANE_FILE_DROP_FILE_COUNT_LIMIT_EXCEEDED',
196
- reason:'file-count-limit-exceeded'
197
- },
198
- operationId
199
- );
200
- return false;
201
- }
202
- currentFiles=options.multiple?files:files.slice(0,1);
191
+ currentFiles=files;
203
192
  setError('');
204
193
  feedback.hidden=false;
205
194
  statusElement.className='status';
@@ -50,27 +50,29 @@
50
50
 
51
51
  function normalizeItem(item,index){
52
52
  const source=item&&typeof item==='object'?item:{};
53
- const date=String(source.date||source.isoDate||'').slice(0,32);
53
+ const date=String(source.date||source.isoDate||'');
54
54
  if(!source.id||!date) return null;
55
55
  return {
56
56
  ...source,
57
- id:String(source.id).slice(0,180),
57
+ id:String(source.id),
58
58
  date,
59
- title:String(source.title||'Dated event').slice(0,500),
60
- summary:String(source.summary||'').slice(0,2400),
61
- category:String(source.category||source.kind||'Event').slice(0,100),
62
- actor:String(source.actor||'Actor not assigned').slice(0,180),
63
- sourceLabel:String(source.sourceLabel||'').slice(0,240),
64
- status:String(source.status||'').slice(0,120),
65
- factualPosture:String(source.factualPosture||'').slice(0,160),
66
- sourceCount:Math.max(0,Math.min(9999,Number(source.sourceCount)||0)),
59
+ title:String(source.title||'Dated event'),
60
+ summary:String(source.summary||''),
61
+ category:String(source.category||source.kind||'Event'),
62
+ actor:String(source.actor||'Actor not assigned'),
63
+ sourceLabel:String(source.sourceLabel||''),
64
+ status:String(source.status||''),
65
+ factualPosture:String(source.factualPosture||''),
66
+ sourceCount:Number.isFinite(Number(source.sourceCount))&&Number(source.sourceCount)>=0
67
+ ?Number(source.sourceCount)
68
+ :0,
67
69
  order:index
68
70
  };
69
71
  }
70
72
 
71
73
  function setItems(value=[],input={}){
72
74
  options={...options,...(input&&typeof input==='object'?input:{})};
73
- items=(Array.isArray(value)?value:[]).slice(0,5000).map(normalizeItem).filter(Boolean)
75
+ items=(Array.isArray(value)?value:[]).map(normalizeItem).filter(Boolean)
74
76
  .sort((left,right)=>left.date.localeCompare(right.date)||left.order-right.order);
75
77
  render();
76
78
  return items.map(item=>({...item}));
@@ -66,21 +66,21 @@
66
66
  function normalizeNode(value,index){
67
67
  const item=value&&typeof value==='object'?value:{};
68
68
  if(!item.id) return null;
69
- return {...item,id:String(item.id).slice(0,180),label:String(item.label||item.title||item.id).slice(0,500),summary:String(item.summary||'').slice(0,1800),lane:String(item.lane||item.kind||'Other').slice(0,100),order:Number.isFinite(Number(item.order))?Number(item.order):index};
69
+ return {...item,id:String(item.id),label:String(item.label||item.title||item.id),summary:String(item.summary||''),lane:String(item.lane||item.kind||'Other'),order:Number.isFinite(Number(item.order))?Number(item.order):index};
70
70
  }
71
71
  function normalizeEdge(value,index,known){
72
72
  const item=value&&typeof value==='object'?value:{};
73
- const from=String(item.from||'').slice(0,180); const to=String(item.to||'').slice(0,180);
73
+ const from=String(item.from||''); const to=String(item.to||'');
74
74
  if(!from||!to||!known.has(from)||!known.has(to)) return null;
75
- return {...item,id:String(item.id||`edge-${index+1}`).slice(0,180),from,to,label:String(item.label||'related to').slice(0,300),summary:String(item.summary||'').slice(0,1200)};
75
+ return {...item,id:String(item.id||`edge-${index+1}`),from,to,label:String(item.label||'related to'),summary:String(item.summary||'')};
76
76
  }
77
77
 
78
78
  function setGraph(graph={},options={}){
79
- nodes=(Array.isArray(graph.nodes)?graph.nodes:[]).slice(0,1500).map(normalizeNode).filter(Boolean);
79
+ nodes=(Array.isArray(graph.nodes)?graph.nodes:[]).map(normalizeNode).filter(Boolean);
80
80
  const known=new Set(nodes.map(item=>item.id));
81
- edges=(Array.isArray(graph.edges)?graph.edges:[]).slice(0,6000).map((item,index)=>normalizeEdge(item,index,known)).filter(Boolean);
81
+ edges=(Array.isArray(graph.edges)?graph.edges:[]).map((item,index)=>normalizeEdge(item,index,known)).filter(Boolean);
82
82
  const supplied=Array.isArray(options.lanes)?options.lanes:Array.isArray(graph.lanes)?graph.lanes:[];
83
- lanes=[...new Set([...supplied.map(String),...nodes.map(item=>item.lane)])].slice(0,12);
83
+ lanes=[...new Set([...supplied.map(String),...nodes.map(item=>item.lane)])];
84
84
  board.style.setProperty('--lane-count',String(Math.max(1,Math.min(6,lanes.length))));
85
85
  if(selectedId&&!known.has(selectedId)) selectedId='';
86
86
  render();
@@ -202,26 +202,24 @@
202
202
  const sourceScroll=host.shadowRoot.querySelector('#sourceScroll');
203
203
  const sourceLines=host.shadowRoot.querySelector('#sourceLines');
204
204
 
205
- const MAXIMUM_CHARACTERS=1048576;
206
- const MAXIMUM_LINES=20000;
207
- const defaultOptions=Object.freeze({
208
- labels:Object.freeze({
205
+ const defaultOptions={
206
+ labels:{
209
207
  content:'Source code',
210
208
  empty:'Choose a source file from the catalog.',
211
209
  error:'This source file could not be displayed.',
212
- loading:'Loading and verifying source code…',
210
+ loading:'Loading source code…',
213
211
  repository:'View repository source'
214
- })
215
- });
212
+ }
213
+ };
216
214
 
217
- let current=Object.freeze({
215
+ let current={
218
216
  language:'text',
219
217
  lineCount:0,
220
218
  repositoryURL:'',
221
219
  sourcePath:'',
222
220
  text:'',
223
221
  title:''
224
- });
222
+ };
225
223
  let destroyed=false;
226
224
  let eventOperationSequence=0;
227
225
  let loadSequence=0;
@@ -244,17 +242,17 @@
244
242
  value:{get:()=>current.text,set:value=>render(value)}
245
243
  });
246
244
 
247
- function boundedText(value,label,maximum,{optional=false}={}){
245
+ function normalizedText(value,label,{optional=false}={}){
248
246
  if(optional&&(value===undefined||value===null||value===''))return '';
249
- if(typeof value!=='string'||!value.trim()||value.length>maximum||/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/.test(value)){
250
- throw new TypeError(`${label} must be bounded text.`);
247
+ if(typeof value!=='string'||!value.trim()||/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/.test(value)){
248
+ throw new TypeError(`${label} must be text.`);
251
249
  }
252
250
  return value.trim();
253
251
  }
254
252
 
255
253
  function safeRepositoryURL(value=''){
256
254
  if(value===undefined||value===null||value==='')return '';
257
- const input=boundedText(value,'Source repositoryURL',2048);
255
+ const input=normalizedText(value,'Source repositoryURL');
258
256
  try{
259
257
  const url=new URL(input,document.baseURI);
260
258
  if(!['http:','https:'].includes(url.protocol)||url.username||url.password){
@@ -278,22 +276,22 @@
278
276
  const labels={...previous.labels};
279
277
  for(const name of ['content','empty','error','loading','repository']){
280
278
  if(labelInput[name]!==undefined){
281
- labels[name]=boundedText(labelInput[name],`Source viewer labels.${name}`,160);
279
+ labels[name]=normalizedText(labelInput[name],`Source viewer labels.${name}`);
282
280
  }
283
281
  }
284
- return Object.freeze({labels:Object.freeze(labels)});
282
+ return {labels};
285
283
  }
286
284
 
287
285
  function normalizeRenderOptions(input={}){
288
286
  if(!input||typeof input!=='object'||Array.isArray(input)){
289
287
  throw new TypeError('Source render options must be an object.');
290
288
  }
291
- return Object.freeze({
292
- language:boundedText(input.language||'text','Source language',64).toLowerCase(),
289
+ return {
290
+ language:normalizedText(input.language||'text','Source language').toLowerCase(),
293
291
  repositoryURL:safeRepositoryURL(input.repositoryURL),
294
- sourcePath:boundedText(input.sourcePath||'Source file','Source path',1024),
295
- title:boundedText(input.title||input.sourcePath||'Source file','Source title',256)
296
- });
292
+ sourcePath:normalizedText(input.sourcePath||'Source file','Source path'),
293
+ title:normalizedText(input.title||input.sourcePath||'Source file','Source title')
294
+ };
297
295
  }
298
296
 
299
297
  function configure(input={}){
@@ -337,14 +335,12 @@
337
335
 
338
336
  function renderSource(text='',renderOptions={}){
339
337
  if(typeof text!=='string')throw new TypeError('Source content must be a string.');
340
- if(text.length>MAXIMUM_CHARACTERS)throw new RangeError('Source content exceeds the viewer character limit.');
341
338
  const metadata=normalizeRenderOptions(renderOptions);
342
339
  const lines=text.replace(/\r\n?/g,'\n').split('\n');
343
- if(lines.length>MAXIMUM_LINES)throw new RangeError('Source content exceeds the viewer line limit.');
344
340
 
345
341
  sourceLines.replaceChildren();
346
342
  if(!text){
347
- current=Object.freeze({...metadata,lineCount:0,text:''});
343
+ current={...metadata,lineCount:0,text:''};
348
344
  transition('empty');
349
345
  return true;
350
346
  }
@@ -363,7 +359,7 @@
363
359
  fragment.append(item);
364
360
  });
365
361
  sourceLines.replaceChildren(fragment);
366
- current=Object.freeze({...metadata,lineCount:lines.length,text});
362
+ current={...metadata,lineCount:lines.length,text};
367
363
  transition('ready');
368
364
  return true;
369
365
  }
@@ -371,14 +367,14 @@
371
367
  function clear(){
372
368
  ++loadSequence;
373
369
  sourceLines.replaceChildren();
374
- current=Object.freeze({
370
+ current={
375
371
  language:'text',
376
372
  lineCount:0,
377
373
  repositoryURL:'',
378
374
  sourcePath:'',
379
375
  text:'',
380
376
  title:''
381
- });
377
+ };
382
378
  transition('empty');
383
379
  return true;
384
380
  }
@@ -423,7 +419,7 @@
423
419
  sourcePath:current.sourcePath,
424
420
  state
425
421
  };
426
- if(error)detail.message=String(error?.message||'Source rendering failed.').slice(0,512);
422
+ if(error)detail.message=String(error?.message||'Source rendering failed.');
427
423
  const reason={
428
424
  empty:'source-code-viewer-cleared',
429
425
  error:'source-code-viewer-render-rejected',
@@ -2031,15 +2031,12 @@ export class AIProviderRuntime {
2031
2031
  }
2032
2032
  }
2033
2033
 
2034
- #pendingSpeechProviderHydrationMatches(role, slot, provider, routes) {
2035
- if (!provider || !slot.selection || !routes.default) {
2034
+ #pendingSpeechProviderHydrationMatches(role, slot) {
2035
+ if (!slot.selection) {
2036
2036
  return false;
2037
2037
  }
2038
2038
  const pending = slot.selection;
2039
- if (this.#providers.has(providerKey(role, pending.providerId))
2040
- || provider.id !== pending.providerId
2041
- || routes.default.providerId !== pending.providerId
2042
- || routes.default.modelId !== pending.modelId) {
2039
+ if (this.#providers.has(providerKey(role, pending.providerId))) {
2043
2040
  return false;
2044
2041
  }
2045
2042
  const currentSelections = [
@@ -2048,10 +2045,12 @@ export class AIProviderRuntime {
2048
2045
  slot.routes.localOnly
2049
2046
  ].filter(Boolean);
2050
2047
  return currentSelections.length > 0
2051
- && currentSelections.every(selection =>
2052
- selection.providerId === pending.providerId
2053
- && selection.modelId === pending.modelId
2054
- && selection.localOnly === null
2048
+ && currentSelections.every(
2049
+ function isSamePendingSpeechSelection(selection) {
2050
+ return selection.providerId === pending.providerId
2051
+ && selection.modelId === pending.modelId
2052
+ && selection.localOnly === null;
2053
+ }
2055
2054
  );
2056
2055
  }
2057
2056
 
@@ -2104,9 +2103,7 @@ export class AIProviderRuntime {
2104
2103
  || slot.selection)
2105
2104
  && !this.#pendingSpeechProviderHydrationMatches(
2106
2105
  role,
2107
- slot,
2108
- replacement.provider,
2109
- replacement.routes
2106
+ slot
2110
2107
  )) {
2111
2108
  throw speechProviderReplacementError(
2112
2109
  'ARCANE_AI_SPEECH_PROVIDER_REPLACEMENT_EXPECTED_PROVIDER_REQUIRED',
@@ -2249,9 +2246,7 @@ export class AIProviderRuntime {
2249
2246
  || slot.selection)
2250
2247
  && !this.#pendingSpeechProviderHydrationMatches(
2251
2248
  role,
2252
- slot,
2253
- replacement.providers[role],
2254
- replacement.routes[role]
2249
+ slot
2255
2250
  )) {
2256
2251
  throw speechProviderReplacementError(
2257
2252
  'ARCANE_AI_SPEECH_PROVIDER_REPLACEMENT_EXPECTED_PROVIDER_REQUIRED',
@@ -403,16 +403,6 @@ function effectiveDashboardVisibility(definitions=[],visibility={}){
403
403
  );
404
404
  }
405
405
 
406
- function byteProgressUnit(value){
407
- const unit=String(value??'')
408
- .trim()
409
- .toLowerCase()
410
- .replace(/[\s_-]+/g,'');
411
- return unit.includes('byte')
412
- ||unit.includes('octet')
413
- ||/^(?:[kmgtpe]?i?b)(?:(?:\/|per)?(?:s|sec|second))?$/u.test(unit);
414
- }
415
-
416
406
  function formatAIRuntimeProgress(progress,fallback){
417
407
  const phase=typeof progress?.phase==='string'&&progress.phase
418
408
  ?progress.phase
@@ -422,13 +412,10 @@ function formatAIRuntimeProgress(progress,fallback){
422
412
  const unit=typeof progress?.unit==='string'
423
413
  ?progress.unit
424
414
  :'';
425
- if(!Number.isSafeInteger(completed)
426
- ||completed<0
427
- ||!Number.isSafeInteger(total)
415
+ if(!Number.isFinite(completed)
416
+ ||!Number.isFinite(total)
428
417
  ||total<=0
429
- ||completed>total
430
- ||!unit
431
- ||byteProgressUnit(unit)){
418
+ ||!unit){
432
419
  return phase;
433
420
  }
434
421
  return `${phase} · ${completed} of ${total} ${unit}`;
@@ -1,10 +1,6 @@
1
- const ROOT_MAX_LENGTH=4096;
2
- const QUERY_MAX_LENGTH=4096;
3
- const TASK_ID_PATTERN=/^[a-z][a-z0-9-]{0,63}$/;
4
-
5
1
  function workspaceRoot(value){
6
2
  const root=String(value??'').trim();
7
- if(!root||root.length>ROOT_MAX_LENGTH||/[\u0000-\u001f]/.test(root)){
3
+ if(!root||/[\u0000-\u001f]/.test(root)){
8
4
  throw new TypeError('Choose one existing development workspace directory.');
9
5
  }
10
6
  return root;
@@ -12,16 +8,16 @@ function workspaceRoot(value){
12
8
 
13
9
  function contextQuery(value){
14
10
  const query=String(value??'').trim();
15
- if(query.length>QUERY_MAX_LENGTH||/[\u0000-\u0008\u000b\u000c\u000e-\u001f]/.test(query)){
16
- throw new TypeError('Development context queries must be bounded plain text.');
11
+ if(/[\u0000-\u0008\u000b\u000c\u000e-\u001f]/.test(query)){
12
+ throw new TypeError('Development context queries must be plain text.');
17
13
  }
18
14
  return query;
19
15
  }
20
16
 
21
17
  function setupTaskId(value){
22
- const taskId=String(value??'').trim().toLowerCase();
23
- if(!TASK_ID_PATTERN.test(taskId)){
24
- throw new TypeError('Choose a registered development setup task.');
18
+ const taskId=String(value??'').trim();
19
+ if(!taskId||/[\u0000-\u001f]/.test(taskId)){
20
+ throw new TypeError('Choose a development setup task.');
25
21
  }
26
22
  return taskId;
27
23
  }
@@ -1,25 +1,27 @@
1
- function bounded(value,maximum){return String(value??'').trim().slice(0,maximum);}
1
+ function completeText(value){return String(value??'');}
2
2
 
3
3
  export function normalizeContentAdvisory(value){
4
4
  if(!value||typeof value!=='object')return null;
5
- return Object.freeze({
5
+ return {
6
6
  level:['critical','high','caution','low','unavailable'].includes(value.level)?value.level:'caution',
7
- title:bounded(value.title||'Content advisory',120),
8
- summary:bounded(value.summary||'Review this message carefully.',500),
9
- signals:Object.freeze(Array.from(value.signals||[],item=>bounded(item,120)).filter(Boolean).slice(0,8)),
10
- actionLabel:bounded(value.actionLabel,80),
11
- });
7
+ title:completeText(value.title??'Content advisory'),
8
+ summary:completeText(value.summary??'Review this message carefully.'),
9
+ signals:Array.from(value.signals||[],completeText),
10
+ actionLabel:completeText(value.actionLabel),
11
+ };
12
12
  }
13
13
 
14
- const unavailableAdvisory=normalizeContentAdvisory({
15
- level:'unavailable',
16
- title:'Safety check unavailable',
17
- summary:'No safety conclusion was made for this message. Pause and review it manually before replying.',
18
- });
14
+ function unavailableAdvisory(){
15
+ return normalizeContentAdvisory({
16
+ level:'unavailable',
17
+ title:'Safety check unavailable',
18
+ summary:'No safety conclusion was made for this message. Pause and review it manually before replying.',
19
+ });
20
+ }
19
21
 
20
22
  export function unavailableMessageInspection(messages){
21
23
  const advisories=new Map();
22
- for(const message of Array.from(messages||[]))advisories.set(message,unavailableAdvisory);
24
+ for(const message of Array.from(messages||[]))advisories.set(message,unavailableAdvisory());
23
25
  return {advisories,failures:advisories.size};
24
26
  }
25
27
 
@@ -32,7 +34,7 @@ export async function inspectMessageRecords(messages,inspector,{prepare}={}){
32
34
  catch{return unavailableMessageInspection(records);}
33
35
  for(const message of records){
34
36
  try{const advisory=normalizeContentAdvisory(await inspector(message,context));if(advisory){if(advisory.level==='unavailable')failures+=1;advisories.set(message,advisory);}}
35
- catch{failures+=1;advisories.set(message,unavailableAdvisory);}
37
+ catch{failures+=1;advisories.set(message,unavailableAdvisory());}
36
38
  }
37
39
  return {advisories,failures};
38
40
  }