dsh-auto-model-routing 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +8 -8
  3. package/README.zh.md +8 -8
  4. package/docs/feedback-evaluation.md +4 -4
  5. package/docs/host-capability-report.md +2 -0
  6. package/docs/jev-policy-v2-validation.md +3 -1
  7. package/docs/magpie-live-acceptance.md +2 -0
  8. package/docs/policy-v2-development.md +5 -3
  9. package/docs/policy-v2-real-evaluation-plan.md +2 -0
  10. package/lib/client/control.d.ts +2 -2
  11. package/lib/client/control.js +10 -20
  12. package/lib/client/index.d.ts +0 -1
  13. package/lib/client/index.js +9 -6
  14. package/lib/client/label.d.ts +7 -10
  15. package/lib/client/label.js +17 -30
  16. package/lib/client/settings-section.d.ts +2 -2
  17. package/lib/client/settings-section.js +7 -69
  18. package/lib/client/strategy-details.js +3 -4
  19. package/lib/client.js +165 -481
  20. package/lib/context.d.ts +1 -3
  21. package/lib/context.js +3 -4
  22. package/lib/effort.d.ts +2 -10
  23. package/lib/effort.js +1 -31
  24. package/lib/endpoints.d.ts +11 -10
  25. package/lib/endpoints.js +29 -46
  26. package/lib/feedback.d.ts +60 -7
  27. package/lib/feedback.js +15 -5
  28. package/lib/index.d.ts +4 -0
  29. package/lib/index.js +17 -86
  30. package/lib/jev.d.ts +17 -29
  31. package/lib/jev.js +6 -34
  32. package/lib/policy-host.d.ts +6 -8
  33. package/lib/policy-host.js +39 -38
  34. package/lib/policy-store.d.ts +9 -20
  35. package/lib/policy-store.js +28 -16
  36. package/lib/routing.d.ts +20 -0
  37. package/lib/routing.js +51 -0
  38. package/lib/rubric.json +15 -6
  39. package/lib/settings-contract.d.ts +5 -21
  40. package/lib/settings-contract.js +3 -2
  41. package/lib/settings-host.d.ts +4 -8
  42. package/lib/settings-host.js +9 -10
  43. package/lib/settings-validation.d.ts +1 -7
  44. package/lib/settings-validation.js +0 -51
  45. package/lib/settings.d.ts +3 -8
  46. package/lib/settings.js +18 -177
  47. package/lib/state.d.ts +2 -4
  48. package/lib/state.js +2 -7
  49. package/lib/strategies/claude.d.ts +21 -0
  50. package/lib/strategies/claude.js +8 -0
  51. package/lib/strategies/codex.d.ts +21 -0
  52. package/lib/strategies/codex.js +9 -0
  53. package/lib/strategies/index.d.ts +22 -0
  54. package/lib/strategies/index.js +85 -0
  55. package/lib/strategies/types.d.ts +14 -0
  56. package/lib/strategies/types.js +1 -0
  57. package/lib/subagent-tool.d.ts +7 -7
  58. package/lib/subagent-tool.js +33 -76
  59. package/lib/support.d.ts +7 -8
  60. package/lib/support.js +9 -13
  61. package/lib/usage-report.d.ts +2 -2
  62. package/lib/usage-report.js +25 -17
  63. package/package.json +3 -4
  64. package/skills/auto-model-routing-report/SKILL.md +4 -3
  65. package/skills/subagent-model-routing/SKILL.md +14 -30
  66. package/lib/client/route-picker.d.ts +0 -21
  67. package/lib/client/route-picker.js +0 -75
  68. package/lib/judgements.d.ts +0 -41
  69. package/lib/judgements.js +0 -55
  70. package/lib/policy-v2-rubric.json +0 -21
  71. package/lib/policy-v2.d.ts +0 -21
  72. package/lib/policy-v2.js +0 -54
  73. package/lib/router.d.ts +0 -68
  74. package/lib/router.js +0 -153
  75. package/scripts/label-report.mjs +0 -50
@@ -1,17 +1,14 @@
1
1
  import { createElement as h, useEffect, useState } from 'react';
2
- import { AUTO_EFFORTS, PROVIDERS } from '../settings-contract.js';
3
- import { selectableModels, supportedFamilyEfforts, validateChildRoute, validateFamilyMainEffort } from '../settings-validation.js';
4
- import { CHILD_MODELS, mapEffort, tiers } from '../effort.js';
5
- import { RoutePicker } from './route-picker.js';
2
+ import { AUTO_EFFORTS } from '../settings-contract.js';
6
3
  import { DECISION_SERVICES, resolveDecisionService } from '../decision-service.js';
7
4
  import { CATEGORY_HINTS } from '../hints.js';
8
5
  import { StrategyDetails } from './strategy-details.js';
