dsh-subagent-profile 0.1.0 → 0.2.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
@@ -1,11 +1,11 @@
1
1
  /**
2
- * dsh-subagent-profile — browser half: a read-only `dispatch` tool-call card
2
+ * dsh-subagent-profile — browser side: a read-only `dispatch` tool-call card
3
3
  * plus a settings page that manages the profile registry.
4
4
  *
5
5
  * Hand-written `__ModuleLoader__` factory (no build step), modeled on
6
6
  * @dsh-external/dsh-persona-ref: React comes from the shared module host, every
7
7
  * data operation goes through the loopback /subagent-profiles/* HTTP routes
8
- * served by the host half (index.mjs), and slots register through the shared
8
+ * served by the host side (index.mjs), and slots register through the shared
9
9
  * `slots` service. The card lives on the keyed `tool.call.toolview` seat (key
10
10
  * `dispatch`); the settings page lives on the `settings.section` list.
11
11
  *
@@ -27,7 +27,7 @@ window.__ModuleLoader__.load({
27
27
  /** Required cordis services: the slot registry only. */
28
28
  const inject = ['slots']
29
29
 
30
- // ── api (loopback HTTP, host half serves /subagent-profiles/*) ──────────
30
+ // ── api (loopback HTTP, host side serves /subagent-profiles/*) ──────────
31
31
 
