bhavikprit Nyk commited on
Commit
b1106ba
·
unverified ·
1 Parent(s): 679f2be

fix: enable editing identity, sandbox, and tools in agent config UI (#148)

Browse files

* fix(#140): enable editing of identity, sandbox, and tools in agent config UI

The ConfigTab's structured view only showed read-only displays for
Identity, Sandbox, and Tools sections even when in edit mode. Added
inline editing controls:

- Identity: emoji, name, theme/role inputs + identity content textarea
- Sandbox: mode/workspace dropdowns + network input
- Tools: allow/deny lists with add/remove buttons and Enter key support

Also added helper functions (updateIdentityField, updateSandboxField,
addTool, removeTool) and state for new tool entries.

Fixes #140

* fix: align sandbox edit values with agent schema

---------

Co-authored-by: Nyk <0xnykcd@googlemail.com>

src/components/panels/agent-detail-tabs.tsx CHANGED
@@ -1241,6 +1241,8 @@ export function ConfigTab({
1241
  const [jsonInput, setJsonInput] = useState('')
1242
  const [availableModels, setAvailableModels] = useState<string[]>([])
1243
  const [newFallbackModel, setNewFallbackModel] = useState('')
 
 
1244
 
1245
  useEffect(() => {
1246
  setConfig(agent.config || {})
@@ -1289,6 +1291,40 @@ export function ConfigTab({
1289
  setNewFallbackModel('')
1290
  }
1291
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1292
  const handleSave = async (writeToGateway: boolean = false) => {
1293
  setSaving(true)
1294
  setError(null)
@@ -1476,60 +1512,205 @@ export function ConfigTab({
1476
  {/* Identity */}
1477
  <div className="bg-surface-1/50 rounded-lg p-4">
1478
  <h5 className="text-sm font-medium text-foreground mb-2">Identity</h5>
1479
- <div className="flex items-center gap-3 text-sm">
1480
- <span className="text-2xl">{identityEmoji}</span>
1481
- <div>
1482
- <div className="text-foreground font-medium">{identityName}</div>
1483
- <div className="text-muted-foreground">{identityTheme}</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1484
  </div>
1485
- </div>
1486
- {identityPreview && (
1487
- <pre className="mt-3 text-xs text-muted-foreground bg-surface-1 rounded p-2 overflow-auto whitespace-pre-wrap">
1488
- {identityPreview}
1489
- </pre>
 
 
 
 
 
 
 
 
 
 
1490
  )}
1491
  </div>
1492
 
1493
  {/* Sandbox */}
1494
  <div className="bg-surface-1/50 rounded-lg p-4">
1495
  <h5 className="text-sm font-medium text-foreground mb-2">Sandbox</h5>
1496
- <div className="grid grid-cols-3 gap-2 text-sm">
1497
- <div><span className="text-muted-foreground">Mode:</span> <span className="text-foreground">{sandboxMode}</span></div>
1498
- <div><span className="text-muted-foreground">Workspace:</span> <span className="text-foreground">{sandboxWorkspace}</span></div>
1499
- <div><span className="text-muted-foreground">Network:</span> <span className="text-foreground">{sandboxNetwork}</span></div>
1500
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1501
  </div>
1502
 
1503
  {/* Tools */}
1504
  <div className="bg-surface-1/50 rounded-lg p-4">
1505
  <h5 className="text-sm font-medium text-foreground mb-2">Tools</h5>
1506
- {toolAllow.length > 0 && (
1507
- <div className="mb-2">
1508
- <span className="text-xs text-green-400 font-medium">Allow ({toolAllow.length}):</span>
1509
- <div className="flex flex-wrap gap-1 mt-1">
1510
- {toolAllow.map((tool: string) => (
1511
- <span key={tool} className="px-2 py-0.5 text-xs bg-green-500/10 text-green-400 rounded border border-green-500/20">{tool}</span>
1512
- ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1513
  </div>
1514
- </div>
1515
- )}
1516
- {toolDeny.length > 0 && (
1517
- <div>
1518
- <span className="text-xs text-red-400 font-medium">Deny ({toolDeny.length}):</span>
1519
- <div className="flex flex-wrap gap-1 mt-1">
1520
- {toolDeny.map((tool: string) => (
1521
- <span key={tool} className="px-2 py-0.5 text-xs bg-red-500/10 text-red-400 rounded border border-red-500/20">{tool}</span>
1522
- ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1523
  </div>
1524
  </div>
1525
- )}
1526
- {toolAllow.length === 0 && toolDeny.length === 0 && !toolRawPreview && (
1527
- <div className="text-xs text-muted-foreground">No tools configured</div>
1528
- )}
1529
- {toolRawPreview && (
1530
- <pre className="mt-3 text-xs text-muted-foreground bg-surface-1 rounded p-2 overflow-auto whitespace-pre-wrap">
1531
- {toolRawPreview}
1532
- </pre>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1533
  )}
1534
  </div>
1535
 
 
1241
  const [jsonInput, setJsonInput] = useState('')
1242
  const [availableModels, setAvailableModels] = useState<string[]>([])
1243
  const [newFallbackModel, setNewFallbackModel] = useState('')
1244
+ const [newAllowTool, setNewAllowTool] = useState('')
1245
+ const [newDenyTool, setNewDenyTool] = useState('')
1246
 
1247
  useEffect(() => {
1248
  setConfig(agent.config || {})
 
1291
  setNewFallbackModel('')
1292
  }
1293
 
1294
+ const updateIdentityField = (field: string, value: string) => {
1295
+ setConfig((prev: any) => ({
1296
+ ...prev,
1297
+ identity: { ...(prev.identity || {}), [field]: value },
1298
+ }))
1299
+ }
1300
+
1301
+ const updateSandboxField = (field: string, value: string) => {
1302
+ setConfig((prev: any) => ({
1303
+ ...prev,
1304
+ sandbox: { ...(prev.sandbox || {}), [field]: value },
1305
+ }))
1306
+ }
1307
+
1308
+ const addTool = (list: 'allow' | 'deny', value: string) => {
1309
+ const trimmed = value.trim()
1310
+ if (!trimmed) return
1311
+ setConfig((prev: any) => {
1312
+ const tools = prev.tools || {}
1313
+ const existing = Array.isArray(tools[list]) ? tools[list] : []
1314
+ if (existing.includes(trimmed)) return prev
1315
+ return { ...prev, tools: { ...tools, [list]: [...existing, trimmed] } }
1316
+ })
1317
+ }
1318
+
1319
+ const removeTool = (list: 'allow' | 'deny', index: number) => {
1320
+ setConfig((prev: any) => {
1321
+ const tools = prev.tools || {}
1322
+ const existing = Array.isArray(tools[list]) ? [...tools[list]] : []
1323
+ existing.splice(index, 1)
1324
+ return { ...prev, tools: { ...tools, [list]: existing } }
1325
+ })
1326
+ }
1327
+
1328
  const handleSave = async (writeToGateway: boolean = false) => {
1329
  setSaving(true)
1330
  setError(null)
 
1512
  {/* Identity */}
1513
  <div className="bg-surface-1/50 rounded-lg p-4">
1514
  <h5 className="text-sm font-medium text-foreground mb-2">Identity</h5>
1515
+ {editing ? (
1516
+ <div className="space-y-3">
1517
+ <div className="grid grid-cols-3 gap-3">
1518
+ <div>
1519
+ <label className="block text-xs text-muted-foreground mb-1">Emoji</label>
1520
+ <input
1521
+ value={identityEmoji}
1522
+ onChange={(e) => updateIdentityField('emoji', e.target.value)}
1523
+ className="w-full bg-surface-1 text-foreground rounded px-3 py-2 text-sm text-center focus:outline-none focus:ring-1 focus:ring-primary/50"
1524
+ placeholder="🤖"
1525
+ />
1526
+ </div>
1527
+ <div>
1528
+ <label className="block text-xs text-muted-foreground mb-1">Name</label>
1529
+ <input
1530
+ value={identity.name || ''}
1531
+ onChange={(e) => updateIdentityField('name', e.target.value)}
1532
+ className="w-full bg-surface-1 text-foreground rounded px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-primary/50"
1533
+ placeholder="Agent name"
1534
+ />
1535
+ </div>
1536
+ <div>
1537
+ <label className="block text-xs text-muted-foreground mb-1">Theme / Role</label>
1538
+ <input
1539
+ value={identity.theme || ''}
1540
+ onChange={(e) => updateIdentityField('theme', e.target.value)}
1541
+ className="w-full bg-surface-1 text-foreground rounded px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-primary/50"
1542
+ placeholder="e.g. backend engineer"
1543
+ />
1544
+ </div>
1545
+ </div>
1546
+ <div>
1547
+ <label className="block text-xs text-muted-foreground mb-1">Identity content</label>
1548
+ <textarea
1549
+ value={identity.content || ''}
1550
+ onChange={(e) => updateIdentityField('content', e.target.value)}
1551
+ rows={4}
1552
+ className="w-full bg-surface-1 text-foreground border border-border rounded-md px-3 py-2 font-mono text-xs focus:outline-none focus:ring-1 focus:ring-primary/50"
1553
+ placeholder="Describe the agent's identity and personality..."
1554
+ />
1555
+ </div>
1556
  </div>
1557
+ ) : (
1558
+ <>
1559
+ <div className="flex items-center gap-3 text-sm">
1560
+ <span className="text-2xl">{identityEmoji}</span>
1561
+ <div>
1562
+ <div className="text-foreground font-medium">{identityName}</div>
1563
+ <div className="text-muted-foreground">{identityTheme}</div>
1564
+ </div>
1565
+ </div>
1566
+ {identityPreview && (
1567
+ <pre className="mt-3 text-xs text-muted-foreground bg-surface-1 rounded p-2 overflow-auto whitespace-pre-wrap">
1568
+ {identityPreview}
1569
+ </pre>
1570
+ )}
1571
+ </>
1572
  )}
1573
  </div>
1574
 
1575
  {/* Sandbox */}
1576
  <div className="bg-surface-1/50 rounded-lg p-4">
1577
  <h5 className="text-sm font-medium text-foreground mb-2">Sandbox</h5>
1578
+ {editing ? (
1579
+ <div className="grid grid-cols-3 gap-3">
1580
+ <div>
1581
+ <label className="block text-xs text-muted-foreground mb-1">Mode</label>
1582
+ <select
1583
+ value={sandbox.mode || ''}
1584
+ onChange={(e) => updateSandboxField('mode', e.target.value)}
1585
+ className="w-full bg-surface-1 text-foreground rounded px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-primary/50"
1586
+ >
1587
+ <option value="">Not configured</option>
1588
+ <option value="all">All</option>
1589
+ <option value="non-main">Non-main</option>
1590
+ <option value="none">None</option>
1591
+ </select>
1592
+ </div>
1593
+ <div>
1594
+ <label className="block text-xs text-muted-foreground mb-1">Workspace Access</label>
1595
+ <select
1596
+ value={sandbox.workspaceAccess || ''}
1597
+ onChange={(e) => updateSandboxField('workspaceAccess', e.target.value)}
1598
+ className="w-full bg-surface-1 text-foreground rounded px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-primary/50"
1599
+ >
1600
+ <option value="">Not configured</option>
1601
+ <option value="rw">Read-write</option>
1602
+ <option value="ro">Read-only</option>
1603
+ <option value="none">None</option>
1604
+ </select>
1605
+ </div>
1606
+ <div>
1607
+ <label className="block text-xs text-muted-foreground mb-1">Network</label>
1608
+ <input
1609
+ value={sandbox.network || ''}
1610
+ onChange={(e) => updateSandboxField('network', e.target.value)}
1611
+ className="w-full bg-surface-1 text-foreground rounded px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-primary/50"
1612
+ placeholder="none"
1613
+ />
1614
+ </div>
1615
+ </div>
1616
+ ) : (
1617
+ <div className="grid grid-cols-3 gap-2 text-sm">
1618
+ <div><span className="text-muted-foreground">Mode:</span> <span className="text-foreground">{sandboxMode}</span></div>
1619
+ <div><span className="text-muted-foreground">Workspace:</span> <span className="text-foreground">{sandboxWorkspace}</span></div>
1620
+ <div><span className="text-muted-foreground">Network:</span> <span className="text-foreground">{sandboxNetwork}</span></div>
1621
+ </div>
1622
+ )}
1623
  </div>
1624
 
1625
  {/* Tools */}
1626
  <div className="bg-surface-1/50 rounded-lg p-4">
1627
  <h5 className="text-sm font-medium text-foreground mb-2">Tools</h5>
1628
+ {editing ? (
1629
+ <div className="space-y-3">
1630
+ <div>
1631
+ <label className="block text-xs text-green-400 font-medium mb-1">Allow list</label>
1632
+ <div className="flex flex-wrap gap-1 mb-2">
1633
+ {toolAllow.map((tool: string, i: number) => (
1634
+ <span key={`${tool}-${i}`} className="px-2 py-0.5 text-xs bg-green-500/10 text-green-400 rounded border border-green-500/20 flex items-center gap-1">
1635
+ {tool}
1636
+ <button onClick={() => removeTool('allow', i)} className="text-green-400/60 hover:text-green-400 ml-1">&times;</button>
1637
+ </span>
1638
+ ))}
1639
+ </div>
1640
+ <div className="flex gap-2">
1641
+ <input
1642
+ value={newAllowTool}
1643
+ onChange={(e) => setNewAllowTool(e.target.value)}
1644
+ onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); addTool('allow', newAllowTool); setNewAllowTool('') } }}
1645
+ placeholder="Add allowed tool name"
1646
+ className="flex-1 bg-surface-1 text-foreground rounded px-3 py-2 text-xs focus:outline-none focus:ring-1 focus:ring-primary/50"
1647
+ />
1648
+ <button
1649
+ onClick={() => { addTool('allow', newAllowTool); setNewAllowTool('') }}
1650
+ className="px-3 py-2 text-xs bg-green-500/20 text-green-400 border border-green-500/30 rounded hover:bg-green-500/30 transition-smooth"
1651
+ >
1652
+ Add
1653
+ </button>
1654
+ </div>
1655
  </div>
1656
+ <div>
1657
+ <label className="block text-xs text-red-400 font-medium mb-1">Deny list</label>
1658
+ <div className="flex flex-wrap gap-1 mb-2">
1659
+ {toolDeny.map((tool: string, i: number) => (
1660
+ <span key={`${tool}-${i}`} className="px-2 py-0.5 text-xs bg-red-500/10 text-red-400 rounded border border-red-500/20 flex items-center gap-1">
1661
+ {tool}
1662
+ <button onClick={() => removeTool('deny', i)} className="text-red-400/60 hover:text-red-400 ml-1">&times;</button>
1663
+ </span>
1664
+ ))}
1665
+ </div>
1666
+ <div className="flex gap-2">
1667
+ <input
1668
+ value={newDenyTool}
1669
+ onChange={(e) => setNewDenyTool(e.target.value)}
1670
+ onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); addTool('deny', newDenyTool); setNewDenyTool('') } }}
1671
+ placeholder="Add denied tool name"
1672
+ className="flex-1 bg-surface-1 text-foreground rounded px-3 py-2 text-xs focus:outline-none focus:ring-1 focus:ring-primary/50"
1673
+ />
1674
+ <button
1675
+ onClick={() => { addTool('deny', newDenyTool); setNewDenyTool('') }}
1676
+ className="px-3 py-2 text-xs bg-red-500/20 text-red-400 border border-red-500/30 rounded hover:bg-red-500/30 transition-smooth"
1677
+ >
1678
+ Add
1679
+ </button>
1680
+ </div>
1681
  </div>
1682
  </div>
1683
+ ) : (
1684
+ <>
1685
+ {toolAllow.length > 0 && (
1686
+ <div className="mb-2">
1687
+ <span className="text-xs text-green-400 font-medium">Allow ({toolAllow.length}):</span>
1688
+ <div className="flex flex-wrap gap-1 mt-1">
1689
+ {toolAllow.map((tool: string) => (
1690
+ <span key={tool} className="px-2 py-0.5 text-xs bg-green-500/10 text-green-400 rounded border border-green-500/20">{tool}</span>
1691
+ ))}
1692
+ </div>
1693
+ </div>
1694
+ )}
1695
+ {toolDeny.length > 0 && (
1696
+ <div>
1697
+ <span className="text-xs text-red-400 font-medium">Deny ({toolDeny.length}):</span>
1698
+ <div className="flex flex-wrap gap-1 mt-1">
1699
+ {toolDeny.map((tool: string) => (
1700
+ <span key={tool} className="px-2 py-0.5 text-xs bg-red-500/10 text-red-400 rounded border border-red-500/20">{tool}</span>
1701
+ ))}
1702
+ </div>
1703
+ </div>
1704
+ )}
1705
+ {toolAllow.length === 0 && toolDeny.length === 0 && !toolRawPreview && (
1706
+ <div className="text-xs text-muted-foreground">No tools configured</div>
1707
+ )}
1708
+ {toolRawPreview && (
1709
+ <pre className="mt-3 text-xs text-muted-foreground bg-surface-1 rounded p-2 overflow-auto whitespace-pre-wrap">
1710
+ {toolRawPreview}
1711
+ </pre>
1712
+ )}
1713
+ </>
1714
  )}
1715
  </div>
1716