9
6
  const CSS = `
10
7
  .amr-settings{font-size:13px;line-height:1.5;color:var(--dsw-alias-label-primary);min-width:0;container-type:inline-size}.amr-settings-section{padding:16px 0;border-bottom:.5px solid var(--dsw-alias-border-l2)}.amr-settings h3{font-size:13px;font-weight:600;margin:0 0 12px}.amr-settings p{margin:6px 0;color:var(--dsw-alias-label-tertiary);overflow-wrap:anywhere}.amr-settings-row{display:flex;align-items:center;justify-content:space-between;gap:16px;margin:14px 0}.amr-settings-row>div:first-child{min-width:0}.amr-settings-number{width:76px;text-align:right;font:inherit;font-variant-numeric:tabular-nums;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);color:inherit;background:var(--dsw-alias-bg-layer-1);padding:6px}.amr-settings-number:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary)}
11
- .amr-settings-table{border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-lg);margin:12px 0;min-width:0}.amr-settings-tier,.amr-settings-head{display:grid;grid-template-columns:minmax(85px,.65fr) minmax(0,1fr) minmax(0,1.5fr);gap:10px;padding:12px;align-items:start}.amr-settings-tier,.amr-settings-head{border-bottom:.5px solid var(--dsw-alias-border-l2)}.amr-settings-tier:last-child{border-bottom:0}.amr-settings-head{color:var(--dsw-alias-label-secondary)}.amr-settings-tier-name{display:flex;align-items:center;gap:4px;min-height:30px}.amr-settings-info,.amr-settings-reset{border:0;background:none;color:var(--dsw-alias-label-tertiary);cursor:pointer;font:inherit;padding:2px}.amr-settings-info{display:inline-flex}.amr-settings-reset{font-size:12px;margin-top:4px}.amr-settings-reset:hover{color:var(--dsw-alias-label-primary)}.amr-settings-info:focus-visible,.amr-settings-reset:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary)}.amr-settings .amr-settings-error{color:var(--dsw-alias-state-error-primary)}.amr-settings-cell{min-width:0}.amr-settings-cell-label{display:none}.amr-settings .amr-settings-segments{display:inline-grid;flex:none;width:max-content;max-width:100%;height:28px;box-sizing:border-box;gap:2px;padding:2px;border-radius:var(--dsw-radius-md)}.amr-settings .amr-settings-segments>button{height:24px;min-width:44px;padding:0 8px;border-radius:var(--dsw-radius-sm);font-size:12px;line-height:20px}.amr-settings .amr-settings-segments>[aria-hidden=true]{top:2px;bottom:2px;left:2px;height:calc(100% - 4px);width:calc((100% - 4px - 2px * (var(--dsh-segment-count) - 1)) / var(--dsh-segment-count))}.amr-settings .amr-settings-tabs{--dsh-segment-count:2}.amr-settings .amr-settings-tabs>[aria-hidden=true]{width:calc((100% - 6px) / 2)!important;transform:translateX(calc(var(--amr-tab-index,0) * (100% + 2px)))!important}.amr-settings .amr-settings-tabs:has([aria-selected=true]:last-child){--amr-tab-index:1}
8
+ .amr-settings-info,.amr-settings-reset{border:0;background:none;color:var(--dsw-alias-label-tertiary);cursor:pointer;font:inherit;padding:2px}.amr-settings-info{display:inline-flex}.amr-settings-reset{font-size:12px;margin-top:4px}.amr-settings-reset:hover{color:var(--dsw-alias-label-primary)}.amr-settings-info:focus-visible,.amr-settings-reset:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary)}.amr-settings .amr-settings-error{color:var(--dsw-alias-state-error-primary)}.amr-settings .amr-settings-segments{display:inline-grid;flex:none;width:max-content;max-width:100%;height:28px;box-sizing:border-box;gap:2px;padding:2px;border-radius:var(--dsw-radius-md)}.amr-settings .amr-settings-segments>button{height:24px;min-width:44px;padding:0 8px;border-radius:var(--dsw-radius-sm);font-size:12px;line-height:20px}.amr-settings .amr-settings-segments>[aria-hidden=true]{top:2px;bottom:2px;left:2px;height:calc(100% - 4px);width:calc((100% - 4px - 2px * (var(--dsh-segment-count) - 1)) / var(--dsh-segment-count))}
12
9
  .amr-test-row{display:flex;align-items:center;flex-wrap:wrap;gap:10px;margin:12px 0 6px}.amr-test-row>span{font-size:12px;color:var(--dsw-alias-label-secondary)}.amr-test-button{height:28px;padding:0 10px;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;cursor:pointer}.amr-test-button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.amr-test-button:disabled{opacity:.5;cursor:not-allowed}.amr-test-button:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}
13
10
  .amr-service-trigger,.amr-service-input{box-sizing:border-box;min-width:0;width:240px;max-width:100%;height:32px;padding:0 10px;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit}.amr-service-trigger{display:flex;align-items:center;justify-content:space-between;gap:12px;cursor:pointer;text-align:left}.amr-service-trigger:hover{background:var(--dsw-alias-interactive-bg-hover)}.amr-service-trigger svg{color:var(--dsw-alias-label-tertiary);flex:none}.amr-service-trigger:focus-visible,.amr-service-input:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}
14
- @container(max-width:480px){.amr-settings-head{display:none}.amr-settings-tier{grid-template-columns:1fr;gap:8px}.amr-settings-cell{display:grid;grid-template-columns:minmax(110px,.7fr) minmax(0,1fr);align-items:center;gap:8px}.amr-settings-cell-label{display:block;color:var(--dsw-alias-label-secondary)}.amr-settings-cell p,.amr-settings-reset{grid-column:2}.amr-settings-row{flex-wrap:wrap}}
11
+ @container(max-width:480px){.amr-settings-row{flex-wrap:wrap}}
15
12
  `;
