@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/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', label: 'Execute threshold (%)',
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', label: 'Force threshold (%)',
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', label: 'Assumed window (tokens)',
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', label: 'Reclaim TTL (ms)',
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', label: 'Protected tool results',
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', label: 'Historian minimum nodes',
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', label: 'Historian max tokens',
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', label: 'Historian retry ceiling',
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', label: 'Historian timeout (ms)',
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', label: 'Baseline tokens',
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', label: 'Memory budget tokens',
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', label: 'Memory row limit',
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', label: 'Journal retention (days)',
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: ['registerCompactionEngine'], kind: 'boolean', label: 'Register compaction engine',
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: ROUTES[0].providerPath, kind: 'route-provider', routeId: 'maintenance', label: 'Verify provider',
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].modelPath, kind: 'route-model', routeId: 'maintenance', label: 'Verify model',
255
- hint: 'Model for the maintenance verify lane. Set both provider and model to pin; “Auto” follows the session route.',
369
+ path: ROUTES[0].providerPath, kind: 'route-provider', routeId: 'maintenance',
370
+ ...fieldCopy(ROUTES[0].providerPath),
256
371
  },
257
372
  {
258
- path: ROUTES[1].providerPath, kind: 'route-provider', routeId: 'historian', label: 'Historian provider',
259
- hint: 'Provider for the historian and /compact summaries. “Auto” follows the session route (recommended).',
373
+ path: ROUTES[0].modelPath, kind: 'route-model', routeId: 'maintenance',
374
+ ...fieldCopy(ROUTES[0].modelPath),
260
375
  },
261
376
  {
262
- path: ROUTES[1].modelPath, kind: 'route-model', routeId: 'historian', label: 'Historian model',
263
- hint: 'Model for the historian and /compact summaries. Set both halves to pin; “Auto” follows the session route.',
377
+ path: ROUTES[1].providerPath, kind: 'route-provider', routeId: 'historian',
378
+ ...fieldCopy(ROUTES[1].providerPath),
264
379
  },
265
380
  {
266
- path: ['historian', 'reasoningEffort'], kind: 'text', label: 'Historian reasoning effort',
267
- hint: 'Optional effort id advertised by the routed model (for example low/medium/high). Blank uses the model default.',
381
+ path: ROUTES[1].modelPath, kind: 'route-model', routeId: 'historian',
382
+ ...fieldCopy(ROUTES[1].modelPath),
268
383
  },
269
384
  {
270
- path: ['journal', 'enabled'], kind: 'boolean', label: 'Journal enabled',
271
- hint: 'Record adapter activity. Turning off applies immediately; turning on from an off start needs an app restart.',
385
+ path: ['historian', 'reasoningEffort'], kind: 'effort', routeId: 'historian',
386
+ ...fieldCopy(['historian', 'reasoningEffort']),
272
387
  },
273
388
  {
274
- path: ['journal', 'mirror'], kind: 'boolean', label: 'Journal text mirror',
275
- hint: 'Also write the one-line activity mirror file. Applies to entries written after the change.',
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 the shared settings form frame renders. */
280
- const FORM_LABELS = {
281
- unavailable: 'Magic Context settings are not served by this deployment.',
282
- readOnly: 'This deployment stores settings read-only.',
283
- saveFailed: 'The Host did not accept the save; the values below are unsaved.',
284
- save: 'Save',
285
- saving: 'Saving…',
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 'Magic Context adapter settings'
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 [' · ' + (route.id === 'maintenance' ? 'verify ' : 'gen ') + provider + '/' + model]
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: 'Auto — follow the session route' })
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
- ? 'Auto — pick a provider first'
657
- : 'Auto — follow the session route',
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: currentText + ' (not in catalog)' })
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.label),
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.hint),
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.label,
699
- hint: spec.hint,
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: 'Modified',
705
- resetLabel: 'Reset',
706
- invalidLabel: 'Invalid',
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.label),
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 ? 'provider id' : 'model id',
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.hint),
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
- * The mutations a save would post, in field order. `blocked` is true when
729
- * a staged draft is not a value its field accepts (save refuses rather
730
- * than dropping the edit).
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.label),
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.label),
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 ? '' : FORM_LABELS.unavailable),
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 ? FORM_LABELS.saving : FORM_LABELS.save),
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
- FORM_LABELS.saveFailed) : null,
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.label),
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.label,
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.label,
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
- }, 'Reset')
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.label,
898
- hint: spec.hint,
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: 'Modified',
904
- resetLabel: 'Reset',
905
- invalidLabel: 'Not a number',
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: FORM_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.