Skip to content

Commit 1e3d408

Browse files
committed
feat: Agent Mode improvements — approval tiers, auto-apply edits, enhanced prompts
- Approval tiers in Settings: Ask All / Auto-Approve Edits / Full Autonomy - Auto-apply respects approval tier (not just agent mode/full access) - Richer agent mode prompt: autonomous, summarizes changes, self-fixes errors - Agent Autonomy section in Settings with radio-style selector - Context indicator already wired to ChatHeader (ops count + files changed)
1 parent 8f8f0ce commit 1e3d408

8 files changed

Lines changed: 124 additions & 74 deletions

File tree

app/globals.css

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,11 +1118,11 @@ body {
11181118

11191119
.shell-utility-button {
11201120
display: flex;
1121-
height: 36px;
1122-
width: 36px;
1121+
height: 30px;
1122+
width: 30px;
11231123
align-items: center;
11241124
justify-content: center;
1125-
border-radius: 14px;
1125+
border-radius: 10px;
11261126
border: 1px solid transparent;
11271127
background: color-mix(in srgb, var(--shell-chip-bg) 88%, transparent);
11281128
color: var(--text-disabled);
@@ -2638,8 +2638,8 @@ p {
26382638
.folder-tab {
26392639
position: relative;
26402640
z-index: 1;
2641-
padding: 7px 18px 8px;
2642-
font-size: 13px;
2641+
padding: 5px 14px 6px;
2642+
font-size: 12px;
26432643
font-weight: 600;
26442644
border: none;
26452645
background: transparent;
@@ -2812,8 +2812,8 @@ p {
28122812
.shell-mode-button {
28132813
position: relative;
28142814
display: flex;
2815-
height: 28px;
2816-
width: 28px;
2815+
height: 24px;
2816+
width: 24px;
28172817
align-items: center;
28182818
justify-content: center;
28192819
border: none;
@@ -2849,7 +2849,7 @@ p {
28492849
[data-theme='supreme'] .folder-tab-strip {
28502850
align-items: center;
28512851
gap: 2px;
2852-
padding: 6px;
2852+
padding: 4px;
28532853
border: 1px solid var(--shell-pill-border);
28542854
border-radius: 999px;
28552855
background: linear-gradient(
@@ -2866,7 +2866,7 @@ p {
28662866
}
28672867

28682868
[data-theme='supreme'] .folder-tab {
2869-
padding: 8px 14px;
2869+
padding: 5px 12px;
28702870
border-radius: 999px;
28712871
color: var(--shell-icon-muted);
28722872
}
@@ -2920,7 +2920,7 @@ p {
29202920
[data-theme='supreme'] .shell-mode-switcher {
29212921
position: relative;
29222922
gap: 0;
2923-
padding: 6px;
2923+
padding: 3px;
29242924
border: 1px solid var(--shell-pill-border);
29252925
background: linear-gradient(
29262926
180deg,
@@ -2932,9 +2932,9 @@ p {
29322932
}
29332933

29342934
[data-theme='supreme'] .shell-mode-button {
2935-
height: 42px;
2936-
width: 42px;
2937-
border-radius: 16px;
2935+
height: 32px;
2936+
width: 32px;
2937+
border-radius: 12px;
29382938
color: var(--shell-icon-muted);
29392939
}
29402940

app/page.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,7 @@ export default function EditorLayout() {
484484
return (
485485
<div
486486
className={`app-shell flex h-full w-full overflow-hidden bg-[var(--bg)] text-[var(--text-primary)] ${
487-
isMobile ? 'gap-0 p-0' : 'gap-1.5 p-1.5'
487+
isMobile ? 'gap-0 p-0' : 'gap-1 p-1'
488488
}`}
489489
style={keyboardOffset > 0 ? { height: `calc(100% - ${keyboardOffset}px)` } : undefined}
490490
>
@@ -579,7 +579,7 @@ export default function EditorLayout() {
579579
) : (
580580
<div
581581
data-tauri-drag-region
582-
className={`shell-topbar flex items-center h-12 shrink-0 px-4 gap-2 tauri-drag-region ${isMacTauri && sidebarCollapsed ? 'pl-20' : ''}`}
582+
className={`shell-topbar flex items-center h-10 shrink-0 px-4 gap-2 tauri-drag-region ${isMacTauri && sidebarCollapsed ? 'pl-20' : ''}`}
583583
>
584584
{/* Folder-style tab strip — hidden in TUI mode */}
585585
{!modeSpec.hideTabs && (
@@ -606,8 +606,8 @@ export default function EditorLayout() {
606606
<span className="flex items-center gap-2">
607607
<Icon
608608
icon={VIEW_ICONS[v].icon}
609-
width={17}
610-
height={17}
609+
width={14}
610+
height={14}
611611
className="folder-tab__icon"
612612
/>
613613
<span className="hidden sm:inline">{VIEW_ICONS[v].label}</span>
@@ -670,7 +670,7 @@ export default function EditorLayout() {
670670
className={`shell-mode-button ${mode === m.id ? 'shell-mode-button--active' : ''}`}
671671
title={`${m.label} mode (⌘⇧${index + 1})`}
672672
>
673-
<Icon icon={m.icon} width={15} height={15} />
673+
<Icon icon={m.icon} width={13} height={13} />
674674
</button>
675675
))}
676676
</div>
@@ -693,7 +693,7 @@ export default function EditorLayout() {
693693
className="shell-utility-button tauri-no-drag"
694694
title="Settings"
695695
>
696-
<Icon icon="lucide:settings" width={18} height={18} className="animate-gear-sway" />
696+
<Icon icon="lucide:settings" width={15} height={15} className="animate-gear-sway" />
697697
</button>
698698
</div>
699699
)}

components/agent-panel.tsx

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { useState, useRef, useEffect, useCallback, useMemo, type DragEvent } fro
44
import { Icon } from '@iconify/react'
55
import { ModeSelector } from '@/components/mode-selector'
66
import { ChatHome } from '@/components/chat-home'
7-
import { KnotLogo } from '@/components/knot-logo'
87
import { ChatHeader } from '@/components/chat-header'
98
import type { AgentMode } from '@/components/mode-selector'
109
import { usePermissions } from '@/components/permissions-toggle'
@@ -50,6 +49,7 @@ import {
5049
CODE_EDITOR_SYSTEM_PROMPT,
5150
buildEditorContext,
5251
getEffectiveSystemPrompt,
52+
getAgentConfig,
5353
} from '@/lib/agent-session'
5454
import {
5555
SKILL_FIRST_OVERRIDE_TOKEN,
@@ -1829,10 +1829,14 @@ export function AgentPanel() {
18291829
setActiveDiff(null)
18301830
}, [activeDiff, appendMessage])
18311831

1832-
// ─── Auto-apply when full access is enabled ──────────────────
1832+
// ─── Auto-apply when full access or approval tier allows ──────
18331833
const autoAppliedRef = useRef(new Set<string>())
1834+
const currentApprovalTier = useMemo(() => {
1835+
try { return getAgentConfig()?.approvalTier ?? 'ask-all' } catch { return 'ask-all' as const }
1836+
}, [messages.length]) // re-check when messages change
18341837
useEffect(() => {
1835-
if (permissions !== 'full' && agentMode !== 'agent') return
1838+
const tierAllows = currentApprovalTier === 'auto-edits' || currentApprovalTier === 'auto-all'
1839+
if (permissions !== 'full' && agentMode !== 'agent' && !tierAllows) return
18361840
const last = messages[messages.length - 1]
18371841
if (!last || last.role !== 'assistant' || !last.editProposals?.length) return
18381842
if (autoAppliedRef.current.has(last.id)) return
@@ -1850,7 +1854,7 @@ export function AgentPanel() {
18501854
id: crypto.randomUUID(),
18511855
role: 'system',
18521856
type: 'tool',
1853-
content: `Auto-applied edits to ${fileNames}${agentMode === 'agent' ? ' (agent mode)' : ' (full access mode)'}.`,
1857+
content: `Auto-applied edits to ${fileNames}.`,
18541858
timestamp: Date.now(),
18551859
})
18561860
}, [messages, permissions, agentMode, getFile, updateFileContent, openFile, appendMessage])
@@ -2061,7 +2065,7 @@ export function AgentPanel() {
20612065
filesChanged={agentActivities.filter(a => a.type === 'edit' || a.type === 'write' || a.type === 'create').reduce((acc, a) => { if (a.file) acc.add(a.file); return acc }, new Set<string>()).size}
20622066
/>
20632067
{messages.length > 0 && (
2064-
<div className="flex items-center justify-between border-b border-[var(--border)] bg-[var(--bg-elevated)] px-2.5 py-1 shrink-0">
2068+
<div className="flex items-center justify-between border-b border-[var(--border)] bg-[var(--bg-elevated)] px-2.5 py-0.5 shrink-0">
20652069
<div className="flex min-w-0 items-center gap-1.5">
20662070
{/* Font size controls */}
20672071
<div className="inline-flex items-center gap-0.5">
@@ -2198,14 +2202,6 @@ export function AgentPanel() {
21982202
/>
21992203
)}
22002204

2201-
{/* Branded footer — hidden on mobile to save space */}
2202-
<div className="shrink-0 hidden sm:flex items-center justify-center gap-1.5 px-3 py-0.5 border-t border-[var(--border)] bg-[var(--bg-elevated)]">
2203-
<KnotLogo size={9} className="opacity-40" />
2204-
<span className="text-[8px] text-[var(--text-disabled)] font-medium tracking-wide">
2205-
KnotCode
2206-
</span>
2207-
<span className="text-[7px] text-[var(--text-disabled)] opacity-50">v1.4.0</span>
2208-
</div>
22092205
</div>
22102206
)
22112207
}

components/chat-header.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export function ChatHeader({
4242

4343
return (
4444
<div className="shrink-0">
45-
<div className="flex items-center justify-between h-10 px-3 border-b border-[var(--border)] bg-[var(--bg-elevated)]">
45+
<div className="flex items-center justify-between h-9 px-3 border-b border-[var(--border)] bg-[var(--bg-elevated)]">
4646
<div className="flex flex-1 min-w-0 items-center gap-2">
4747
{/* Streaming status indicator */}
4848
{isStreaming ? (

components/code-editor.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -918,7 +918,7 @@ export function CodeEditor() {
918918
return (
919919
<div ref={containerRef} tabIndex={-1} className="flex-1 flex flex-col min-h-0 outline-none">
920920
{/* Breadcrumb navigation */}
921-
<div className="flex items-center justify-between gap-2 px-3 py-1 border-b border-[var(--border)] bg-[var(--bg)] shrink-0">
921+
<div className="flex items-center justify-between gap-2 px-3 py-0.5 border-b border-[var(--border)] bg-[var(--bg)] shrink-0">
922922
<div className="flex items-center gap-0.5 overflow-x-auto no-scrollbar min-w-0">
923923
<Icon
924924
icon={fileIcon}

components/settings-panel.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
getRecents, addRecent, type SavedRepo,
1616
} from '@/lib/github-repos-store'
1717
import { THEME_PRESETS, useTheme, type ThemeMode, type ThemePreset } from '@/context/theme-context'
18+
import { getAgentConfig, saveAgentConfig, APPROVAL_TIERS, type ApprovalTier } from '@/lib/agent-session'
1819

1920
type SettingsTab = 'connect' | 'general'
2021

@@ -68,6 +69,17 @@ export function SettingsPanel({
6869
const [repoSearch, setRepoSearch] = useState('')
6970
const [showGatewayUrl, setShowGatewayUrl] = useState(false)
7071
const [connectExpanded, setConnectExpanded] = useState(false)
72+
const [approvalTier, setApprovalTierState] = useState<ApprovalTier>(() => {
73+
try { return getAgentConfig()?.approvalTier ?? 'ask-all' } catch { return 'ask-all' }
74+
})
75+
76+
const updateApprovalTier = useCallback((tier: ApprovalTier) => {
77+
setApprovalTierState(tier)
78+
try {
79+
const cfg = getAgentConfig() ?? { persona: '', systemPrompt: '', behaviors: {}, modelPreference: '' }
80+
saveAgentConfig({ ...cfg, approvalTier: tier })
81+
} catch {}
82+
}, [])
7183

7284
// Fetch GitHub user on token change
7385
const ghTokenRef = useRef(ghToken)
@@ -410,6 +422,48 @@ export function SettingsPanel({
410422
<CaffeinateToggle />
411423
</section>
412424

425+
{/* Agent Autonomy */}
426+
<section className="rounded-[24px] border border-[var(--glass-border)] bg-[color-mix(in_srgb,var(--bg-elevated)_88%,transparent)] p-4 shadow-[var(--shadow-sm)]">
427+
<div className="mb-3 flex items-center gap-2">
428+
<span className="flex h-8 w-8 items-center justify-center rounded-2xl bg-[color-mix(in_srgb,var(--brand)_12%,transparent)] text-[var(--brand)]">
429+
<Icon icon="lucide:shield-check" width={14} />
430+
</span>
431+
<div>
432+
<h3 className="text-sm font-medium text-[var(--text-primary)]">Agent Autonomy</h3>
433+
<p className="text-[11px] text-[var(--text-secondary)]">
434+
Control what the agent can do without asking.
435+
</p>
436+
</div>
437+
</div>
438+
<div className="space-y-1.5">
439+
{APPROVAL_TIERS.map(tier => (
440+
<button
441+
key={tier.id}
442+
onClick={() => updateApprovalTier(tier.id)}
443+
className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-xl border text-left transition-all cursor-pointer ${
444+
approvalTier === tier.id
445+
? 'border-[color-mix(in_srgb,var(--brand)_40%,var(--border))] bg-[color-mix(in_srgb,var(--brand)_8%,transparent)]'
446+
: 'border-transparent hover:bg-[color-mix(in_srgb,var(--text-primary)_4%,transparent)]'
447+
}`}
448+
>
449+
<div className={`w-4 h-4 rounded-full border-2 flex items-center justify-center shrink-0 ${
450+
approvalTier === tier.id
451+
? 'border-[var(--brand)]'
452+
: 'border-[var(--text-disabled)]'
453+
}`}>
454+
{approvalTier === tier.id && <div className="w-2 h-2 rounded-full bg-[var(--brand)]" />}
455+
</div>
456+
<div>
457+
<p className={`text-[12px] font-medium ${approvalTier === tier.id ? 'text-[var(--text-primary)]' : 'text-[var(--text-secondary)]'}`}>
458+
{tier.label}
459+
</p>
460+
<p className="text-[10px] text-[var(--text-disabled)]">{tier.description}</p>
461+
</div>
462+
</button>
463+
))}
464+
</div>
465+
</section>
466+
413467
{/* GitHub Account */}
414468
<section className="rounded-[24px] border border-[var(--glass-border)] bg-[color-mix(in_srgb,var(--bg-elevated)_88%,transparent)] p-4 shadow-[var(--shadow-sm)]">
415469
<div className="mb-3 flex items-center gap-2">

0 commit comments

Comments
 (0)