@vllm-sr/dsh 0.4.1 → 0.5.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/web/client.js CHANGED
@@ -30,7 +30,7 @@ window.__ModuleLoader__.load({
30
30
  const FRESH_MS = 20000
31
31
  const ROWS_SHOWN = 4
32
32
 
33
- const REFLEX = { triage: 'Triage', guard: 'Guard', shield: 'Shield', done: 'Done-check', tool: 'Batch' }
33
+ const REFLEX = { triage: 'Triage', guard: 'Guard', shield: 'Shield', privacy: 'Privacy', done: 'Done-check', tool: 'Batch' }
34
34
  const KINDS = { code: 'Code', data: 'Data', research: 'Research', writing: 'Writing', ops: 'Ops', chat: 'Chat' }
35
35
  const EFFORT = ['Trivial', 'Small', 'Medium', 'Large']
36
36
  const RISK = ['Safe', 'Normal', 'Dangerous', 'Critical']
@@ -59,12 +59,14 @@ window.__ModuleLoader__.load({
59
59
  waiting: { label: 'Waiting on you', tone: 'info' },
60
60
  unfinished: { label: 'Sent back', tone: 'warn' },
61
61
  unchecked: { label: 'Sent back to verify', tone: 'warn' },
62
+ unsupported: { label: 'Sent back to re-verify', tone: 'warn' },
62
63
  }
63
64
 
64
65
  const num = value => (typeof value === 'number' && Number.isFinite(value) ? value : undefined)
65
66
  const p2 = value => (num(value) === undefined ? '?' : value.toFixed(2))
66
67
  const secs = ms => (num(ms) === undefined ? '' : `${(ms / 1000).toFixed(2)} s`)
67
- const shortModel = model => (typeof model === 'string' ? model.replace(/^vllm-sr\//, '') : '')
68
+ // System One's models are vllm-sr's; the name always shows its owner, whatever form the answer gave it in
69
+ const fullModel = model => (typeof model === 'string' && model ? (model.includes('/') ? model : `vllm-sr/${model}`) : '')
68
70
  const plural = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`
69
71
  const clamp01 = value => Math.min(1, Math.max(0, value))
70
72
  const level = (labels, score) => labels[Math.min(labels.length - 1, Math.max(0, Math.round(score)))]
@@ -82,12 +84,12 @@ window.__ModuleLoader__.load({
82
84
  return data && typeof data === 'object' && data.v === 1 && typeof data.reflex === 'string' ? data : undefined
83
85
  }
84
86
 
85
- // one card per triage of a turn, per guard or shield step (parallel calls share it), per done-check
87
+ // one card per triage of a turn, per guard, shield or privacy step (parallel calls share it), per done-check
86
88
  function groupOf(data, seq) {
87
89
  const turn = Number.isSafeInteger(data.turn) ? data.turn : undefined
88
90
  const step = Number.isSafeInteger(data.step) ? data.step : undefined
89
91
  if (data.reflex === 'triage' && turn !== undefined) return `t${turn}:triage`
90
- if ((data.reflex === 'guard' || data.reflex === 'shield') && turn !== undefined && step !== undefined) return `t${turn}:s${step}:${data.reflex}`
92
+ if (['guard', 'shield', 'privacy'].includes(data.reflex) && turn !== undefined && step !== undefined) return `t${turn}:s${step}:${data.reflex}`
91
93
  return `r${seq}`
92
94
  }
93
95
 
@@ -197,8 +199,9 @@ window.__ModuleLoader__.load({
197
199
  if (risk !== undefined) facts.push({ id: 'risk', label: 'Risk', value: `${level(RISK, Math.min(3, risk))} · ${risk.toFixed(2)} of 3`, bar: risk / 3, tone: risk >= 2.5 ? 'bad' : risk >= 1.8 ? 'warn' : 'ok' })
198
200
  if (leaks !== undefined) facts.push({ id: 'leaks', label: 'Leaks secrets', value: p2(leaks), bar: leaks, tone: leaks >= 0.7 ? 'bad' : undefined })
199
201
  if (onTask !== undefined) facts.push({ id: 'on-task', label: 'On task', value: p2(onTask), bar: onTask, tone: onTask < 0.15 ? 'warn' : undefined })
202
+ const danger = Array.isArray(row.danger) ? row.danger.filter(span => typeof span?.text === 'string' && span.text) : []
200
203
  return {
201
- verdict: row.verdict, ...verdict, subject, facts, outcome, outcomeKey: outcome ? row.outcome : undefined, verdictTone: verdict.tone,
204
+ verdict: row.verdict, ...verdict, subject: danger.length ? { ...subject, marks: danger, kind: 'dangerous' } : subject, facts, outcome, outcomeKey: outcome ? row.outcome : undefined, verdictTone: verdict.tone,
202
205
  tone: outcome && SEVERITY[outcome.tone] > SEVERITY[verdict.tone] ? outcome.tone : verdict.tone,
203
206
  note: row.verdict !== 'allow' && row.why ? `Because ${row.why}.` : undefined,
204
207
  }
@@ -208,14 +211,33 @@ window.__ModuleLoader__.load({
208
211
  if (row.error) return errorView(row, { text: 'so the result was passed on unscreened', tone: 'muted' })
209
212
  const verdict = SHIELD[row.verdict] ?? SHIELD.clean
210
213
  const lines = num(row.removedLines) ?? 0
214
+ const spans = num(row.removedSpans) ?? 0
215
+ const what = spans ? `${plural(spans, 'span')} of text (${plural(num(row.removedChars) ?? 0, 'character')})` : plural(lines, 'line')
211
216
  return {
212
217
  verdict: row.verdict, ...verdict, subject: { tool: row.tool, text: row.call },
213
218
  facts: [
214
219
  { id: 'passages', label: 'Passages', value: String(num(row.passages) ?? '?') },
215
220
  { id: 'injection', label: 'Injection', value: p2(row.maxP), bar: num(row.maxP), tone: row.maxP >= 0.5 ? 'bad' : 'ok' },
221
+ ...(spans ? [{ id: 'spans', label: 'Spans removed', value: String(spans) }] : []),
216
222
  ],
217
- note: row.verdict === 'removed' ? `Removed ${plural(lines, 'line')} that gave orders to AI agents before System Two read the result.`
218
- : row.verdict === 'flagged' ? `Found ${plural(lines, 'line')} that give orders to AI agents; the result was withheld.` : undefined,
223
+ excerpts: Array.isArray(row.excerpts) ? { list: row.excerpts, kind: 'removed' } : undefined,
224
+ note: row.verdict === 'removed' ? `Removed ${what} that gave orders to AI agents before System Two read the result${spans ? '; the text around it stayed' : ''}.`
225
+ : row.verdict === 'flagged' ? `Found ${what} that give orders to AI agents; the result was withheld.` : undefined,
226
+ }
227
+ }
228
+
229
+ function privacyView(row) {
230
+ if (row.error) return errorView(row, { text: 'so the result went to System Two unmasked', tone: 'warn' })
231
+ const masked = num(row.masked) ?? 0
232
+ const labels = row.labels && typeof row.labels === 'object' ? Object.entries(row.labels).filter(([, n]) => num(n)) : []
233
+ return {
234
+ verdict: 'masked', label: 'Masked', short: 'masked', tone: 'info',
235
+ headline: `${plural(masked, 'personal detail')} masked${row.tool ? ` · ${row.tool}` : ''}`,
236
+ subject: row.call ? { tool: row.tool, text: row.call } : undefined,
237
+ facts: [{ id: 'masked', label: 'Masked', value: String(masked) }],
238
+ labels: labels.map(([label, n]) => ({ label, n })),
239
+ excerpts: Array.isArray(row.excerpts) ? { list: row.excerpts, kind: 'masked' } : undefined,
240
+ note: 'System Two read [LABEL] placeholders instead of these details.',
219
241
  }
220
242
  }
221
243
 
@@ -225,6 +247,7 @@ window.__ModuleLoader__.load({
225
247
  const why = DONE[row.why] ? row.why : row.verdict === 'continue' ? 'unfinished' : 'complete'
226
248
  const verdict = DONE[why]
227
249
  const round = `${num(row.steers) ?? '?'} of ${num(row.maxSteers) ?? '?'}`
250
+ const unsupported = num(row.unsupported) ?? 0
228
251
  return {
229
252
  verdict: row.verdict === 'continue' ? 'continue' : 'stop', ...verdict, why, short: verdict.label.toLowerCase(),
230
253
  headline: row.verdict === 'continue' ? `${verdict.label} to System Two (${round})` : why === 'waiting' ? 'Waiting on you' : 'The work is done',
@@ -232,9 +255,13 @@ window.__ModuleLoader__.load({
232
255
  { id: 'complete', label: 'Complete', value: p2(yesOf(a.complete)), bar: yesOf(a.complete), tone: yesOf(a.complete) < 0.5 ? 'warn' : 'ok' },
233
256
  { id: 'checked', label: 'Checked', value: p2(yesOf(a.checked)), bar: yesOf(a.checked) },
234
257
  { id: 'waiting', label: 'Waiting on you', value: p2(yesOf(a.waiting)), bar: yesOf(a.waiting) },
258
+ ...(row.spanModel ? [{ id: 'unsupported', label: 'Unsupported', value: String(unsupported), tone: unsupported ? 'warn' : 'ok' }] : []),
235
259
  ],
260
+ excerpts: unsupported && Array.isArray(row.excerpts) ? { list: row.excerpts, kind: 'unsupported' } : undefined,
236
261
  note: why === 'unfinished' ? `Not finished yet, so System Two goes back to work (${round}).`
237
- : why === 'unchecked' ? `The changes were not checked, so System Two goes back to verify them (${round}).` : undefined,
262
+ : why === 'unchecked' ? `The changes were not checked, so System Two goes back to verify them (${round}).`
263
+ : why === 'unsupported' ? `Nothing System Two read supports ${plural(unsupported, 'claim')} in its answer, so it goes back to re-verify just ${unsupported === 1 ? 'that one' : 'those'} (${round}).`
264
+ : unsupported ? `Nothing System Two read supports ${plural(unsupported, 'claim')} in its answer.` : undefined,
238
265
  }
239
266
  }
240
267
 
@@ -244,6 +271,7 @@ window.__ModuleLoader__.load({
244
271
  case 'guard': return guardView(row)
245
272
  case 'shield': return shieldView(row)
246
273
  case 'done': return doneView(row)
274
+ case 'privacy': return privacyView(row)
247
275
  default: return row.error ? errorView(row) : { verdict: 'unknown', label: 'Decision', short: 'decision', tone: 'muted', facts: [] }
248
276
  }
249
277
  }
@@ -264,7 +292,7 @@ window.__ModuleLoader__.load({
264
292
  const key = view.outcome?.short ?? view.short
265
293
  counts.set(key, (counts.get(key) ?? 0) + 1)
266
294
  }
267
- const noun = reflex === 'guard' ? 'call' : reflex === 'shield' ? 'result' : 'decision'
295
+ const noun = reflex === 'guard' ? 'call' : reflex === 'shield' || reflex === 'privacy' ? 'result' : 'decision'
268
296
  return [plural(views.length, noun), ...[...counts].map(([short, n]) => `${n} ${short}`)].join(' · ')
269
297
  }
270
298
 
@@ -273,7 +301,7 @@ window.__ModuleLoader__.load({
273
301
  const label = view.outcome ? `${view.label} → ${view.outcome.label}` : view.label
274
302
  const rest = []
275
303
  if (view.subject?.text) rest.push(`${view.subject.tool ?? ''} ${view.subject.text}`.trim())
276
- const lead = view.facts.find(fact => ['risk', 'injection', 'complete', 'effort'].includes(fact.id))
304
+ const lead = view.facts.find(fact => ['risk', 'injection', 'complete', 'effort', 'masked'].includes(fact.id))
277
305
  if (lead) rest.push(`${lead.label.toLowerCase()} ${lead.value}`)
278
306
  if (view.error) rest.push(view.error)
279
307
  return { label, tone: view.tone, rest: rest.join(' · ') }
@@ -319,6 +347,43 @@ window.__ModuleLoader__.load({
319
347
  secs(ms) ? h('span', { className: 'vsr-s1__ms', 'data-system-one-part': 'latency' }, secs(ms)) : null)
320
348
  }
321
349
 
350
+ function Model({ model, also }) {
351
+ const names = [...new Set([fullModel(model), fullModel(also)].filter(Boolean))]
352
+ return names.length ? h('b', { className: 'vsr-s1__model', 'data-system-one-part': 'model', title: names.join(' + ') }, names.join(' + ')) : null
353
+ }
354
+
355
+ // what a span model marked: removed injections, masked personal data (only its label), unsupported claims
356
+ function Mark({ mark, kind }) {
357
+ const p = num(mark.probability)
358
+ return h('mark', {
359
+ className: cx('vsr-s1__span', `vsr-s1__span--${kind}`),
360
+ 'data-system-one-part': 'span',
361
+ 'data-system-one-span-label': text(mark.label) || undefined,
362
+ 'data-system-one-probability': p === undefined ? undefined : String(p),
363
+ title: [text(mark.label), p === undefined ? '' : p.toFixed(2)].filter(Boolean).join(' · '),
364
+ }, kind === 'masked' ? text(mark.label) || 'PII' : text(mark.text))
365
+ }
366
+
367
+ function Excerpts({ excerpts }) {
368
+ return h('div', { className: 'vsr-s1__excerpts', 'data-system-one-part': 'excerpts' },
369
+ excerpts.list.slice(0, 3).map((parts, index) => h('div', { key: index, className: 'vsr-s1__excerpt', 'data-system-one-part': 'excerpt' },
370
+ (Array.isArray(parts) ? parts : []).map((part, at) => (part?.mark ? h(Mark, { key: at, mark: part.mark, kind: excerpts.kind }) : h('span', { key: at }, text(part?.text)))))))
371
+ }
372
+
373
+ // the subject with each marked text highlighted where it first occurs
374
+ function Marked({ value, marks, kind }) {
375
+ const pieces = []
376
+ let rest = value
377
+ for (const mark of marks) {
378
+ const at = rest.indexOf(mark.text)
379
+ if (at < 0) continue
380
+ if (at > 0) pieces.push(rest.slice(0, at))
381
+ pieces.push(h(Mark, { key: pieces.length, mark, kind }))
382
+ rest = rest.slice(at + mark.text.length)
383
+ }
384
+ return [...pieces, rest]
385
+ }
386
+
322
387
  function Pill({ tone, children, arrow, part }) {
323
388
  return h('span', { className: cx('vsr-s1__pill', `vsr-s1__pill--${tone}`, arrow && 'vsr-s1__pill--then'), 'data-system-one-part': part }, children)
324
389
  }
@@ -344,8 +409,12 @@ window.__ModuleLoader__.load({
344
409
  h('div', { className: 'vsr-s1__main' },
345
410
  view.subject?.text ? h('div', { className: 'vsr-s1__subject', 'data-system-one-part': 'subject' },
346
411
  view.subject.tool ? h('span', { className: 'vsr-s1__tool' }, view.subject.tool) : null,
347
- h('code', { className: 'vsr-s1__code', title: view.subject.text }, view.subject.text)) : null,
412
+ h('code', { className: 'vsr-s1__code', title: view.subject.text }, view.subject.marks ? h(Marked, { value: view.subject.text, marks: view.subject.marks, kind: view.subject.kind }) : view.subject.text)) : null,
413
+ view.subject?.marks && view.subject.marks.some(mark => !view.subject.text.includes(mark.text))
414
+ ? h('div', { className: 'vsr-s1__note', 'data-system-one-part': 'note' }, 'The dangerous part: ', view.subject.marks.map((mark, index) => h(Mark, { key: index, mark, kind: view.subject.kind }))) : null,
348
415
  view.facts.length ? h('div', { className: 'vsr-s1__facts', 'data-system-one-part': 'facts' }, view.facts.map(fact => h(Fact, { key: fact.id, ...fact }))) : null,
416
+ view.labels?.length ? h('div', { className: 'vsr-s1__labels', 'data-system-one-part': 'labels' }, view.labels.map(entry => h('span', { key: entry.label, className: 'vsr-s1__label', 'data-system-one-span-label': entry.label }, entry.label, h('b', null, String(entry.n))))) : null,
417
+ view.excerpts?.list?.length ? h(Excerpts, { excerpts: view.excerpts }) : null,
349
418
  view.note ? h('div', { className: 'vsr-s1__note', 'data-system-one-part': 'note' }, view.note) : null),
350
419
  !single && secs(row.ms) ? h('span', { className: 'vsr-s1__rowms', 'data-system-one-part': 'latency' }, secs(row.ms)) : null)
351
420
  }
@@ -360,6 +429,12 @@ window.__ModuleLoader__.load({
360
429
 
361
430
  const whole = value => (Number.isSafeInteger(value) ? String(value) : undefined)
362
431
 
432
+ // how many spans a span model marked in an element's decisions
433
+ function spanCount(state) {
434
+ const n = state.rows.reduce((sum, row) => sum + (num(row.removedSpans) ?? 0) + (num(row.masked) ?? 0) + (num(row.unsupported) ?? 0) + (Array.isArray(row.danger) ? row.danger.length : 0), 0)
435
+ return n ? String(n) : undefined
436
+ }
437
+
363
438
  // a public contract (README.md, "Selectors"); React leaves out an attribute whose value is undefined
364
439
  function attributes(kind, state, worst) {
365
440
  return {
@@ -372,6 +447,9 @@ window.__ModuleLoader__.load({
372
447
  'data-system-one-count': String(state.rows.length),
373
448
  'data-system-one-turn': whole(state.rows[0].turn),
374
449
  'data-system-one-step': state.reflex === 'guard' || state.reflex === 'shield' ? whole(state.rows[0].step) : undefined,
450
+ 'data-system-one-model': fullModel(state.rows[state.rows.length - 1].model) || undefined,
451
+ 'data-system-one-span-model': fullModel(state.rows.find(row => row.spanModel)?.spanModel) || undefined,
452
+ 'data-system-one-spans': spanCount(state) || undefined,
375
453
  }
376
454
  }
377
455
 
@@ -393,7 +471,7 @@ window.__ModuleLoader__.load({
393
471
  h(Badge, { key: idOf(last), id: idOf(last), ms: last.ms, time: last.time }),
394
472
  h('span', { className: 'vsr-s1__reflex', 'data-system-one-part': 'reflex' }, reflex),
395
473
  h('span', { className: 'vsr-s1__summary', 'data-system-one-part': 'summary', title: headline }, headline),
396
- last.model ? h('span', { className: 'vsr-s1__model', 'data-system-one-part': 'model', title: last.model }, shortModel(last.model)) : null),
474
+ h(Model, { model: last.model, also: state.rows.find(row => row.spanModel)?.spanModel })),
397
475
  h(Rows, { reflex: state.reflex, rows: state.rows, views }))
398
476
  }
399
477
 
@@ -423,15 +501,48 @@ window.__ModuleLoader__.load({
423
501
  h('span', { className: 'vsr-s1__summary', 'data-system-one-part': 'summary', title: said },
424
502
  h('span', { className: cx('vsr-s1__verdict', line.tone && `vsr-s1__verdict--${line.tone}`), 'data-system-one-part': 'verdict' }, line.label),
425
503
  line.rest ? ` · ${line.rest}` : null),
504
+ open ? h(Model, { model: last.model, also: state.rows.find(row => row.spanModel)?.spanModel }) : null,
426
505
  h(Chevron)),
427
506
  open ? h(Rows, { reflex: state.reflex, rows: state.rows, views }) : null)
428
507
  }
429
508
 
509
+ function KeyGlyph() {
510
+ return h('svg', { className: 'vsr-s1__glyph', viewBox: '0 0 16 16', width: 12, height: 12, 'aria-hidden': true },
511
+ h('circle', { cx: 5.5, cy: 8, r: 3, fill: 'none', stroke: 'currentColor', strokeWidth: 1.6 }),
512
+ h('path', { d: 'M8.5 8H14M12 8v2.5M14 8v2', fill: 'none', stroke: 'currentColor', strokeWidth: 1.6, strokeLinecap: 'round' }))
513
+ }
514
+
515
+ // The free key a start fetched, or why it could not: one line under the request, in either mode.
516
+ function KeyNotice({ state }) {
517
+ const row = state.rows[state.rows.length - 1]
518
+ const created = row.verdict === 'created'
519
+ const fresh = useFresh(`key:${idOf(row)}`, row.time)
520
+ const site = text(row.site) || 'app.vllm-sr.ai'
521
+ const said = created
522
+ ? `Free API key created for ${site}, saved in ${text(row.saved) || '~/.dsh/.env'}`
523
+ : `No free API key from ${site}: ${text(row.error?.message) || 'the site refused'}. Get one at ${site} and save it as ${text(row.ref) || 'VLLM_SR_API_KEY'}.`
524
+ const link = (href, label) => (typeof href === 'string' && /^https:\/\//.test(href) ? h('a', { className: 'vsr-s1__link', href, target: '_blank', rel: 'noreferrer' }, label) : null)
525
+ return h('div', {
526
+ className: cx('vsr-s1', 'vsr-s1--notice', created ? 'vsr-s1--ok' : 'vsr-s1--bad', fresh && 'vsr-s1--fresh'),
527
+ role: 'note',
528
+ 'aria-label': `System Duo: ${said}`,
529
+ 'data-system-one': 'notice',
530
+ 'data-system-one-reflex': 'key',
531
+ 'data-system-one-verdict': created ? 'created' : 'failed',
532
+ 'data-system-one-tone': created ? 'ok' : 'bad',
533
+ 'data-system-one-turn': whole(row.turn),
534
+ },
535
+ h('span', { className: cx('vsr-s1__duo', fresh && 'vsr-s1__badge--fresh'), 'data-system-one-part': 'badge' }, h(KeyGlyph), 'System Duo'),
536
+ h('span', { className: cx('vsr-s1__summary', !created && 'vsr-s1__summary--bad'), 'data-system-one-part': 'summary', title: said }, said),
537
+ created && (row.terms || row.privacy) ? h('span', { className: 'vsr-s1__links', 'data-system-one-part': 'links' }, link(row.terms, 'Terms'), link(row.privacy, 'Privacy')) : null)
538
+ }
539
+
430
540
  // A routine check (an allowed call, a clean result) is one line in either mode; a decision that changed
431
541
  // something, and the triage and done-check that frame the turn, are cards unless the mode is compact.
432
542
  function SystemOneNode(props) {
433
543
  const state = props?.node?.data
434
544
  if (!state || !Array.isArray(state.rows) || state.rows.length === 0) return null
545
+ if (state.reflex === 'key') return h(Contained, null, h(KeyNotice, { state }))
435
546
  const slim = state.ui === 'compact' || !standsOut(state)
436
547
  return h(Contained, null, h(slim ? Compact : Card, { state }))
437
548
  }
@@ -561,13 +672,17 @@ window.__ModuleLoader__.load({
561
672
 
562
673
  const ms = num(meta.seconds) === undefined ? undefined : meta.seconds * 1000
563
674
  const headline = [plural(num(meta.items) ?? 0, 'item'), plural(num(meta.questions) ?? 0, 'question'), plural(num(meta.requests) ?? 0, 'request')].join(' · ')
564
- const done = { ...common, 'data-system-one-verdict': 'done', 'data-system-one-tone': 'ok', 'data-system-one-count': String(meta.items ?? '') }
675
+ const done = {
676
+ ...common, 'data-system-one-verdict': 'done', 'data-system-one-tone': 'ok', 'data-system-one-count': String(meta.items ?? ''),
677
+ 'data-system-one-model': fullModel(meta.model) || undefined,
678
+ }
565
679
  if (ui === 'compact') {
566
680
  return h('div', { className: cx('vsr-s1', 'vsr-s1--compact', 'vsr-s1--ok', open && 'vsr-s1--open', fresh && 'vsr-s1--fresh'), 'data-system-one': 'tool', ...done },
567
681
  h('button', { type: 'button', className: 'vsr-s1__chip', 'data-system-one-part': 'toggle', 'aria-expanded': open, onClick: () => setOpen(!open) },
568
682
  h(Badge, { id: `${callId}:done`, ms, time: block?.time }),
569
683
  h('span', { className: 'vsr-s1__reflex', 'data-system-one-part': 'reflex' }, REFLEX.tool),
570
684
  h('span', { className: 'vsr-s1__summary', 'data-system-one-part': 'summary', title: headline }, headline),
685
+ open ? h(Model, { model: meta.model }) : null,
571
686
  h(Chevron)),
572
687
  open ? h(ToolBody, { meta, fresh, cwd, openFile }) : null)
573
688
  }
@@ -583,7 +698,7 @@ window.__ModuleLoader__.load({
583
698
  h(Badge, { id: `${callId}:done`, ms, time: block?.time }),
584
699
  h('span', { className: 'vsr-s1__reflex', 'data-system-one-part': 'reflex' }, REFLEX.tool),
585
700
  h('span', { className: 'vsr-s1__summary', 'data-system-one-part': 'summary', title: headline }, headline),
586
- meta.model ? h('span', { className: 'vsr-s1__model', 'data-system-one-part': 'model', title: meta.model }, shortModel(meta.model)) : null),
701
+ h(Model, { model: meta.model })),
587
702
  h(ToolBody, { meta, fresh, cwd, openFile }))
588
703
  }
589
704
 
@@ -612,7 +727,13 @@ window.__ModuleLoader__.load({
612
727
  .vsr-s1__summary--bad{color:var(--vsr-s1-bad)}
613
728
  .vsr-s1__verdict{font-weight:600;color:var(--vsr-s1-text)}
614
729
  .vsr-s1__verdict--ok{color:var(--vsr-s1-ok)}.vsr-s1__verdict--warn{color:color-mix(in srgb,var(--vsr-s1-warn) 82%,var(--vsr-s1-text))}.vsr-s1__verdict--bad{color:var(--vsr-s1-bad)}.vsr-s1__verdict--info{color:var(--vsr-s1-ember-deep)}.vsr-s1__verdict--muted{color:var(--vsr-s1-text-3)}
615
- .vsr-s1__model{flex:none;color:var(--vsr-s1-text-3);font-size:12px;white-space:nowrap}
730
+ .vsr-s1__model{flex:none;color:var(--vsr-s1-text-2);font-size:12px;font-weight:700;letter-spacing:-.005em;white-space:nowrap}
731
+ .vsr-s1--notice{display:flex;align-items:center;gap:9px;min-width:0;margin:4px 0;padding:3px 0}
732
+ .vsr-s1--notice .vsr-s1__summary{white-space:normal}
733
+ .vsr-s1__duo{position:relative;display:inline-flex;flex:none;align-items:center;gap:5px;height:22px;padding:0 9px 0 8px;border-radius:999px;color:#fff;font-size:12px;font-weight:650;white-space:nowrap;background:linear-gradient(110deg,var(--vsr-s1-ember) 0%,#B04FD0 58%,#2F7BF6 100%);box-shadow:0 1px 2px rgba(90,60,170,.25),inset 0 1px 0 rgba(255,255,255,.32)}
734
+ .vsr-s1__glyph{flex:none}
735
+ .vsr-s1__links{display:inline-flex;flex:none;gap:10px}
736
+ a.vsr-s1__link{text-decoration:none}
616
737
  .vsr-s1__rows{display:grid;grid-template-columns:auto minmax(0,1fr) auto;column-gap:12px;row-gap:8px;margin-top:9px}
617
738
  .vsr-s1__rows>*{grid-column:1/-1}
618
739
  .vsr-s1__row{display:grid;grid-template-columns:auto minmax(0,1fr) auto;grid-template-columns:subgrid;column-gap:12px;align-items:start}
@@ -632,6 +753,18 @@ window.__ModuleLoader__.load({
632
753
  .vsr-s1__bar>i{position:absolute;inset:0;border-radius:inherit;background:color-mix(in srgb,var(--vsr-s1-ember) 70%,var(--vsr-s1-text-3));transform-origin:left center;transform:scaleX(var(--p))}
633
754
  .vsr-s1__bar--ok>i{background:var(--vsr-s1-ok)}.vsr-s1__bar--warn>i{background:var(--vsr-s1-warn)}.vsr-s1__bar--bad>i{background:var(--vsr-s1-bad)}
634
755
  .vsr-s1__note{color:var(--vsr-s1-text-2);font-size:12.5px}
756
+ .vsr-s1__span{padding:0 3px;border-radius:4px;color:inherit;background:none;font:inherit}
757
+ .vsr-s1__span--removed{color:var(--vsr-s1-bad);background:color-mix(in srgb,var(--vsr-s1-bad) 15%,transparent);text-decoration:line-through;text-decoration-thickness:1.5px}
758
+ .vsr-s1__span--dangerous{color:var(--vsr-s1-bad);background:color-mix(in srgb,var(--vsr-s1-bad) 17%,transparent);box-shadow:inset 0 -2px 0 var(--vsr-s1-bad);font-weight:650}
759
+ .vsr-s1__span--unsupported{background:color-mix(in srgb,var(--vsr-s1-warn) 24%,transparent);text-decoration:underline wavy color-mix(in srgb,var(--vsr-s1-warn) 85%,var(--vsr-s1-text));text-decoration-thickness:1.5px;text-underline-offset:3px}
760
+ .vsr-s1__span--masked{display:inline-block;padding:0 6px;color:var(--dsw-alias-bg-layer-1,#fff);background:repeating-linear-gradient(135deg,color-mix(in srgb,var(--vsr-s1-text) 80%,transparent) 0 6px,color-mix(in srgb,var(--vsr-s1-text) 66%,transparent) 6px 12px);font-family:var(--vsr-s1-mono);font-size:11px;font-weight:650;letter-spacing:.02em;line-height:17px}
761
+ .vsr-s1__excerpts{display:grid;gap:5px;margin:3px 0 1px}
762
+ .vsr-s1__excerpt{padding:6px 9px;border-left:2px solid color-mix(in srgb,var(--vsr-s1-ember) 50%,transparent);border-radius:0 6px 6px 0;color:var(--vsr-s1-text-2);background:color-mix(in srgb,var(--vsr-s1-text-3) 9%,transparent);font-family:var(--vsr-s1-mono);font-size:12px;line-height:1.6;white-space:pre-wrap;word-break:break-word}
763
+ .vsr-s1--bad .vsr-s1__excerpt{border-left-color:color-mix(in srgb,var(--vsr-s1-bad) 55%,transparent)}
764
+ .vsr-s1--warn .vsr-s1__excerpt{border-left-color:color-mix(in srgb,var(--vsr-s1-warn) 60%,transparent)}
765
+ .vsr-s1__labels{display:flex;flex-wrap:wrap;gap:4px 6px}
766
+ .vsr-s1__label{display:inline-flex;align-items:center;gap:5px;padding:0 7px;border-radius:5px;color:var(--vsr-s1-text-2);background:color-mix(in srgb,var(--vsr-s1-text-3) 14%,transparent);font-family:var(--vsr-s1-mono);font-size:11.5px;line-height:20px}
767
+ .vsr-s1__label b{color:var(--vsr-s1-text);font-weight:650}
635
768
  .vsr-s1__rowms{color:var(--vsr-s1-text-3);font-size:12px;line-height:22px;font-variant-numeric:tabular-nums;white-space:nowrap}
636
769
  .vsr-s1__more,.vsr-s1__link{justify-self:start;padding:0;border:0;background:none;color:var(--dsw-alias-link,#2F7BF6);font:inherit;font-size:12.5px;cursor:pointer}
637
770
  .vsr-s1__more:hover,.vsr-s1__link:hover{text-decoration:underline}
@@ -673,6 +806,8 @@ window.__ModuleLoader__.load({
673
806
  .vsr-s1__badge--fresh::after{content:"";position:absolute;inset:-1px;border:2px solid var(--vsr-s1-ember);border-radius:inherit;pointer-events:none;animation:vsr-s1-ring .85s ease-out .06s both}
674
807
  .vsr-s1__row--fresh{animation:vsr-s1-row .34s cubic-bezier(.2,.8,.2,1) .06s both}
675
808
  .vsr-s1__row--fresh .vsr-s1__bar>i{animation:vsr-s1-fill .7s cubic-bezier(.2,.8,.2,1) .2s both}
809
+ @keyframes vsr-s1-mark{0%{outline:2px solid color-mix(in srgb,var(--vsr-s1-ember) 70%,transparent);outline-offset:3px}100%{outline:2px solid transparent;outline-offset:0}}
810
+ .vsr-s1__row--fresh .vsr-s1__span{animation:vsr-s1-mark .9s ease-out .35s both}
676
811
  .vsr-s1__stack--fresh>i{animation:vsr-s1-grow .6s cubic-bezier(.2,.8,.2,1) both}
677
812
  .vsr-s1--running .vsr-s1__dot{animation:vsr-s1-blink 1s ease-in-out infinite}
678
813
  .vsr-s1__shimmer{background:linear-gradient(90deg,var(--vsr-s1-text-2) 35%,var(--vsr-s1-ember) 50%,var(--vsr-s1-text-2) 65%);background-size:250% 100%;-webkit-background-clip:text;background-clip:text;color:transparent;animation:vsr-s1-shimmer 1.6s linear infinite}