16
13
  async function request(rpc, action, payload = {}) {
17
14
  const response = await rpc.call('/api', `auto-model-routing.${action}`, payload);
@@ -22,13 +19,10 @@ async function request(rpc, action, payload = {}) {
22
19
  throw Object.assign(new Error(action === 'setKey' ? 'API key 未保存' : '设置未保存,请检查字段错误并重试'), { issues: value.issues });
23
20
  return value;
24
21
  }
25
- const has = (object, key) => !!object && Object.hasOwn(object, key);
26
22
  /** Settings drafts are independent from the official session model selection. */
27
23
  export function RoutingSettings({ rpc, ui }) {
28
24
  const [view, setView] = useState();
29
25
  const [patch, setPatch] = useState({});
30
- const [childDrafts, setChildDrafts] = useState({});
31
- const [provider, setProvider] = useState('claude');
32
26
  const [serviceMenu, setServiceMenu] = useState(false);
33
27
  const [modelMenu, setModelMenu] = useState(false);
34
28
  const [key, setKey] = useState('');
@@ -63,60 +57,10 @@ export function RoutingSettings({ rpc, ui }) {
63
57
  catch {
64
58
  invalidService = true;
65
59
  }
66
- const invalidFamily = (p) => {
67
- const family = view.settings.mainEfforts?.[p];
68
- return family !== undefined && (!family || typeof family !== 'object' || Array.isArray(family) || Object.keys(family).some(key => !tiers.includes(key)));
69
- };
70
- const familyUnresolved = (p) => invalidFamily(p) && !has(patch.mainEfforts, p);
71
- const mainValue = (p, tier) => {
72
- const cells = patch.mainEfforts?.[p];
73
- if (has(patch.mainEfforts, p) && (cells === null || invalidFamily(p)))
74
- return cells?.[tier] ?? undefined;
75
- return has(cells ?? undefined, tier) ? cells[tier] ?? undefined : view.settings.mainEfforts?.[p]?.[tier];
76
- };
77
- const childValue = (p, tier) => {
78
- if (childDrafts[`${p}.${tier}`])
79
- return childDrafts[`${p}.${tier}`];
80
- const cells = patch.childRoutes?.[p];
81
- const explicit = has(cells, tier) ? cells[tier] ?? undefined : view.settings.childRoutes?.[p]?.[tier];
82
- if (explicit)
83
- return {
84
- model: typeof explicit.model === 'string' ? explicit.model : '无效配置',
85
- reasoningEffort: typeof explicit.reasoningEffort === 'string' ? explicit.reasoningEffort : undefined,
86
- };
87
- const model = CHILD_MODELS[p][tiers.indexOf(tier)];
88
- const info = view.models.find(m => m.provider === p && m.model === model);
89
- return { model, reasoningEffort: info ? mapEffort(model, tier, info.reasoningEfforts, cap) : undefined };
90
- };
91
- const mainExplicit = (p, tier) => mainValue(p, tier) !== undefined;
92
- const childExplicit = (p, tier) => !!childDrafts[`${p}.${tier}`] || (has(patch.childRoutes?.[p], tier) ? patch.childRoutes[p][tier] !== null : !!view.settings.childRoutes?.[p]?.[tier]);
93
- const changed = (field) => Object.hasOwn(patch, field);
94
- const capChanged = changed('effortCap') && cap !== view.settings.effortCap;
95
60
  const conflicts = [];
96
- for (const p of PROVIDERS.map(p => p.id)) {
97
- for (const tier of tiers) {
98
- const value = mainValue(p, tier);
99
- if (value !== undefined && (capChanged || has(patch.mainEfforts?.[p] ?? undefined, tier)))
100
- conflicts.push(...validateFamilyMainEffort(view, p, value, cap, `mainEfforts.${p}.${tier}`));
101
- }
102
- if (capChanged && familyUnresolved(p))
103
- conflicts.push({ path: `mainEfforts.${p}`, code: 'invalid', message: '主会话映射格式无效,请恢复该体系默认值或重新配置' });
104
- for (const tier of tiers)
105
- if (childExplicit(p, tier) && (capChanged || has(patch.childRoutes?.[p], tier) || !!childDrafts[`${p}.${tier}`])) {
106
- conflicts.push(...validateChildRoute(view, p, childValue(p, tier), cap, `childRoutes.${p}.${tier}`));
107
- }
108
- }
109
- const invalidTimeout = !Number.isInteger(settings.jevTimeoutMs) || settings.jevTimeoutMs < 1000 || settings.jevTimeoutMs > 4500;
61
+ const invalidTimeout = !Number.isInteger(settings.jevTimeoutMs) || settings.jevTimeoutMs < 1000 || settings.jevTimeoutMs > 8000;
110
62
  const invalidKey = key.length > 512 || /\s/.test(key);
111
63
  const dirty = Object.keys(patch).length > 0 || key.length > 0 || removeKey;
112
- const setMain = (tier, effort) => setPatch(old => ({ ...old, mainEfforts: { ...old.mainEfforts, [provider]: { ...(old.mainEfforts?.[provider] === null ? Object.fromEntries(tiers.map(tier => [tier, null])) : old.mainEfforts?.[provider]), [tier]: effort } } }));
113
- const setChild = (tier, route) => {
114
- setChildDrafts(old => { const next = { ...old }; if (route)
115
- next[`${provider}.${tier}`] = route;
116
- else
117
- delete next[`${provider}.${tier}`]; return next; });
118
- setPatch(old => ({ ...old, childRoutes: { ...old.childRoutes, [provider]: { ...old.childRoutes?.[provider], [tier]: route === null ? null : { model: route.model, reasoningEffort: route.reasoningEffort } } } }));
119
- };
120
64
  const save = async () => {
121
65
  if (saving)
122
66
  return;
@@ -143,7 +87,6 @@ export function RoutingSettings({ rpc, ui }) {
143
87
  try {
144
88
  nextView = await request(rpc, 'saveSettings', patch);
145
89
  setPatch({});
146
- setChildDrafts({});
147
90
  results.push('设置已保存');
148
91
  }
149
92
  catch (error) {
@@ -156,14 +99,12 @@ export function RoutingSettings({ rpc, ui }) {
156
99
  setMessage(results.join(';'));
157
100
  setSaving(false);
158
101
  };
159
- const issueText = (path) => [...conflicts, ...saveIssues, ...view.issues].filter(issue => issue.path === path).map(issue => issue.message).join(';');
160
- const cell = (label, control, overridden, reset, issue) => h('div', { className: 'amr-settings-cell' }, h('span', { className: 'amr-settings-cell-label' }, label), control, overridden && h('button', { className: 'amr-settings-reset', type: 'button', disabled: saving, onClick: reset, 'aria-label': `${label}恢复默认` }, '恢复默认'), issue && h('p', { className: 'amr-settings-error', role: 'status' }, issue));
161
102
  const keyState = !view.key.available ? '不可用' : !view.key.writable ? '来自启动环境' : view.key.configured ? '已配置' : '未配置';
162
- const keyHint = !view.key.available ? 'DSH 凭据服务不可用。' : !view.key.writable ? '先自行保留现有 key,从启动环境(如 .env.local)删除 OPENROUTER_API_KEY 并重启,再在这里保存。' : view.key.configured ? '保存在 DSH 凭据存储,不写入设置文件;留空表示保持当前 key。' : '未配置时 AUTO 判档会停用;保存 key 后不会自动重启已停用的会话。';
103
+ const keyHint = !view.key.available ? 'DSH 凭据服务不可用。' : !view.key.writable ? '先自行保留现有 key,从启动环境(如 .env.local)删除 OPENROUTER_API_KEY 并重启,再在这里保存。' : view.key.configured ? '保存在 DSH 凭据存储,不写入设置文件;留空表示保持当前 key。' : '未配置时 AUTO 判断会停用;保存 key 后不会自动重启已停用的会话。';
163
104
  return h('div', { className: 'amr-settings' }, h('style', null, CSS), h(ui.SettingsForm, {
164
105
  labels: { unavailable: '无法读取设置', readOnly: '设置只读', saveFailed: '部分更改未保存,请检查提示', save: '保存', saving: '保存中…' },
165
106
  state: { available: true, writable: true, dirty, invalid: conflicts.length > 0 || invalidTimeout || invalidKey || invalidService, saving, failed }, onSave: () => { void save(); }, onDiscard: () => { },
166
- children: h('fieldset', { disabled: saving || testing, style: { border: 0, padding: 0, margin: 0, minWidth: 0 } }, view.configStatus === 'invalid' && h('p', { role: 'status' }, '设置文件无法解析或含无效字段,已按可解析值与默认值显示;保存会覆盖损坏部分'), message && h('p', { role: 'status', className: failed ? 'amr-settings-error' : undefined }, message), h('section', { className: 'amr-settings-section' }, h('h3', null, '判档服务'), h('p', null, 'AUTO 把脱敏任务发送给所选决策服务;不跨服务自动回退。'), h('div', { className: 'amr-settings-row' }, h('span', null, '决策服务'), h(ui.Menu, { open: serviceMenu, portal: true, side: 'bottom', selectedId: serviceProvider, onClose: () => setServiceMenu(false),
107
+ children: h('fieldset', { disabled: saving || testing, style: { border: 0, padding: 0, margin: 0, minWidth: 0 } }, view.configStatus === 'invalid' && h('p', { role: 'status' }, '设置文件无法解析或含无效字段,已按可解析值与默认值显示;保存会覆盖损坏部分'), view.deprecated?.length ? h('p', { role: 'status', 'data-deprecated': 'true' }, `设置文件中的旧映射(${view.deprecated.join('、')})已不再生效,下次保存设置时移除。`) : null, message && h('p', { role: 'status', className: failed ? 'amr-settings-error' : undefined }, message), h('section', { className: 'amr-settings-section' }, h('h3', null, '决策服务'), h('p', null, 'AUTO 把脱敏任务发送给所选决策服务;不跨服务自动回退。'), h('div', { className: 'amr-settings-row' }, h('span', null, '决策服务'), h(ui.Menu, { open: serviceMenu, portal: true, side: 'bottom', selectedId: serviceProvider, onClose: () => setServiceMenu(false),
167
108
  items: Object.entries(DECISION_SERVICES).map(([id, service]) => ({ id, label: service.label })),
168
109
  onSelect: id => { if (!Object.hasOwn(DECISION_SERVICES, id))
169
110
  return; setKey(''); setRemoveKey(false); setPatch(old => ({ ...old, decisionService: { provider: id } })); setServiceMenu(false); },
@@ -191,9 +132,6 @@ export function RoutingSettings({ rpc, ui }) {
191
132
  finally {
192
133
  setTesting(false);
193
134
  }
194
- } }, testing ? '测试中…' : '测试连接'), h('span', { role: 'status', 'aria-live': 'polite', className: testResult?.failed ? 'amr-settings-error' : undefined }, testing ? '正在请求所选决策服务…' : testResult?.text ?? (dirty ? '请先保存设置,再测试连接' : ''))), h('p', null, '测试会产生一次决策请求,不包含会话内容;请先保存设置。'), invalidService && h('p', { role: 'alert', className: 'amr-settings-error' }, '请填写有效模型和兼容端点'), h(ui.SettingsSecretField, { id: 'amr-key', label: `${DECISION_SERVICES[serviceProvider].label} API Key`, hint: changingService ? '请先保存服务选择' : keyHint, text: key, disabled: changingService || saving || !view.key.available || !view.key.writable, onEdit: text => { setKey(text); setRemoveKey(false); }, configured: !changingService && view.key.configured, stateLabel: changingService ? '保存后读取' : keyState }), !changingService && view.key.available && view.key.writable && view.key.configured && h('button', { type: 'button', className: 'amr-settings-reset', onClick: () => { setRemoveKey(!removeKey); setKey(''); } }, removeKey ? '取消删除 key' : '删除 key'), invalidKey && h('p', { className: 'amr-settings-error' }, 'API key 不能含空白,且不能超过 512 字符'), h('div', { className: 'amr-settings-row' }, h('div', null, h('label', { htmlFor: 'amr-timeout' }, '判档等待上限'), h('p', null, '1000–4500 毫秒;超时后该会话停用 AUTO。')), h('span', null, h('input', { id: 'amr-timeout', type: 'number', className: 'amr-settings-number', min: 1000, max: 4500, step: 1, value: Number.isNaN(settings.jevTimeoutMs) ? '' : settings.jevTimeoutMs, 'aria-invalid': invalidTimeout, onChange: e => setPatch(old => ({ ...old, jevTimeoutMs: e.currentTarget.value === '' ? NaN : Number(e.currentTarget.value) })) }), ' 毫秒'))), h('section', { className: 'amr-settings-section' }, h('h3', null, '会话'), h('p', { role: 'status' }, settings.policy === 'legacy' ? '当前使用旧策略回滚配置。' : 'AUTO 已使用新策略,无需额外启用。主模型不变;子任务独立选择模型能力与推理投入,并在创建时应用。MANUAL 不进行自动判档。'), h('div', { className: 'amr-settings-row' }, h('div', null, '新会话默认模式', h('p', null, settings.defaultMode === 'AUTO' ? '仅影响新会话;每个用户回合都会把脱敏任务发送给所选决策服务请求 Jev。' : '仅影响新会话,不改变已有会话。')), h(ui.SegmentedControl, { id: 'amr-default-mode', className: 'amr-settings-segments', label: '新会话默认模式', value: settings.defaultMode, options: [{ value: 'MANUAL', label: 'MANUAL' }, { value: 'AUTO', label: 'AUTO' }], onChange: value => setPatch(old => ({ ...old, defaultMode: value })) })), h('div', { className: 'amr-settings-row' }, h('span', null, '判档提示'), h(ui.Switch, { checked: settings.decisionToast, label: '判档提示', onChange: value => setPatch(old => ({ ...old, decisionToast: value })) })), h('div', { className: 'amr-settings-row' }, h('span', null, '推理强度上限'), h(ui.SegmentedControl, { id: 'amr-cap', className: 'amr-settings-segments', label: '推理强度上限', value: cap, options: AUTO_EFFORTS.map(value => ({ value, label: value })), onChange: value => setPatch(old => ({ ...old, effortCap: value })) }))), settings.policy !== 'legacy' && h('section', { className: 'amr-settings-section' }, h('h3', null, '自动选配'), h('p', null, '主会话仅调整推理投入;子任务按目标与范围选择合适的能力档,再确定投入。保留显式指定的模型和推理等级,不自动重试或升级。'), h('p', null, 'Magpie 使用完整模型 ID 与官方允许列表;目录不可用或目标不清楚时停止并提示。fresh 子任务受支持,fork 暂不支持。旧四档配置保留供回滚,不参与当前策略。'), h(StrategyDetails)), settings.policy === 'legacy' && h('section', { className: 'amr-settings-section' }, h('h3', null, '路由映射'), h(ui.SegmentedTabs, { className: 'amr-settings-segments amr-settings-tabs', label: '模型体系', value: provider, items: [{ value: 'claude', label: 'Claude', id: 'amr-tab-claude', panelId: 'amr-panel-claude' }, { value: 'codex', label: 'GPT', id: 'amr-tab-codex', panelId: 'amr-panel-codex' }], onChange: value => setProvider(value) }), h('div', { role: 'tabpanel', id: `amr-panel-${provider}`, 'aria-labelledby': `amr-tab-${provider}` }, h('p', null, '主会话推理等级适用于本体系所有模型;默认随实际模型计算。可选覆盖值为当前体系模型共同支持且不超过上限的等级。'), !supportedFamilyEfforts(view, provider, cap).length && h('p', { role: 'status' }, '当前体系没有共同支持且不超过上限的推理等级;主会话覆盖暂不可选,可恢复默认。'), familyUnresolved(provider) && h('p', { className: 'amr-settings-error', role: 'status' }, '主会话映射格式无效(含旧版按模型配置),不会按默认值执行。请重新配置或', h('button', { type: 'button', className: 'amr-settings-reset', onClick: () => setPatch(old => ({ ...old, mainEfforts: { ...old.mainEfforts, [provider]: null } })) }, '恢复本体系默认值')), h('p', null, '主会话保留当前模型,只调 effort;子会话在 route_subagent 判档后返回推荐 model + effort,不改既有子会话、不决定是否派发。'), view.childAllowlist.status !== 'enabled' || !selectableModels(view, provider, 'child').length ? h('p', { role: 'status' }, view.childAllowlist.status === 'unavailable' ? '无法读取子智能体允许列表;请检查官方子智能体设置。' : '请在官方子智能体设置启用并配置允许列表。') : null, h('div', { className: 'amr-settings-table' }, h('div', { className: 'amr-settings-head', 'aria-hidden': true }, h('span', null, '档位'), h('span', null, '主会话推理等级'), h('span', null, '子会话推荐')), ...tiers.map(tier => {
195
- const effort = mainValue(provider, tier);
196
- return h('div', { key: tier, className: 'amr-settings-tier', 'data-tier': tier }, h('div', { className: 'amr-settings-tier-name' }, tier, h(ui.Tooltip, { label: view.criteria[tier], side: 'top', portal: true, children: h('button', { type: 'button', className: 'amr-settings-info', 'aria-label': `${tier} 判档标准`, onFocus: () => { } }, h(ui.IconInfoOutlineRegular, { size: 14 })) })), cell('主会话推理等级', h(RoutePicker, { ui, catalog: view, provider, cap, mode: 'effort', label: `${tier} 主会话推理等级`, value: { model: '', reasoningEffort: effort === undefined ? undefined : typeof effort === 'string' ? effort : '无效配置' }, invalid: familyUnresolved(provider), disabled: saving, onChange: value => setMain(tier, value.reasoningEffort) }), mainExplicit(provider, tier), () => setMain(tier, null), issueText(`mainEfforts.${provider}.${tier}`)), cell('子会话推荐', h(RoutePicker, { ui, catalog: view, provider, cap, label: `${tier} 子会话推荐`, value: childValue(provider, tier), disabled: saving || !selectableModels(view, provider, 'child').length, onChange: value => setChild(tier, value) }), childExplicit(provider, tier), () => setChild(tier, null), issueText(`childRoutes.${provider}.${tier}`)));
197
- })), h('p', null, '官方允许列表是会话创建时的快照;已有会话仍可能拒绝新配置的推荐。'))), ...conflicts.filter(issue => !issue.path.startsWith(`childRoutes.${provider}.`) && !issue.path.startsWith(`mainEfforts.${provider}.`)).map(issue => h('p', { key: issue.path, role: 'status', className: 'amr-settings-error' }, `${issue.path}:${issue.message}`))),
135
+ } }, testing ? '测试中…' : '测试连接'), h('span', { role: 'status', 'aria-live': 'polite', className: testResult?.failed ? 'amr-settings-error' : undefined }, testing ? '正在请求所选决策服务…' : testResult?.text ?? (dirty ? '请先保存设置,再测试连接' : ''))), h('p', null, '测试会产生一次决策请求,不包含会话内容;请先保存设置。'), invalidService && h('p', { role: 'alert', className: 'amr-settings-error' }, '请填写有效模型和兼容端点'), h(ui.SettingsSecretField, { id: 'amr-key', label: `${DECISION_SERVICES[serviceProvider].label} API Key`, hint: changingService ? '请先保存服务选择' : keyHint, text: key, disabled: changingService || saving || !view.key.available || !view.key.writable, onEdit: text => { setKey(text); setRemoveKey(false); }, configured: !changingService && view.key.configured, stateLabel: changingService ? '保存后读取' : keyState }), !changingService && view.key.available && view.key.writable && view.key.configured && h('button', { type: 'button', className: 'amr-settings-reset', onClick: () => { setRemoveKey(!removeKey); setKey(''); } }, removeKey ? '取消删除 key' : '删除 key'), invalidKey && h('p', { className: 'amr-settings-error' }, 'API key 不能含空白,且不能超过 512 字符'), h('div', { className: 'amr-settings-row' }, h('div', null, h('label', { htmlFor: 'amr-timeout' }, '判断等待上限'), h('p', null, '1000–8000 毫秒,默认 5000;超时后该会话停用 AUTO。')), h('span', null, h('input', { id: 'amr-timeout', type: 'number', className: 'amr-settings-number', min: 1000, max: 8000, step: 1, value: Number.isNaN(settings.jevTimeoutMs) ? '' : settings.jevTimeoutMs, 'aria-invalid': invalidTimeout, onChange: e => setPatch(old => ({ ...old, jevTimeoutMs: e.currentTarget.value === '' ? NaN : Number(e.currentTarget.value) })) }), ' 毫秒'))), h('section', { className: 'amr-settings-section' }, h('h3', null, '会话'), h('p', null, '主模型不变;子任务独立选择模型能力与推理投入,并在创建时应用。MANUAL 不进行自动判断。'), h('div', { className: 'amr-settings-row' }, h('div', null, '新会话默认模式', h('p', null, settings.defaultMode === 'AUTO' ? '仅影响新会话;每个用户回合都会把脱敏任务发送给所选决策服务请求 Jev。' : '仅影响新会话,不改变已有会话。')), h(ui.SegmentedControl, { id: 'amr-default-mode', className: 'amr-settings-segments', label: '新会话默认模式', value: settings.defaultMode, options: [{ value: 'MANUAL', label: 'MANUAL' }, { value: 'AUTO', label: 'AUTO' }], onChange: value => setPatch(old => ({ ...old, defaultMode: value })) })), h('div', { className: 'amr-settings-row' }, h('span', null, '判断提示'), h(ui.Switch, { checked: settings.decisionToast, label: '判断提示', onChange: value => setPatch(old => ({ ...old, decisionToast: value })) })), h('div', { className: 'amr-settings-row' }, h('span', null, '推理强度上限'), h(ui.SegmentedControl, { id: 'amr-cap', className: 'amr-settings-segments', label: '推理强度上限', value: cap, options: AUTO_EFFORTS.map(value => ({ value, label: value })), onChange: value => setPatch(old => ({ ...old, effortCap: value })) }))), h('section', { className: 'amr-settings-section' }, h('h3', null, '自动选配'), h('p', null, '主会话仅调整推理投入;子任务按目标与范围选择合适的能力档,再确定投入。保留显式指定的模型和推理等级,不自动重试或升级。'), h('p', null, 'Magpie 使用完整模型 ID 与官方允许列表;目录不可用或目标不清楚时停止并提示。fresh 子任务受支持,fork 暂不支持。'), h(StrategyDetails)), ...[...conflicts, ...saveIssues].map(issue => h('p', { key: issue.path, role: 'status', className: 'amr-settings-error' }, `${issue.path}:${issue.message}`))),
198
136
  }));
199
137
  }
@@ -1,8 +1,7 @@
1
1
  import { createElement as h } from 'react';
2
- import questions from '../policy-v2-rubric.json' with { type: 'json' };
3
- import { POLICY_MODELS, mappingVersion } from '../policy-v2.js';
4
- import { targetEffort } from '../effort.js';
2
+ import questions from '../rubric.json' with { type: 'json' };
3
+ import { registry } from '../strategies/index.js';
5
4
  /** Read-only: criteria and slots are imported from the executable policy, never copied. */
6
5
  export function StrategyDetails() {
7
- return h('details', { className: 'amr-strategy' }, h('summary', { style: { cursor: 'pointer', padding: '8px 0' } }, '查看决策策略'), h('p', null, 'Agent 负责拆解、派发与验收;Jev 只做有限判断,AUTO 验证目录并应用资源配置。任务数量、风险和重要性不直接等于推理难度。以下为只读说明,不会修改设置。'), h('h4', null, '能力需求(仅子任务)'), h('p', null, 'bounded:方法明确、结果可验证的执行;analytical:需要调查、关联证据或选择方案;frontier:异常困难的原创推理或相互耦合的未知;insufficient_context:缺少目标或范围,停止并补充描述。'), h('h4', null, '推理投入(主会话与子任务分别判断)'), h('p', null, '主模型不变。以具体模型推荐投入为起点,lower / baseline / higher 分别在受支持序列中降低一档、保持、提高一档;受当前设置上限约束,自动不选 max。轻量模型不意味着自动降低投入。'), h('h4', null, '当前子模型能力映射'), ...Object.entries(POLICY_MODELS).map(([family, slots]) => h('div', { key: family }, h('strong', null, family === 'claude' ? 'Claude' : 'GPT'), h('ul', null, ...Object.entries(slots).map(([level, model]) => h('li', { key: level }, `${level} → ${model}(推荐起点 ${targetEffort(model, 'standard')})`))))), h('p', null, `映射版本:${mappingVersion}。Magpie 保留对应家族前缀与真实 provider,不跨入口回退;模型与 effort 必须同时通过当前目录及官方允许列表校验。其他家族需显式指定模型及可验证投入。`), h('h4', null, '约束、失败与恢复'), h('p', null, '显式指定的模型与投入优先;两者都指定时只校验、不请求 Jev。目标不足、目录失效或子判断失败时停止派发,不偷偷继承或换模。主判断失败则可见停用 AUTO。fresh 子任务创建时绑定精确身份;后续消息和恢复不重新选模。fork 暂不支持。MANUAL 停止新判断,已有绑定子任务仍保持初始路线。'), h('h4', null, 'Jev 实际判据原文'), h('p', null, '下面直接展示执行中使用的英文规则,避免翻译改变含义;两个问题相互独立,不能读取对方答案。'), ...Object.entries(questions).map(([dimension, question]) => h('section', { key: dimension, style: { overflowWrap: 'anywhere' } }, h('strong', null, dimension), h('p', null, question.instructions), h('dl', null, ...Object.entries(question.criteria).flatMap(([label, criterion]) => [h('dt', { key: `${label}-label` }, h('strong', null, label)), h('dd', { key: label, style: { margin: '4px 0 12px 12px' } }, criterion)])))));
6
+ return h('details', { className: 'amr-strategy' }, h('summary', { style: { cursor: 'pointer', padding: '8px 0' } }, '查看决策策略'), h('p', null, 'Agent 负责拆解、派发与验收;Jev 只做有限判断,AUTO 验证目录并应用资源配置。任务数量、风险和重要性不直接等于推理难度。以下为只读说明,不会修改设置。'), h('h4', null, '能力需求(仅子任务)'), h('p', null, 'bounded:方法明确、结果可验证的执行;analytical:需要调查、关联证据或选择方案;frontier:异常困难的原创推理或相互耦合的未知;insufficient_context:缺少目标或范围,停止并补充描述。'), h('h4', null, '推理投入(主会话与子任务分别判断)'), h('p', null, '主模型不变。以具体模型推荐投入为起点,lower / baseline / higher 分别在受支持序列中降低一档、保持、提高一档;受当前设置上限约束,自动不选 max。轻量模型不意味着自动降低投入。'), h('h4', null, '当前子模型能力映射'), ...registry.strategies.map(family => h('div', { key: family.id }, h('strong', null, family.label), h('ul', null, ...Object.entries(family.childModels).map(([level, model]) => h('li', { key: level }, `${level} → ${model}(推荐起点 ${registry.effortStart(model) ?? '目录默认'})`))))), h('p', null, `Magpie 保留对应家族前缀与真实 provider,不跨入口回退;模型与 effort 必须同时通过当前目录及官方允许列表校验。其他家族需显式指定模型及可验证投入。`), h('h4', null, '约束、失败与恢复'), h('p', null, '显式指定的模型与投入优先;两者都指定时只校验、不请求 Jev。目标不足、目录失效或子判断失败时停止派发,不偷偷继承或换模。主判断失败则可见停用 AUTO。fresh 子任务创建时绑定精确身份;后续消息和恢复不重新选模。fork 暂不支持。MANUAL 停止新判断,已有绑定子任务仍保持初始路线。'), h('h4', null, 'Jev 实际判据原文'), h('p', null, '下面直接展示执行中使用的英文规则,避免翻译改变含义;两个问题相互独立,不能读取对方答案。'), ...Object.entries(questions).map(([dimension, question]) => h('section', { key: dimension, style: { overflowWrap: 'anywhere' } }, h('strong', null, dimension), h('p', null, question.instructions), h('dl', null, ...Object.entries(question.criteria).flatMap(([label, criterion]) => [h('dt', { key: `${label}-label` }, h('strong', null, label)), h('dd', { key: label, style: { margin: '4px 0 12px 12px' } }, criterion)])))));
8
7
  }