@vllm-sr/dsh 0.4.1 → 0.4.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/README.md +17 -10
- package/package.json +2 -2
- package/src/index.js +23 -26
- package/src/keys.js +40 -3
- package/web/client.js +55 -5
package/README.md
CHANGED
|
@@ -11,8 +11,8 @@ agent orders, is the work really done, which team should get each of these 230 t
|
|
|
11
11
|
- **System One**, the decision model, decides: `vllm-sr/d3-ultra`. It answers typed questions (choice, yes/no, score,
|
|
12
12
|
box) with probabilities, from one forward pass per question instead of generated text.
|
|
13
13
|
|
|
14
|
-
Both
|
|
15
|
-
|
|
14
|
+
Both take a free [System Duo](https://app.vllm-sr.ai) key, which the plugin gets for you: System One answers at `https://app.vllm-sr.ai/v1/systemone`,
|
|
15
|
+
and `vllm-sr/auto` at `https://app.vllm-sr.ai/v1/chat/completions`.
|
|
16
16
|
|
|
17
17
|
## Quick start
|
|
18
18
|
|
|
@@ -26,9 +26,10 @@ The first line installs DeepSeek Harness (0.2.1-rc.5 or a later 0.2 release, on
|
|
|
26
26
|
`dsh plugin` uses to install plugins; without pnpm, `dsh plugin add` stops with "pnpm was not found". On a machine
|
|
27
27
|
with git, the package can also come straight from GitHub: `dsh plugin --profile web add github:Xunzhuo/dsh-system-one-plugin`.
|
|
28
28
|
|
|
29
|
-
On its first start the plugin gets
|
|
30
|
-
|
|
31
|
-
by you only). A
|
|
29
|
+
On its first start the plugin gets a free System Duo key, with the same small proof of work the site's "Get your API
|
|
30
|
+
key" button runs in the browser, and saves it in DeepSeek Harness's credential store and in `~/.dsh/.env` (readable
|
|
31
|
+
by you only). A line under your first request says so: "Free API key created for app.vllm-sr.ai, saved in
|
|
32
|
+
~/.dsh/.env", or why the site refused one. `vllm-sr/auto` becomes the default model; choose another model
|
|
32
33
|
in the app and System One keeps working beside it.
|
|
33
34
|
|
|
34
35
|
Headless:
|
|
@@ -70,11 +71,13 @@ badge that carries the decision's latency:
|
|
|
70
71
|
- **Done-check**: a card after the answer: done, waiting on you, or sent back to finish.
|
|
71
72
|
- **`system_one` tool**: its row shows the batch: items, questions, each question's answer distribution and the
|
|
72
73
|
results file.
|
|
74
|
+
- **Free key**: a **System Duo** line under your first request when the plugin created a key, or could not.
|
|
73
75
|
|
|
74
76
|
Routine checks (an allowed call, a clean result) are one line and fold with the turn's work. Cards stay visible when
|
|
75
77
|
the work folds, so a finished turn still shows what System One decided. Parallel calls in one step share one line or
|
|
76
78
|
card, and every decision pops in with a short animation the first time it appears (none with reduced motion). Each
|
|
77
|
-
element shows the Decision API's own answer: the probabilities, the model and the
|
|
79
|
+
element shows the Decision API's own answer: the probabilities, the model (in bold, as `vllm-sr/d3-ultra`) and the
|
|
80
|
+
measured latency.
|
|
78
81
|
|
|
79
82
|
Set `systemOneUi` to `compact` for one line per decision, or to `off` to hide them.
|
|
80
83
|
|
|
@@ -85,18 +88,20 @@ attributes that say what it shows:
|
|
|
85
88
|
|
|
86
89
|
| Attribute | Values |
|
|
87
90
|
| --- | --- |
|
|
88
|
-
| `data-system-one` | `card`, `compact` (one line)
|
|
89
|
-
| `data-system-one-reflex` | `triage`, `guard`, `shield`, `done` or `
|
|
90
|
-
| `data-system-one-verdict` | triage `brief`; guard `allow`, `ask` or `deny`; shield `clean`, `removed` or `flagged`; done-check `stop` or `continue`; tool `running` or `done`; `error` when System One did not answer |
|
|
91
|
+
| `data-system-one` | `card`, `compact` (one line), `tool` (the `system_one` row) or `notice` (the free-key line) |
|
|
92
|
+
| `data-system-one-reflex` | `triage`, `guard`, `shield`, `done`, `tool` or `key` |
|
|
93
|
+
| `data-system-one-verdict` | triage `brief`; guard `allow`, `ask` or `deny`; shield `clean`, `removed` or `flagged`; done-check `stop` or `continue`; tool `running` or `done`; key `created` or `failed`; `error` when System One did not answer |
|
|
91
94
|
| `data-system-one-outcome` | guard, once you answer its approval: `allowed`, `rejected`, `cancelled` or `failed` |
|
|
92
95
|
| `data-system-one-why` | done-check: `complete`, `waiting`, `unfinished` or `unchecked` |
|
|
93
96
|
| `data-system-one-tone` | its colour: `ok` (green), `info` (ember), `warn` (amber), `bad` (red) or `muted` |
|
|
94
97
|
| `data-system-one-count` | how many decisions it holds (a batch: how many items) |
|
|
95
98
|
| `data-system-one-turn`, `data-system-one-step` | where it acted (the step for guard and shield) |
|
|
99
|
+
| `data-system-one-model` | the model that answered, as `vllm-sr/d3-ultra` |
|
|
96
100
|
|
|
97
101
|
Inside a root, `data-system-one-part` marks `badge` (the ember **System One** pill), `latency`, `reflex`, `summary`,
|
|
98
102
|
`model`, `head`, `toggle` (the button of a one-line element), `rows`, `verdict`, `outcome`, `subject`, `facts`,
|
|
99
|
-
`note` and `more`,
|
|
103
|
+
`note` and `more`, in the batch row also `stack`, `legend`, `failed`, `results` and `raw`, and in the free-key line
|
|
104
|
+
`badge` (the **System Duo** pill), `summary` and `links`. Each number is a
|
|
100
105
|
`data-system-one-fact`: `kind`, `effort`, `unclear` and `risky` (triage), `risk`, `leaks` and `on-task` (guard),
|
|
101
106
|
`passages` and `injection` (shield), `complete`, `checked` and `waiting` (done-check). Decision rows carry
|
|
102
107
|
`data-system-one-decision` and `data-system-one-call`, batch questions `data-system-one-question`. For example:
|
|
@@ -113,6 +118,8 @@ Inside a root, `data-system-one-part` marks `badge` (the ember **System One** pi
|
|
|
113
118
|
| The ember badge, and its latency | `[data-system-one-part="badge"]`, `[data-system-one-part="latency"]` |
|
|
114
119
|
| "Dangerous", and "You rejected it" | `[data-system-one-fact="risk"]`, `[data-system-one-part="outcome"]` |
|
|
115
120
|
| The reason | `[data-system-one-part="note"]` |
|
|
121
|
+
| The model's name (bold) | `[data-system-one-part="model"]` |
|
|
122
|
+
| Free key created | `[data-system-one="notice"][data-system-one-verdict="created"]` |
|
|
116
123
|
|
|
117
124
|
## Your data
|
|
118
125
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vllm-sr/dsh",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "System Duo for DeepSeek Harness. Thinking, fast and slow: System One decision models guard, shield, triage and check your agent around System Two (vllm-sr/auto), with
|
|
3
|
+
"version": "0.4.2",
|
|
4
|
+
"description": "System Duo for DeepSeek Harness. Thinking, fast and slow: System One decision models guard, shield, triage and check your agent around System Two (vllm-sr/auto), with a free app.vllm-sr.ai key.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
7
7
|
"dsh",
|
package/src/index.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - done: agent/turn-stopping is the work really finished and checked?
|
|
10
10
|
* - tool: system_one one rubric over many items, for the LLM to call
|
|
11
11
|
*
|
|
12
|
-
* On first use it gets
|
|
12
|
+
* On first use it gets a free System Duo key (app.vllm-sr.ai) for vllm-sr/auto and the decision models (keys.js).
|
|
13
13
|
* Each decision is also recorded in the session (records.js), and the browser half (web/client.js) draws it in Chat.
|
|
14
14
|
*/
|
|
15
15
|
import { homedir } from 'node:os'
|
|
@@ -19,7 +19,7 @@ import z from '@deepseek-ai/schemastery'
|
|
|
19
19
|
import { createUserMessage } from '@deepseek-ai/dsh-llm'
|
|
20
20
|
import * as dshSession from '@deepseek-ai/dsh-session'
|
|
21
21
|
import { createDecisionClient, compactAnswer, yes } from './client.js'
|
|
22
|
-
import { SERVICES, appendDotenv, ensureKeys, readDotenv } from './keys.js'
|
|
22
|
+
import { SERVICES, appendDotenv, describeKeys, ensureKeys, readDotenv } from './keys.js'
|
|
23
23
|
import { DONE, EFFORT_LEVELS, GUARD, RISK_LEVELS, TRIAGE } from './questions.js'
|
|
24
24
|
import { UI_MODES, approvalOutcome, createRecorder, relativeTo, subject } from './records.js'
|
|
25
25
|
import { screen } from './shield.js'
|
|
@@ -125,7 +125,9 @@ function briefCall(exec) {
|
|
|
125
125
|
export function apply(ctx, config) {
|
|
126
126
|
let credentials
|
|
127
127
|
let keysReady
|
|
128
|
+
let keysSettled = false
|
|
128
129
|
let keysNotice
|
|
130
|
+
let keysRecord
|
|
129
131
|
const keysAbort = new AbortController()
|
|
130
132
|
// DeepSeek Harness's user dotenv: the credential store falls back to it, and a headless run reads it at launch
|
|
131
133
|
const envFile = join(process.env.DSH_HOME || join(homedir(), '.dsh'), '.env')
|
|
@@ -161,7 +163,7 @@ export function apply(ctx, config) {
|
|
|
161
163
|
if (!id) return undefined
|
|
162
164
|
let state = sessions.get(id)
|
|
163
165
|
if (!state) {
|
|
164
|
-
state = { id, user: '', calls: [], assistant: '', steers: 0, triage: undefined, turn: 0, step: 0, asked: new Map(), pendingTriage: undefined }
|
|
166
|
+
state = { id, user: '', calls: [], assistant: '', steers: 0, triage: undefined, turn: 0, step: 0, asked: new Map(), pendingTriage: undefined, pendingKey: undefined }
|
|
165
167
|
sessions.set(id, state)
|
|
166
168
|
}
|
|
167
169
|
return state
|
|
@@ -192,24 +194,15 @@ export function apply(ctx, config) {
|
|
|
192
194
|
return ensureKeys({ resolve, store: keep, services, label: config.keyLabel, signal: keysAbort.signal })
|
|
193
195
|
})().then(({ allocated, failed }) => {
|
|
194
196
|
trace({ ev: 'keys', store: Boolean(store), allocated: allocated.map(s => s.site), failed: failed.map(f => ({ site: f.service.site, code: f.error.code, message: clip(String(f.error.message), 200) })) })
|
|
195
|
-
const
|
|
196
|
-
if (
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
lines.push(`[System Duo] One free key for vllm-sr/auto and the decision models (${new URL(s.site).host}) is saved in ${saved}. Using it accepts the terms (${s.terms}); requests are logged to improve the services and to train models (${s.privacy}).${later}`)
|
|
201
|
-
}
|
|
202
|
-
for (const { service, error } of failed) {
|
|
203
|
-
if (keysAbort.signal.aborted) continue
|
|
204
|
-
// a DOMException's code is a legacy number (20 for an abort, 23 for a timeout); its name says what happened
|
|
205
|
-
const why = typeof error.code === 'number' ? error.name : error.code ?? error.status ?? 'error'
|
|
206
|
-
lines.push(`[System Duo] No free key from ${new URL(service.site).host} (${why}: ${clip(String(error.message), 160)}). Get one at ${service.site} and save it as ${service.ref}.`)
|
|
207
|
-
}
|
|
208
|
-
if (lines.length === 0) return
|
|
209
|
-
if (!store) process.stderr.write(`${lines.join('\n')}\n`)
|
|
210
|
-
keysNotice = notice(lines.join('\n'), allocated.length ? 'System Duo got a free key for vllm-sr/auto and the decision models' : 'System Duo could not get a free key')
|
|
197
|
+
const said = describeKeys({ allocated, failed: keysAbort.signal.aborted ? [] : failed }, { file: where, store: Boolean(store) })
|
|
198
|
+
if (!said) return
|
|
199
|
+
if (!store) process.stderr.write(`${said.text}\n`)
|
|
200
|
+
keysNotice = notice(said.text, said.summary)
|
|
201
|
+
keysRecord = said.record
|
|
211
202
|
}, error => {
|
|
212
203
|
trace({ ev: 'keys', error: clip(String(error?.message ?? error), 300) })
|
|
204
|
+
}).finally(() => {
|
|
205
|
+
keysSettled = true
|
|
213
206
|
})
|
|
214
207
|
}
|
|
215
208
|
|
|
@@ -222,10 +215,13 @@ export function apply(ctx, config) {
|
|
|
222
215
|
startKeys(scoped.credentials)
|
|
223
216
|
})
|
|
224
217
|
|
|
225
|
-
|
|
218
|
+
// Chat hides notices, so the keys' one also goes to the session as a record that Chat draws under the request.
|
|
219
|
+
function withNotices(decision, messages, state, agent) {
|
|
226
220
|
if (decision.kind !== 'enter') return decision
|
|
227
221
|
const extra = [keysNotice, ...messages].filter(Boolean)
|
|
222
|
+
if (keysNotice && keysRecord && state && agent?.session) state.pendingKey = { session: agent.session, data: { ...keysRecord, turn: state.turn, step: state.step } }
|
|
228
223
|
keysNotice = undefined
|
|
224
|
+
keysRecord = undefined
|
|
229
225
|
return extra.length === 0 ? decision : { ...decision, messages: [...decision.messages, ...extra] }
|
|
230
226
|
}
|
|
231
227
|
|
|
@@ -254,10 +250,11 @@ export function apply(ctx, config) {
|
|
|
254
250
|
const AFTER_REQUEST = new Set(['request/header', 'request/context', 'assistant/message', 'step/end', 'turn/end'])
|
|
255
251
|
ctx.on('session/event', (session, event) => {
|
|
256
252
|
const state = sessions.get(session?.id)
|
|
257
|
-
const pending = state?.pendingTriage
|
|
258
|
-
if (pending && ((event?.type === 'user/message' && event.data?.source?.kind === 'user') || AFTER_REQUEST.has(event?.type))) {
|
|
253
|
+
const pending = [state?.pendingKey, state?.pendingTriage].filter(Boolean)
|
|
254
|
+
if (pending.length && ((event?.type === 'user/message' && event.data?.source?.kind === 'user') || AFTER_REQUEST.has(event?.type))) {
|
|
255
|
+
state.pendingKey = undefined
|
|
259
256
|
state.pendingTriage = undefined
|
|
260
|
-
queueMicrotask(() => record(
|
|
257
|
+
queueMicrotask(() => pending.forEach(entry => record(entry.session, entry.data)))
|
|
261
258
|
}
|
|
262
259
|
if (event?.type !== 'assistant/message') return
|
|
263
260
|
const text = textOf(event.data?.message?.content)
|
|
@@ -289,7 +286,7 @@ export function apply(ctx, config) {
|
|
|
289
286
|
// Triage: one look at each new user request before the model sees it.
|
|
290
287
|
ctx.on('agent/pre-step', async (payload, next) => {
|
|
291
288
|
if (!keysReady && !credentials) startKeys(undefined)
|
|
292
|
-
if (keysReady) await Promise.race([keysReady, sleep(KEYS_WAIT_MS)])
|
|
289
|
+
if (keysReady && !keysSettled) await Promise.race([keysReady, sleep(KEYS_WAIT_MS, undefined, { ref: false })])
|
|
293
290
|
const { agent, messages, signal, turn, step } = payload
|
|
294
291
|
const userMessages = messages.filter(message => message.source?.kind === 'user')
|
|
295
292
|
const state = sessionState(agent)
|
|
@@ -302,7 +299,7 @@ export function apply(ctx, config) {
|
|
|
302
299
|
state.calls = []
|
|
303
300
|
state.steers = 0
|
|
304
301
|
trace({ ev: 'user', session: state.id, turn, text: clip(state.user, 4000) })
|
|
305
|
-
if (!config.triage || !state.user) return withNotices(await next(), [])
|
|
302
|
+
if (!config.triage || !state.user) return withNotices(await next(), [], state, agent)
|
|
306
303
|
let brief
|
|
307
304
|
try {
|
|
308
305
|
const response = await client.ask({ model: config.model, questions: TRIAGE, inputs: [{ state: clip(state.user, 12000) }], signal, timeoutMs: config.hookTimeoutMs, retries: config.hookRetries })
|
|
@@ -326,7 +323,7 @@ export function apply(ctx, config) {
|
|
|
326
323
|
trace({ ev: 's1', reflex: 'triage', session: state.id, turn, error: String(error?.message ?? error) })
|
|
327
324
|
if (!signal?.aborted) state.pendingTriage = { session: agent.session, data: { reflex: 'triage', turn, step, error: subject(error?.message ?? error) } }
|
|
328
325
|
}
|
|
329
|
-
return withNotices(await next(), [brief])
|
|
326
|
+
return withNotices(await next(), [brief], state, agent)
|
|
330
327
|
})
|
|
331
328
|
|
|
332
329
|
// Guard: every side-effecting call is reviewed before it runs.
|
package/src/keys.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The free System Duo key (app.vllm-sr.ai)
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* The free System Duo key (app.vllm-sr.ai), which both vllm-sr/auto, the LLM (System Two), and the decision models
|
|
3
|
+
* (System One) take. The site hands it out for a small proof of work, the same one its "Get your API key" button runs
|
|
4
|
+
* in the browser; the key is kept in DeepSeek Harness's credential store.
|
|
5
5
|
*/
|
|
6
6
|
import { createHash, hash as oneShot } from 'node:crypto'
|
|
7
7
|
import { closeSync, fchmodSync, mkdirSync, openSync, readFileSync, writeSync } from 'node:fs'
|
|
@@ -172,3 +172,40 @@ export async function ensureKeys({ resolve, store, services = SERVICES, label, f
|
|
|
172
172
|
}))
|
|
173
173
|
return { allocated, failed }
|
|
174
174
|
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* What the session says about the keys a start fetched: the notice the model reads (and Trajectory shows), and the
|
|
178
|
+
* record Chat draws as an inline line.
|
|
179
|
+
* @param {object} outcome - ensureKeys' result
|
|
180
|
+
* @param {object} where
|
|
181
|
+
* @param {string} where.file - the dotenv file the key went to, as the user writes it (~/.dsh/.env)
|
|
182
|
+
* @param {boolean} where.store - whether DeepSeek Harness's credential store holds it too
|
|
183
|
+
* @returns {{text: string, summary: string, record: object} | undefined} nothing when no key was fetched or refused
|
|
184
|
+
*/
|
|
185
|
+
export function describeKeys({ allocated, failed }, { file, store }) {
|
|
186
|
+
const host = site => new URL(site).host
|
|
187
|
+
const lines = []
|
|
188
|
+
if (allocated.length) {
|
|
189
|
+
const s = allocated[0]
|
|
190
|
+
const saved = store ? `DeepSeek Harness's credential store and ${file}` : file
|
|
191
|
+
const later = store ? '' : ' A headless run reads keys when it starts, so vllm-sr/auto answers from the next run.'
|
|
192
|
+
lines.push(`[System Duo] Free API key created for ${host(s.site)}, saved in ${saved}. Using it accepts the terms (${s.terms}); requests are logged to improve the services and to train models (${s.privacy}).${later}`)
|
|
193
|
+
}
|
|
194
|
+
const refused = failed.map(({ service, error }) => {
|
|
195
|
+
// a DOMException's code is a legacy number (20 for an abort, 23 for a timeout); its name says what happened
|
|
196
|
+
const why = typeof error.code === 'number' ? error.name : error.code ?? error.status ?? 'error'
|
|
197
|
+
const message = String(error.message).slice(0, 160)
|
|
198
|
+
lines.push(`[System Duo] No free API key from ${host(service.site)} (${why}: ${message}). Get one at ${service.site} and save it as ${service.ref}.`)
|
|
199
|
+
return { service, why, message }
|
|
200
|
+
})
|
|
201
|
+
if (lines.length === 0) return undefined
|
|
202
|
+
const s = allocated[0]
|
|
203
|
+
const record = s
|
|
204
|
+
? { reflex: 'key', verdict: 'created', site: host(s.site), saved: file, store, terms: s.terms, privacy: s.privacy }
|
|
205
|
+
: { reflex: 'key', verdict: 'failed', site: host(refused[0].service.site), url: refused[0].service.site, ref: refused[0].service.ref, error: { code: String(refused[0].why), message: refused[0].message } }
|
|
206
|
+
return {
|
|
207
|
+
text: lines.join('\n'),
|
|
208
|
+
summary: s ? `Free API key created for ${host(s.site)}` : `No free API key from ${host(refused[0].service.site)}`,
|
|
209
|
+
record,
|
|
210
|
+
}
|
|
211
|
+
}
|
package/web/client.js
CHANGED
|
@@ -64,7 +64,8 @@ window.__ModuleLoader__.load({
|
|
|
64
64
|
const num = value => (typeof value === 'number' && Number.isFinite(value) ? value : undefined)
|
|
65
65
|
const p2 = value => (num(value) === undefined ? '?' : value.toFixed(2))
|
|
66
66
|
const secs = ms => (num(ms) === undefined ? '' : `${(ms / 1000).toFixed(2)} s`)
|
|
67
|
-
|
|
67
|
+
// System One's models are vllm-sr's; the name always shows its owner, whatever form the answer gave it in
|
|
68
|
+
const fullModel = model => (typeof model === 'string' && model ? (model.includes('/') ? model : `vllm-sr/${model}`) : '')
|
|
68
69
|
const plural = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`
|
|
69
70
|
const clamp01 = value => Math.min(1, Math.max(0, value))
|
|
70
71
|
const level = (labels, score) => labels[Math.min(labels.length - 1, Math.max(0, Math.round(score)))]
|
|
@@ -319,6 +320,11 @@ window.__ModuleLoader__.load({
|
|
|
319
320
|
secs(ms) ? h('span', { className: 'vsr-s1__ms', 'data-system-one-part': 'latency' }, secs(ms)) : null)
|
|
320
321
|
}
|
|
321
322
|
|
|
323
|
+
function Model({ model }) {
|
|
324
|
+
const name = fullModel(model)
|
|
325
|
+
return name ? h('b', { className: 'vsr-s1__model', 'data-system-one-part': 'model', title: name }, name) : null
|
|
326
|
+
}
|
|
327
|
+
|
|
322
328
|
function Pill({ tone, children, arrow, part }) {
|
|
323
329
|
return h('span', { className: cx('vsr-s1__pill', `vsr-s1__pill--${tone}`, arrow && 'vsr-s1__pill--then'), 'data-system-one-part': part }, children)
|
|
324
330
|
}
|
|
@@ -372,6 +378,7 @@ window.__ModuleLoader__.load({
|
|
|
372
378
|
'data-system-one-count': String(state.rows.length),
|
|
373
379
|
'data-system-one-turn': whole(state.rows[0].turn),
|
|
374
380
|
'data-system-one-step': state.reflex === 'guard' || state.reflex === 'shield' ? whole(state.rows[0].step) : undefined,
|
|
381
|
+
'data-system-one-model': fullModel(state.rows[state.rows.length - 1].model) || undefined,
|
|
375
382
|
}
|
|
376
383
|
}
|
|
377
384
|
|
|
@@ -393,7 +400,7 @@ window.__ModuleLoader__.load({
|
|
|
393
400
|
h(Badge, { key: idOf(last), id: idOf(last), ms: last.ms, time: last.time }),
|
|
394
401
|
h('span', { className: 'vsr-s1__reflex', 'data-system-one-part': 'reflex' }, reflex),
|
|
395
402
|
h('span', { className: 'vsr-s1__summary', 'data-system-one-part': 'summary', title: headline }, headline),
|
|
396
|
-
|
|
403
|
+
h(Model, { model: last.model })),
|
|
397
404
|
h(Rows, { reflex: state.reflex, rows: state.rows, views }))
|
|
398
405
|
}
|
|
399
406
|
|
|
@@ -423,15 +430,48 @@ window.__ModuleLoader__.load({
|
|
|
423
430
|
h('span', { className: 'vsr-s1__summary', 'data-system-one-part': 'summary', title: said },
|
|
424
431
|
h('span', { className: cx('vsr-s1__verdict', line.tone && `vsr-s1__verdict--${line.tone}`), 'data-system-one-part': 'verdict' }, line.label),
|
|
425
432
|
line.rest ? ` · ${line.rest}` : null),
|
|
433
|
+
open ? h(Model, { model: last.model }) : null,
|
|
426
434
|
h(Chevron)),
|
|
427
435
|
open ? h(Rows, { reflex: state.reflex, rows: state.rows, views }) : null)
|
|
428
436
|
}
|
|
429
437
|
|
|
438
|
+
function KeyGlyph() {
|
|
439
|
+
return h('svg', { className: 'vsr-s1__glyph', viewBox: '0 0 16 16', width: 12, height: 12, 'aria-hidden': true },
|
|
440
|
+
h('circle', { cx: 5.5, cy: 8, r: 3, fill: 'none', stroke: 'currentColor', strokeWidth: 1.6 }),
|
|
441
|
+
h('path', { d: 'M8.5 8H14M12 8v2.5M14 8v2', fill: 'none', stroke: 'currentColor', strokeWidth: 1.6, strokeLinecap: 'round' }))
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// The free key a start fetched, or why it could not: one line under the request, in either mode.
|
|
445
|
+
function KeyNotice({ state }) {
|
|
446
|
+
const row = state.rows[state.rows.length - 1]
|
|
447
|
+
const created = row.verdict === 'created'
|
|
448
|
+
const fresh = useFresh(`key:${idOf(row)}`, row.time)
|
|
449
|
+
const site = text(row.site) || 'app.vllm-sr.ai'
|
|
450
|
+
const said = created
|
|
451
|
+
? `Free API key created for ${site}, saved in ${text(row.saved) || '~/.dsh/.env'}`
|
|
452
|
+
: `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'}.`
|
|
453
|
+
const link = (href, label) => (typeof href === 'string' && /^https:\/\//.test(href) ? h('a', { className: 'vsr-s1__link', href, target: '_blank', rel: 'noreferrer' }, label) : null)
|
|
454
|
+
return h('div', {
|
|
455
|
+
className: cx('vsr-s1', 'vsr-s1--notice', created ? 'vsr-s1--ok' : 'vsr-s1--bad', fresh && 'vsr-s1--fresh'),
|
|
456
|
+
role: 'note',
|
|
457
|
+
'aria-label': `System Duo: ${said}`,
|
|
458
|
+
'data-system-one': 'notice',
|
|
459
|
+
'data-system-one-reflex': 'key',
|
|
460
|
+
'data-system-one-verdict': created ? 'created' : 'failed',
|
|
461
|
+
'data-system-one-tone': created ? 'ok' : 'bad',
|
|
462
|
+
'data-system-one-turn': whole(row.turn),
|
|
463
|
+
},
|
|
464
|
+
h('span', { className: cx('vsr-s1__duo', fresh && 'vsr-s1__badge--fresh'), 'data-system-one-part': 'badge' }, h(KeyGlyph), 'System Duo'),
|
|
465
|
+
h('span', { className: cx('vsr-s1__summary', !created && 'vsr-s1__summary--bad'), 'data-system-one-part': 'summary', title: said }, said),
|
|
466
|
+
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)
|
|
467
|
+
}
|
|
468
|
+
|
|
430
469
|
// A routine check (an allowed call, a clean result) is one line in either mode; a decision that changed
|
|
431
470
|
// something, and the triage and done-check that frame the turn, are cards unless the mode is compact.
|
|
432
471
|
function SystemOneNode(props) {
|
|
433
472
|
const state = props?.node?.data
|
|
434
473
|
if (!state || !Array.isArray(state.rows) || state.rows.length === 0) return null
|
|
474
|
+
if (state.reflex === 'key') return h(Contained, null, h(KeyNotice, { state }))
|
|
435
475
|
const slim = state.ui === 'compact' || !standsOut(state)
|
|
436
476
|
return h(Contained, null, h(slim ? Compact : Card, { state }))
|
|
437
477
|
}
|
|
@@ -561,13 +601,17 @@ window.__ModuleLoader__.load({
|
|
|
561
601
|
|
|
562
602
|
const ms = num(meta.seconds) === undefined ? undefined : meta.seconds * 1000
|
|
563
603
|
const headline = [plural(num(meta.items) ?? 0, 'item'), plural(num(meta.questions) ?? 0, 'question'), plural(num(meta.requests) ?? 0, 'request')].join(' · ')
|
|
564
|
-
const done = {
|
|
604
|
+
const done = {
|
|
605
|
+
...common, 'data-system-one-verdict': 'done', 'data-system-one-tone': 'ok', 'data-system-one-count': String(meta.items ?? ''),
|
|
606
|
+
'data-system-one-model': fullModel(meta.model) || undefined,
|
|
607
|
+
}
|
|
565
608
|
if (ui === 'compact') {
|
|
566
609
|
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
610
|
h('button', { type: 'button', className: 'vsr-s1__chip', 'data-system-one-part': 'toggle', 'aria-expanded': open, onClick: () => setOpen(!open) },
|
|
568
611
|
h(Badge, { id: `${callId}:done`, ms, time: block?.time }),
|
|
569
612
|
h('span', { className: 'vsr-s1__reflex', 'data-system-one-part': 'reflex' }, REFLEX.tool),
|
|
570
613
|
h('span', { className: 'vsr-s1__summary', 'data-system-one-part': 'summary', title: headline }, headline),
|
|
614
|
+
open ? h(Model, { model: meta.model }) : null,
|
|
571
615
|
h(Chevron)),
|
|
572
616
|
open ? h(ToolBody, { meta, fresh, cwd, openFile }) : null)
|
|
573
617
|
}
|
|
@@ -583,7 +627,7 @@ window.__ModuleLoader__.load({
|
|
|
583
627
|
h(Badge, { id: `${callId}:done`, ms, time: block?.time }),
|
|
584
628
|
h('span', { className: 'vsr-s1__reflex', 'data-system-one-part': 'reflex' }, REFLEX.tool),
|
|
585
629
|
h('span', { className: 'vsr-s1__summary', 'data-system-one-part': 'summary', title: headline }, headline),
|
|
586
|
-
|
|
630
|
+
h(Model, { model: meta.model })),
|
|
587
631
|
h(ToolBody, { meta, fresh, cwd, openFile }))
|
|
588
632
|
}
|
|
589
633
|
|
|
@@ -612,7 +656,13 @@ window.__ModuleLoader__.load({
|
|
|
612
656
|
.vsr-s1__summary--bad{color:var(--vsr-s1-bad)}
|
|
613
657
|
.vsr-s1__verdict{font-weight:600;color:var(--vsr-s1-text)}
|
|
614
658
|
.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-
|
|
659
|
+
.vsr-s1__model{flex:none;color:var(--vsr-s1-text-2);font-size:12px;font-weight:700;letter-spacing:-.005em;white-space:nowrap}
|
|
660
|
+
.vsr-s1--notice{display:flex;align-items:center;gap:9px;min-width:0;margin:4px 0;padding:3px 0}
|
|
661
|
+
.vsr-s1--notice .vsr-s1__summary{white-space:normal}
|
|
662
|
+
.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)}
|
|
663
|
+
.vsr-s1__glyph{flex:none}
|
|
664
|
+
.vsr-s1__links{display:inline-flex;flex:none;gap:10px}
|
|
665
|
+
a.vsr-s1__link{text-decoration:none}
|
|
616
666
|
.vsr-s1__rows{display:grid;grid-template-columns:auto minmax(0,1fr) auto;column-gap:12px;row-gap:8px;margin-top:9px}
|
|
617
667
|
.vsr-s1__rows>*{grid-column:1/-1}
|
|
618
668
|
.vsr-s1__row{display:grid;grid-template-columns:auto minmax(0,1fr) auto;grid-template-columns:subgrid;column-gap:12px;align-items:start}
|