dsh-plugin-admin 0.4.3 → 0.5.1

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
@@ -650,7 +650,16 @@ function PluginsSection(props) {
650
650
  /** Upgrade one plugin to its latest version (registry install by name). */
651
651
  function upgradePlugin(name) {
652
652
  if (pView.busy) return
653
- var spec = name + '@latest'
653
+ // Prefer the exact version already discovered by checkUpdates(): passing
654
+ // `name@latest` to pnpm is unreliable when the manifest already carries a
655
+ // range constraint (e.g. "^0.4.2") — pnpm can resolve @latest against the
656
+ // satisfied range and report "Already up to date" WITHOUT fetching the new
657
+ // version (exit 0, nothing changed). Pinning the exact latest version makes
658
+ // pnpm bump the constraint and actually download the new package. The host
659
+ // install() also falls back to resolving @latest server-side, so a direct
660
+ // `name@latest` still works as a second guard.
661
+ var known = pView.updates && pView.updates[name]
662
+ var spec = (known && known.latest) ? (name + '@' + known.latest) : (name + '@latest')
654
663
  patchPlugin({ busy: true, error: '', confirming: null, note: '' })
655
664
  callRemote('pluginAdmin/install', { spec: spec }).then(function (result) {
656
665
  if (!alive.current) return
@@ -1878,7 +1887,7 @@ function renderPluginCard(plugin, view, remove, patch, upgrade) {
1878
1887
  key: 'btn-upgrade',
1879
1888
  className: 'btn sm',
1880
1889
  disabled: view.busy,
1881
- title: '升级到 v' + updateInfo.latest + '(npm install ' + plugin.name + '@latest)',
1890
+ title: '升级到 v' + updateInfo.latest + '(npm install ' + plugin.name + '@' + updateInfo.latest + ')',
1882
1891
  onClick: function () { upgrade(plugin.name) },
1883
1892
  }, '⬆ 更新'))
1884
1893
  }
@@ -2278,12 +2287,1529 @@ function renderSessionCard(session, view, act, patch) {
2278
2287
  return createElement('div', { key: session.id, className: 'card' }, cardChildren)
2279
2288
  }
2280
2289
 
2290
+ /* ========================================================================== */
2291
+ /* Subagent Administration (merged from dsh-plugin-subagents) */
2292
+ /* ========================================================================== */
2293
+
2294
+ /**
2295
+ * dsh-plugin-subagents browser half: the 子智能体管理 settings section.
2296
+ *
2297
+ * Renders a management panel over the shell's `settings.section` slot (same
2298
+ * platform-shared React and --dsw-* design tokens as the admin plugin) and
2299
+ * drives the host's `subagentAdmin` remote over the /api RPC gateway. One
2300
+ * managed row in the profile cordis.patch.yml equals one named subagent: a
2301
+ * `@deepseek-ai/dsh-tool-subagent` instance whose config carries the agent
2302
+ * name (toolName), the persona prompt, the tool constraint lists, and the
2303
+ * model specification (agentOptions).
2304
+ */
2305
+
2306
+ var TOOLNAME_RE = /^[a-z][a-z0-9_]{1,48}$/
2307
+ var TOOL_REF_RE = /^[a-z][a-z0-9_]*$/
2308
+ var ID_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/
2309
+ var RESERVED_TOOL_NAMES = ['subagent', 'subagent_fork', 'run_code']
2310
+
2311
+ var SA_CSS_TEXT = [
2312
+ '@keyframes dsh-sa-spin { to { transform: rotate(360deg); } }',
2313
+ '[data-dsh-sa-section] { display: flex; flex-direction: column; width: 100%; gap: 14px; padding: 2px; font-size: 13px; line-height: 1.5; color: var(--dsw-alias-label-primary, #222); overflow: visible; }',
2314
+ '[data-dsh-sa-section] * { box-sizing: border-box; }',
2315
+ '[data-dsh-sa-section] .tabs { display: flex; padding: 3px; background: var(--dsw-alias-interactive-bg-hover, rgba(200,200,210,0.2)); border-radius: 9px; border: 1px solid var(--dsw-alias-border-subtle, rgba(200,200,210,0.3)); gap: 3px; }',
2316
+ '[data-dsh-sa-section] .tab { flex: 1; border: 0; background: transparent; border-radius: 7px; padding: 7px 14px; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 7px; font-size: 12px; font-weight: 500; color: var(--dsw-alias-label-secondary, #666); transition: all 0.15s ease; font-family: inherit; }',
2317
+ '[data-dsh-sa-section] .tab:hover:not(.active) { color: var(--dsw-alias-label-primary, #222); background: var(--dsw-alias-interactive-bg-hover, rgba(200,200,210,0.3)); }',
2318
+ '[data-dsh-sa-section] .tab.active { background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, transparent)); color: var(--dsw-alias-label-primary, #333); font-weight: 600; box-shadow: 0 1px 3px rgba(0,0,0,0.08), 0 1px 2px rgba(0,0,0,0.04); }',
2319
+ '[data-dsh-sa-section] .tab-count { font-size: 10px; font-weight: 600; padding: 1px 6px; border-radius: 10px; background: var(--dsw-alias-interactive-bg-hover, rgba(200,200,210,0.4)); color: var(--dsw-alias-label-secondary, #555); }',
2320
+ '[data-dsh-sa-section] .tab.active .tab-count { background: var(--dsw-static-blue-500, #3b82f6); color: #fff; }',
2321
+ '[data-dsh-sa-section] .toolbar { display: flex; gap: 8px; align-items: center; padding: 10px; border: 1px solid var(--dsw-alias-border-subtle, rgba(200,200,210,0.4)); border-radius: 12px; background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, transparent)); box-shadow: 0 1px 2px rgba(0,0,0,0.02); flex-wrap: wrap; }',
2322
+ '[data-dsh-sa-section] .filterbar { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; padding: 8px 10px; border: 1px solid var(--dsw-alias-border-subtle, rgba(200,200,210,0.4)); border-radius: 12px; background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, transparent)); }',
2323
+ '[data-dsh-sa-section] .filter-label { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-secondary, #666); flex: none; margin-right: 2px; }',
2324
+ '[data-dsh-sa-section] .search-wrap { flex: 1; min-width: 150px; position: relative; display: flex; align-items: center; height: 32px; }',
2325
+ '[data-dsh-sa-section] .search-wrap .input { flex: 1; min-width: 0; padding-left: 30px; font-size: 13px; height: 32px; }',
2326
+ '[data-dsh-sa-section] .search-icon { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); font-size: 12px; opacity: 0.7; pointer-events: none; z-index: 1; }',
2327
+ '[data-dsh-sa-section] .input { width: 100%; padding: 7px 12px; border-radius: 9px; border: 1px solid var(--dsw-alias-border-subtle, rgba(200,200,210,0.5)); background: var(--dsw-alias-bg-base, transparent); color: inherit; font: inherit; outline: none; transition: border-color 0.15s, box-shadow 0.15s, background 0.15s; }',
2328
+ '[data-dsh-sa-section] .input:hover { border-color: var(--dsw-alias-label-tertiary, rgba(180,180,195,0.6)); }',
2329
+ '[data-dsh-sa-section] .input:focus { border-color: var(--dsw-static-blue-500, #3b82f6); box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.16); }',
2330
+ '[data-dsh-sa-section] textarea.input { resize: vertical; min-height: 80px; font-family: var(--dsw-font-mono, ui-monospace, monospace); font-size: 12px; line-height: 1.55; }',
2331
+ '[data-dsh-sa-section] select.input { height: 32px; padding: 0 8px; cursor: pointer; }',
2332
+ '[data-dsh-sa-section] .btn { display: inline-flex; align-items: center; justify-content: center; gap: 5px; height: 32px; padding: 0 14px; border-radius: 9px; font-size: 13px; font-weight: 500; cursor: pointer; font-family: inherit; transition: all 0.15s ease; border: 1px solid var(--dsw-alias-border-subtle, rgba(200,200,210,0.5)); background: var(--dsw-alias-interactive-bg-hover, rgba(240,240,245,0.6)); color: var(--dsw-alias-label-secondary, #555); white-space: nowrap; flex: none; box-shadow: 0 1px 2px rgba(0,0,0,0.04); }',
2333
+ '[data-dsh-sa-section] .btn:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(220,220,230,0.7)); border-color: var(--dsw-alias-label-tertiary, rgba(180,180,195,0.65)); color: var(--dsw-alias-label-primary, #222); }',
2334
+ '[data-dsh-sa-section] .btn:active:not(:disabled) { transform: translateY(1px); }',
2335
+ '[data-dsh-sa-section] .btn:focus-visible { outline: 2px solid var(--dsw-static-blue-500, #3b82f6); outline-offset: 2px; }',
2336
+ '[data-dsh-sa-section] .btn:disabled { opacity: 0.45; cursor: not-allowed; }',
2337
+ '[data-dsh-sa-section] .btn.primary { background: var(--dsw-static-blue-500, #3b82f6); color: #fff; border-color: transparent; font-weight: 600; box-shadow: 0 1px 3px rgba(0,0,0,0.12); }',
2338
+ '[data-dsh-sa-section] .btn.primary:hover:not(:disabled) { background: var(--dsw-static-blue-400, #60a5fa); color: #fff; }',
2339
+ '[data-dsh-sa-section] .btn.danger { color: var(--dsw-alias-state-error-primary, #dc2626); border-color: var(--dsw-alias-border-subtle, rgba(200,200,210,0.4)); }',
2340
+ '[data-dsh-sa-section] .btn.danger:hover:not(:disabled) { background: rgba(239,68,68,0.1); border-color: rgba(220,38,38,0.35); color: #b91c1c; }',
2341
+ '[data-dsh-sa-section] .btn.danger-solid { background: var(--dsw-alias-state-error-primary, #dc2626); color: #fff; border-color: transparent; }',
2342
+ '[data-dsh-sa-section] .btn.sm { height: 28px; padding: 0 10px; font-size: 12px; border-radius: 6px; }',
2343
+ '[data-dsh-sa-section] .btn.sm.active { background: var(--dsw-alias-interactive-bg-hover, rgba(200,200,210,0.4)); color: var(--dsw-static-blue-500, #3b82f6); font-weight: 600; border-color: currentColor; }',
2344
+ '[data-dsh-sa-section] .spinner { width: 12px; height: 12px; border: 2px solid transparent; border-top-color: currentColor; border-radius: 50%; animation: dsh-sa-spin 0.7s linear infinite; display: inline-block; flex: none; }',
2345
+ '[data-dsh-sa-section] .list { display: flex; flex-direction: column; gap: 10px; flex: 1 1 auto; min-height: 0; max-height: 560px; overflow-y: auto; padding: 2px 4px 2px 2px; scrollbar-width: thin; scrollbar-color: var(--dsw-alias-border-subtle, rgba(200,200,210,0.4)) transparent; }',
2346
+ '[data-dsh-sa-section] .card { display: flex; flex-direction: column; gap: 7px; padding: 12px; border-radius: 12px; border: 1px solid var(--dsw-alias-border-subtle, rgba(200,200,210,0.4)); background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, transparent)); box-shadow: 0 1px 2px rgba(0,0,0,0.02); transition: all 0.15s ease; }',
2347
+ '[data-dsh-sa-section] .card:hover { transform: translateY(-1px); border-color: var(--dsw-alias-label-tertiary, rgba(180,180,195,0.6)); box-shadow: 0 8px 20px rgba(0,0,0,0.06); }',
2348
+ '[data-dsh-sa-section] .card-header { display: flex; align-items: center; gap: 8px; min-width: 0; }',
2349
+ '[data-dsh-sa-section] .card-title { font-size: 13px; font-weight: 600; color: var(--dsw-alias-label-primary); display: flex; align-items: center; gap: 7px; min-width: 0; flex: 1; font-family: var(--dsw-font-mono, ui-monospace, monospace); }',
2350
+ '[data-dsh-sa-section] .card-actions { display: flex; gap: 5px; align-items: center; flex: none; }',
2351
+ '[data-dsh-sa-section] .persona-box { display: flex; align-items: flex-start; gap: 6px; padding: 6px 9px; border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover, rgba(200,200,210,0.25)); border-left: 2px solid var(--dsw-static-blue-500, #3b82f6); font-size: 12px; line-height: 1.45; color: var(--dsw-alias-label-secondary, #555); }',
2352
+ '[data-dsh-sa-section] .persona-text { flex: 1; min-width: 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; text-overflow: ellipsis; word-break: break-word; white-space: pre-wrap; }',
2353
+ '[data-dsh-sa-section] .card-sub { font-size: 11px; color: var(--dsw-alias-label-secondary, #666); display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }',
2354
+ '[data-dsh-sa-section] .tag { font-size: 10px; font-weight: 500; padding: 1px 6px; border-radius: 4px; flex: none; display: inline-flex; align-items: center; gap: 4px; background: var(--dsw-alias-interactive-bg-hover, rgba(200,200,210,0.25)); color: var(--dsw-alias-label-secondary, #555); }',
2355
+ '[data-dsh-sa-section] .tag.provider { background: rgba(139, 92, 246, 0.12); color: #7c3aed; }',
2356
+ '[data-dsh-sa-section] .tag.model { background: rgba(59, 130, 246, 0.12); color: var(--dsw-static-blue-500, #3b82f6); }',
2357
+ '[data-dsh-sa-section] .tag.live { background: rgba(16, 185, 129, 0.12); color: var(--dsw-alias-state-success-primary, #10b981); }',
2358
+ '[data-dsh-sa-section] .tag.warn { background: rgba(245, 158, 11, 0.14); color: #d97706; font-weight: 600; }',
2359
+ '[data-dsh-sa-section] .tag.dead { background: rgba(239, 68, 68, 0.12); color: var(--dsw-alias-state-error-primary, #dc2626); }',
2360
+ '[data-dsh-sa-section] .chips { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; }',
2361
+ '[data-dsh-sa-section] .chip { font-size: 10px; font-family: var(--dsw-font-mono, ui-monospace, monospace); padding: 1px 6px; border-radius: 4px; background: var(--dsw-alias-interactive-bg-hover, rgba(200,200,210,0.3)); border: 1px solid var(--dsw-alias-border-subtle, rgba(200,200,210,0.4)); color: var(--dsw-alias-label-secondary, #555); }',
2362
+ '[data-dsh-sa-section] .chip.deny { background: rgba(239, 68, 68, 0.08); border-color: rgba(220,38,38,0.25); color: #b91c1c; }',
2363
+ '[data-dsh-sa-section] .chip.allow { background: rgba(16, 185, 129, 0.08); border-color: rgba(16,185,129,0.3); color: #047857; }',
2364
+ '[data-dsh-sa-section] .form { flex: 1 1 auto; min-height: 0; max-height: calc(100vh - 220px); overflow-y: auto; display: flex; flex-direction: column; gap: 10px; padding: 14px; border-radius: 12px; border: 1px solid var(--dsw-static-blue-500, #3b82f6); background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, transparent)); box-shadow: 0 4px 14px rgba(59,130,246,0.08); }',
2365
+ '[data-dsh-sa-section] .form-title { font-size: 13px; font-weight: 600; color: var(--dsw-alias-label-primary); display: flex; align-items: center; gap: 6px; }',
2366
+ '[data-dsh-sa-section] .form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }',
2367
+ '[data-dsh-sa-section] .form-grid .full { grid-column: 1 / -1; }',
2368
+ '[data-dsh-sa-section] .field { display: flex; flex-direction: column; gap: 4px; min-width: 0; }',
2369
+ '[data-dsh-sa-section] .field-label { font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-secondary, #666); display: flex; align-items: center; gap: 5px; }',
2370
+ '[data-dsh-sa-section] .field-hint { font-size: 10px; color: var(--dsw-alias-label-tertiary, #999); line-height: 1.4; }',
2371
+ '[data-dsh-sa-section] .fieldset { border: 1px solid var(--dsw-alias-border-subtle, rgba(200,200,210,0.4)); border-radius: 10px; padding: 10px; display: flex; flex-direction: column; gap: 8px; }',
2372
+ '[data-dsh-sa-section] .fieldset-legend { font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-secondary, #666); padding: 0 4px; }',
2373
+ '[data-dsh-sa-section] .tag-input { display: flex; flex-wrap: wrap; gap: 4px; padding: 6px 8px; border-radius: 9px; border: 1px solid var(--dsw-alias-border-subtle, rgba(200,200,210,0.5)); background: var(--dsw-alias-bg-base, transparent); min-height: 34px; align-items: center; cursor: text; }',
2374
+ '[data-dsh-sa-section] .tag-input:focus-within { border-color: var(--dsw-static-blue-500, #3b82f6); box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.16); }',
2375
+ '[data-dsh-sa-section] .tag-input input { border: 0; outline: none; background: transparent; color: inherit; font: inherit; font-size: 12px; flex: 1; min-width: 90px; }',
2376
+ '[data-dsh-sa-section] .tag-chip { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; font-family: var(--dsw-font-mono, ui-monospace, monospace); padding: 2px 4px 2px 8px; border-radius: 5px; }',
2377
+ '[data-dsh-sa-section] .tag-chip.allow { background: rgba(16, 185, 129, 0.1); border: 1px solid rgba(16,185,129,0.3); color: #047857; }',
2378
+ '[data-dsh-sa-section] .tag-chip.deny { background: rgba(239, 68, 68, 0.08); border: 1px solid rgba(220,38,38,0.25); color: #b91c1c; }',
2379
+ '[data-dsh-sa-section] .tag-chip button { border: 0; background: transparent; cursor: pointer; color: inherit; opacity: 0.65; font-size: 11px; padding: 0 3px; line-height: 1; }',
2380
+ '[data-dsh-sa-section] .tag-chip button:hover { opacity: 1; }',
2381
+ '[data-dsh-sa-section] .checkbox-row { display: flex; align-items: center; gap: 7px; font-size: 12px; color: var(--dsw-alias-label-secondary, #555); cursor: pointer; }',
2382
+ '[data-dsh-sa-section] .checkbox-row input { accent-color: var(--dsw-static-blue-500, #3b82f6); }',
2383
+ '[data-dsh-sa-section] .env-row { display: flex; gap: 6px; align-items: center; }',
2384
+ '[data-dsh-sa-section] .env-row .env-key { flex: 2; min-width: 0; }',
2385
+ '[data-dsh-sa-section] .env-row .env-value { flex: 3; min-width: 0; }',
2386
+ '[data-dsh-sa-section] .cli-toggle { border: 0; background: transparent; cursor: pointer; color: var(--dsw-alias-label-secondary, #555); font-size: 12px; line-height: 1; padding: 3px 6px; border-radius: 6px; }',
2387
+ '[data-dsh-sa-section] .cli-toggle:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(200,200,210,0.3)); color: var(--dsw-alias-label-primary, #222); }',
2388
+ '[data-dsh-sa-section] .cli-title { font-size: 13px; font-weight: 600; color: var(--dsw-alias-label-primary, #222); display: inline-flex; align-items: center; flex: none; white-space: nowrap; font-family: var(--dsw-font-mono, ui-monospace, monospace); cursor: pointer; }',
2389
+ '[data-dsh-sa-section] .cli-tags { display: flex; flex-wrap: wrap; gap: 4px; flex: 1; min-width: 0; align-items: center; }',
2390
+ '[data-dsh-sa-section] .cli-detail { display: flex; flex-direction: column; gap: 8px; }',
2391
+ '[data-dsh-sa-section] .cli-scan-list { display: flex; flex-direction: column; gap: 6px; }',
2392
+ '[data-dsh-sa-section] .cli-scan-row { display: flex; gap: 8px; align-items: center; }',
2393
+ '[data-dsh-sa-section] .cli-scan-row .input { flex: 1; min-width: 0; }',
2394
+ '[data-dsh-sa-section] .cli-scan-hint { font-size: 12px; line-height: 1.5; color: var(--dsw-alias-label-secondary, #666); }',
2395
+ '[data-dsh-sa-section] .cli-scan-card { display: flex; gap: 8px; align-items: center; padding: 8px 10px; border: 1px solid var(--dsw-alias-border-subtle, rgba(200,200,210,0.4)); border-radius: 10px; background: var(--dsw-alias-interactive-bg-hover, rgba(200,200,210,0.12)); }',
2396
+ '[data-dsh-sa-section] .cli-scan-card .btn { margin-left: auto; flex: none; }',
2397
+ '[data-dsh-sa-section] .error-strip { display: flex; align-items: flex-start; gap: 8px; padding: 9px 12px; border-radius: 9px; border: 1px solid rgba(220,38,38,0.35); background: rgba(239,68,68,0.08); color: var(--dsw-alias-state-error-primary, #dc2626); font-size: 12px; line-height: 1.45; word-break: break-word; }',
2398
+ '[data-dsh-sa-section] .warn-strip { display: flex; flex-direction: column; gap: 4px; padding: 9px 12px; border-radius: 9px; border: 1px solid rgba(217,119,6,0.35); background: rgba(245,158,11,0.08); color: #b45309; font-size: 12px; line-height: 1.5; word-break: break-word; }',
2399
+ '[data-dsh-sa-section] .warn-strip div { display: flex; gap: 6px; align-items: flex-start; }',
2400
+ '[data-dsh-sa-section] .empty { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 34px 12px; border: 1px dashed var(--dsw-alias-border-subtle, rgba(200,200,210,0.5)); border-radius: 12px; color: var(--dsw-alias-label-tertiary, #999); font-size: 12px; text-align: center; }',
2401
+ '[data-dsh-sa-section] .empty .big { font-size: 26px; }',
2402
+ '[data-dsh-sa-section] .toast { position: fixed; top: 16px; left: 50%; transform: translateX(-50%); z-index: 10000; max-width: 520px; padding: 10px 16px; border-radius: 10px; font-size: 12px; line-height: 1.5; box-shadow: 0 8px 24px rgba(0,0,0,0.18); background: rgba(239,68,68,0.96); color: #fff; word-break: break-word; white-space: pre-line; }',
2403
+ '[data-dsh-sa-section] .history-row { display: flex; flex-direction: column; gap: 3px; padding: 9px 12px; border-radius: 9px; border: 1px solid var(--dsw-alias-border-subtle, rgba(200,200,210,0.4)); background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, transparent)); }',
2404
+ '[data-dsh-sa-section] .history-head { display: flex; align-items: center; gap: 8px; font-size: 12px; flex-wrap: wrap; }',
2405
+ '[data-dsh-sa-section] .history-time { font-family: var(--dsw-font-mono, ui-monospace, monospace); font-size: 10px; color: var(--dsw-alias-label-tertiary, #999); margin-left: auto; }',
2406
+ '[data-dsh-sa-section] .history-detail { font-size: 11px; color: var(--dsw-alias-label-tertiary, #888); font-family: var(--dsw-font-mono, ui-monospace, monospace); word-break: break-all; max-height: 60px; overflow: hidden; }',
2407
+ '[data-dsh-sa-section] .footer-note { font-size: 10px; color: var(--dsw-alias-label-tertiary, #999); line-height: 1.5; padding: 0 2px; }',
2408
+ '[data-dsh-sa-section] .sa-picker { position: relative; display: flex; flex-wrap: wrap; gap: 4px; align-items: center; }',
2409
+ '[data-dsh-sa-section] .sa-picker-input { border: 0; outline: none; background: transparent; color: inherit; font: inherit; font-size: 12px; flex: 1; min-width: 120px; padding: 2px 4px; }',
2410
+ '[data-dsh-sa-section] .sa-picker-list { width: 100%; margin-top: 4px; max-height: 220px; overflow-y: auto; background: var(--dsh-alias-bg-elevated, #fff); border: 1px solid var(--dsw-alias-border-subtle, rgba(200,200,210,0.5)); border-radius: 8px; box-shadow: 0 6px 18px rgba(0,0,0,0.14); padding: 3px; scrollbar-width: thin; }',
2411
+ '[data-dsh-sa-section] .sa-picker-opt { padding: 6px 9px; font-size: 12px; cursor: pointer; border-radius: 5px; color: var(--dsw-alias-label-primary, #222); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }',
2412
+ '[data-dsh-sa-section] .sa-picker-opt.active, [data-dsh-sa-section] .sa-picker-opt:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(59,130,246,0.14)); color: var(--dsw-static-blue-500, #3b82f6); }',
2413
+ '[data-dsh-sa-section] .sa-picker-empty { padding: 6px 9px; font-size: 11px; color: var(--dsw-alias-label-tertiary, #999); }',
2414
+ ]
2415
+
2416
+ var SA_STYLE_SEEN = false
2417
+ function injectSaStyles() {
2418
+ if (SA_STYLE_SEEN) return
2419
+ var style = document.createElement('style')
2420
+ style.setAttribute('data-dsh-sa-styles', '')
2421
+ style.textContent = SA_CSS_TEXT.join('\n')
2422
+ document.head.appendChild(style)
2423
+ SA_STYLE_SEEN = true
2424
+ }
2425
+
2426
+ /** The gateway wraps every remote result in an { ok, value | error } envelope. */
2427
+ function unwrap(result) {
2428
+ if (result && typeof result === 'object' && 'ok' in result) {
2429
+ if (result.ok) return result.value
2430
+ var detail = result.error && result.error.message ? result.error.message : String(result.error)
2431
+ throw new Error(detail)
2432
+ }
2433
+ return result
2434
+ }
2435
+
2436
+ /* ========================================================================== */
2437
+ /* Small components */
2438
+ /* ========================================================================== */
2439
+
2440
+ function Spinner() {
2441
+ return createElement('span', { className: 'spinner' })
2442
+ }
2443
+
2444
+ /** Two-click inline confirm; disarms after 3.2s. */
2445
+ function ConfirmButton(props) {
2446
+ var armed = useState(false)
2447
+ var isArmed = armed[0]
2448
+ var setArmed = armed[1]
2449
+ var timer = useRef(null)
2450
+ useEffect(function () {
2451
+ return function () { if (timer.current) clearTimeout(timer.current) }
2452
+ }, [])
2453
+ var label = isArmed ? (props.confirmLabel || '确认?') : props.label
2454
+ return createElement('button', {
2455
+ type: 'button',
2456
+ className: 'btn sm ' + (isArmed ? 'danger-solid' : 'danger'),
2457
+ disabled: props.disabled === true,
2458
+ onClick: function () {
2459
+ if (!isArmed) {
2460
+ setArmed(true)
2461
+ timer.current = setTimeout(function () { setArmed(false) }, 3200)
2462
+ return
2463
+ }
2464
+ if (timer.current) clearTimeout(timer.current)
2465
+ setArmed(false)
2466
+ props.onConfirm()
2467
+ },
2468
+ }, label)
2469
+ }
2470
+
2471
+ /* ========================================================================== */
2472
+ /* Form draft handling */
2473
+ /* ========================================================================== */
2474
+
2475
+ function emptyDraft() {
2476
+ return {
2477
+ id: '',
2478
+ toolName: '',
2479
+ provider: 'spawn',
2480
+ persona: '',
2481
+ allow: [],
2482
+ deny: [],
2483
+ agentProvider: '',
2484
+ agentModel: '',
2485
+ maxTokens: '',
2486
+ maxDepthManaged: false,
2487
+ maxDepth: '3',
2488
+ backgroundMode: 'one-shot',
2489
+ enableRunInBackground: true,
2490
+ }
2491
+ }
2492
+
2493
+ function draftFromEntry(entry) {
2494
+ var config = entry.config || {}
2495
+ return {
2496
+ id: entry.id,
2497
+ toolName: config.toolName || '',
2498
+ provider: config.provider || 'spawn',
2499
+ persona: config.persona || '',
2500
+ allow: (config.toolFilter && config.toolFilter.allow) || [],
2501
+ deny: (config.toolFilter && config.toolFilter.deny) || [],
2502
+ agentProvider: (config.agentOptions && config.agentOptions.provider) || '',
2503
+ agentModel: (config.agentOptions && config.agentOptions.model) || '',
2504
+ maxTokens: config.agentOptions && config.agentOptions.maxTokens !== undefined ? String(config.agentOptions.maxTokens) : '',
2505
+ maxDepthManaged: config.maxDepth === 'provider-managed',
2506
+ maxDepth: typeof config.maxDepth === 'number' ? String(config.maxDepth) : '3',
2507
+ backgroundMode: config.backgroundMode || 'one-shot',
2508
+ enableRunInBackground: config.enableRunInBackground !== false,
2509
+ }
2510
+ }
2511
+
2512
+ /** Client-side validation mirroring the host rules; returns an error string or null. */
2513
+ function validateDraft(draft, isCreate, otherToolNames, providerMeta, candidateNames) {
2514
+ if (isCreate && !ID_RE.test(draft.id)) return '实例 ID 只能包含字母、数字、下划线和中划线(字母或数字开头,最长 64 位)'
2515
+ if (!TOOLNAME_RE.test(draft.toolName)) return '子智能体名称必须是 2-48 位小写字母/数字/下划线且字母开头'
2516
+ if (RESERVED_TOOL_NAMES.indexOf(draft.toolName) !== -1) return '子智能体名称 "' + draft.toolName + '" 是保留名(内置预设已占用)'
2517
+ if (otherToolNames.indexOf(draft.toolName) !== -1) return '子智能体名称 "' + draft.toolName + '" 已被其他实例使用'
2518
+ var meta = providerMeta[draft.provider]
2519
+ if (!meta) return '未知的执行后端 "' + draft.provider + '"'
2520
+ if (draft.persona !== '' && meta.capabilities.persona === false) return '后端 "' + draft.provider + '" 不支持 persona(提示词)'
2521
+ if ((draft.allow.length > 0 || draft.deny.length > 0) && meta.capabilities.toolFilter === false) return '后端 "' + draft.provider + '" 不支持 toolFilter(工具约束)'
2522
+ if (!draft.maxDepthManaged) {
2523
+ if (draft.maxDepth !== '' && !/^\d+$/.test(draft.maxDepth)) return '最大委托深度必须是非负整数'
2524
+ if (draft.maxDepth !== '' && meta.capabilities.depthLimit === false) {
2525
+ return '后端 "' + draft.provider + '" 无法执行数值 maxDepth;请勾选「交由后端管理」或换后端'
2526
+ }
2527
+ }
2528
+ if (draft.maxTokens !== '' && !/^\d+$/.test(draft.maxTokens)) return 'maxTokens 必须是正整数'
2529
+ var filterNames = (draft.allow || []).concat(draft.deny || [])
2530
+ for (var fi = 0; fi < filterNames.length; fi++) {
2531
+ var tname = filterNames[fi]
2532
+ if (RESERVED_TOOL_NAMES.indexOf(tname) !== -1) return '工具 "' + tname + '" 是保留名(' + RESERVED_TOOL_NAMES.join('/') + '),不能用于工具约束'
2533
+ if (candidateNames && candidateNames.length > 0 && candidateNames.indexOf(tname) === -1) return '工具 "' + tname + '" 不在可选清单中(来自运行中工具或内置名录),保存会被拒绝'
2534
+ }
2535
+ return null
2536
+ }
2537
+
2538
+ function draftToPayload(draft) {
2539
+ var config = { provider: draft.provider, toolName: draft.toolName }
2540
+ if (draft.persona.trim() !== '') config.persona = draft.persona
2541
+ if (draft.allow.length > 0 || draft.deny.length > 0) {
2542
+ config.toolFilter = {}
2543
+ if (draft.allow.length > 0) config.toolFilter.allow = draft.allow.slice()
2544
+ if (draft.deny.length > 0) config.toolFilter.deny = draft.deny.slice()
2545
+ }
2546
+ var agentOptions = {}
2547
+ if (draft.agentProvider.trim() !== '') agentOptions.provider = draft.agentProvider.trim()
2548
+ if (draft.agentModel.trim() !== '') agentOptions.model = draft.agentModel.trim()
2549
+ if (draft.maxTokens.trim() !== '') agentOptions.maxTokens = Number(draft.maxTokens)
2550
+ if (Object.keys(agentOptions).length > 0) config.agentOptions = agentOptions
2551
+ if (draft.maxDepthManaged) config.maxDepth = 'provider-managed'
2552
+ else if (draft.maxDepth !== '') config.maxDepth = Number(draft.maxDepth)
2553
+ config.backgroundMode = draft.backgroundMode
2554
+ config.enableRunInBackground = draft.enableRunInBackground === true
2555
+ return { id: draft.id.trim(), config: config }
2556
+ }
2557
+
2558
+ /* ========================================================================== */
2559
+ /* Searchable picker (single/multi) */
2560
+ /* ========================================================================== */
2561
+
2562
+ /**
2563
+ * Filterable picker used for the model/provider selects (single) and the
2564
+ * tool-constraint chips (multi). Renders a text input plus a scrollable,
2565
+ * filtered dropdown; click or Enter selects. Single mode also commits the
2566
+ * typed text (so arbitrary values still work); multi mode appends chips and
2567
+ * hides already-picked options from the list.
2568
+ */
2569
+ function Picker(props) {
2570
+ var multi = props.multi === true
2571
+ var values = props.values || []
2572
+ var options = props.options || []
2573
+ var allowCustom = props.allowCustom !== false
2574
+ var inputRef = useRef(null)
2575
+ var textState = useState('')
2576
+ var text = textState[0]
2577
+ var setText = textState[1]
2578
+ var openState = useState(false)
2579
+ var open = openState[0]
2580
+ var setOpen = openState[1]
2581
+ var highlightState = useState(0)
2582
+ var highlight = highlightState[0]
2583
+ var setHighlight = highlightState[1]
2584
+
2585
+ var selected = {}
2586
+ for (var si = 0; si < values.length; si++) selected[values[si]] = true
2587
+
2588
+ var filtered = options.filter(function (o) {
2589
+ if (multi && selected[o.value]) return false
2590
+ var q = text.trim().toLowerCase()
2591
+ if (q === '') return true
2592
+ var label = (o.label || o.value).toLowerCase()
2593
+ return label.indexOf(q) !== -1 || o.value.toLowerCase().indexOf(q) !== -1
2594
+ })
2595
+
2596
+ function emit(next) { props.onChange(next) }
2597
+
2598
+ function pick(opt) {
2599
+ if (multi) {
2600
+ if (selected[opt.value] || !TOOL_REF_RE.test(opt.value)) return
2601
+ emit(values.concat([opt.value]))
2602
+ setText(''); setHighlight(0); setOpen(true)
2603
+ } else {
2604
+ emit([opt.value]); setText(''); setOpen(false)
2605
+ }
2606
+ }
2607
+ function addTyped(v) {
2608
+ var val = (v || '').trim()
2609
+ if (val === '') return
2610
+ if (multi && (selected[val] || !TOOL_REF_RE.test(val))) return
2611
+ emit(multi ? values.concat([val]) : [val])
2612
+ setText(''); setHighlight(0)
2613
+ }
2614
+ function onInputChange(e) {
2615
+ var v = e.target.value
2616
+ setText(v); setOpen(true); setHighlight(0)
2617
+ if (!multi) emit([v])
2618
+ }
2619
+ function onKeyDown(e) {
2620
+ var live = (inputRef.current ? inputRef.current.value : text).trim()
2621
+ if (e.key === 'ArrowDown') { e.preventDefault(); setHighlight(function (h) { return Math.min(h + 1, filtered.length - 1) }); return }
2622
+ if (e.key === 'ArrowUp') { e.preventDefault(); setHighlight(function (h) { return Math.max(h - 1, 0) }); return }
2623
+ if (e.key === 'Enter') {
2624
+ e.preventDefault()
2625
+ if (filtered.length > 0 && highlight >= 0 && highlight < filtered.length) {
2626
+ var hl = filtered[highlight]
2627
+ if (live === '' || live === hl.value || live === (hl.label || hl.value)) { pick(hl); return }
2628
+ }
2629
+ if (allowCustom && live !== '') addTyped(live)
2630
+ return
2631
+ }
2632
+ if (e.key === 'Escape') setOpen(false)
2633
+ }
2634
+
2635
+ var tokens = multi ? values.map(function (v) {
2636
+ return createElement('span', { className: 'tag-chip ' + (props.kind || 'allow'), key: 'tok-' + v },
2637
+ v,
2638
+ createElement('button', {
2639
+ type: 'button', 'aria-label': '移除 ' + v,
2640
+ onClick: function (ev) { ev.stopPropagation(); emit(values.filter(function (x) { return x !== v })) },
2641
+ }, '✕'))
2642
+ }) : null
2643
+
2644
+ var list = open
2645
+ ? createElement('div', { className: 'sa-picker-list' },
2646
+ filtered.length === 0
2647
+ ? createElement('div', { className: 'sa-picker-empty' }, allowCustom && text.trim() !== '' ? '回车添加:' + text.trim() : '无匹配')
2648
+ : filtered.map(function (o, idx) {
2649
+ return createElement('div', {
2650
+ key: o.value,
2651
+ className: 'sa-picker-opt' + (idx === highlight ? ' active' : ''),
2652
+ onMouseDown: function (ev) { ev.preventDefault(); pick(o) },
2653
+ onMouseEnter: function () { setHighlight(idx) },
2654
+ }, o.label || o.value)
2655
+ }))
2656
+ : null
2657
+
2658
+ return createElement('div', { className: 'sa-picker' + (multi ? ' tag-input' : '') },
2659
+ tokens,
2660
+ createElement('input', {
2661
+ ref: inputRef,
2662
+ className: 'sa-picker-input',
2663
+ value: multi ? text : (values[0] || ''),
2664
+ placeholder: props.placeholder || '',
2665
+ 'aria-label': props.ariaLabel || props.placeholder || '',
2666
+ onChange: onInputChange,
2667
+ onKeyDown: onKeyDown,
2668
+ onFocus: function () { setOpen(true) },
2669
+ onBlur: function () { setOpen(false) },
2670
+ }),
2671
+ list,
2672
+ )
2673
+ }
2674
+
2675
+ /* ========================================================================== */
2676
+ /* Subagents panel */
2677
+ /* ========================================================================== */
2678
+
2679
+ function SubagentsPanel(props) {
2680
+ var call = props.call
2681
+
2682
+ var viewState = useState({ loading: true, error: null, data: null })
2683
+ var view = viewState[0]
2684
+ var setView = viewState[1]
2685
+
2686
+ var formState = useState(null)
2687
+ var form = formState[0]
2688
+ var setForm = formState[1]
2689
+
2690
+ var needleState = useState('')
2691
+ var needle = needleState[0]
2692
+ var setNeedle = needleState[1]
2693
+
2694
+ var providerFilterState = useState('全部')
2695
+ var providerFilter = providerFilterState[0]
2696
+ var setProviderFilter = providerFilterState[1]
2697
+
2698
+ var noticeState = useState(null)
2699
+ var notice = noticeState[0]
2700
+ var setNotice = noticeState[1]
2701
+
2702
+ var toastState = useState(null)
2703
+ var toast = toastState[0]
2704
+ var setToast = toastState[1]
2705
+
2706
+ var reload = function () {
2707
+ setView(function (prev) { return Object.assign({}, prev, { loading: true, error: null }) })
2708
+ call('subagentAdmin/list', {}).then(function (raw) {
2709
+ var result = unwrap(raw)
2710
+ setView({ loading: false, error: null, data: result })
2711
+ }).catch(function (error) {
2712
+ setView({ loading: false, error: String((error && error.message) || error), data: null })
2713
+ })
2714
+ }
2715
+ useEffect(reload, [])
2716
+
2717
+ useEffect(function () {
2718
+ if (!toast) return undefined
2719
+ var timer = setTimeout(function () { setToast(null) }, 4200)
2720
+ return function () { clearTimeout(timer) }
2721
+ }, [toast])
2722
+
2723
+ var data = view.data
2724
+ var entries = (data && data.entries) || []
2725
+ var meta = (data && data.meta) || { tools: [], providers: [], llmProviders: [], llmModels: {} }
2726
+ var candidateNames = meta.tools.map(function (tool) { return tool.name })
2727
+ var providerMeta = {}
2728
+ meta.providers.forEach(function (provider) { providerMeta[provider.name] = provider })
2729
+ var llmProviders = meta.llmProviders || []
2730
+ var llmModels = meta.llmModels || {}
2731
+
2732
+ var filtered = entries.filter(function (entry) {
2733
+ var config = entry.config || {}
2734
+ if (providerFilter !== '全部' && config.provider !== providerFilter) return false
2735
+ if (needle === '') return true
2736
+ var haystack = [entry.id, config.toolName, config.provider, config.persona, config.agentOptions && config.agentOptions.model]
2737
+ .filter(Boolean).join(' ').toLowerCase()
2738
+ return haystack.indexOf(needle.toLowerCase()) !== -1
2739
+ })
2740
+
2741
+ var openCreate = function () {
2742
+ setNotice(null)
2743
+ setForm({ draft: emptyDraft(), editing: false, saving: false, error: null })
2744
+ }
2745
+ var openEdit = function (entry) {
2746
+ setNotice(null)
2747
+ setForm({ draft: draftFromEntry(entry), editing: true, saving: false, error: null })
2748
+ }
2749
+ var closeForm = function () { setForm(null) }
2750
+
2751
+ var saveForm = function () {
2752
+ if (!form || form.saving) return
2753
+ var otherToolNames = entries
2754
+ .filter(function (entry) { return !form.editing || entry.id !== form.draft.id })
2755
+ .map(function (entry) { return (entry.config || {}).toolName })
2756
+ .filter(Boolean)
2757
+ var clientError = validateDraft(form.draft, !form.editing, otherToolNames, providerMeta, candidateNames)
2758
+ if (clientError) {
2759
+ setForm(Object.assign({}, form, { error: clientError }))
2760
+ return
2761
+ }
2762
+ var saving = Object.assign({}, form, { saving: true, error: null })
2763
+ setForm(saving)
2764
+ var payload = draftToPayload(saving.draft)
2765
+ call('subagentAdmin/upsert', { entry: payload }).then(function (raw) {
2766
+ var result = unwrap(raw)
2767
+ setView(function (prev) { return Object.assign({}, prev, { data: result, loading: false }) })
2768
+ setForm(null)
2769
+ setNotice(result.warnings && result.warnings.length > 0 ? result.warnings : null)
2770
+ }).catch(function (error) {
2771
+ var message = String((error && error.message) || error)
2772
+ setForm(Object.assign({}, saving, { saving: false, error: message }))
2773
+ setToast('保存失败:' + message)
2774
+ })
2775
+ }
2776
+
2777
+ var removeEntry = function (entry) {
2778
+ call('subagentAdmin/remove', { id: entry.id }).then(function (raw) {
2779
+ var result = unwrap(raw)
2780
+ setView(function (prev) { return Object.assign({}, prev, { data: result, loading: false }) })
2781
+ setNotice(null)
2782
+ }).catch(function (error) {
2783
+ setToast('删除失败:' + String((error && error.message) || error))
2784
+ })
2785
+ }
2786
+
2787
+ var patchDraft = function (patch) {
2788
+ setForm(function (prev) {
2789
+ return Object.assign({}, prev, { draft: Object.assign({}, prev.draft, patch) })
2790
+ })
2791
+ }
2792
+
2793
+ var children = []
2794
+
2795
+ // Main toolbar: search + primary actions. Provider filters live on their own
2796
+ // row below so a growing backend list never squeezes the search box.
2797
+ children.push(createElement('div', { className: 'toolbar', key: 'toolbar' },
2798
+ createElement('div', { className: 'search-wrap', key: 'search' },
2799
+ createElement('span', { className: 'search-icon' }, '🔍'),
2800
+ createElement('input', {
2801
+ className: 'input',
2802
+ placeholder: '搜索子智能体(名称/ID/后端/提示词/模型)...',
2803
+ value: needle,
2804
+ onChange: function (event) { setNeedle(event.target.value) },
2805
+ })
2806
+ ),
2807
+ createElement('button', { type: 'button', className: 'btn sm primary', key: 'create', onClick: openCreate }, '+ 新建子智能体'),
2808
+ createElement('button', { type: 'button', className: 'btn sm', key: 'refresh', onClick: reload, disabled: view.loading },
2809
+ view.loading ? createElement(Spinner, { key: 'spin' }) : '↻', '刷新')
2810
+ ))
2811
+
2812
+ if (meta.providers.length > 1) {
2813
+ children.push(createElement('div', { className: 'filterbar', key: 'filterbar' },
2814
+ createElement('span', { className: 'filter-label' }, '执行后端'),
2815
+ createElement('button', {
2816
+ type: 'button', key: 'pill-all',
2817
+ className: 'btn sm' + (providerFilter === '全部' ? ' active' : ''),
2818
+ onClick: function () { setProviderFilter('全部') },
2819
+ }, '全部'),
2820
+ meta.providers.map(function (provider) {
2821
+ return createElement('button', {
2822
+ type: 'button', key: 'pill-' + provider.name,
2823
+ className: 'btn sm' + (providerFilter === provider.name ? ' active' : ''),
2824
+ onClick: function () { setProviderFilter(providerFilter === provider.name ? '全部' : provider.name) },
2825
+ }, provider.name)
2826
+ })
2827
+ ))
2828
+ }
2829
+
2830
+ if (form) {
2831
+ children.push(SubagentForm({
2832
+ form: form,
2833
+ meta: meta,
2834
+ candidateNames: candidateNames,
2835
+ providerMeta: providerMeta,
2836
+ llmProviders: llmProviders,
2837
+ llmModels: llmModels,
2838
+ entries: entries,
2839
+ onPatch: patchDraft,
2840
+ onClose: closeForm,
2841
+ onSave: saveForm,
2842
+ }))
2843
+ }
2844
+
2845
+ if (notice) {
2846
+ children.push(createElement('div', { className: 'warn-strip', key: 'notice' },
2847
+ notice.map(function (warning, index) {
2848
+ return createElement('div', { key: index }, '⚠️ ', warning)
2849
+ })
2850
+ ))
2851
+ }
2852
+
2853
+ if (!form) {
2854
+ if (view.loading && entries.length === 0) {
2855
+ children.push(createElement('div', { className: 'empty', key: 'loading' },
2856
+ createElement('span', { className: 'big' }, '⏳'),
2857
+ '正在加载子智能体配置…'))
2858
+ } else if (view.error) {
2859
+ children.push(createElement('div', { className: 'error-strip', key: 'error' }, '⚠️ 加载失败:', view.error))
2860
+ } else if (filtered.length === 0) {
2861
+ children.push(createElement('div', { className: 'empty', key: 'empty' },
2862
+ createElement('span', { className: 'big' }, '🤖'),
2863
+ entries.length === 0
2864
+ ? '还没有受管子智能体。点击「+ 新建子智能体」创建第一个:名称、提示词、工具约束、模型指定全部可配。'
2865
+ : '没有匹配当前搜索/筛选的子智能体。'))
2866
+ } else {
2867
+ children.push(createElement('div', { className: 'list', key: 'cards' }, filtered.map(function (entry) {
2868
+ return SubagentCard({
2869
+ entry: entry,
2870
+ onEdit: openEdit,
2871
+ onRemove: removeEntry,
2872
+ })
2873
+ })))
2874
+ }
2875
+ }
2876
+
2877
+ children.push(createElement('div', { className: 'footer-note', key: 'note' },
2878
+ '每张卡片对应 profile cordis.patch.yml 中一个 @deepseek-ai/dsh-tool-subagent 行;保存即写入该文件,由 Cordis HMR 热加载生效(无需重启),重启 dsh 后同样自动加载。首次修改前原文件自动备份为 cordis.patch.yml.bak-subagent-admin。'))
2879
+
2880
+ return createElement('div', null,
2881
+ toast ? createElement('div', { className: 'toast' }, toast) : null,
2882
+ children
2883
+ )
2884
+ }
2885
+
2886
+ function SubagentForm(props) {
2887
+ var form = props.form
2888
+ var draft = form.draft
2889
+ var meta = props.meta
2890
+ var candidateNames = props.candidateNames
2891
+ var providerMeta = props.providerMeta
2892
+ var llmProviders = props.llmProviders || []
2893
+ var llmModels = props.llmModels || {}
2894
+ var entries = props.entries
2895
+ var onPatch = props.onPatch
2896
+ var onClose = props.onClose
2897
+ var onSave = props.onSave
2898
+
2899
+ var providerInfo = providerMeta[draft.provider]
2900
+ var capabilityHint = providerInfo
2901
+ ? '能力:' + [
2902
+ providerInfo.capabilities.persona ? '✓ 提示词' : '✗ 提示词',
2903
+ providerInfo.capabilities.toolFilter ? '✓ 工具约束' : '✗ 工具约束',
2904
+ providerInfo.capabilities.depthLimit ? '✓ 深度上限' : '✗ 深度上限',
2905
+ providerInfo.continuable ? '✓ 可持续会话' : '仅一次性',
2906
+ ].join(' / ')
2907
+ : ''
2908
+ // Model picker options: the selected provider's catalog, else every model.
2909
+ var modelOptions = draft.agentProvider && llmModels[draft.agentProvider]
2910
+ ? llmModels[draft.agentProvider]
2911
+ : [].concat.apply([], Object.keys(llmModels).map(function (k) { return llmModels[k] }))
2912
+
2913
+ return createElement('div', { className: 'form', key: 'form' },
2914
+ createElement('div', { className: 'form-title' },
2915
+ form.editing ? '✏️ 编辑子智能体' : '✨ 新建子智能体',
2916
+ createElement('span', { style: { marginLeft: 'auto', display: 'flex', gap: '6px' } },
2917
+ createElement('button', { type: 'button', className: 'btn sm', onClick: onClose, disabled: form.saving }, '取消'),
2918
+ createElement('button', { type: 'button', className: 'btn sm primary', onClick: onSave, disabled: form.saving },
2919
+ form.saving ? createElement(Spinner, { key: 'spin' }) : null,
2920
+ '保存')
2921
+ )
2922
+ ),
2923
+ form.error ? createElement('div', { className: 'error-strip' }, '⚠️ ', form.error) : null,
2924
+ createElement('div', { className: 'form-grid' },
2925
+ createElement('div', { className: 'field' },
2926
+ createElement('span', { className: 'field-label' }, '实例 ID'),
2927
+ createElement('input', {
2928
+ className: 'input', value: draft.id, disabled: form.editing || form.saving,
2929
+ placeholder: '如 researcher、code-reviewer',
2930
+ onChange: function (event) { onPatch({ id: event.target.value }) },
2931
+ }),
2932
+ createElement('span', { className: 'field-hint' }, '补丁行标识,创建后不可改;持久化在 profile 的 cordis.patch.yml')
2933
+ ),
2934
+ createElement('div', { className: 'field' },
2935
+ createElement('span', { className: 'field-label' }, '子智能体名称(模型可见工具名)'),
2936
+ createElement('input', {
2937
+ className: 'input', value: draft.toolName, disabled: form.saving,
2938
+ placeholder: '如 web_researcher(模型用它发起委托)',
2939
+ onChange: function (event) { onPatch({ toolName: event.target.value }) },
2940
+ }),
2941
+ createElement('span', { className: 'field-hint' }, '不能用保留名 subagent / subagent_fork / run_code,且各实例间唯一')
2942
+ ),
2943
+ createElement('div', { className: 'field' },
2944
+ createElement('span', { className: 'field-label' }, '执行后端(provider)'),
2945
+ createElement('select', {
2946
+ className: 'input', value: draft.provider, disabled: form.saving,
2947
+ onChange: function (event) { onPatch({ provider: event.target.value }) },
2948
+ },
2949
+ meta.providers.map(function (provider) {
2950
+ return createElement('option', { key: provider.name, value: provider.name }, provider.name)
2951
+ })
2952
+ ),
2953
+ createElement('span', { className: 'field-hint' }, capabilityHint)
2954
+ ),
2955
+ createElement('div', { className: 'field' },
2956
+ createElement('span', { className: 'field-label' }, '后台模式'),
2957
+ createElement('select', {
2958
+ className: 'input', value: draft.backgroundMode, disabled: form.saving,
2959
+ onChange: function (event) { onPatch({ backgroundMode: event.target.value }) },
2960
+ },
2961
+ createElement('option', { value: 'one-shot' }, 'one-shot(一次性任务)'),
2962
+ createElement('option', { value: 'continuable' }, 'continuable(可持续会话)')
2963
+ ),
2964
+ createElement('label', { className: 'checkbox-row' },
2965
+ createElement('input', {
2966
+ type: 'checkbox', checked: draft.enableRunInBackground === true, disabled: form.saving,
2967
+ onChange: function (event) { onPatch({ enableRunInBackground: event.target.checked }) },
2968
+ }),
2969
+ '暴露 run_in_background 参数'
2970
+ )
2971
+ ),
2972
+ createElement('div', { className: 'field full' },
2973
+ createElement('span', { className: 'field-label' }, '提示词(persona,留空继承部署默认)'),
2974
+ createElement('textarea', {
2975
+ className: 'input', value: draft.persona, rows: 4, disabled: form.saving,
2976
+ placeholder: '该子智能体的人设/职责说明…支持 {{model}} 与 {{cwd}} 模板变量',
2977
+ onChange: function (event) { onPatch({ persona: event.target.value }) },
2978
+ }),
2979
+ createElement('span', { className: 'field-hint' }, '保存后影子覆盖(shadow)部署级 persona,仅对该子智能体生效')
2980
+ ),
2981
+ createElement('div', { className: 'fieldset full' },
2982
+ createElement('span', { className: 'fieldset-legend' }, '工具约束(toolFilter,留空则不限制;可选项来自 harness 已注册工具与内置工具名录,也可手填未知名)'),
2983
+ createElement('div', { className: 'field' },
2984
+ createElement('span', { className: 'field-label' }, '仅允许(allow 白名单)'),
2985
+ createElement(Picker, {
2986
+ multi: true, kind: 'allow', allowCustom: true,
2987
+ values: draft.allow, options: candidateNames.map(function (n) { return { value: n, label: n } }),
2988
+ placeholder: '输入或选择工具名,如 read / glob / grep', ariaLabel: '仅允许工具',
2989
+ onChange: function (next) { onPatch({ allow: next }) },
2990
+ }),
2991
+ createElement('span', { className: 'field-hint' }, '设置后子智能体只保留名单内工具,其余从提示词移除且拒绝执行')
2992
+ ),
2993
+ createElement('div', { className: 'field' },
2994
+ createElement('span', { className: 'field-label' }, '禁止(deny 黑名单)'),
2995
+ createElement(Picker, {
2996
+ multi: true, kind: 'deny', allowCustom: true,
2997
+ values: draft.deny, options: candidateNames.map(function (n) { return { value: n, label: n } }),
2998
+ placeholder: '输入或选择工具名,如 bash / pwsh', ariaLabel: '禁止工具',
2999
+ onChange: function (next) { onPatch({ deny: next }) },
3000
+ }),
3001
+ createElement('span', { className: 'field-hint' }, '名单内工具对子智能体不可见;未知工具名保存时会被拒绝')
3002
+ )
3003
+ ),
3004
+ createElement('div', { className: 'fieldset full' },
3005
+ createElement('span', { className: 'fieldset-legend' }, '模型指定(agentOptions,留空字段继承父代理当前路由)'),
3006
+ createElement('div', { className: 'form-grid' },
3007
+ createElement('div', { className: 'field' },
3008
+ createElement('span', { className: 'field-label' }, 'LLM provider'),
3009
+ createElement(Picker, {
3010
+ multi: false, allowCustom: true,
3011
+ values: draft.agentProvider ? [draft.agentProvider] : [],
3012
+ options: llmProviders.map(function (p) { return { value: p.id, label: p.name } }),
3013
+ placeholder: '留空继承,如 optirouter / deepseek-official', ariaLabel: 'LLM provider',
3014
+ onChange: function (next) { onPatch({ agentProvider: next[0] || '' }) },
3015
+ })
3016
+ ),
3017
+ createElement('div', { className: 'field' },
3018
+ createElement('span', { className: 'field-label' }, '模型标识(model)'),
3019
+ createElement(Picker, {
3020
+ multi: false, allowCustom: true,
3021
+ values: draft.agentModel ? [draft.agentModel] : [],
3022
+ options: modelOptions.map(function (m) { return { value: m.id, label: m.name } }),
3023
+ placeholder: '留空继承,如 auto', ariaLabel: '模型标识',
3024
+ onChange: function (next) { onPatch({ agentModel: next[0] || '' }) },
3025
+ }),
3026
+ createElement('span', { className: 'field-hint' }, '从已配置模型中选择,或手填模型 id(需在该 provider 路由上注册)')
3027
+ ),
3028
+ createElement('div', { className: 'field' },
3029
+ createElement('span', { className: 'field-label' }, 'maxTokens(单次回复上限)'),
3030
+ createElement('input', {
3031
+ className: 'input', value: draft.maxTokens, disabled: form.saving,
3032
+ placeholder: '留空使用默认', inputMode: 'numeric',
3033
+ onChange: function (event) { onPatch({ maxTokens: event.target.value.replace(/[^0-9]/g, '') }) },
3034
+ })
3035
+ ),
3036
+ createElement('div', { className: 'field' },
3037
+ createElement('span', { className: 'field-label' }, '最大委托深度(maxDepth,0 = 禁止再委托)'),
3038
+ createElement('div', { style: { display: 'flex', gap: '8px', alignItems: 'center' } },
3039
+ createElement('input', {
3040
+ className: 'input', value: draft.maxDepthManaged ? '' : draft.maxDepth,
3041
+ disabled: form.saving || draft.maxDepthManaged,
3042
+ placeholder: '3(默认)', inputMode: 'numeric',
3043
+ onChange: function (event) { onPatch({ maxDepth: event.target.value.replace(/[^0-9]/g, '') }) },
3044
+ }),
3045
+ createElement('label', { className: 'checkbox-row', style: { flex: 'none' } },
3046
+ createElement('input', {
3047
+ type: 'checkbox', checked: draft.maxDepthManaged, disabled: form.saving,
3048
+ onChange: function (event) { onPatch({ maxDepthManaged: event.target.checked }) },
3049
+ }),
3050
+ '交由后端管理'
3051
+ )
3052
+ )
3053
+ )
3054
+ )
3055
+ )
3056
+ )
3057
+ )
3058
+ }
3059
+
3060
+ function SubagentCard(props) {
3061
+ var entry = props.entry
3062
+ var config = entry.config || {}
3063
+ var live = entry.live || {}
3064
+ var liveTag = live.providerPresent === false
3065
+ ? createElement('span', { className: 'tag dead' }, '❌ 后端未注册')
3066
+ : live.toolRegistered
3067
+ ? createElement('span', { className: 'tag live' }, '✅ 已挂载')
3068
+ : createElement('span', { className: 'tag warn' }, '⚠️ 工具未挂载')
3069
+ var hasModelRoute = config.agentOptions && (config.agentOptions.model || config.agentOptions.provider)
3070
+ var modelLabel = hasModelRoute
3071
+ ? ((config.agentOptions.model || '') + (config.agentOptions.provider ? (config.agentOptions.model ? ' @ ' : '') + config.agentOptions.provider : ''))
3072
+ : '继承父代理'
3073
+
3074
+ var children = [
3075
+ createElement('div', { className: 'card-header', key: 'head' },
3076
+ createElement('span', { className: 'card-title' }, '🤖 ', config.toolName || entry.id),
3077
+ createElement('span', { className: 'card-actions' },
3078
+ createElement('button', { type: 'button', className: 'btn sm', onClick: function () { props.onEdit(entry) } }, '编辑'),
3079
+ createElement(ConfirmButton, {
3080
+ label: '删除', confirmLabel: '确认删除?',
3081
+ onConfirm: function () { props.onRemove(entry) },
3082
+ })
3083
+ )
3084
+ ),
3085
+ createElement('div', { className: 'card-sub', key: 'tags' },
3086
+ createElement('span', { className: 'tag provider' }, '⚙ ' + (config.provider || '?')),
3087
+ createElement('span', { className: 'tag model' }, '🧠 ' + modelLabel),
3088
+ createElement('span', { className: 'tag' }, config.backgroundMode === 'continuable' ? '可持续' : '一次性'),
3089
+ typeof config.maxDepth === 'number' ? createElement('span', { className: 'tag' }, '深度≤' + config.maxDepth) : null,
3090
+ config.maxDepth === 'provider-managed' ? createElement('span', { className: 'tag' }, '深度=后端管理') : null,
3091
+ liveTag
3092
+ ),
3093
+ ]
3094
+
3095
+ if (config.persona) {
3096
+ children.push(createElement('div', { className: 'persona-box', key: 'persona' },
3097
+ createElement('span', null, '📝'),
3098
+ createElement('span', { className: 'persona-text' }, config.persona)
3099
+ ))
3100
+ }
3101
+
3102
+ var chips = []
3103
+ if (config.toolFilter && config.toolFilter.allow && config.toolFilter.allow.length > 0) {
3104
+ chips.push(createElement('span', { key: 'allow-label', style: { fontSize: '10px', color: '#047857', fontWeight: 600 } }, '仅允许'))
3105
+ config.toolFilter.allow.forEach(function (name) {
3106
+ chips.push(createElement('span', { key: 'a-' + name, className: 'chip allow' }, name))
3107
+ })
3108
+ }
3109
+ if (config.toolFilter && config.toolFilter.deny && config.toolFilter.deny.length > 0) {
3110
+ chips.push(createElement('span', { key: 'deny-label', style: { fontSize: '10px', color: '#b91c1c', fontWeight: 600 } }, '禁止'))
3111
+ config.toolFilter.deny.forEach(function (name) {
3112
+ chips.push(createElement('span', { key: 'd-' + name, className: 'chip deny' }, name))
3113
+ })
3114
+ }
3115
+ if (chips.length > 0) {
3116
+ children.push(createElement('div', { className: 'chips', key: 'chips' }, chips))
3117
+ }
3118
+
3119
+ children.push(createElement('div', { className: 'card-sub', key: 'meta' },
3120
+ createElement('span', { className: 'tag', title: entry.id }, 'ID: ' + entry.id),
3121
+ config.toolFilter === undefined ? createElement('span', { className: 'tag' }, '工具不限制') : null
3122
+ ))
3123
+
3124
+ return createElement('div', { className: 'card', key: entry.id }, children)
3125
+ }
3126
+
3127
+ /* ========================================================================== */
3128
+ /* History panel */
3129
+ /* ========================================================================== */
3130
+
3131
+ function HistoryPanel(props) {
3132
+ var call = props.call
3133
+ var state = useState({ loading: true, error: null, data: null })
3134
+ var view = state[0]
3135
+ var setView = state[1]
3136
+
3137
+ var reload = function () {
3138
+ setView(function (prev) { return Object.assign({}, prev, { loading: true, error: null }) })
3139
+ call('subagentAdmin/history', { limit: 200 }).then(function (raw) {
3140
+ setView({ loading: false, error: null, data: unwrap(raw) })
3141
+ }).catch(function (error) {
3142
+ setView({ loading: false, error: String((error && error.message) || error), data: null })
3143
+ })
3144
+ }
3145
+ useEffect(reload, [])
3146
+
3147
+ var actionLabels = { create: '新建', update: '更新', delete: '删除', corrupt: '损坏记录', mount: '挂载', unmount: '卸载', 'cli-update': 'CLI 配置' }
3148
+ var children = [
3149
+ createElement('div', { className: 'toolbar', key: 'bar' },
3150
+ createElement('span', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-secondary, #666)', flex: 1 } },
3151
+ '配置台账:每次新建/更新/删除都追加一条记录(JSONL),文件路径见下方。'),
3152
+ createElement('button', { type: 'button', className: 'btn sm', onClick: reload, disabled: view.loading },
3153
+ view.loading ? createElement(Spinner, { key: 'spin' }) : '↻', '刷新')
3154
+ ),
3155
+ ]
3156
+
3157
+ if (view.error) {
3158
+ children.push(createElement('div', { className: 'error-strip', key: 'err' }, '⚠️ 加载失败:', view.error))
3159
+ } else if (view.data && view.data.records && view.data.records.length > 0) {
3160
+ children.push(createElement('div', { className: 'footer-note', key: 'path' }, '台账文件:' + view.data.path))
3161
+ children.push(createElement('div', { className: 'list', key: 'rows' }, view.data.records.map(function (record, index) {
3162
+ return createElement('div', { className: 'history-row', key: index },
3163
+ createElement('div', { className: 'history-head' },
3164
+ createElement('span', {
3165
+ className: 'tag ' + (record.action === 'delete' || record.action === 'unmount' ? 'dead'
3166
+ : record.action === 'create' || record.action === 'mount' ? 'live' : 'warn'),
3167
+ }, actionLabels[record.action] || record.action),
3168
+ createElement('span', { style: { fontWeight: 600 } }, record.id || '—'),
3169
+ record.toolName ? createElement('span', { className: 'tag' }, record.toolName) : null,
3170
+ createElement('span', { className: 'history-time' }, record.at || '')
3171
+ ),
3172
+ record.entry ? createElement('div', { className: 'history-detail' }, JSON.stringify(record.entry.config || record.entry)) : null
3173
+ )
3174
+ })))
3175
+ } else if (!view.loading) {
3176
+ children.push(createElement('div', { className: 'empty', key: 'empty' },
3177
+ createElement('span', { className: 'big' }, '📜'),
3178
+ '还没有变更记录。每次新建、编辑或删除子智能体后都会在这里留痕。'))
3179
+ }
3180
+
3181
+ return createElement('div', null, children)
3182
+ }
3183
+
3184
+ /* ========================================================================== */
3185
+ /* CLI backends */
3186
+ /* ========================================================================== */
3187
+
3188
+ /** Draft shape for one CLI backend's editable config. */
3189
+ function cliDraftFromConfig(config) {
3190
+ config = config || {}
3191
+ return {
3192
+ providerName: config.providerName !== undefined ? String(config.providerName) : '',
3193
+ permissionMode: config.permissionMode !== undefined ? String(config.permissionMode) : '',
3194
+ disposeGraceMs: config.disposeGraceMs !== undefined ? String(config.disposeGraceMs) : '3000',
3195
+ envPairs: Object.keys(config.env || {}).map(function (key) {
3196
+ return { key: key, value: String(config.env[key]) }
3197
+ }),
3198
+ }
3199
+ }
3200
+
3201
+ /** Convert a draft back into the wire config object. */
3202
+ function cliConfigFromDraft(draft) {
3203
+ var config = {
3204
+ providerName: draft.providerName.trim(),
3205
+ permissionMode: draft.permissionMode,
3206
+ disposeGraceMs: Number(draft.disposeGraceMs.trim()),
3207
+ env: {},
3208
+ }
3209
+ draft.envPairs.forEach(function (pair) {
3210
+ var key = pair.key.trim()
3211
+ if (key !== '') config.env[key] = pair.value
3212
+ })
3213
+ return config
3214
+ }
3215
+
3216
+ /** Shared env key/value pair editor (used by every CLI backend card). */
3217
+ function EnvPairsEditor(props) {
3218
+ var pairs = props.pairs || []
3219
+ return createElement('div', { className: 'fieldset' },
3220
+ createElement('span', { className: 'fieldset-legend' }, 'env(传给 CLI 子进程的额外环境变量)'),
3221
+ pairs.map(function (pair, index) {
3222
+ return createElement('div', { className: 'env-row', key: index },
3223
+ createElement('input', {
3224
+ className: 'input env-key', value: pair.key,
3225
+ placeholder: '变量名(如 OPENAI_API_KEY)',
3226
+ onChange: function (event) { props.onPatchPair(index, { key: event.target.value }) },
3227
+ }),
3228
+ createElement('input', {
3229
+ className: 'input env-value', value: pair.value,
3230
+ placeholder: '变量值',
3231
+ onChange: function (event) { props.onPatchPair(index, { value: event.target.value }) },
3232
+ }),
3233
+ createElement('button', { type: 'button', className: 'btn sm', onClick: function () { props.onRemove(index) } }, '✕'),
3234
+ )
3235
+ }),
3236
+ createElement('button', { type: 'button', className: 'btn sm', onClick: props.onAdd }, '+ 添加变量'),
3237
+ )
3238
+ }
3239
+
3240
+ /** One CLI backend card: collapsible status header + config form + mount/save/unmount. */
3241
+ function CliBackendCard(props) {
3242
+ var backend = props.backend
3243
+ var draft = props.draft
3244
+ var busy = props.busy === true
3245
+ var expanded = props.expanded === true
3246
+ var onPatch = props.onPatch
3247
+ var onPatchEnvPair = props.onPatchEnvPair
3248
+ var onAddEnvPair = props.onAddEnvPair
3249
+ var onRemoveEnvPair = props.onRemoveEnvPair
3250
+ var onToggle = props.onToggle
3251
+ var onSave = props.onSave
3252
+ var onUnmount = props.onUnmount
3253
+
3254
+ var packageOk = !!(backend.providerPackage && backend.providerPackage.ok)
3255
+ var runner = backend.runner || { ok: false, version: null }
3256
+ var cli = backend.cli || { ok: false, version: null }
3257
+ var modeValue = backend.permissionModes.indexOf(draft.permissionMode) !== -1
3258
+ ? draft.permissionMode
3259
+ : backend.permissionModes[0]
3260
+
3261
+ var onToggle = props.onToggle
3262
+ var onSave = props.onSave
3263
+ var onInstall = props.onInstall
3264
+ var onUnmount = props.onUnmount
3265
+
3266
+ var packageOk = !!(backend.providerPackage && backend.providerPackage.ok)
3267
+ var runner = backend.runner || { ok: false, version: null }
3268
+ var cli = backend.cli || { ok: false, version: null }
3269
+ var missingPackages = backend.missing || []
3270
+ var depsMissing = missingPackages.length > 0
3271
+ var missingLabel = missingPackages.join(' + ')
3272
+ var modeValue = backend.permissionModes.indexOf(draft.permissionMode) !== -1
3273
+ ? draft.permissionMode
3274
+ : backend.permissionModes[0]
3275
+
3276
+ return createElement('div', { className: 'card', key: backend.id },
3277
+ createElement('div', { className: 'card-header' },
3278
+ createElement('button', {
3279
+ type: 'button', className: 'cli-toggle',
3280
+ 'aria-expanded': expanded,
3281
+ 'aria-label': expanded ? '收起明细' : '展开明细',
3282
+ title: expanded ? '收起明细' : '展开明细',
3283
+ onClick: onToggle,
3284
+ }, expanded ? '▾' : '▸'),
3285
+ createElement('span', { className: 'cli-title', onClick: onToggle }, '🔌 ' + backend.label),
3286
+ createElement('span', { className: 'cli-tags' },
3287
+ createElement('span', { className: 'tag ' + (backend.mounted ? 'live' : '') }, backend.mounted ? '已挂载' : '未挂载'),
3288
+ createElement('span', { className: 'tag ' + (packageOk ? 'live' : 'dead') },
3289
+ packageOk
3290
+ ? 'provider 包' + (backend.providerPackage.version ? ' v' + backend.providerPackage.version : ' ✓')
3291
+ : 'provider 包 ✗'),
3292
+ createElement('span', { className: 'tag ' + (runner.ok ? 'live' : 'warn') },
3293
+ runner.ok ? 'CLI 依赖 ✓' : 'CLI 依赖 ✗'),
3294
+ createElement('span', { className: 'tag ' + (cli.ok ? 'model' : 'warn') },
3295
+ cli.ok ? 'PATH ' + cli.version : 'PATH ✗ ' + backend.cliCommand),
3296
+ ),
3297
+ createElement('span', { style: { display: 'flex', gap: '5px', flex: 'none' } },
3298
+ depsMissing
3299
+ ? createElement('button', {
3300
+ type: 'button', className: 'btn sm',
3301
+ disabled: busy,
3302
+ title: 'npm install -g 全局安装缺失包:' + missingLabel,
3303
+ onClick: onInstall,
3304
+ }, busy ? createElement(Spinner, { key: 'spin' }) : null, busy ? '安装中…' : '安装依赖包')
3305
+ : null,
3306
+ backend.mounted
3307
+ ? createElement('button', { type: 'button', className: 'btn sm primary', disabled: busy, onClick: onSave },
3308
+ busy ? createElement(Spinner, { key: 'spin' }) : null, '保存配置')
3309
+ : packageOk
3310
+ ? createElement('button', { type: 'button', className: 'btn sm primary', disabled: busy, onClick: onSave },
3311
+ busy ? createElement(Spinner, { key: 'spin' }) : null, '挂载')
3312
+ : null,
3313
+ backend.mounted
3314
+ ? createElement(ConfirmButton, { label: '卸载', confirmLabel: '确认卸载?', disabled: busy, onConfirm: onUnmount })
3315
+ : null,
3316
+ )
3317
+ ),
3318
+ expanded ? createElement('div', { className: 'cli-detail', key: 'detail' },
3319
+ createElement('div', { className: 'form-grid' },
3320
+ createElement('div', { className: 'field' },
3321
+ createElement('span', { className: 'field-label' }, 'providerName(执行后端注册名)'),
3322
+ createElement('input', {
3323
+ className: 'input', value: draft.providerName,
3324
+ placeholder: '如 codex / claude-code(子智能体表单的执行后端选项)',
3325
+ onChange: function (event) { onPatch({ providerName: event.target.value }) },
3326
+ }),
3327
+ ),
3328
+ createElement('div', { className: 'field' },
3329
+ createElement('span', { className: 'field-label' }, 'permissionMode(CLI 权限模式)'),
3330
+ createElement('select', {
3331
+ className: 'input', value: modeValue,
3332
+ onChange: function (event) { onPatch({ permissionMode: event.target.value }) },
3333
+ }, backend.permissionModes.map(function (mode) {
3334
+ return createElement('option', { key: mode, value: mode }, mode)
3335
+ })),
3336
+ createElement('span', { className: 'field-hint' }, '枚举来自该 provider 包的 Config schema')
3337
+ ),
3338
+ createElement('div', { className: 'field' },
3339
+ createElement('span', { className: 'field-label' }, 'disposeGraceMs(进程树终止宽限,毫秒)'),
3340
+ createElement('input', {
3341
+ className: 'input', value: draft.disposeGraceMs,
3342
+ placeholder: '3000',
3343
+ onChange: function (event) { onPatch({ disposeGraceMs: event.target.value }) },
3344
+ }),
3345
+ ),
3346
+ ),
3347
+ createElement(EnvPairsEditor, {
3348
+ pairs: draft.envPairs,
3349
+ onPatchPair: onPatchEnvPair,
3350
+ onAdd: onAddEnvPair,
3351
+ onRemove: onRemoveEnvPair,
3352
+ }),
3353
+ ) : null,
3354
+ )
3355
+ }
3356
+
3357
+ /** Draft shape for one generic external-CLI backend's editable config. */
3358
+ function cliDraftFromGeneric(backend) {
3359
+ backend = backend || {}
3360
+ return {
3361
+ command: backend.command !== undefined ? String(backend.command) : '',
3362
+ argsText: Array.isArray(backend.args) ? backend.args.join(' ') : '{prompt}',
3363
+ providerName: backend.providerName !== undefined ? String(backend.providerName) : '',
3364
+ disposeGraceMs: backend.disposeGraceMs !== undefined ? String(backend.disposeGraceMs) : '3000',
3365
+ envPairs: Object.keys(backend.env || {}).map(function (key) {
3366
+ return { key: key, value: String(backend.env[key]) }
3367
+ }),
3368
+ }
3369
+ }
3370
+
3371
+ /** Convert a generic draft into the wire config object (args split on whitespace). */
3372
+ function cliConfigFromGenericDraft(draft) {
3373
+ var config = {
3374
+ command: draft.command.trim(),
3375
+ args: draft.argsText.trim().split(/\s+/).filter(Boolean),
3376
+ providerName: draft.providerName.trim(),
3377
+ disposeGraceMs: Number(draft.disposeGraceMs.trim()),
3378
+ env: {},
3379
+ }
3380
+ draft.envPairs.forEach(function (pair) {
3381
+ var key = pair.key.trim()
3382
+ if (key !== '') config.env[key] = pair.value
3383
+ })
3384
+ return config
3385
+ }
3386
+
3387
+ /** One generic external-CLI backend card (served by this plugin's command provider). */
3388
+ function GenericCliCard(props) {
3389
+ var backend = props.backend
3390
+ var draft = props.draft
3391
+ var busy = props.busy === true
3392
+ var expanded = props.expanded === true
3393
+ var onToggle = props.onToggle
3394
+ var onPatch = props.onPatch
3395
+ var onPatchEnvPair = props.onPatchEnvPair
3396
+ var onAddEnvPair = props.onAddEnvPair
3397
+ var onRemoveEnvPair = props.onRemoveEnvPair
3398
+ var onSave = props.onSave
3399
+ var onUnmount = props.onUnmount
3400
+
3401
+ var cli = backend.cli || { ok: false, version: null }
3402
+
3403
+ return createElement('div', { className: 'card', key: backend.id },
3404
+ createElement('div', { className: 'card-header' },
3405
+ createElement('button', {
3406
+ type: 'button', className: 'cli-toggle',
3407
+ 'aria-expanded': expanded,
3408
+ 'aria-label': expanded ? '收起明细' : '展开明细',
3409
+ title: expanded ? '收起明细' : '展开明细',
3410
+ onClick: onToggle,
3411
+ }, expanded ? '▾' : '▸'),
3412
+ createElement('span', { className: 'cli-title', onClick: onToggle }, '⌨️ ' + backend.command),
3413
+ createElement('span', { className: 'cli-tags' },
3414
+ createElement('span', { className: 'tag live' }, '已挂载'),
3415
+ createElement('span', { className: 'tag ' + (backend.providerPresent ? 'live' : 'warn') },
3416
+ backend.providerPresent ? '后端在线 ✓' : '后端未注册'),
3417
+ createElement('span', { className: 'tag ' + (cli.ok ? 'model' : 'warn') },
3418
+ cli.ok ? 'PATH ' + cli.version : 'PATH ✗ ' + backend.command),
3419
+ ),
3420
+ createElement('span', { style: { display: 'flex', gap: '5px', flex: 'none' } },
3421
+ createElement('button', { type: 'button', className: 'btn sm primary', disabled: busy, onClick: onSave },
3422
+ busy ? createElement(Spinner, { key: 'spin' }) : null, '保存配置'),
3423
+ createElement(ConfirmButton, { label: '卸载', confirmLabel: '确认卸载?', disabled: busy, onConfirm: onUnmount }),
3424
+ )
3425
+ ),
3426
+ expanded ? createElement('div', { className: 'cli-detail', key: 'detail' },
3427
+ createElement('div', { className: 'form-grid' },
3428
+ createElement('div', { className: 'field' },
3429
+ createElement('span', { className: 'field-label' }, 'command(PATH 命令名或绝对路径)'),
3430
+ createElement('input', {
3431
+ className: 'input', value: draft.command,
3432
+ placeholder: '如 gemini / qwen / C:\\tools\\aider.exe',
3433
+ onChange: function (event) { onPatch({ command: event.target.value }) },
3434
+ }),
3435
+ ),
3436
+ createElement('div', { className: 'field' },
3437
+ createElement('span', { className: 'field-label' }, 'providerName(执行后端注册名)'),
3438
+ createElement('input', {
3439
+ className: 'input', value: draft.providerName,
3440
+ placeholder: '如 cli-gemini(子智能体表单的执行后端选项)',
3441
+ onChange: function (event) { onPatch({ providerName: event.target.value }) },
3442
+ }),
3443
+ ),
3444
+ createElement('div', { className: 'field full' },
3445
+ createElement('span', { className: 'field-label' }, 'args(空格分隔,{prompt} 占位提示词)'),
3446
+ createElement('input', {
3447
+ className: 'input', value: draft.argsText,
3448
+ placeholder: '-p {prompt}',
3449
+ onChange: function (event) { onPatch({ argsText: event.target.value }) },
3450
+ }),
3451
+ createElement('span', { className: 'field-hint' }, 'one-shot 纯文本:stdout 即委托结果,非零退出记为失败;prompt 经 {prompt} 传入'),
3452
+ ),
3453
+ createElement('div', { className: 'field' },
3454
+ createElement('span', { className: 'field-label' }, 'disposeGraceMs(进程树终止宽限,毫秒)'),
3455
+ createElement('input', {
3456
+ className: 'input', value: draft.disposeGraceMs,
3457
+ placeholder: '3000',
3458
+ onChange: function (event) { onPatch({ disposeGraceMs: event.target.value }) },
3459
+ }),
3460
+ ),
3461
+ ),
3462
+ createElement(EnvPairsEditor, {
3463
+ pairs: draft.envPairs,
3464
+ onPatchPair: onPatchEnvPair,
3465
+ onAdd: onAddEnvPair,
3466
+ onRemove: onRemoveEnvPair,
3467
+ }),
3468
+ ) : null,
3469
+ )
3470
+ }
3471
+
3472
+ function CliPanel(props) {
3473
+ var call = props.call
3474
+
3475
+ var viewState = useState({ loading: true, error: null, data: null })
3476
+ var view = viewState[0]
3477
+ var setView = viewState[1]
3478
+
3479
+ var draftsState = useState({})
3480
+ var drafts = draftsState[0]
3481
+ var setDrafts = draftsState[1]
3482
+
3483
+ var busyState = useState({})
3484
+ var busy = busyState[0]
3485
+ var setBusy = busyState[1]
3486
+
3487
+ var customCommandState = useState('')
3488
+ var customCommand = customCommandState[0]
3489
+ var setCustomCommand = customCommandState[1]
3490
+
3491
+ var expandedState = useState({})
3492
+ var expanded = expandedState[0]
3493
+ var setExpanded = expandedState[1]
3494
+
3495
+ var toggleExpanded = function (backendId) {
3496
+ setExpanded(function (prev) {
3497
+ var next = Object.assign({}, prev)
3498
+ next[backendId] = prev[backendId] !== true
3499
+ return next
3500
+ })
3501
+ }
3502
+
3503
+ var toastState = useState(null)
3504
+ var toast = toastState[0]
3505
+ var setToast = toastState[1]
3506
+
3507
+ useEffect(function () {
3508
+ if (!toast) return undefined
3509
+ var timer = setTimeout(function () { setToast(null) }, 4200)
3510
+ return function () { clearTimeout(timer) }
3511
+ }, [toast])
3512
+
3513
+ /** Adopt a detection payload: view data + reset drafts to the served configs. */
3514
+ var absorb = function (result) {
3515
+ setView({ loading: false, error: null, data: result })
3516
+ var next = {}
3517
+ ;(result.backends || []).forEach(function (backend) {
3518
+ next[backend.id] = backend.kind === 'generic'
3519
+ ? cliDraftFromGeneric(backend)
3520
+ : cliDraftFromConfig(backend.config)
3521
+ })
3522
+ setDrafts(next)
3523
+ setBusy({})
3524
+ }
3525
+
3526
+ var reload = function () {
3527
+ setView(function (prev) { return Object.assign({}, prev, { loading: true, error: null }) })
3528
+ call('subagentAdmin/cliList', {}).then(function (raw) {
3529
+ absorb(unwrap(raw))
3530
+ }).catch(function (error) {
3531
+ setView({ loading: false, error: String((error && error.message) || error), data: null })
3532
+ })
3533
+ }
3534
+ useEffect(reload, [])
3535
+
3536
+ var patchDraft = function (backendId, patch) {
3537
+ setDrafts(function (prev) {
3538
+ var current = prev[backendId] || cliDraftFromConfig(null)
3539
+ var next = Object.assign({}, prev)
3540
+ next[backendId] = Object.assign({}, current, patch)
3541
+ return next
3542
+ })
3543
+ }
3544
+
3545
+ // Env-pair edits derive from the latest queued state (functional update), so
3546
+ // rapid consecutive edits can never overwrite each other via stale drafts.
3547
+ var patchEnvPair = function (backendId, index, patch) {
3548
+ setDrafts(function (prev) {
3549
+ var current = prev[backendId] || cliDraftFromConfig(null)
3550
+ var pairs = (current.envPairs || []).map(function (pair, i) {
3551
+ return i === index ? Object.assign({}, pair, patch) : pair
3552
+ })
3553
+ var next = Object.assign({}, prev)
3554
+ next[backendId] = Object.assign({}, current, { envPairs: pairs })
3555
+ return next
3556
+ })
3557
+ }
3558
+ var addEnvPair = function (backendId) {
3559
+ setDrafts(function (prev) {
3560
+ var current = prev[backendId] || cliDraftFromConfig(null)
3561
+ var next = Object.assign({}, prev)
3562
+ next[backendId] = Object.assign({}, current, { envPairs: (current.envPairs || []).concat([{ key: '', value: '' }]) })
3563
+ return next
3564
+ })
3565
+ }
3566
+ var removeEnvPair = function (backendId, index) {
3567
+ setDrafts(function (prev) {
3568
+ var current = prev[backendId] || cliDraftFromConfig(null)
3569
+ var next = Object.assign({}, prev)
3570
+ next[backendId] = Object.assign({}, current, { envPairs: (current.envPairs || []).filter(function (_, i) { return i !== index }) })
3571
+ return next
3572
+ })
3573
+ }
3574
+
3575
+ var runUpsert = function (backend) {
3576
+ if (busy[backend.id]) return
3577
+ var draft = drafts[backend.id]
3578
+ if (!draft) return
3579
+ var payload
3580
+ if (backend.kind === 'generic') {
3581
+ if (!/^[^\s]+$/.test(draft.command.trim())) {
3582
+ setToast('command 不能包含空格(PATH 命令名或绝对路径)')
3583
+ return
3584
+ }
3585
+ var args = draft.argsText.trim().split(/\s+/).filter(Boolean)
3586
+ if (args.length === 0 || args.length > 20 || !args.every(function (arg) { return arg.length <= 256 })) {
3587
+ setToast('args 必须是 1-20 个非空片段(单条 ≤ 256 字符),用 {prompt} 占位提示词')
3588
+ return
3589
+ }
3590
+ if (args.indexOf('{prompt}') === -1) {
3591
+ setToast('args 必须包含 {prompt} 占位符(提示词将替换该占位符传入 CLI)')
3592
+ return
3593
+ }
3594
+ if (!/^[a-z][a-z0-9_-]{0,47}$/.test(draft.providerName.trim())) {
3595
+ setToast('providerName 必须是 1-48 位小写字母/数字/下划线/中划线且字母开头')
3596
+ return
3597
+ }
3598
+ if (!/^\d+$/.test(draft.disposeGraceMs.trim())) {
3599
+ setToast('disposeGraceMs 必须是非负整数(毫秒)')
3600
+ return
3601
+ }
3602
+ payload = { kind: 'generic', backendId: backend.id, config: cliConfigFromGenericDraft(draft) }
3603
+ } else {
3604
+ if (!/^[a-z][a-z0-9_-]{0,47}$/.test(draft.providerName.trim())) {
3605
+ setToast('providerName 必须是 1-48 位小写字母/数字/下划线/中划线且字母开头')
3606
+ return
3607
+ }
3608
+ if (!/^\d+$/.test(draft.disposeGraceMs.trim())) {
3609
+ setToast('disposeGraceMs 必须是非负整数(毫秒)')
3610
+ return
3611
+ }
3612
+ payload = { backendId: backend.id, config: cliConfigFromDraft(draft) }
3613
+ }
3614
+ var busyPatch = {}
3615
+ busyPatch[backend.id] = true
3616
+ setBusy(Object.assign({}, busy, busyPatch))
3617
+ call('subagentAdmin/cliUpsert', { payload: payload }).then(function (raw) {
3618
+ absorb(unwrap(raw))
3619
+ }).catch(function (error) {
3620
+ setBusy({})
3621
+ setToast('保存失败:' + String((error && error.message) || error))
3622
+ })
3623
+ }
3624
+
3625
+ var runUnmount = function (backend) {
3626
+ if (busy[backend.id]) return
3627
+ var busyPatch = {}
3628
+ busyPatch[backend.id] = true
3629
+ setBusy(Object.assign({}, busy, busyPatch))
3630
+ // Generic backends are recognized server-side by their "cli-" id prefix.
3631
+ call('subagentAdmin/cliRemove', { id: backend.id }).then(function (raw) {
3632
+ absorb(unwrap(raw))
3633
+ }).catch(function (error) {
3634
+ setBusy({})
3635
+ setToast('卸载失败:' + String((error && error.message) || error))
3636
+ })
3637
+ }
3638
+
3639
+ var runInstall = function (backend) {
3640
+ if (busy[backend.id]) return
3641
+ var busyPatch = {}
3642
+ busyPatch[backend.id] = true
3643
+ setBusy(Object.assign({}, busy, busyPatch))
3644
+ call('subagentAdmin/cliInstall', { backendId: backend.id }).then(function (raw) {
3645
+ var result = unwrap(raw)
3646
+ absorb(result)
3647
+ setToast((result && result.output ? result.output : '依赖包安装完成') + ',已重新检测')
3648
+ }).catch(function (error) {
3649
+ var message = String((error && error.message) || error)
3650
+ if (message.indexOf('404') !== -1) {
3651
+ message += '(宿主端未注册该接口:请重启 dsh 加载最新插件后重试)'
3652
+ }
3653
+ setBusy({})
3654
+ setToast('安装失败:' + message)
3655
+ })
3656
+ }
3657
+
3658
+ var runGenericMount = function (command) {
3659
+ if (!/^[^\s]+$/.test(command)) {
3660
+ setToast('command 不能包含空格(PATH 命令名或绝对路径)')
3661
+ return
3662
+ }
3663
+ var busyPatch = { __scan__: true }
3664
+ setBusy(Object.assign({}, busy, busyPatch))
3665
+ call('subagentAdmin/cliUpsert', { payload: { kind: 'generic', config: { command: command } } }).then(function (raw) {
3666
+ absorb(unwrap(raw))
3667
+ setCustomCommand('')
3668
+ }).catch(function (error) {
3669
+ setBusy({})
3670
+ setToast('挂载失败:' + String((error && error.message) || error))
3671
+ })
3672
+ }
3673
+
3674
+ var children = []
3675
+
3676
+ // 本机 CLI card sits on top: scan list + custom command + refresh; the
3677
+ // builtin codex/claude-code cards and mounted generic cards follow below.
3678
+ var backends = (view.data && view.data.backends) || []
3679
+ var others = (view.data && view.data.others) || []
3680
+ var busyAny = Object.keys(busy).some(function (key) { return busy[key] === true })
3681
+ var genericBackends = backends.filter(function (backend) { return backend.kind === 'generic' })
3682
+
3683
+ children.push(createElement('div', { className: 'card', key: 'local-cli' },
3684
+ createElement('div', { className: 'card-header' },
3685
+ createElement('span', { className: 'card-title' }, '🛰️ 本机 CLI'),
3686
+ createElement('span', { className: 'tag' }, '通用命令行后端 · one-shot 纯文本'),
3687
+ createElement('span', { style: { marginLeft: 'auto', display: 'flex' } },
3688
+ createElement('button', { type: 'button', className: 'btn sm', onClick: reload, disabled: view.loading },
3689
+ view.loading ? createElement(Spinner, { key: 'spin' }) : '⟳', '重新检测')
3690
+ )
3691
+ ),
3692
+ createElement('div', { className: 'cli-scan-hint' },
3693
+ '检测并挂载 harness 内置的外部 CLI 后端(codex / claude-code)与本机其他命令行工具;挂载后即可在「子智能体」表单的执行后端下拉中选用。'),
3694
+ createElement('div', { className: 'cli-scan-list' },
3695
+ createElement('div', { className: 'cli-scan-row', key: 'custom' },
3696
+ createElement('input', {
3697
+ className: 'input', value: customCommand,
3698
+ placeholder: '添加自定义 CLI:输入命令名或绝对路径,如 aider(挂载为 one-shot 后端)',
3699
+ onChange: function (event) { setCustomCommand(event.target.value) },
3700
+ }),
3701
+ createElement('button', {
3702
+ type: 'button', className: 'btn sm',
3703
+ disabled: busyAny || customCommand.trim() === '',
3704
+ onClick: function () { runGenericMount(customCommand.trim()) },
3705
+ }, '挂载'),
3706
+ ),
3707
+ (function () {
3708
+ var mountedCommands = new Set(genericBackends.map(function (backend) {
3709
+ return backend.command.toLowerCase()
3710
+ }))
3711
+ return others
3712
+ .filter(function (item) { return !!(item.cli && item.cli.ok) })
3713
+ .filter(function (item) { return !mountedCommands.has(item.name.toLowerCase()) })
3714
+ .map(function (item) {
3715
+ return createElement('div', { className: 'cli-scan-card', key: item.name },
3716
+ createElement('span', { className: 'cli-title' }, '⌨️ ' + item.name),
3717
+ createElement('span', { className: 'cli-tags' },
3718
+ createElement('span', { className: 'tag' }, '未挂载'),
3719
+ createElement('span', { className: 'tag live' }, 'PATH ' + item.cli.version),
3720
+ ),
3721
+ createElement('button', {
3722
+ type: 'button', className: 'btn sm', disabled: busyAny,
3723
+ onClick: function () { runGenericMount(item.name) },
3724
+ }, '挂载'),
3725
+ )
3726
+ })
3727
+ })(),
3728
+ others.length > 0 && others.every(function (item) { return !(item.cli && item.cli.ok) })
3729
+ ? createElement('span', { className: 'tag', key: 'none' }, '未检测到其他 agent CLI')
3730
+ : null,
3731
+ ),
3732
+ ))
3733
+
3734
+ if (view.error) {
3735
+ children.push(createElement('div', { className: 'error-strip', key: 'error' }, '⚠️ 加载失败:', view.error))
3736
+ } else {
3737
+ genericBackends.forEach(function (backend) {
3738
+ children.push(GenericCliCard({
3739
+ backend: backend,
3740
+ draft: drafts[backend.id] || cliDraftFromGeneric(backend),
3741
+ busy: busy[backend.id] === true,
3742
+ expanded: expanded[backend.id] === true,
3743
+ onToggle: function () { toggleExpanded(backend.id) },
3744
+ onPatch: function (patch) { patchDraft(backend.id, patch) },
3745
+ onPatchEnvPair: function (index, patch) { patchEnvPair(backend.id, index, patch) },
3746
+ onAddEnvPair: function () { addEnvPair(backend.id) },
3747
+ onRemoveEnvPair: function (index) { removeEnvPair(backend.id, index) },
3748
+ onSave: function () { runUpsert(backend) },
3749
+ onUnmount: function () { runUnmount(backend) },
3750
+ }))
3751
+ })
3752
+ backends.filter(function (backend) { return backend.kind !== 'generic' }).forEach(function (backend) {
3753
+ children.push(CliBackendCard({
3754
+ backend: backend,
3755
+ draft: drafts[backend.id] || cliDraftFromConfig(backend.config),
3756
+ busy: busy[backend.id] === true,
3757
+ expanded: expanded[backend.id] === true,
3758
+ onToggle: function () { toggleExpanded(backend.id) },
3759
+ onPatch: function (patch) { patchDraft(backend.id, patch) },
3760
+ onPatchEnvPair: function (index, patch) { patchEnvPair(backend.id, index, patch) },
3761
+ onAddEnvPair: function () { addEnvPair(backend.id) },
3762
+ onRemoveEnvPair: function (index) { removeEnvPair(backend.id, index) },
3763
+ onSave: function () { runUpsert(backend) },
3764
+ onInstall: function () { runInstall(backend) },
3765
+ onUnmount: function () { runUnmount(backend) },
3766
+ }))
3767
+ })
3768
+ }
3769
+
3770
+ return createElement('div', null,
3771
+ toast ? createElement('div', { className: 'toast' }, toast) : null,
3772
+ children
3773
+ )
3774
+ }
3775
+
3776
+ /* ========================================================================== */
3777
+ /* Section entrypoint */
3778
+ /* ========================================================================== */
3779
+
3780
+ function SubagentAdminSection(props) {
3781
+ var tabState = useState('subagents')
3782
+ var activeTab = tabState[0]
3783
+ var setActiveTab = tabState[1]
3784
+ var tabs = [
3785
+ { id: 'subagents', label: '子智能体', component: SubagentsPanel },
3786
+ { id: 'cli', label: 'CLI 后端', component: CliPanel },
3787
+ { id: 'history', label: '变更记录', component: HistoryPanel },
3788
+ ]
3789
+ var selected = tabs.find(function (tab) { return tab.id === activeTab }) || tabs[0]
3790
+
3791
+ return createElement('div', { 'data-dsh-sa-section': '' },
3792
+ createElement('div', { className: 'tabs', role: 'tablist', 'aria-label': '子智能体管理' },
3793
+ tabs.map(function (tab) {
3794
+ return createElement('button', {
3795
+ type: 'button', key: tab.id,
3796
+ className: 'tab' + (tab.id === selected.id ? ' active' : ''),
3797
+ role: 'tab', 'aria-selected': tab.id === selected.id,
3798
+ onClick: function () { setActiveTab(tab.id) },
3799
+ }, tab.label)
3800
+ })
3801
+ ),
3802
+ createElement(selected.component, { key: selected.id, call: props.call })
3803
+ )
3804
+ }
3805
+
2281
3806
  /* ========================================================================== */
2282
3807
  /* Plugin Entrypoint */
2283
3808
  /* ========================================================================== */
2284
3809
 
2285
3810
  function apply(ctx) {
2286
3811
  injectStyles()
3812
+ injectSaStyles()
2287
3813
 
2288
3814
  var call = function (method, args) {
2289
3815
  return ctx.connection.rpc.call('/api', method, { args: args })
@@ -2304,11 +3830,11 @@ function apply(ctx) {
2304
3830
  } catch (e) { refreshSessions = null }
2305
3831
  var disposeSidebar = ctx.effect(function () { return setupMenuInjection(call, refreshSessions) })
2306
3832
 
2307
- // Three surfaces replace the former single 管理中心 page, each parked where
2308
- // the shell already renders its domain. 插件管理 joins the shell-owned 插件
2309
- // section as its third tab (插件配置 0, 插件列表 10, 扩展插件 20); MCP and
2310
- // sessions become standalone settings pages (MCP服务器 takes the 25 slot
2311
- // right after Agent 预设; 历史会话 sorts last). inject() waits on each
3833
+ // Four surfaces, each parked where the shell already renders its domain.
3834
+ // 插件管理 joins the shell-owned 插件 section as its third tab (插件配置 0,
3835
+ // 插件列表 10, 扩展插件 20); MCP / 子智能体 / 历史会话 become standalone
3836
+ // settings pages (MCP服务器 takes the 25 slot right after Agent 预设,
3837
+ // 子智能体 follows at 26, 历史会话 sorts last). inject() waits on each
2312
3838
  // slot's declaration, so registration order never matters.
2313
3839
  ctx.slots.inject('settings.plugins.tab', function () {
2314
3840
  return ctx.slots.register({
@@ -2337,6 +3863,15 @@ function apply(ctx) {
2337
3863
  inject: function () { return { call: call, refreshSessions: refreshSessions } },
2338
3864
  }, SessionsSection)
2339
3865
  })
3866
+ ctx.slots.inject('settings.section', function () {
3867
+ return ctx.slots.register({
3868
+ name: 'settings.section',
3869
+ id: 'subagent-admin',
3870
+ order: 26,
3871
+ label: '子智能体',
3872
+ inject: function () { return { call: call } },
3873
+ }, SubagentAdminSection)
3874
+ })
2340
3875
  }
2341
3876
 
2342
3877
  module.exports = { apply: apply, inject: ['slots', 'connection'] }