@elvisw/dsh-magic-context 0.13.0 → 0.14.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/README.md +92 -43
- package/README.zh-CN.md +65 -35
- package/lib/client.js +569 -86
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -178,6 +178,128 @@ window.__ModuleLoader__.load({
|
|
|
178
178
|
]
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
/** Sentinel option value for the always-available Custom branch. */
|
|
182
|
+
const EFFORT_CUSTOM_SENTINEL = '__custom__'
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Normalize one model's `reasoning` metadata from the host catalog.
|
|
186
|
+
* Malformed rows and duplicate ids are dropped; host order is kept.
|
|
187
|
+
*/
|
|
188
|
+
function normalizeModelReasoning(value) {
|
|
189
|
+
if (value === null || typeof value !== 'object') return undefined
|
|
190
|
+
const raw = Array.isArray(value.efforts) ? value.efforts : []
|
|
191
|
+
const efforts = []
|
|
192
|
+
const seen = new Set()
|
|
193
|
+
for (const effort of raw) {
|
|
194
|
+
if (effort === null || typeof effort !== 'object') continue
|
|
195
|
+
if (typeof effort.id !== 'string') continue
|
|
196
|
+
const id = effort.id.trim()
|
|
197
|
+
if (id === '' || id === EFFORT_CUSTOM_SENTINEL || seen.has(id)) continue
|
|
198
|
+
seen.add(id)
|
|
199
|
+
const next = { id }
|
|
200
|
+
if (typeof effort.name === 'string' && effort.name !== '') next.name = effort.name
|
|
201
|
+
if (typeof effort.description === 'string' && effort.description !== '') next.description = effort.description
|
|
202
|
+
efforts.push(next)
|
|
203
|
+
}
|
|
204
|
+
if (efforts.length === 0) return undefined
|
|
205
|
+
const defaultEffort = typeof value.defaultEffort === 'string' ? value.defaultEffort.trim() : ''
|
|
206
|
+
return defaultEffort === ''
|
|
207
|
+
? { efforts }
|
|
208
|
+
: { efforts, defaultEffort }
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* The efforts one routed model advertises, or why the picker cannot list
|
|
213
|
+
* them. `auto` means the route is unpinned, `missing` means the catalog has
|
|
214
|
+
* no such provider/model, and `none` means the model advertises no efforts.
|
|
215
|
+
*/
|
|
216
|
+
function reasoningEffortsOf(groups, provider, model) {
|
|
217
|
+
const providerId = typeof provider === 'string' ? provider.trim() : ''
|
|
218
|
+
const modelId = typeof model === 'string' ? model.trim() : ''
|
|
219
|
+
if (providerId === '' || modelId === '') return { kind: 'auto', efforts: [] }
|
|
220
|
+
if (!Array.isArray(groups)) return { kind: 'missing', efforts: [] }
|
|
221
|
+
const group = groups.find((candidate) => candidate !== null && typeof candidate === 'object'
|
|
222
|
+
&& candidate.id === providerId)
|
|
223
|
+
if (group === undefined || !Array.isArray(group.models)) return { kind: 'missing', efforts: [] }
|
|
224
|
+
const entry = group.models.find((candidate) => candidate !== null && typeof candidate === 'object'
|
|
225
|
+
&& candidate.id === modelId)
|
|
226
|
+
if (entry === undefined) return { kind: 'missing', efforts: [] }
|
|
227
|
+
const reasoning = normalizeModelReasoning(entry.reasoning)
|
|
228
|
+
return reasoning === undefined
|
|
229
|
+
? { kind: 'none', efforts: [] }
|
|
230
|
+
: { kind: 'ready', efforts: reasoning.efforts, defaultEffort: reasoning.defaultEffort }
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Classify a stored/current effort id against the advertised options. */
|
|
234
|
+
function effortSelectionOf(options, current) {
|
|
235
|
+
const value = typeof current === 'string' ? current.trim() : ''
|
|
236
|
+
if (value === '') return { mode: 'default', value: '' }
|
|
237
|
+
if (Array.isArray(options) && options.some((option) => option !== null
|
|
238
|
+
&& typeof option === 'object' && option.id === value)) {
|
|
239
|
+
return { mode: 'value', value }
|
|
240
|
+
}
|
|
241
|
+
return { mode: 'custom', value }
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** The writes a staged effort draft posts; never blocks the save. */
|
|
245
|
+
function effortPlanOps(path, draft) {
|
|
246
|
+
if (draft === undefined) return { ops: [], blocked: false }
|
|
247
|
+
if (draft.clear === true) return { ops: [{ op: 'unset', path: path.slice() }], blocked: false }
|
|
248
|
+
const text = String(draft.text ?? '').trim()
|
|
249
|
+
return {
|
|
250
|
+
ops: text === ''
|
|
251
|
+
? [{ op: 'unset', path: path.slice() }]
|
|
252
|
+
: [{ op: 'set', path: path.slice(), value: text }],
|
|
253
|
+
blocked: false,
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* The effort picker's render state. `draft.clear` wins over any base value;
|
|
259
|
+
* non-empty draft text wins over the stored value; an opened Custom branch
|
|
260
|
+
* keeps an empty input instead of snapping back to Default.
|
|
261
|
+
*/
|
|
262
|
+
function effortDraftOf(input) {
|
|
263
|
+
const source = input !== null && typeof input === 'object' ? input : {}
|
|
264
|
+
const options = Array.isArray(source.options) ? source.options : []
|
|
265
|
+
const customOpen = source.customOpen === true
|
|
266
|
+
if (source.draft !== null && typeof source.draft === 'object' && source.draft.clear === true) {
|
|
267
|
+
return { mode: 'default', value: '', displayValue: '', customOpen: false, cleared: true }
|
|
268
|
+
}
|
|
269
|
+
const classify = (value, open) => {
|
|
270
|
+
const text = typeof value === 'string' ? value.trim() : ''
|
|
271
|
+
if (text === '') {
|
|
272
|
+
return open
|
|
273
|
+
? { mode: 'custom', value: '', displayValue: '', customOpen: true, cleared: false }
|
|
274
|
+
: { mode: 'default', value: '', displayValue: '', customOpen: false, cleared: false }
|
|
275
|
+
}
|
|
276
|
+
if (!open) {
|
|
277
|
+
const found = options.find((option) => option !== null && typeof option === 'object' && option.id === text)
|
|
278
|
+
if (found !== undefined) {
|
|
279
|
+
return {
|
|
280
|
+
mode: 'value',
|
|
281
|
+
value: text,
|
|
282
|
+
displayValue: typeof found.name === 'string' && found.name !== '' ? found.name : text,
|
|
283
|
+
customOpen: false,
|
|
284
|
+
cleared: false,
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return { mode: 'custom', value: text, displayValue: text, customOpen: true, cleared: false }
|
|
289
|
+
}
|
|
290
|
+
const draft = source.draft !== null && typeof source.draft === 'object' ? source.draft : undefined
|
|
291
|
+
if (draft !== undefined && draft.text !== undefined) {
|
|
292
|
+
const text = String(draft.text).trim()
|
|
293
|
+
if (text !== '') return classify(text, false)
|
|
294
|
+
if (customOpen) return classify('', true)
|
|
295
|
+
}
|
|
296
|
+
const stored = typeof source.stored === 'string' ? source.stored : ''
|
|
297
|
+
if (stored.trim() !== '') return classify(stored, customOpen)
|
|
298
|
+
const base = typeof source.baseValue === 'string' ? source.baseValue : ''
|
|
299
|
+
if (base.trim() !== '') return classify(base, customOpen)
|
|
300
|
+
return classify('', customOpen)
|
|
301
|
+
}
|
|
302
|
+
|
|
181
303
|
/**
|
|
182
304
|
* One leaf the card edits.
|
|
183
305
|
*
|
|
@@ -188,101 +310,148 @@ window.__ModuleLoader__.load({
|
|
|
188
310
|
* `SettingsFormModel` only models flat fields, which is why this card owns
|
|
189
311
|
* its own small staging model.
|
|
190
312
|
*/
|
|
313
|
+
/**
|
|
314
|
+
* Dictionary keys for one field are derived from its path, so a new field
|
|
315
|
+
* cannot acquire copy that lives outside the settings namespace.
|
|
316
|
+
*/
|
|
317
|
+
function fieldCopy(path) {
|
|
318
|
+
const prefix = 'settings.field.' + path.join('.')
|
|
319
|
+
return { labelKey: prefix + '.label', hintKey: prefix + '.hint' }
|
|
320
|
+
}
|
|
321
|
+
|
|
191
322
|
const FIELDS = [
|
|
192
323
|
{
|
|
193
|
-
path: ['policy', 'executeThreshold'], kind: 'number',
|
|
194
|
-
hint: 'Adoption percentage at which the execute band opens (0-100).',
|
|
324
|
+
path: ['policy', 'executeThreshold'], kind: 'number', ...fieldCopy(['policy', 'executeThreshold']),
|
|
195
325
|
},
|
|
196
326
|
{
|
|
197
|
-
path: ['policy', 'forceThreshold'], kind: 'number',
|
|
198
|
-
hint: 'Adoption percentage at which reclaim becomes unconditional (0-100).',
|
|
327
|
+
path: ['policy', 'forceThreshold'], kind: 'number', ...fieldCopy(['policy', 'forceThreshold']),
|
|
199
328
|
},
|
|
200
329
|
{
|
|
201
|
-
path: ['policy', 'assumedContextWindow'], kind: 'number',
|
|
202
|
-
hint: 'Window used for scheduling when the routed model discloses none.',
|
|
330
|
+
path: ['policy', 'assumedContextWindow'], kind: 'number', ...fieldCopy(['policy', 'assumedContextWindow']),
|
|
203
331
|
},
|
|
204
332
|
{
|
|
205
|
-
path: ['policy', 'ttlMs'], kind: 'number',
|
|
206
|
-
hint: 'How long a reclaimed compartment stays reversible.',
|
|
333
|
+
path: ['policy', 'ttlMs'], kind: 'number', ...fieldCopy(['policy', 'ttlMs']),
|
|
207
334
|
},
|
|
208
335
|
{
|
|
209
|
-
path: ['policy', 'protectedToolResults'], kind: 'number',
|
|
210
|
-
hint: 'Newest tool results the policy never reclaims.',
|
|
336
|
+
path: ['policy', 'protectedToolResults'], kind: 'number', ...fieldCopy(['policy', 'protectedToolResults']),
|
|
211
337
|
},
|
|
212
338
|
{
|
|
213
|
-
path: ['policy', 'historianMinNodes'], kind: 'number',
|
|
214
|
-
hint: 'Assistant/tool nodes required before the historian runs.',
|
|
339
|
+
path: ['policy', 'historianMinNodes'], kind: 'number', ...fieldCopy(['policy', 'historianMinNodes']),
|
|
215
340
|
},
|
|
216
341
|
{
|
|
217
|
-
path: ['policy', 'historianMaxTokens'], kind: 'number',
|
|
218
|
-
hint: 'Generation cap for one historian call; anything below 8192 is raised to 8192.',
|
|
342
|
+
path: ['policy', 'historianMaxTokens'], kind: 'number', ...fieldCopy(['policy', 'historianMaxTokens']),
|
|
219
343
|
},
|
|
220
344
|
{
|
|
221
|
-
path: ['policy', 'historianMaxTokensCeiling'], kind: 'number',
|
|
222
|
-
hint: 'Ceiling the historian may escalate to after a token-cap truncation (doubles per attempt).',
|
|
345
|
+
path: ['policy', 'historianMaxTokensCeiling'], kind: 'number', ...fieldCopy(['policy', 'historianMaxTokensCeiling']),
|
|
223
346
|
},
|
|
224
347
|
{
|
|
225
|
-
path: ['policy', 'historianTimeoutMs'], kind: 'number',
|
|
226
|
-
hint: 'Wall-clock budget for one historian run; a spent budget journals a failed run.',
|
|
348
|
+
path: ['policy', 'historianTimeoutMs'], kind: 'number', ...fieldCopy(['policy', 'historianTimeoutMs']),
|
|
227
349
|
},
|
|
228
350
|
{
|
|
229
|
-
path: ['policy', 'baselineTokens'], kind: 'number',
|
|
230
|
-
hint: 'Token budget reserved for the m[0]/m[1] baseline.',
|
|
351
|
+
path: ['policy', 'baselineTokens'], kind: 'number', ...fieldCopy(['policy', 'baselineTokens']),
|
|
231
352
|
},
|
|
232
353
|
{
|
|
233
|
-
path: ['policy', 'memoryBudgetTokens'], kind: 'number',
|
|
234
|
-
hint: 'Token budget the recalled-memory block may spend.',
|
|
354
|
+
path: ['policy', 'memoryBudgetTokens'], kind: 'number', ...fieldCopy(['policy', 'memoryBudgetTokens']),
|
|
235
355
|
},
|
|
236
356
|
{
|
|
237
|
-
path: ['policy', 'memoryLimit'], kind: 'number',
|
|
238
|
-
hint: 'Maximum shared memory rows recalled per pass.',
|
|
357
|
+
path: ['policy', 'memoryLimit'], kind: 'number', ...fieldCopy(['policy', 'memoryLimit']),
|
|
239
358
|
},
|
|
240
359
|
{
|
|
241
|
-
path: ['journal', 'retentionDays'], kind: 'number',
|
|
242
|
-
hint: 'How long journal rows are kept. Re-prunes once the value changes (minimum 1 day).',
|
|
360
|
+
path: ['journal', 'retentionDays'], kind: 'number', ...fieldCopy(['journal', 'retentionDays']),
|
|
243
361
|
},
|
|
244
|
-
{ path: ['historianEnabled'], kind: 'boolean', label: 'Historian enabled', hint: 'Run the out-of-band historian.' },
|
|
245
362
|
{
|
|
246
|
-
path: ['
|
|
247
|
-
hint: 'Register the adapter as the compaction engine seam. Mounts or unmounts on the next agent step — disable the host compaction row first.',
|
|
363
|
+
path: ['historianEnabled'], kind: 'boolean', ...fieldCopy(['historianEnabled']),
|
|
248
364
|
},
|
|
249
365
|
{
|
|
250
|
-
path:
|
|
251
|
-
hint: 'Provider for the maintenance verify lane. “Auto” leaves the pin off and follows the session route.',
|
|
366
|
+
path: ['registerCompactionEngine'], kind: 'boolean', ...fieldCopy(['registerCompactionEngine']),
|
|
252
367
|
},
|
|
253
368
|
{
|
|
254
|
-
path: ROUTES[0].
|
|
255
|
-
|
|
369
|
+
path: ROUTES[0].providerPath, kind: 'route-provider', routeId: 'maintenance',
|
|
370
|
+
...fieldCopy(ROUTES[0].providerPath),
|
|
256
371
|
},
|
|
257
372
|
{
|
|
258
|
-
path: ROUTES[
|
|
259
|
-
|
|
373
|
+
path: ROUTES[0].modelPath, kind: 'route-model', routeId: 'maintenance',
|
|
374
|
+
...fieldCopy(ROUTES[0].modelPath),
|
|
260
375
|
},
|
|
261
376
|
{
|
|
262
|
-
path: ROUTES[1].
|
|
263
|
-
|
|
377
|
+
path: ROUTES[1].providerPath, kind: 'route-provider', routeId: 'historian',
|
|
378
|
+
...fieldCopy(ROUTES[1].providerPath),
|
|
264
379
|
},
|
|
265
380
|
{
|
|
266
|
-
path: [
|
|
267
|
-
|
|
381
|
+
path: ROUTES[1].modelPath, kind: 'route-model', routeId: 'historian',
|
|
382
|
+
...fieldCopy(ROUTES[1].modelPath),
|
|
268
383
|
},
|
|
269
384
|
{
|
|
270
|
-
path: ['
|
|
271
|
-
|
|
385
|
+
path: ['historian', 'reasoningEffort'], kind: 'effort', routeId: 'historian',
|
|
386
|
+
...fieldCopy(['historian', 'reasoningEffort']),
|
|
272
387
|
},
|
|
273
388
|
{
|
|
274
|
-
path: ['journal', '
|
|
275
|
-
|
|
389
|
+
path: ['journal', 'enabled'], kind: 'boolean', ...fieldCopy(['journal', 'enabled']),
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
path: ['journal', 'mirror'], kind: 'boolean', ...fieldCopy(['journal', 'mirror']),
|
|
276
393
|
},
|
|
277
394
|
]
|
|
278
395
|
|
|
279
|
-
/** Copy
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
396
|
+
/** Copy owned by the form frame, summary line and effort picker. */
|
|
397
|
+
const SETTINGS_EXTRA_COPY_KEYS = [
|
|
398
|
+
'settings.summary.notReady',
|
|
399
|
+
'settings.summary.line',
|
|
400
|
+
'settings.summary.route',
|
|
401
|
+
'settings.summary.on',
|
|
402
|
+
'settings.summary.off',
|
|
403
|
+
'settings.summary.verify',
|
|
404
|
+
'settings.summary.gen',
|
|
405
|
+
'settings.form.unavailable',
|
|
406
|
+
'settings.form.readOnly',
|
|
407
|
+
'settings.form.saveFailed',
|
|
408
|
+
'settings.form.save',
|
|
409
|
+
'settings.form.saving',
|
|
410
|
+
'settings.form.modified',
|
|
411
|
+
'settings.form.reset',
|
|
412
|
+
'settings.form.invalid',
|
|
413
|
+
'settings.form.invalidNumber',
|
|
414
|
+
'settings.route.autoSession',
|
|
415
|
+
'settings.route.autoPickProvider',
|
|
416
|
+
'settings.route.notInCatalog',
|
|
417
|
+
'settings.route.providerPlaceholder',
|
|
418
|
+
'settings.route.modelPlaceholder',
|
|
419
|
+
'settings.effort.default',
|
|
420
|
+
'settings.effort.custom',
|
|
421
|
+
'settings.effort.customCurrent',
|
|
422
|
+
'settings.effort.customPlaceholder',
|
|
423
|
+
'settings.effort.autoHint',
|
|
424
|
+
'settings.effort.noneHint',
|
|
425
|
+
'settings.effort.unknownHint',
|
|
426
|
+
]
|
|
427
|
+
|
|
428
|
+
/** Every card-owned key; the node-side checks assert zh/en coverage. */
|
|
429
|
+
function settingsCopyKeys() {
|
|
430
|
+
const keys = []
|
|
431
|
+
for (const spec of FIELDS) keys.push(spec.labelKey, spec.hintKey)
|
|
432
|
+
keys.push(...SETTINGS_EXTRA_COPY_KEYS)
|
|
433
|
+
return keys
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/** The labels the shared SettingsForm shell renders. */
|
|
437
|
+
function formLabels(t) {
|
|
438
|
+
return {
|
|
439
|
+
unavailable: t('settings.form.unavailable'),
|
|
440
|
+
readOnly: t('settings.form.readOnly'),
|
|
441
|
+
saveFailed: t('settings.form.saveFailed'),
|
|
442
|
+
save: t('settings.form.save'),
|
|
443
|
+
saving: t('settings.form.saving'),
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/** The labels SettingsValueField/our picker render around one value. */
|
|
448
|
+
function settingsValueLabels(t) {
|
|
449
|
+
return {
|
|
450
|
+
modified: t('settings.form.modified'),
|
|
451
|
+
reset: t('settings.form.reset'),
|
|
452
|
+
invalid: t('settings.form.invalid'),
|
|
453
|
+
invalidNumber: t('settings.form.invalidNumber'),
|
|
454
|
+
}
|
|
286
455
|
}
|
|
287
456
|
|
|
288
457
|
/** Read a value along a path inside a nested plain object. */
|
|
@@ -409,6 +578,7 @@ window.__ModuleLoader__.load({
|
|
|
409
578
|
models.push({
|
|
410
579
|
id: model.id,
|
|
411
580
|
name: typeof model.name === 'string' && model.name !== '' ? model.name : model.id,
|
|
581
|
+
reasoning: normalizeModelReasoning(model.reasoning),
|
|
412
582
|
})
|
|
413
583
|
}
|
|
414
584
|
groups.push({ id: group.id, models })
|
|
@@ -515,12 +685,16 @@ window.__ModuleLoader__.load({
|
|
|
515
685
|
const drafts = useRef(new Map())
|
|
516
686
|
const [saving, setSaving] = useState(false)
|
|
517
687
|
const [failed, setFailed] = useState(false)
|
|
688
|
+
const [effortCustomOpen, setEffortCustomOpen] = useState(false)
|
|
518
689
|
const catalogState = useModelCatalog(catalog)
|
|
519
690
|
|
|
520
691
|
useEffect(() => scope.subscribe(() => setRevision((value) => value + 1)), [scope])
|
|
521
692
|
|
|
522
693
|
const snapshot = scope.getSnapshot()
|
|
523
694
|
const staged = drafts.current
|
|
695
|
+
const t = typeof props.t === 'function' ? props.t : REFERENCE_T
|
|
696
|
+
const labels = formLabels(t)
|
|
697
|
+
const valueLabels = settingsValueLabels(t)
|
|
524
698
|
|
|
525
699
|
// The summary view is the one-liner the Plugins page shows in its list
|
|
526
700
|
// (and as a row's description fallback); it must render without the form.
|
|
@@ -528,15 +702,23 @@ window.__ModuleLoader__.load({
|
|
|
528
702
|
const execute = formatNumber(readPath(snapshot.value, ['policy', 'executeThreshold']))
|
|
529
703
|
const force = formatNumber(readPath(snapshot.value, ['policy', 'forceThreshold']))
|
|
530
704
|
const historian = asBool(readPath(snapshot.value, ['historianEnabled']))
|
|
531
|
-
if (snapshot.status !== 'ready') return '
|
|
705
|
+
if (snapshot.status !== 'ready') return t('settings.summary.notReady')
|
|
532
706
|
const pinnedRoutes = ROUTES.flatMap((route) => {
|
|
533
707
|
const provider = formatText(readPath(snapshot.value, route.providerPath))
|
|
534
708
|
const model = formatText(readPath(snapshot.value, route.modelPath))
|
|
535
709
|
if (provider === '' || model === '') return []
|
|
536
|
-
return ['
|
|
710
|
+
return [t('settings.summary.route', {
|
|
711
|
+
kind: t(route.id === 'maintenance' ? 'settings.summary.verify' : 'settings.summary.gen'),
|
|
712
|
+
provider,
|
|
713
|
+
model,
|
|
714
|
+
})]
|
|
715
|
+
})
|
|
716
|
+
return t('settings.summary.line', {
|
|
717
|
+
execute: execute || '—',
|
|
718
|
+
force: force || '—',
|
|
719
|
+
historian: t(historian ? 'settings.summary.on' : 'settings.summary.off'),
|
|
720
|
+
routes: pinnedRoutes.join(''),
|
|
537
721
|
})
|
|
538
|
-
return 'execute ' + (execute || '—') + '% · force ' + (force || '—') + '% · historian '
|
|
539
|
-
+ (historian ? 'on' : 'off') + pinnedRoutes.join('')
|
|
540
722
|
}
|
|
541
723
|
|
|
542
724
|
/** Stage one field's draft and re-render. */
|
|
@@ -545,6 +727,12 @@ window.__ModuleLoader__.load({
|
|
|
545
727
|
setFailed(false)
|
|
546
728
|
setRevision((value) => value + 1)
|
|
547
729
|
}
|
|
730
|
+
/** Drop one draft without leaving a `size > 0` ghost entry. */
|
|
731
|
+
const unstage = (key) => {
|
|
732
|
+
if (!staged.delete(key)) return
|
|
733
|
+
setFailed(false)
|
|
734
|
+
setRevision((value) => value + 1)
|
|
735
|
+
}
|
|
548
736
|
/** Drop every draft. */
|
|
549
737
|
const discard = () => {
|
|
550
738
|
if (staged.size === 0 && !failed) return
|
|
@@ -618,6 +806,22 @@ window.__ModuleLoader__.load({
|
|
|
618
806
|
return group === undefined ? [] : group.models
|
|
619
807
|
}
|
|
620
808
|
|
|
809
|
+
const effortSpec = FIELDS.find((spec) => spec.kind === 'effort')
|
|
810
|
+
const historianRoute = routes.find((route) => route.id === 'historian')
|
|
811
|
+
const effortOptions = reasoningEffortsOf(
|
|
812
|
+
catalogGroups,
|
|
813
|
+
historianRoute.providerText,
|
|
814
|
+
historianRoute.modelText,
|
|
815
|
+
)
|
|
816
|
+
const effortDraft = staged.get(fieldKey(effortSpec.path))
|
|
817
|
+
const effortView = effortDraftOf({
|
|
818
|
+
baseValue: formatText(readPath(snapshot.base, effortSpec.path)),
|
|
819
|
+
stored: formatText(readPath(snapshot.value, effortSpec.path)),
|
|
820
|
+
draft: effortDraft,
|
|
821
|
+
customOpen: effortCustomOpen,
|
|
822
|
+
options: effortOptions.efforts,
|
|
823
|
+
})
|
|
824
|
+
|
|
621
825
|
/** Stage one side of a pin; a provider switch drops a stale model. */
|
|
622
826
|
const stageRoute = (spec, value) => {
|
|
623
827
|
const route = routeOf(spec)
|
|
@@ -647,14 +851,14 @@ window.__ModuleLoader__.load({
|
|
|
647
851
|
if (catalogReady) {
|
|
648
852
|
const options = []
|
|
649
853
|
if (isProvider) {
|
|
650
|
-
options.push({ value: '', label: '
|
|
854
|
+
options.push({ value: '', label: t('settings.route.autoSession') })
|
|
651
855
|
for (const group of catalogGroups) options.push({ value: group.id, label: group.id })
|
|
652
856
|
} else {
|
|
653
857
|
options.push({
|
|
654
858
|
value: '',
|
|
655
859
|
label: providerText === ''
|
|
656
|
-
? '
|
|
657
|
-
: '
|
|
860
|
+
? t('settings.route.autoPickProvider')
|
|
861
|
+
: t('settings.route.autoSession'),
|
|
658
862
|
})
|
|
659
863
|
for (const model of modelsOf(providerText)) {
|
|
660
864
|
options.push({
|
|
@@ -664,13 +868,14 @@ window.__ModuleLoader__.load({
|
|
|
664
868
|
}
|
|
665
869
|
}
|
|
666
870
|
if (currentText !== '' && !options.some((option) => option.value === currentText)) {
|
|
667
|
-
options.push({ value: currentText, label:
|
|
871
|
+
options.push({ value: currentText, label: t('settings.route.notInCatalog', { value: currentText }) })
|
|
668
872
|
}
|
|
669
873
|
return el('div', { key: id, style: { display: 'grid', gap: 4, margin: '8px 0' } }, [
|
|
670
|
-
el('span', { key: 'label', style: { fontSize: 12 } }, spec.
|
|
874
|
+
el('span', { key: 'label', style: { fontSize: 12 } }, t(spec.labelKey)),
|
|
671
875
|
el('select', {
|
|
672
876
|
key: 'select',
|
|
673
877
|
id,
|
|
878
|
+
'aria-label': t(spec.labelKey),
|
|
674
879
|
value: currentText,
|
|
675
880
|
disabled,
|
|
676
881
|
onChange: (event) => stageRoute(spec, event.target.value),
|
|
@@ -688,47 +893,161 @@ window.__ModuleLoader__.load({
|
|
|
688
893
|
fontSize: 12.5,
|
|
689
894
|
},
|
|
690
895
|
}, options.map((option) => el('option', { key: option.value, value: option.value }, option.label))),
|
|
691
|
-
el('span', { key: 'hint', style: { fontSize: 11, opacity: 0.6 } }, spec.
|
|
896
|
+
el('span', { key: 'hint', style: { fontSize: 11, opacity: 0.6 } }, t(spec.hintKey)),
|
|
692
897
|
])
|
|
693
898
|
}
|
|
694
899
|
if (usePrimitives && SettingsValueField !== undefined) {
|
|
695
900
|
return el(SettingsValueField, {
|
|
696
901
|
key: id,
|
|
697
902
|
id,
|
|
698
|
-
label: spec.
|
|
699
|
-
hint: spec.
|
|
903
|
+
label: t(spec.labelKey),
|
|
904
|
+
hint: t(spec.hintKey),
|
|
700
905
|
numeric: false,
|
|
701
906
|
text: state.text,
|
|
702
907
|
overridden: state.overridden,
|
|
703
908
|
invalid: false,
|
|
704
|
-
overriddenLabel:
|
|
705
|
-
resetLabel:
|
|
706
|
-
invalidLabel:
|
|
909
|
+
overriddenLabel: valueLabels.modified,
|
|
910
|
+
resetLabel: valueLabels.reset,
|
|
911
|
+
invalidLabel: valueLabels.invalid,
|
|
707
912
|
disabled: !shell.writable,
|
|
708
913
|
onEdit: (text) => stageRoute(spec, text),
|
|
709
914
|
onReset: () => stage(fieldKey(spec.path), { clear: true }),
|
|
710
915
|
})
|
|
711
916
|
}
|
|
712
917
|
return el('div', { key: id, style: { margin: '8px 0' } }, [
|
|
713
|
-
el('span', { key: 'label', style: { display: 'block', fontSize: 12 } }, spec.
|
|
918
|
+
el('span', { key: 'label', style: { display: 'block', fontSize: 12 } }, t(spec.labelKey)),
|
|
714
919
|
el('input', {
|
|
715
920
|
key: 'input',
|
|
716
921
|
id,
|
|
922
|
+
'aria-label': t(spec.labelKey),
|
|
717
923
|
type: 'text',
|
|
718
924
|
value: state.text,
|
|
719
|
-
placeholder: isProvider
|
|
925
|
+
placeholder: isProvider
|
|
926
|
+
? t('settings.route.providerPlaceholder')
|
|
927
|
+
: t('settings.route.modelPlaceholder'),
|
|
720
928
|
disabled: !shell.writable,
|
|
721
929
|
onChange: (event) => stageRoute(spec, event.target.value),
|
|
722
930
|
}),
|
|
723
|
-
el('span', { key: 'hint', style: { display: 'block', fontSize: 11, opacity: 0.6 } }, spec.
|
|
931
|
+
el('span', { key: 'hint', style: { display: 'block', fontSize: 11, opacity: 0.6 } }, t(spec.hintKey)),
|
|
724
932
|
])
|
|
725
933
|
}
|
|
726
934
|
|
|
727
935
|
/**
|
|
728
|
-
*
|
|
729
|
-
*
|
|
730
|
-
*
|
|
936
|
+
* One effort field: a catalog-backed select when the routed model
|
|
937
|
+
* advertises efforts, otherwise the manual text input plus Default.
|
|
938
|
+
* Values are never validated here; the provider stays authoritative.
|
|
731
939
|
*/
|
|
940
|
+
const renderEffortField = (spec, state) => {
|
|
941
|
+
const id = 'mc-field-' + fieldKey(spec.path)
|
|
942
|
+
const label = t(spec.labelKey)
|
|
943
|
+
const hint = t(spec.hintKey)
|
|
944
|
+
const disabled = !shell.writable
|
|
945
|
+
const customUnknown = effortSelectionOf(effortOptions.efforts, effortView.value).mode === 'custom'
|
|
946
|
+
const customLabel = customUnknown
|
|
947
|
+
? t('settings.effort.customCurrent', { value: effortView.value })
|
|
948
|
+
: t('settings.effort.custom')
|
|
949
|
+
const reset = () => {
|
|
950
|
+
setEffortCustomOpen(false)
|
|
951
|
+
stage(fieldKey(spec.path), { clear: true })
|
|
952
|
+
}
|
|
953
|
+
const inputStyle = {
|
|
954
|
+
width: '100%',
|
|
955
|
+
maxWidth: 520,
|
|
956
|
+
boxSizing: 'border-box',
|
|
957
|
+
height: 32,
|
|
958
|
+
padding: '0 8px',
|
|
959
|
+
borderRadius: 'var(--dsw-radius-sm, 6px)',
|
|
960
|
+
border: '1px solid var(--dsw-alias-border-secondary, rgba(127,127,127,.35))',
|
|
961
|
+
background: 'var(--dsw-alias-bg-layer-3, transparent)',
|
|
962
|
+
color: 'var(--dsw-alias-label-primary, inherit)',
|
|
963
|
+
font: 'inherit',
|
|
964
|
+
fontSize: 12.5,
|
|
965
|
+
}
|
|
966
|
+
if (effortOptions.kind === 'ready') {
|
|
967
|
+
const options = [{ value: '', label: t('settings.effort.default') }]
|
|
968
|
+
for (const effort of effortOptions.efforts) {
|
|
969
|
+
options.push({
|
|
970
|
+
value: effort.id,
|
|
971
|
+
label: typeof effort.name === 'string' && effort.name !== effort.id
|
|
972
|
+
? effort.name + ' (' + effort.id + ')'
|
|
973
|
+
: effort.id,
|
|
974
|
+
})
|
|
975
|
+
}
|
|
976
|
+
options.push({ value: EFFORT_CUSTOM_SENTINEL, label: customLabel })
|
|
977
|
+
const selected = effortView.mode === 'custom'
|
|
978
|
+
? EFFORT_CUSTOM_SENTINEL
|
|
979
|
+
: effortView.mode === 'value' ? effortView.value : ''
|
|
980
|
+
const showCustomInput = effortView.mode === 'custom' || effortView.customOpen
|
|
981
|
+
return el('div', { key: id, style: { display: 'grid', gap: 4, margin: '8px 0' } }, [
|
|
982
|
+
el('label', { key: 'label', htmlFor: id, style: { fontSize: 12 } }, label),
|
|
983
|
+
el('select', {
|
|
984
|
+
key: 'select',
|
|
985
|
+
id,
|
|
986
|
+
'aria-label': label,
|
|
987
|
+
value: selected,
|
|
988
|
+
disabled,
|
|
989
|
+
onChange: (event) => {
|
|
990
|
+
const next = event.target.value
|
|
991
|
+
if (next === EFFORT_CUSTOM_SENTINEL) {
|
|
992
|
+
// Drop any staged clear/listed value: `draft.clear` wins over
|
|
993
|
+
// `customOpen` in effortDraftOf and would bounce the select
|
|
994
|
+
// straight back to Default.
|
|
995
|
+
unstage(fieldKey(spec.path))
|
|
996
|
+
setEffortCustomOpen(true)
|
|
997
|
+
return
|
|
998
|
+
}
|
|
999
|
+
setEffortCustomOpen(false)
|
|
1000
|
+
stage(fieldKey(spec.path), next === '' ? { clear: true } : { clear: false, text: next })
|
|
1001
|
+
},
|
|
1002
|
+
style: inputStyle,
|
|
1003
|
+
}, options.map((option) => el('option', { key: option.value, value: option.value }, option.label))),
|
|
1004
|
+
showCustomInput ? el('input', {
|
|
1005
|
+
key: 'custom',
|
|
1006
|
+
id: id + '-custom',
|
|
1007
|
+
'aria-label': label,
|
|
1008
|
+
type: 'text',
|
|
1009
|
+
value: effortView.mode === 'custom' ? effortView.value : '',
|
|
1010
|
+
placeholder: t('settings.effort.customPlaceholder'),
|
|
1011
|
+
disabled,
|
|
1012
|
+
onChange: (event) => stage(fieldKey(spec.path), { clear: false, text: event.target.value }),
|
|
1013
|
+
style: inputStyle,
|
|
1014
|
+
}) : null,
|
|
1015
|
+
el('span', { key: 'hint', style: { fontSize: 11, opacity: 0.6 } }, hint),
|
|
1016
|
+
customUnknown
|
|
1017
|
+
? el('span', { key: 'customHint', style: { fontSize: 11, opacity: 0.6 } },
|
|
1018
|
+
t('settings.effort.unknownHint'))
|
|
1019
|
+
: null,
|
|
1020
|
+
state.overridden
|
|
1021
|
+
? el('button', { key: 'reset', type: 'button', disabled, onClick: reset }, valueLabels.reset)
|
|
1022
|
+
: null,
|
|
1023
|
+
])
|
|
1024
|
+
}
|
|
1025
|
+
const textValue = effortView.mode === 'custom' ? effortView.value : ''
|
|
1026
|
+
return el('div', { key: id, style: { display: 'grid', gap: 4, margin: '8px 0' } }, [
|
|
1027
|
+
el('label', { key: 'label', htmlFor: id, style: { fontSize: 12 } }, label),
|
|
1028
|
+
el('input', {
|
|
1029
|
+
key: 'input',
|
|
1030
|
+
id,
|
|
1031
|
+
'aria-label': label,
|
|
1032
|
+
type: 'text',
|
|
1033
|
+
value: textValue,
|
|
1034
|
+
placeholder: t('settings.effort.customPlaceholder'),
|
|
1035
|
+
disabled,
|
|
1036
|
+
onChange: (event) => {
|
|
1037
|
+
setEffortCustomOpen(true)
|
|
1038
|
+
stage(fieldKey(spec.path), { clear: false, text: event.target.value })
|
|
1039
|
+
},
|
|
1040
|
+
style: inputStyle,
|
|
1041
|
+
}),
|
|
1042
|
+
el('span', { key: 'hint', style: { fontSize: 11, opacity: 0.6 } }, hint),
|
|
1043
|
+
el('span', { key: 'routeHint', style: { fontSize: 11, opacity: 0.6 } },
|
|
1044
|
+
t(effortOptions.kind === 'none' ? 'settings.effort.noneHint' : 'settings.effort.autoHint')),
|
|
1045
|
+
textValue !== '' || state.overridden
|
|
1046
|
+
? el('button', { key: 'default', type: 'button', disabled, onClick: reset }, t('settings.effort.default'))
|
|
1047
|
+
: null,
|
|
1048
|
+
])
|
|
1049
|
+
}
|
|
1050
|
+
|
|
732
1051
|
const plan = () => {
|
|
733
1052
|
const ops = []
|
|
734
1053
|
let blocked = false
|
|
@@ -737,6 +1056,11 @@ window.__ModuleLoader__.load({
|
|
|
737
1056
|
if (spec.kind === 'route-provider' || spec.kind === 'route-model') continue
|
|
738
1057
|
const draft = staged.get(fieldKey(spec.path))
|
|
739
1058
|
if (draft === undefined) continue
|
|
1059
|
+
if (spec.kind === 'effort') {
|
|
1060
|
+
const planned = effortPlanOps(spec.path, draft)
|
|
1061
|
+
ops.push(...planned.ops)
|
|
1062
|
+
continue
|
|
1063
|
+
}
|
|
740
1064
|
if (draft.clear) {
|
|
741
1065
|
ops.push({ op: 'unset', path: spec.path.slice() })
|
|
742
1066
|
continue
|
|
@@ -825,14 +1149,15 @@ window.__ModuleLoader__.load({
|
|
|
825
1149
|
disabled: !shell.writable,
|
|
826
1150
|
onChange: (event) => stage(fieldKey(spec.path), { clear: false, bool: event.target.checked }),
|
|
827
1151
|
}),
|
|
828
|
-
el('label', { key: 'label', htmlFor: id, style: { marginLeft: 6, fontSize: 12 } }, spec.
|
|
1152
|
+
el('label', { key: 'label', htmlFor: id, style: { marginLeft: 6, fontSize: 12 } }, t(spec.labelKey)),
|
|
829
1153
|
])
|
|
830
1154
|
}
|
|
831
1155
|
if (spec.kind === 'route-provider' || spec.kind === 'route-model') {
|
|
832
1156
|
return renderRouteField(spec, state, false)
|
|
833
1157
|
}
|
|
1158
|
+
if (spec.kind === 'effort') return renderEffortField(spec, state)
|
|
834
1159
|
return el('div', { key: id, style: { margin: '6px 0' } }, [
|
|
835
|
-
el('label', { key: 'label', htmlFor: id, style: { display: 'block', fontSize: 12 } }, spec.
|
|
1160
|
+
el('label', { key: 'label', htmlFor: id, style: { display: 'block', fontSize: 12 } }, t(spec.labelKey)),
|
|
836
1161
|
el('input', {
|
|
837
1162
|
key: 'input',
|
|
838
1163
|
id,
|
|
@@ -845,14 +1170,14 @@ window.__ModuleLoader__.load({
|
|
|
845
1170
|
})
|
|
846
1171
|
return el('div', { style: { display: 'grid', gap: 4, maxWidth: 520 } }, [
|
|
847
1172
|
el('p', { key: 'unavailable', style: { margin: 0, fontSize: 12 } },
|
|
848
|
-
shell.available ? '' :
|
|
1173
|
+
shell.available ? '' : labels.unavailable),
|
|
849
1174
|
...rows,
|
|
850
1175
|
el('button', {
|
|
851
1176
|
key: 'save', type: 'button', disabled: !shell.dirty || shell.invalid || shell.saving || !shell.writable,
|
|
852
1177
|
onClick: save,
|
|
853
|
-
}, shell.saving ?
|
|
1178
|
+
}, shell.saving ? labels.saving : labels.save),
|
|
854
1179
|
failed ? el('span', { key: 'failed', style: { color: 'var(--dsw-alias-label-error, #c33)', fontSize: 12 } },
|
|
855
|
-
|
|
1180
|
+
labels.saveFailed) : null,
|
|
856
1181
|
])
|
|
857
1182
|
}
|
|
858
1183
|
|
|
@@ -861,13 +1186,13 @@ window.__ModuleLoader__.load({
|
|
|
861
1186
|
const id = 'mc-field-' + fieldKey(spec.path)
|
|
862
1187
|
if (spec.kind === 'boolean') {
|
|
863
1188
|
return el('div', { key: id, style: { display: 'flex', alignItems: 'center', gap: 8, margin: '8px 0' } }, [
|
|
864
|
-
el('span', { key: 'label', style: { fontSize: 12 } }, spec.
|
|
1189
|
+
el('span', { key: 'label', style: { fontSize: 12 } }, t(spec.labelKey)),
|
|
865
1190
|
Switch !== undefined
|
|
866
1191
|
? el(Switch, {
|
|
867
1192
|
key: 'switch',
|
|
868
1193
|
checked: state.value,
|
|
869
1194
|
disabled: !shell.writable,
|
|
870
|
-
label: spec.
|
|
1195
|
+
label: t(spec.labelKey),
|
|
871
1196
|
onChange: (next) => stage(fieldKey(spec.path), { clear: false, bool: next }),
|
|
872
1197
|
})
|
|
873
1198
|
: el('input', {
|
|
@@ -875,7 +1200,7 @@ window.__ModuleLoader__.load({
|
|
|
875
1200
|
type: 'checkbox',
|
|
876
1201
|
checked: state.value,
|
|
877
1202
|
disabled: !shell.writable,
|
|
878
|
-
'aria-label': spec.
|
|
1203
|
+
'aria-label': t(spec.labelKey),
|
|
879
1204
|
onChange: (event) => stage(fieldKey(spec.path), { clear: false, bool: event.target.checked }),
|
|
880
1205
|
}),
|
|
881
1206
|
state.overridden
|
|
@@ -884,25 +1209,26 @@ window.__ModuleLoader__.load({
|
|
|
884
1209
|
type: 'button',
|
|
885
1210
|
disabled: !shell.writable,
|
|
886
1211
|
onClick: () => stage(fieldKey(spec.path), { clear: true }),
|
|
887
|
-
},
|
|
1212
|
+
}, valueLabels.reset)
|
|
888
1213
|
: null,
|
|
889
1214
|
])
|
|
890
1215
|
}
|
|
891
1216
|
if (spec.kind === 'route-provider' || spec.kind === 'route-model') {
|
|
892
1217
|
return renderRouteField(spec, state, true)
|
|
893
1218
|
}
|
|
1219
|
+
if (spec.kind === 'effort') return renderEffortField(spec, state)
|
|
894
1220
|
return el(SettingsValueField, {
|
|
895
1221
|
key: id,
|
|
896
1222
|
id,
|
|
897
|
-
label: spec.
|
|
898
|
-
hint: spec.
|
|
1223
|
+
label: t(spec.labelKey),
|
|
1224
|
+
hint: t(spec.hintKey),
|
|
899
1225
|
numeric: spec.kind === 'number',
|
|
900
1226
|
text: state.text,
|
|
901
1227
|
overridden: state.overridden,
|
|
902
1228
|
invalid: state.invalid,
|
|
903
|
-
overriddenLabel:
|
|
904
|
-
resetLabel:
|
|
905
|
-
invalidLabel:
|
|
1229
|
+
overriddenLabel: valueLabels.modified,
|
|
1230
|
+
resetLabel: valueLabels.reset,
|
|
1231
|
+
invalidLabel: valueLabels.invalidNumber,
|
|
906
1232
|
disabled: !shell.writable,
|
|
907
1233
|
onEdit: (text) => stage(fieldKey(spec.path), { clear: false, text }),
|
|
908
1234
|
onReset: () => stage(fieldKey(spec.path), { clear: true }),
|
|
@@ -910,7 +1236,7 @@ window.__ModuleLoader__.load({
|
|
|
910
1236
|
})
|
|
911
1237
|
|
|
912
1238
|
return el(SettingsForm, {
|
|
913
|
-
labels
|
|
1239
|
+
labels,
|
|
914
1240
|
state: shell,
|
|
915
1241
|
onSave: save,
|
|
916
1242
|
onDiscard: discard,
|
|
@@ -1135,6 +1461,79 @@ window.__ModuleLoader__.load({
|
|
|
1135
1461
|
'records.foot': '悬停条目看详情 · /ctx-status 看全量',
|
|
1136
1462
|
'records.beats': '节拍: {beats}',
|
|
1137
1463
|
// Slot labels (ledger rows the shell projects).
|
|
1464
|
+
// Settings card copy.
|
|
1465
|
+
'settings.summary.notReady': 'Magic Context 适配器设置',
|
|
1466
|
+
'settings.summary.line': '执行 {execute}% · 强制 {force}% · Historian {historian}{routes}',
|
|
1467
|
+
'settings.summary.route': ' · {kind} {provider}/{model}',
|
|
1468
|
+
'settings.summary.on': '开',
|
|
1469
|
+
'settings.summary.off': '关',
|
|
1470
|
+
'settings.summary.verify': '校验',
|
|
1471
|
+
'settings.summary.gen': '生成',
|
|
1472
|
+
'settings.form.unavailable': '此部署未提供 Magic Context 设置。',
|
|
1473
|
+
'settings.form.readOnly': '此部署以只读方式保存设置。',
|
|
1474
|
+
'settings.form.saveFailed': '宿主没有接受本次保存;下面的值仍未保存。',
|
|
1475
|
+
'settings.form.save': '保存',
|
|
1476
|
+
'settings.form.saving': '保存中…',
|
|
1477
|
+
'settings.form.modified': '已修改',
|
|
1478
|
+
'settings.form.reset': '重置',
|
|
1479
|
+
'settings.form.invalid': '无效',
|
|
1480
|
+
'settings.form.invalidNumber': '不是数字',
|
|
1481
|
+
'settings.route.autoSession': 'Auto — 跟随会话路由',
|
|
1482
|
+
'settings.route.autoPickProvider': 'Auto — 先选择 provider',
|
|
1483
|
+
'settings.route.notInCatalog': '{value}(不在目录中)',
|
|
1484
|
+
'settings.route.providerPlaceholder': 'provider id',
|
|
1485
|
+
'settings.route.modelPlaceholder': 'model id',
|
|
1486
|
+
'settings.effort.default': '默认',
|
|
1487
|
+
'settings.effort.custom': '自定义…',
|
|
1488
|
+
'settings.effort.customCurrent': '自定义({value})',
|
|
1489
|
+
'settings.effort.customPlaceholder': '输入 effort id',
|
|
1490
|
+
'settings.effort.autoHint': '固定 Historian provider 与 model 后,可从模型公布的 effort 列表中选择。',
|
|
1491
|
+
'settings.effort.noneHint': '该模型未公布 reasoning effort;留空使用模型默认值。',
|
|
1492
|
+
'settings.effort.unknownHint': '该 id 不在当前模型目录中;在你修改前会保留。',
|
|
1493
|
+
'settings.field.policy.executeThreshold.label': '执行阈值(%)',
|
|
1494
|
+
'settings.field.policy.executeThreshold.hint': '执行回收带开启时的上下文占用百分比(0-100)。',
|
|
1495
|
+
'settings.field.policy.forceThreshold.label': '强制阈值(%)',
|
|
1496
|
+
'settings.field.policy.forceThreshold.hint': '回收变为无条件时的占用百分比(0-100)。',
|
|
1497
|
+
'settings.field.policy.assumedContextWindow.label': '假定窗口(tokens)',
|
|
1498
|
+
'settings.field.policy.assumedContextWindow.hint': '路由模型未披露窗口时用于调度的窗口大小。',
|
|
1499
|
+
'settings.field.policy.ttlMs.label': '回收 TTL(毫秒)',
|
|
1500
|
+
'settings.field.policy.ttlMs.hint': '被回收的区间保持可恢复的时长。',
|
|
1501
|
+
'settings.field.policy.protectedToolResults.label': '保护工具结果数',
|
|
1502
|
+
'settings.field.policy.protectedToolResults.hint': '策略永不回收的最新工具结果数量。',
|
|
1503
|
+
'settings.field.policy.historianMinNodes.label': 'Historian 最小节点数',
|
|
1504
|
+
'settings.field.policy.historianMinNodes.hint': 'Historian 运行前需要的 assistant/tool 节点数。',
|
|
1505
|
+
'settings.field.policy.historianMaxTokens.label': 'Historian 最大 tokens',
|
|
1506
|
+
'settings.field.policy.historianMaxTokens.hint': '单次 Historian 调用的生成上限;低于 8192 会按 8192 处理。',
|
|
1507
|
+
'settings.field.policy.historianMaxTokensCeiling.label': 'Historian 重试上限',
|
|
1508
|
+
'settings.field.policy.historianMaxTokensCeiling.hint': 'token 截断后允许升级到的上限(每次尝试翻倍)。',
|
|
1509
|
+
'settings.field.policy.historianTimeoutMs.label': 'Historian 超时(毫秒)',
|
|
1510
|
+
'settings.field.policy.historianTimeoutMs.hint': '单次 Historian 运行的墙钟预算;超时会记录失败。',
|
|
1511
|
+
'settings.field.policy.baselineTokens.label': '基线 tokens',
|
|
1512
|
+
'settings.field.policy.baselineTokens.hint': '为 m[0]/m[1] 基线预留的 token 预算。',
|
|
1513
|
+
'settings.field.policy.memoryBudgetTokens.label': '记忆预算 tokens',
|
|
1514
|
+
'settings.field.policy.memoryBudgetTokens.hint': '召回记忆块可使用的 token 预算。',
|
|
1515
|
+
'settings.field.policy.memoryLimit.label': '记忆行上限',
|
|
1516
|
+
'settings.field.policy.memoryLimit.hint': '每次 pass 最多召回的共享记忆行数。',
|
|
1517
|
+
'settings.field.journal.retentionDays.label': 'Journal 保留天数',
|
|
1518
|
+
'settings.field.journal.retentionDays.hint': 'Journal 行的保留时长。修改后会重新清理(最少 1 天)。',
|
|
1519
|
+
'settings.field.historianEnabled.label': 'Historian 启用',
|
|
1520
|
+
'settings.field.historianEnabled.hint': '运行带外 Historian。',
|
|
1521
|
+
'settings.field.registerCompactionEngine.label': '注册压缩引擎',
|
|
1522
|
+
'settings.field.registerCompactionEngine.hint': '把适配器注册为压缩引擎 seam;下一次 agent step 挂载或卸载——先禁用宿主压缩行。',
|
|
1523
|
+
'settings.field.maintenance.verifyProvider.label': '校验 provider',
|
|
1524
|
+
'settings.field.maintenance.verifyProvider.hint': '维护校验通道使用的 provider。“Auto” 表示不固定,跟随会话路由。',
|
|
1525
|
+
'settings.field.maintenance.verifyModel.label': '校验模型',
|
|
1526
|
+
'settings.field.maintenance.verifyModel.hint': '维护校验通道使用的模型。同时设置 provider 和 model 才会固定;“Auto” 跟随会话路由。',
|
|
1527
|
+
'settings.field.historian.provider.label': 'Historian provider',
|
|
1528
|
+
'settings.field.historian.provider.hint': 'Historian 与 /compact 摘要使用的 provider。“Auto” 跟随会话路由(推荐)。',
|
|
1529
|
+
'settings.field.historian.model.label': 'Historian 模型',
|
|
1530
|
+
'settings.field.historian.model.hint': 'Historian 与 /compact 摘要使用的模型。两半都设置才会固定;“Auto” 跟随会话路由。',
|
|
1531
|
+
'settings.field.historian.reasoningEffort.label': 'Historian 推理强度',
|
|
1532
|
+
'settings.field.historian.reasoningEffort.hint': '路由模型公布的 effort id(例如 off/low/high/max);留空使用模型默认值。',
|
|
1533
|
+
'settings.field.journal.enabled.label': 'Journal 启用',
|
|
1534
|
+
'settings.field.journal.enabled.hint': '记录适配器活动。关闭立即生效;从关闭状态开启需要重启应用。',
|
|
1535
|
+
'settings.field.journal.mirror.label': 'Journal 文本镜像',
|
|
1536
|
+
'settings.field.journal.mirror.hint': '同时写单行活动镜像文件。仅对修改后写入的条目生效。',
|
|
1138
1537
|
'title': 'Magic Context',
|
|
1139
1538
|
'label.telemetry': 'Magic Context 遥测',
|
|
1140
1539
|
},
|
|
@@ -1221,6 +1620,79 @@ window.__ModuleLoader__.load({
|
|
|
1221
1620
|
'records.count': 'last {n}',
|
|
1222
1621
|
'records.foot': 'Hover a row for detail · /ctx-status for the full ledger',
|
|
1223
1622
|
'records.beats': 'Beats: {beats}',
|
|
1623
|
+
// Settings card copy.
|
|
1624
|
+
'settings.summary.notReady': 'Magic Context adapter settings',
|
|
1625
|
+
'settings.summary.line': 'execute {execute}% · force {force}% · historian {historian}{routes}',
|
|
1626
|
+
'settings.summary.route': ' · {kind} {provider}/{model}',
|
|
1627
|
+
'settings.summary.on': 'on',
|
|
1628
|
+
'settings.summary.off': 'off',
|
|
1629
|
+
'settings.summary.verify': 'verify',
|
|
1630
|
+
'settings.summary.gen': 'gen',
|
|
1631
|
+
'settings.form.unavailable': 'Magic Context settings are not served by this deployment.',
|
|
1632
|
+
'settings.form.readOnly': 'This deployment stores settings read-only.',
|
|
1633
|
+
'settings.form.saveFailed': 'The Host did not accept the save; the values below are unsaved.',
|
|
1634
|
+
'settings.form.save': 'Save',
|
|
1635
|
+
'settings.form.saving': 'Saving…',
|
|
1636
|
+
'settings.form.modified': 'Modified',
|
|
1637
|
+
'settings.form.reset': 'Reset',
|
|
1638
|
+
'settings.form.invalid': 'Invalid',
|
|
1639
|
+
'settings.form.invalidNumber': 'Not a number',
|
|
1640
|
+
'settings.route.autoSession': 'Auto — follow the session route',
|
|
1641
|
+
'settings.route.autoPickProvider': 'Auto — pick a provider first',
|
|
1642
|
+
'settings.route.notInCatalog': '{value} (not in catalog)',
|
|
1643
|
+
'settings.route.providerPlaceholder': 'provider id',
|
|
1644
|
+
'settings.route.modelPlaceholder': 'model id',
|
|
1645
|
+
'settings.effort.default': 'Default',
|
|
1646
|
+
'settings.effort.custom': 'Custom…',
|
|
1647
|
+
'settings.effort.customCurrent': 'Custom ({value})',
|
|
1648
|
+
'settings.effort.customPlaceholder': 'Enter an effort id',
|
|
1649
|
+
'settings.effort.autoHint': 'Pin the Historian provider and model to pick from the model\'s advertised efforts.',
|
|
1650
|
+
'settings.effort.noneHint': 'This model advertises no reasoning efforts; blank uses the model default.',
|
|
1651
|
+
'settings.effort.unknownHint': 'This id is not in the current model catalog; it is preserved until you change it.',
|
|
1652
|
+
'settings.field.policy.executeThreshold.label': 'Execute threshold (%)',
|
|
1653
|
+
'settings.field.policy.executeThreshold.hint': 'Adoption percentage at which the execute band opens (0-100).',
|
|
1654
|
+
'settings.field.policy.forceThreshold.label': 'Force threshold (%)',
|
|
1655
|
+
'settings.field.policy.forceThreshold.hint': 'Adoption percentage at which reclaim becomes unconditional (0-100).',
|
|
1656
|
+
'settings.field.policy.assumedContextWindow.label': 'Assumed window (tokens)',
|
|
1657
|
+
'settings.field.policy.assumedContextWindow.hint': 'Window used for scheduling when the routed model discloses none.',
|
|
1658
|
+
'settings.field.policy.ttlMs.label': 'Reclaim TTL (ms)',
|
|
1659
|
+
'settings.field.policy.ttlMs.hint': 'How long a reclaimed compartment stays reversible.',
|
|
1660
|
+
'settings.field.policy.protectedToolResults.label': 'Protected tool results',
|
|
1661
|
+
'settings.field.policy.protectedToolResults.hint': 'Newest tool results the policy never reclaims.',
|
|
1662
|
+
'settings.field.policy.historianMinNodes.label': 'Historian minimum nodes',
|
|
1663
|
+
'settings.field.policy.historianMinNodes.hint': 'Assistant/tool nodes required before the historian runs.',
|
|
1664
|
+
'settings.field.policy.historianMaxTokens.label': 'Historian max tokens',
|
|
1665
|
+
'settings.field.policy.historianMaxTokens.hint': 'Generation cap for one historian call; anything below 8192 is raised to 8192.',
|
|
1666
|
+
'settings.field.policy.historianMaxTokensCeiling.label': 'Historian retry ceiling',
|
|
1667
|
+
'settings.field.policy.historianMaxTokensCeiling.hint': 'Ceiling the historian may escalate to after a token-cap truncation (doubles per attempt).',
|
|
1668
|
+
'settings.field.policy.historianTimeoutMs.label': 'Historian timeout (ms)',
|
|
1669
|
+
'settings.field.policy.historianTimeoutMs.hint': 'Wall-clock budget for one historian run; a spent budget journals a failed run.',
|
|
1670
|
+
'settings.field.policy.baselineTokens.label': 'Baseline tokens',
|
|
1671
|
+
'settings.field.policy.baselineTokens.hint': 'Token budget reserved for the m[0]/m[1] baseline.',
|
|
1672
|
+
'settings.field.policy.memoryBudgetTokens.label': 'Memory budget tokens',
|
|
1673
|
+
'settings.field.policy.memoryBudgetTokens.hint': 'Token budget the recalled-memory block may spend.',
|
|
1674
|
+
'settings.field.policy.memoryLimit.label': 'Memory row limit',
|
|
1675
|
+
'settings.field.policy.memoryLimit.hint': 'Maximum shared memory rows recalled per pass.',
|
|
1676
|
+
'settings.field.journal.retentionDays.label': 'Journal retention (days)',
|
|
1677
|
+
'settings.field.journal.retentionDays.hint': 'How long journal rows are kept. Re-prunes once the value changes (minimum 1 day).',
|
|
1678
|
+
'settings.field.historianEnabled.label': 'Historian enabled',
|
|
1679
|
+
'settings.field.historianEnabled.hint': 'Run the out-of-band historian.',
|
|
1680
|
+
'settings.field.registerCompactionEngine.label': 'Register compaction engine',
|
|
1681
|
+
'settings.field.registerCompactionEngine.hint': 'Register the adapter as the compaction engine seam. Mounts or unmounts on the next agent step — disable the host compaction row first.',
|
|
1682
|
+
'settings.field.maintenance.verifyProvider.label': 'Verify provider',
|
|
1683
|
+
'settings.field.maintenance.verifyProvider.hint': 'Provider for the maintenance verify lane. “Auto” leaves the pin off and follows the session route.',
|
|
1684
|
+
'settings.field.maintenance.verifyModel.label': 'Verify model',
|
|
1685
|
+
'settings.field.maintenance.verifyModel.hint': 'Model for the maintenance verify lane. Set both provider and model to pin; “Auto” follows the session route.',
|
|
1686
|
+
'settings.field.historian.provider.label': 'Historian provider',
|
|
1687
|
+
'settings.field.historian.provider.hint': 'Provider for the historian and /compact summaries. “Auto” follows the session route (recommended).',
|
|
1688
|
+
'settings.field.historian.model.label': 'Historian model',
|
|
1689
|
+
'settings.field.historian.model.hint': 'Model for the historian and /compact summaries. Set both halves to pin; “Auto” follows the session route.',
|
|
1690
|
+
'settings.field.historian.reasoningEffort.label': 'Historian reasoning effort',
|
|
1691
|
+
'settings.field.historian.reasoningEffort.hint': 'Optional effort id advertised by the routed model (for example off/low/high/max). Blank uses the model default.',
|
|
1692
|
+
'settings.field.journal.enabled.label': 'Journal enabled',
|
|
1693
|
+
'settings.field.journal.enabled.hint': 'Record adapter activity. Turning off applies immediately; turning on from an off start needs an app restart.',
|
|
1694
|
+
'settings.field.journal.mirror.label': 'Journal text mirror',
|
|
1695
|
+
'settings.field.journal.mirror.hint': 'Also write the one-line activity mirror file. Applies to entries written after the change.',
|
|
1224
1696
|
'title': 'Magic Context',
|
|
1225
1697
|
'label.telemetry': 'Magic Context telemetry',
|
|
1226
1698
|
},
|
|
@@ -2211,6 +2683,13 @@ window.__ModuleLoader__.load({
|
|
|
2211
2683
|
const scope = settingsCtx.configForms.get(CFG_NS)
|
|
2212
2684
|
const Card = makeCard(scope, catalog)
|
|
2213
2685
|
settingsCtx.effect(() => settingsCtx.configForms.whileServed([CFG_NS], () => {
|
|
2686
|
+
const offSection = ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
2687
|
+
name: 'settings.section',
|
|
2688
|
+
id: 'magic-context',
|
|
2689
|
+
order: 30,
|
|
2690
|
+
label: () => t('title'),
|
|
2691
|
+
locale: LOCALE_NS,
|
|
2692
|
+
}, Card))
|
|
2214
2693
|
const offItem = ctx.slots.inject('plugins.item', () => ctx.slots.register({
|
|
2215
2694
|
name: 'plugins.item',
|
|
2216
2695
|
id: CFG_NS,
|
|
@@ -2225,6 +2704,7 @@ window.__ModuleLoader__.load({
|
|
|
2225
2704
|
locale: LOCALE_NS,
|
|
2226
2705
|
}, Card))
|
|
2227
2706
|
return () => {
|
|
2707
|
+
offSection()
|
|
2228
2708
|
offItem()
|
|
2229
2709
|
offRow()
|
|
2230
2710
|
}
|
|
@@ -2255,6 +2735,9 @@ window.__ModuleLoader__.load({
|
|
|
2255
2735
|
createModelCatalog,
|
|
2256
2736
|
/** The pin-pair write semantics (pure; pinned by the client spec). */
|
|
2257
2737
|
routePairOps,
|
|
2738
|
+
/** Settings copy + catalog-backed effort picker helpers. */
|
|
2739
|
+
settingsCopyKeys, reasoningEffortsOf, effortSelectionOf, effortPlanOps, effortDraftOf,
|
|
2740
|
+
EFFORT_CUSTOM_SENTINEL, fieldSpecs: FIELDS,
|
|
2258
2741
|
// The number/text formatters are the panel's display contract; the
|
|
2259
2742
|
// node-side spec pins them so a formatting regression (the scope bug
|
|
2260
2743
|
// that briefly unmounted the pill) cannot come back silently.
|