dsh-plugin-admin 0.4.3 → 0.5.0

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