32
32
  async function api(path, payload) {
33
33
  const init = payload === undefined
@@ -65,10 +65,15 @@ window.__ModuleLoader__.load({
65
65
  '.sap-rowTag{border:1px solid var(--dsw-alias-border-l3);color:var(--dsw-alias-label-secondary);border-radius:4px;flex:none;padding:1px 6px;font-size:11px;line-height:16px}',
66
66
  '.sap-rowActions{align-items:center;gap:4px;margin-left:auto;display:inline-flex}',
67
67
  '.sap-desc{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:18px}',
68
- '.sap-meta{flex-wrap:wrap;gap:6px 14px;display:flex}',
69
- '.sap-metaItem{display:inline-flex;align-items:baseline;gap:6px;font-size:12px;line-height:18px}',
70
- '.sap-metaLabel{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}',
71
- '.sap-metaValue{color:var(--dsw-alias-label-primary)}',
68
+ '.sap-warn{color:var(--dsw-alias-state-warn-label);margin:0;font-size:12px;line-height:18px}',
69
+ '.sap-chips{flex-wrap:wrap;gap:6px;display:flex}',
70
+ '.sap-chip{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:6px;flex:none;align-items:center;gap:5px;padding:2px 8px;font-size:12px;line-height:18px;display:inline-flex}',
71
+ '.sap-chipLabel{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}',
72
+ '.sap-chipValue{color:var(--dsw-alias-label-primary)}',
73
+ '.sap-chipValueDim{color:var(--dsw-alias-label-tertiary)}',
74
+ '.sap-chipWarn{background:var(--dsw-alias-state-warn-tertiary);border-color:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-label);font-weight:500}',
75
+ '.sap-chipSuccess{color:var(--dsw-alias-state-success-primary);font-weight:500}',
76
+ '.sap-chipDisabled{color:var(--dsw-alias-state-warn-label);font-weight:500}',
72
77
  '.sap-metaValueDefault{color:var(--dsw-alias-label-tertiary)}',
73
78
  '.sap-addBlock{margin-top:4px}',
74
79
  '.sap-addActions{flex-wrap:wrap;gap:10px;display:flex}',
@@ -105,7 +110,8 @@ window.__ModuleLoader__.load({
105
110
  '.sap-switch input::before{content:"";position:absolute;width:16px;height:16px;border-radius:50%;background:#fff;top:2px;left:2px;transition:left .2s;box-shadow:0 1px 2px rgba(0,0,0,.2)}',
106
111
  '.sap-switch input:checked{background:var(--dsw-alias-brand-primary)}',
107
112
  '.sap-switch input:checked::before{left:18px}',
108
- '.sap-toolview{display:flex;align-items:baseline;gap:6px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);padding:4px 8px;white-space:pre-wrap;word-break:break-word}',
113
+ '.sap-toolview{flex-direction:column;align-items:flex-start;gap:6px;padding:4px 8px;display:flex}',
114
+ '.sap-toolText{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);white-space:pre-wrap;word-break:break-word}',
109
115
  '.sap-toolModes{flex-wrap:wrap;gap:4px 16px;display:flex}',
110
116
  '.sap-toolGroups{flex-direction:column;gap:8px;display:flex}',
111
117
  '.sap-toolLayerCard{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:6px 10px}',
@@ -130,45 +136,214 @@ window.__ModuleLoader__.load({
130
136
 
131
137
  // ── dispatch tool-call card ─────────────────────────────────────────────
132
138
 
139
+ // 面向用户的固定文案(G1)。注意:此表仅集中 dispatch 卡片文案;设置页文案
140
+ // 仍为 v0.1.0 散落硬编码,属 G1 待迁移债(V2.0 中期一次性迁入)。
141
+ const ZH = {
142
+ inlineProfile: '内联',
143
+ inherit: '继承父',
144
+ effortDefault: '默认(继承父)',
145
+ chipProfile: '方案',
146
+ chipPreset: '预设',
147
+ chipProvider: '提供方',
148
+ chipModel: '模型',
149
+ chipEffort: '推理强度',
150
+ chipTier: '档位',
151
+ chipEnabled: '已启用',
152
+ chipDisabled: '已禁用',
153
+ ignoredMark: '已忽略',
154
+ toolLabel: '派发子 Agent',
155
+ cont: 'continuable 子 Agent',
156
+ canContinue: '可续跑',
157
+ toolBackground: '后台任务',
158
+ toolForeground: '前台',
159
+ toolRunning: '运行中',
160
+ tierZh: { cheap: '省 token(cheap)', balanced: '均衡(balanced)', premium: '高成本(premium)' },
161
+ persistFail: '已保存但未持久化',
162
+ persistFailHint: '(磁盘写入未成功,重启 dsh web 后该改动可能丢失)'
163
+ }
164
+
165
+ const effortZh = (id) => {
166
+ if (id === undefined || id === null || id === '') return ZH.inherit
167
+ if (id === '(default)') return ZH.effortDefault
168
+ if (id === '(parent)' || id === 'undefined') return ZH.inherit
169
+ return EFFORT_ZH[id] ? `${EFFORT_ZH[id]}(${id})` : id
170
+ }
171
+ const presetZh = (v) => (v === 'inherit' ? ZH.inherit : (typeof v === 'string' && v !== '' ? v : ZH.inherit))
172
+ const profileZh = (v) => (v === undefined || v === '' || v === '(inline)' ? ZH.inlineProfile : v)
173
+ const rawOrInherit = (v) => (v === '(parent)' ? ZH.inherit : (typeof v === 'string' && v !== '' ? v : ZH.inherit))
174
+
175
+ /** 统一 chip 元素:label + value,dim=继承/默认值弱显。 */
176
+ const chip = (key, label, value, opts) => {
177
+ const valueCls = opts && opts.dim ? 'sap-chipValueDim' : 'sap-chipValue'
178
+ return el('span', {
179
+ key,
180
+ className: 'sap-chip',
181
+ ...(opts && typeof opts.title === 'string' && opts.title !== '' ? { title: opts.title } : {})
182
+ },
183
+ el('span', { className: 'sap-chipLabel' }, label),
184
+ el('span', { className: valueCls }, value)
185
+ )
186
+ }
187
+
188
+ // 请求值:argsRaw(运行中 / 已结算两种 block 形态都有)。
189
+ function readDispatchRequest(block) {
190
+ const out = { profile: '', preset: '', provider: '', model: '', reasoningEffort: '', continuable: false, runInBackground: false }
191
+ if (!block || typeof block !== 'object') return out
192
+ const done = 'kind' in block
193
+ const argsRaw = (done ? (block.call && block.call.argsRaw) : block.argsRaw) ?? ''
194
+ if (typeof argsRaw !== 'string' || argsRaw === '') return out
195
+ try {
196
+ const parsed = JSON.parse(argsRaw)
197
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
198
+ for (const key of ['profile', 'preset', 'provider', 'model', 'reasoningEffort']) {
199
+ if (typeof parsed[key] === 'string' && parsed[key] !== '') out[key] = parsed[key]
200
+ }
201
+ out.continuable = parsed.continuable === true
202
+ out.runInBackground = parsed.run_in_background === true
203
+ }
204
+ } catch {
205
+ // unparsable argsRaw:保持默认(继承/内联口径)。
206
+ }
207
+ return out
208
+ }
209
+
210
+ // 结果 meta(生效值):宿主 render 行("[dispatch] …")是持久回放的唯一
211
+ // 生效值来源——结构化 value 只存在于执行局部(dsh-tools 约定),且宿主未
212
+ // 定义 output.presentationMeta(block.meta 为空)。解析成功返回
213
+ // {kind, id, fields, ignored};任何一步不符预期返回 null,调用方回退请求值。
214
+ const DISPATCH_RESULT_KEYS = ['profile', 'preset', 'provider', 'model', 'reasoningEffort', 'tokenTier']
215
+ function parseDispatchText(text) {
216
+ if (typeof text !== 'string') return null
217
+ const firstLine = text.split('\n')[0] ?? ''
218
+ if (!firstLine.startsWith('[dispatch] ')) return null
219
+ let rest = firstLine.slice('[dispatch] '.length)
220
+ let kind = 'foreground'
221
+ let id = ''
222
+ if (rest.startsWith('background job ')) {
223
+ kind = 'background'
224
+ rest = rest.slice('background job '.length)
225
+ } else if (rest.startsWith('started subagent ')) {
226
+ kind = 'continuable'
227
+ rest = rest.slice('started subagent '.length)
228
+ }
229
+ if (kind !== 'foreground') {
230
+ const sep = rest.indexOf(' · ')
231
+ id = sep >= 0 ? rest.slice(0, sep) : rest
232
+ rest = sep >= 0 ? rest.slice(sep + 3) : ''
233
+ }
234
+ const fields = {}
235
+ let ignored = []
236
+ let known = 0
237
+ for (const seg of rest.split(' · ')) {
238
+ const eq = seg.indexOf('=')
239
+ if (eq <= 0) continue
240
+ const key = seg.slice(0, eq)
241
+ if (!DISPATCH_RESULT_KEYS.includes(key)) continue
242
+ let value = seg.slice(eq + 1)
243
+ const m = /^(.+)\(ignored:\s*([^)]*)\)$/.exec(value)
244
+ if (m !== null) {
245
+ value = m[1]
246
+ ignored = m[2].split(',').map((s) => s.trim()).filter((s) => s !== '')
247
+ }
248
+ if (value === 'undefined') value = '' // v0.1.0 渲染该字段为字面 undefined
249
+ fields[key] = value
250
+ known += 1
251
+ }
252
+ if (known <= 0) return null
253
+ return { kind, id, fields, ignored }
254
+ }
255
+
256
+ function readDispatchResult(block) {
257
+ if (!block || typeof block !== 'object' || !('kind' in block)) return null
258
+ const content = block.content
259
+ if (!Array.isArray(content)) return null
260
+ for (const item of content) {
261
+ if (item && typeof item === 'object' && typeof item.text === 'string') {
262
+ const parsed = parseDispatchText(item.text)
263
+ if (parsed !== null) return parsed
264
+ }
265
+ }
266
+ return null
267
+ }
268
+
133
269
  function DispatchToolview(props) {
134
270
  const block = props && props.block
135
- let args = {}
136
- if (block && typeof block === 'object') {
137
- const done = 'kind' in block
138
- const argsRaw = (done ? (block.call && block.call.argsRaw) : block.argsRaw) ?? ''
139
- if (typeof argsRaw === 'string' && argsRaw !== '') {
140
- try {
141
- const parsed = JSON.parse(argsRaw)
142
- if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) args = parsed
143
- } catch {
144
- // unparsable argsRaw: fall through to the default field rendering
145
- }
271
+ const request = readDispatchRequest(block)
272
+ const result = readDispatchResult(block)
273
+ const settled = !!(block && typeof block === 'object' && 'kind' in block)
274
+ // 生效值:结果解析成功用结果(优先),字段缺失回退请求值。
275
+ const eff = {
276
+ profile: result ? (result.fields.profile || request.profile) : request.profile,
277
+ preset: result ? (result.fields.preset || request.preset) : request.preset,
278
+ provider: result ? (result.fields.provider || request.provider) : request.provider,
279
+ model: result ? (result.fields.model || request.model) : request.model,
280
+ reasoningEffort: result ? (result.fields.reasoningEffort || request.reasoningEffort) : request.reasoningEffort
281
+ }
282
+ // ignored 列表:已结算读结果;运行中(无结果)按已知降级规则预判——
283
+ // continuable 恒忽略 preset 换用与 reasoningEffort(与结果同口径)。
284
+ const ignored = result ? result.ignored.slice() : []
285
+ if (!settled && request.continuable) {
286
+ if (request.preset !== '' && request.preset !== 'inherit' && !ignored.includes('preset')) ignored.push('preset')
287
+ if (request.reasoningEffort !== '' && !ignored.includes('reasoningEffort')) ignored.push('reasoningEffort')
288
+ }
289
+ // P2(请求≠生效):被忽略字段的生效值 = 确定性降级值。
290
+ if (ignored.includes('preset')) eff.preset = 'inherit'
291
+ if (ignored.includes('reasoningEffort')) eff.reasoningEffort = '(default)'
292
+
293
+ let kindLabel
294
+ const kindPrefix = request.continuable ? ZH.cont : (request.runInBackground ? ZH.toolBackground : ZH.toolForeground)
295
+ // 运行/结算标签判据 = settled(`'kind' in block`):已结算块即使 render 为
296
+ // 旧格式不可解析(result===null)也绝不落入「运行中」分支。
297
+ if (settled) {
298
+ if (result) {
299
+ if (result.kind === 'background') kindLabel = `${ZH.toolBackground} #${result.id}`
300
+ else if (result.kind === 'continuable') kindLabel = `${ZH.cont} #${result.id}(${ZH.canContinue})`
301
+ else kindLabel = ZH.toolForeground
302
+ } else {
303
+ // 已结算但结果不可解析:kind 由请求值推导,不带「运行中」。
304
+ kindLabel = kindPrefix
146
305
  }
306
+ } else {
307
+ kindLabel = `${kindPrefix}(${ZH.toolRunning})`
308
+ }
309
+
310
+ const chips = []
311
+ chips.push(chip('profile', ZH.chipProfile, profileZh(eff.profile)))
312
+ // preset 继承值('inherit' 或未请求即空串,均渲染为「继承父」)与
313
+ // provider·model / effort 一样弱显。
314
+ chips.push(chip('preset', ZH.chipPreset, presetZh(eff.preset), { dim: eff.preset === 'inherit' || eff.preset === '' }))
315
+ const p = rawOrInherit(eff.provider)
316
+ const m = rawOrInherit(eff.model)
317
+ chips.push(chip('providerModel', `${ZH.chipProvider}·${ZH.chipModel}`, p === ZH.inherit && m === ZH.inherit ? ZH.inherit : `${p} / ${m}`, { dim: p === ZH.inherit && m === ZH.inherit }))
318
+ chips.push(chip('effort', ZH.chipEffort, effortZh(eff.reasoningEffort), { dim: eff.reasoningEffort === '' || eff.reasoningEffort === '(default)' }))
319
+ // tokenTier:宿主当前结果里未产出(§8.3 属中期切片);字段出现即渲染(向前兼容)。
320
+ let tier = ''
321
+ if (result && result.fields && typeof result.fields.tokenTier === 'string' && result.fields.tokenTier !== '') {
322
+ tier = result.fields.tokenTier
323
+ } else if (block && block.meta && typeof block.meta === 'object' && typeof block.meta.tokenTier === 'string' && block.meta.tokenTier !== '') {
324
+ tier = block.meta.tokenTier
147
325
  }
148
- const field = (key, fallback) => {
149
- const value = args[key]
150
- return typeof value === 'string' && value !== '' ? value : fallback
326
+ if (tier !== '') chips.push(chip('tier', ZH.chipTier, ZH.tierZh[tier] || tier))
327
+ // ignored 项:警示色、置于 chip 行末尾(BP-03 根治:非静默)。
328
+ for (const key of ignored) {
329
+ const reqVal = request[key] || ''
330
+ chips.push(el('span', {
331
+ key: `ignored-${key}`,
332
+ className: 'sap-chip sap-chipWarn',
333
+ title: reqVal !== '' ? `请求值 "${reqVal}" 已忽略` : '请求值已忽略'
334
+ }, `⬆${key}(${ZH.ignoredMark})`))
151
335
  }
152
- const profile = field('profile', '内联')
153
- const preset = field('preset', '继承父')
154
- const provider = field('provider', '继承父')
155
- const model = field('model', '继承父')
156
- const rawEffort = field('reasoningEffort', '继承父')
157
- const reasoningEffort = EFFORT_ZH[rawEffort] ?? rawEffort
158
- const continuable = args.continuable === true
159
- const text = `派发子 Agent:方案=${profile} · 预设=${preset} · 提供方=${provider} · 模型=${model} · 推理强度=${reasoningEffort}${continuable ? ' · 可续跑' : ''}`
160
- return el('div', { className: 'sap-toolview' }, text)
336
+
337
+ const text = `${ZH.toolLabel}:${kindLabel} · 方案=${profileZh(eff.profile)} · 预设=${presetZh(eff.preset)} · 提供方=${p} · 模型=${m} · 推理强度=${effortZh(eff.reasoningEffort)}`
338
+ return el('div', { className: 'sap-toolview' },
339
+ el('div', { className: 'sap-chips' }, chips),
340
+ el('div', { className: 'sap-toolText' }, text)
341
+ )
161
342
  }
162
343
 
163
344
  // ── settings page ───────────────────────────────────────────────────────
164
345
 
165
346
  const EMPTY_FORM = { id: '', description: '', preset: '', provider: '', model: '', reasoningEffort: '', toolMode: 'none', toolList: [] }
166
- const META_DEFS = [
167
- ['预设', 'preset'],
168
- ['提供方', 'provider'],
169
- ['模型', 'model'],
170
- ['推理强度', 'reasoningEffort'],
171
- ]
172
347
  const EFFORT_ZH = { off: '关闭', high: '高', max: '最大' }
173
348
  const effortLabel = (effort) => {
174
349
  const id = effort && effort.id
@@ -183,6 +358,7 @@ window.__ModuleLoader__.load({
183
358
  const [editingId, setEditingId] = useState(null)
184
359
  const [error, setError] = useState('')
185
360
  const [savedNotice, setSavedNotice] = useState('')
361
+ const [persistWarning, setPersistWarning] = useState('')
186
362
  const [form, setForm] = useState(EMPTY_FORM)
187
363
  const [tools, setTools] = useState([])
188
364
 
@@ -255,16 +431,30 @@ window.__ModuleLoader__.load({
255
431
  return { ...prev, toolList: [...cur.filter((n) => !toRemove.has(n)), ...toAdd] }
256
432
  })
257
433
 
434
+ // D5/B1 写路径契约:HTTP 200 但 persisted:false 时 host 附
435
+ // persistWarning「已保存但未持久化」——这里改为 amber 警示(非绿色成功态)。
436
+ const applyWrite = (data, successNotice) => {
437
+ if (data && data.persisted === false) {
438
+ setSavedNotice('')
439
+ setPersistWarning(typeof data.persistWarning === 'string' && data.persistWarning !== '' ? data.persistWarning : ZH.persistFail)
440
+ return
441
+ }
442
+ setPersistWarning('')
443
+ if (typeof successNotice === 'string' && successNotice !== '') setSavedNotice(successNotice)
444
+ else setSavedNotice('')
445
+ }
446
+
258
447
  const openAdd = () => {
259
448
  setError('')
260
449
  setSavedNotice('')
450
+ setPersistWarning('')
261
451
  setForm(EMPTY_FORM)
262
452
  setEditingId(null)
263
453
  setAdding(true)
264
454
  }
265
455
 
266
456
  const openEdit = (profile) => () => {
267
- setError(''); setSavedNotice('')
457
+ setError(''); setSavedNotice(''); setPersistWarning('')
268
458
  const tf = profile.toolFilter && typeof profile.toolFilter === 'object' ? profile.toolFilter : {}
269
459
  const allowArr = Array.isArray(tf.allow) ? tf.allow.slice() : []
270
460
  const denyArr = Array.isArray(tf.deny) ? tf.deny.slice() : []
@@ -287,6 +477,7 @@ window.__ModuleLoader__.load({
287
477
  setAdding(false)
288
478
  setEditingId(null)
289
479
  setError('')
480
+ setPersistWarning('')
290
481
  setForm(EMPTY_FORM)
291
482
  }
292
483
 
@@ -312,9 +503,9 @@ window.__ModuleLoader__.load({
312
503
  if (existing.enabled === false) payload.enabled = false
313
504
  }
314
505
  api('/add', payload)
315
- .then(() => {
506
+ .then((data) => {
316
507
  setForm(EMPTY_FORM); setAdding(false); setEditingId(null); setError('')
317
- setSavedNotice(`已保存 ${payload.id}`)
508
+ applyWrite(data, `已保存 ${payload.id}`)
318
509
  refresh()
319
510
  })
320
511
  .catch((err) => setError(String((err && err.message) || err)))
@@ -323,7 +514,7 @@ window.__ModuleLoader__.load({
323
514
  const setProfileEnabled = (profile, next) => () => {
324
515
  setError('')
325
516
  api('/set-profile-enabled', { id: profile.id, enabled: next })
326
- .then(() => refresh())
517
+ .then((data) => { applyWrite(data, ''); refresh() })
327
518
  .catch((err) => setError(String((err && err.message) || err)))
328
519
  }
329
520
 
@@ -331,16 +522,16 @@ window.__ModuleLoader__.load({
331
522
  if (!window.confirm('将所有内置方案重置为默认配置?已删除的内置也会恢复。')) return
332
523
  setError('')
333
524
  api('/reset-all')
334
- .then(() => { setSavedNotice('已重置所有内置方案'); refresh() })
525
+ .then((data) => { applyWrite(data, '已重置所有内置方案'); refresh() })
335
526
  .catch((err) => setError(String((err && err.message) || err)))
336
527
  }
337
528
 
338
529
  const remove = (profile) => () => {
339
530
  if (!window.confirm(`删除方案「${profile.name || profile.id}」?${profile.builtin === true ? '内置方案删除后可在上方「重置内置方案」恢复' : ''}`)) return
340
531
  api('/remove', { id: profile.id })
341
- .then(() => {
532
+ .then((data) => {
342
533
  setError('')
343
- setSavedNotice(`已删除 ${profile.id}`)
534
+ applyWrite(data, `已删除 ${profile.id}`)
344
535
  refresh()
345
536
  })
346
537
  .catch((err) => setError(String((err && err.message) || err)))
@@ -361,21 +552,23 @@ window.__ModuleLoader__.load({
361
552
  const builtin = profile.builtin === true
362
553
  const isDisabled = profile.enabled === false
363
554
  const hasName = typeof profile.name === 'string' && profile.name !== '' && profile.name !== String(profile.id)
364
- const metaItems = META_DEFS.map(([label, key]) => {
365
- const value = profile[key]
366
- const has = typeof value === 'string' && value !== ''
367
- return el('span', { key: label, className: 'sap-metaItem' },
368
- el('span', { className: 'sap-metaLabel' }, `${label} `),
369
- el('span', { className: has ? 'sap-metaValue' : 'sap-metaValueDefault' }, has ? value : '继承父')
370
- )
371
- })
555
+ const hasPreset = typeof profile.preset === 'string' && profile.preset !== ''
556
+ const hasModel = typeof profile.model === 'string' && profile.model !== ''
557
+ const hasEffort = typeof profile.reasoningEffort === 'string' && profile.reasoningEffort !== ''
558
+ const chipItems = [
559
+ chip('preset', ZH.chipPreset, presetZh(profile.preset), { dim: !hasPreset }),
560
+ chip('model', ZH.chipModel, rawOrInherit(profile.model), { dim: !hasModel }),
561
+ chip('effort', ZH.chipEffort, effortZh(profile.reasoningEffort), { dim: !hasEffort }),
562
+ isDisabled
563
+ ? el('span', { key: 'state', className: 'sap-chip sap-chipDisabled' }, ZH.chipDisabled)
564
+ : el('span', { key: 'state', className: 'sap-chip sap-chipSuccess' }, ZH.chipEnabled)
565
+ ]
372
566
  return el('li', { key: String(profile.id), className: 'sap-rowCard' },
373
567
  el('div', { className: 'sap-rowHead' },
374
568
  el('span', { className: 'sap-rowIdentity' },
375
569
  el('span', { className: 'sap-rowName' }, hasName ? profile.name : String(profile.id)),
376
570
  hasName ? el('span', { className: 'sap-rowId' }, String(profile.id)) : null,
377
- builtin ? el('span', { className: 'sap-rowTag' }, '内置') : null,
378
- isDisabled ? el('span', { className: 'sap-rowTag' }, '已禁用') : null
571
+ builtin ? el('span', { className: 'sap-rowTag' }, '内置') : null
379
572
  ),
380
573
  el('span', { className: 'sap-rowActions' },
381
574
  el('label', { className: 'sap-switch', title: '启用/禁用' },
@@ -386,10 +579,10 @@ window.__ModuleLoader__.load({
386
579
  el('button', { className: 'sap-dangerButton', onClick: remove(profile) }, '删除')
387
580
  )
388
581
  ),
582
+ el('div', { className: 'sap-chips' }, chipItems),
389
583
  typeof profile.description === 'string' && profile.description !== ''
390
584
  ? el('p', { className: 'sap-desc' }, profile.description)
391
- : null,
392
- el('div', { className: 'sap-meta' }, metaItems)
585
+ : null
393
586
  )
394
587
  })
395
588
 
@@ -488,6 +681,7 @@ window.__ModuleLoader__.load({
488
681
  !enabled ? el('p', { className: 'sap-notice' }, '插件已禁用:「派发子 Agent」工具已从模型工具列表中移除,打开开关即可恢复。') : null,
489
682
  error !== '' ? el('p', { className: 'sap-notice' }, error) : null,
490
683
  savedNotice !== '' ? el('p', { className: 'sap-savedNotice', role: 'status' }, savedNotice) : null,
684
+ persistWarning !== '' ? el('p', { className: 'sap-warn', role: 'alert' }, `${persistWarning}${ZH.persistFailHint}`) : null,
491
685
  el('ul', { className: 'sap-rows' }, rows),
492
686
  el('div', { className: 'sap-addBlock' },
493
687
  adding