@workerdeck/ui 2.7.2 → 2.9.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/README.md +30 -30
- package/build/{SessionPanel-B5FfiBxE.mjs → SessionPanel-BClL7zUS.mjs} +694 -413
- package/build/SessionPanel-BClL7zUS.mjs.map +1 -0
- package/build/{SessionPanel-_lQLhWOw.d.mts → SessionPanel-BVbau2eR.d.mts} +68 -4
- package/build/format.mjs +1 -1
- package/build/index.d.mts +22 -15
- package/build/index.mjs +6 -6
- package/build/index.mjs.map +1 -1
- package/build/scoped.css +26 -2
- package/build/{status-BO9wJloK.mjs → status-kmsBhuFG.mjs} +3 -3
- package/build/status-kmsBhuFG.mjs.map +1 -0
- package/build/workspace.d.mts +3 -2
- package/build/workspace.mjs +7 -8
- package/build/workspace.mjs.map +1 -1
- package/package.json +18 -18
- package/src/components/agent/Composer.tsx +86 -84
- package/src/components/agent/ContextDialog.tsx +1 -1
- package/src/components/agent/EntryIcon.tsx +1 -1
- package/src/components/agent/FileTree.tsx +1 -1
- package/src/components/agent/FileViewer.tsx +1 -1
- package/src/components/agent/HostFilesDialog.tsx +2 -2
- package/src/components/agent/PermissionModeSelect.tsx +2 -2
- package/src/components/agent/PermissionPrompt.tsx +2 -2
- package/src/components/agent/SessionBrowser.tsx +1 -1
- package/src/components/agent/SessionItem.tsx +4 -2
- package/src/components/agent/SessionPanel.tsx +306 -226
- package/src/components/agent/SessionWorkspace.tsx +3 -0
- package/src/components/agent/SkillsDialog.tsx +3 -3
- package/src/components/agent/StatusBar.tsx +1 -1
- package/src/components/agent/TaskList.tsx +2 -2
- package/src/components/agent/TranscriptRows.tsx +1 -1
- package/src/components/agent/UsageDialog.tsx +1 -1
- package/src/components/agent/UsageMeters.tsx +17 -4
- package/src/components/agent/composer-commands.ts +142 -0
- package/src/components/agent/use-path-links.ts +2 -7
- package/src/components/agent/use-subagent-frame.ts +2 -2
- package/src/components/agent/use-transcript-jumps.ts +2 -2
- package/src/components/prompt-area/clipboard-helpers.ts +2 -2
- package/src/components/prompt-area/cursor-helpers.ts +7 -7
- package/src/components/prompt-area/dom-helpers.ts +8 -8
- package/src/components/prompt-area/html-to-markdown.ts +2 -2
- package/src/components/prompt-area/prompt-area-engine.ts +4 -4
- package/src/components/prompt-area/prompt-area-list-ops.ts +10 -10
- package/src/components/prompt-area/prompt-area.tsx +2 -2
- package/src/components/prompt-area/trigger-presets.ts +6 -6
- package/src/components/prompt-area/types.ts +6 -6
- package/src/components/prompt-area/use-chip-editing.ts +8 -8
- package/src/components/prompt-area/use-markdown-mode.ts +3 -3
- package/src/components/prompt-area/use-prompt-area-events.ts +3 -3
- package/src/components/prompt-area/use-prompt-area-keydown.ts +3 -3
- package/src/components/prompt-area/use-prompt-area-state.ts +4 -4
- package/src/components/prompt-area/use-prompt-area.ts +5 -5
- package/src/components/prompt-area/use-trigger-search.ts +1 -1
- package/src/components/terminal/PermissionPrompt.tsx +4 -4
- package/src/components/terminal/affordances.tsx +1 -1
- package/src/components/terminal/file-link.tsx +20 -0
- package/src/components/terminal/height.ts +2 -2
- package/src/components/terminal/items.tsx +2 -2
- package/src/components/terminal/markdown.tsx +31 -5
- package/src/components/terminal/scrubber.tsx +1 -1
- package/src/components/terminal/todos.ts +1 -1
- package/src/components/ui/Splitter.tsx +1 -1
- package/src/index.ts +11 -1
- package/src/lib/context-note.ts +3 -3
- package/src/lib/file-link.ts +70 -0
- package/src/lib/format.ts +2 -2
- package/src/styles/scoped.entry.css +2 -2
- package/src/styles/terminal.css +94 -79
- package/src/styles/theme.css +39 -39
- package/build/SessionPanel-B5FfiBxE.mjs.map +0 -1
- package/build/status-BO9wJloK.mjs.map +0 -1
|
@@ -11,13 +11,14 @@ import {
|
|
|
11
11
|
} from 'react'
|
|
12
12
|
import type { SkillInfo, SlashCommandInfo } from '@workerdeck/protocol'
|
|
13
13
|
import type { StagedAttachment, UseAttachmentsResult } from '@workerdeck/react'
|
|
14
|
-
import { ArrowUp, FileText, Paperclip, RotateCw, Sparkles, Square, TriangleAlert, X } from 'lucide-react'
|
|
14
|
+
import { ArrowUp, FileText, Paperclip, RotateCw, SlidersHorizontal, Sparkles, Square, TriangleAlert, X } from 'lucide-react'
|
|
15
15
|
import { Button } from '../ui/Button.tsx'
|
|
16
16
|
import { Spinner } from '../ui/Spinner.tsx'
|
|
17
17
|
import { PromptArea } from '../prompt-area/prompt-area.tsx'
|
|
18
18
|
import { usePromptAreaState } from '../prompt-area/use-prompt-area-state.ts'
|
|
19
19
|
import { plainTextToSegments } from '../prompt-area/prompt-area-engine.ts'
|
|
20
20
|
import { commandTrigger, launchTrigger, mentionTrigger } from '../prompt-area/trigger-presets.ts'
|
|
21
|
+
import { mergeComposerRows, rankComposerRows, skillPrompt, type ClientCommand } from './composer-commands.ts'
|
|
21
22
|
import { useTranscriptVariant } from './transcript-variant.tsx'
|
|
22
23
|
import type { TerminalAffordances } from '../terminal/affordances.tsx'
|
|
23
24
|
import { PROMPT_GLYPH } from '../terminal/items.tsx'
|
|
@@ -44,9 +45,12 @@ export interface ComposerProps {
|
|
|
44
45
|
placeholder?: string
|
|
45
46
|
commands?: SlashCommandInfo[]
|
|
46
47
|
skills?: SkillInfo[]
|
|
48
|
+
// Session controls the host offers as `/` rows. Merged into the same ranked list as the engine's own
|
|
49
|
+
// commands, and suppressed name-for-name by an engine command so the real one always wins.
|
|
50
|
+
clientCommands?: ClientCommand[]
|
|
47
51
|
onSearchFiles?: (query: string, options: { signal: AbortSignal }) => Promise<ComposerFileMatch[]>
|
|
48
52
|
// Shell mode: `!` as the first character turns the composer into a host shell prompt. Omit to leave the
|
|
49
|
-
// mode off entirely
|
|
53
|
+
// mode off entirely - the gateway only offers it to an operator on a session whose engine reaches a host cwd.
|
|
50
54
|
onShellCommand?: (command: string) => void
|
|
51
55
|
attachments?: UseAttachmentsResult
|
|
52
56
|
toolbar?: ReactNode
|
|
@@ -61,24 +65,6 @@ export interface ComposerProps {
|
|
|
61
65
|
ref?: Ref<ComposerHandle>
|
|
62
66
|
}
|
|
63
67
|
|
|
64
|
-
function cleanName(name: string) {
|
|
65
|
-
return name.replace(/\s*\(MCP\)$/i, '')
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function skillPrompt(skill: SkillInfo): string {
|
|
69
|
-
const base = skill.defaultPrompt?.trim() || `$${skill.name}`
|
|
70
|
-
return /\s$/.test(base) ? base : base + ' '
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function matchScore(query: string, haystacks: string[]): number {
|
|
74
|
-
const needle = query.toLowerCase()
|
|
75
|
-
const lowered = haystacks.map((s) => s.toLowerCase())
|
|
76
|
-
if (lowered.some((h) => h.startsWith(needle))) {
|
|
77
|
-
return 2
|
|
78
|
-
}
|
|
79
|
-
return lowered.some((h) => h.includes(needle)) ? 1 : 0
|
|
80
|
-
}
|
|
81
|
-
|
|
82
68
|
export function Composer({
|
|
83
69
|
onSend,
|
|
84
70
|
onInterrupt,
|
|
@@ -87,6 +73,7 @@ export function Composer({
|
|
|
87
73
|
placeholder = 'Message the agent…',
|
|
88
74
|
commands,
|
|
89
75
|
skills,
|
|
76
|
+
clientCommands,
|
|
90
77
|
onSearchFiles,
|
|
91
78
|
onShellCommand,
|
|
92
79
|
attachments,
|
|
@@ -109,6 +96,7 @@ export function Composer({
|
|
|
109
96
|
const fileInput = useRef<HTMLInputElement>(null)
|
|
110
97
|
const [dragging, setDragging] = useState(false)
|
|
111
98
|
const [shellMode, setShellMode] = useState(false)
|
|
99
|
+
const [helpOpen, setHelpOpen] = useState(false)
|
|
112
100
|
|
|
113
101
|
useImperativeHandle(
|
|
114
102
|
ref,
|
|
@@ -125,76 +113,43 @@ export function Composer({
|
|
|
125
113
|
|
|
126
114
|
const triggers = useMemo(() => {
|
|
127
115
|
const configured = []
|
|
128
|
-
const
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (seen.has(name)) {
|
|
134
|
-
return []
|
|
135
|
-
}
|
|
136
|
-
seen.add(name)
|
|
137
|
-
return [{ ...c, name }]
|
|
138
|
-
})
|
|
116
|
+
const rows = mergeComposerRows({ commands, clientCommands, skills })
|
|
117
|
+
if (rows.length > 0) {
|
|
118
|
+
// Keyed by kind as well as name: an engine command and a skill may share one, and a chip's `data`
|
|
119
|
+
// is serialized into the DOM, so the row itself never travels there.
|
|
120
|
+
const byTag = new Map(rows.map((row) => [`${row.kind}:${row.name}`, row]))
|
|
139
121
|
configured.push(
|
|
140
122
|
commandTrigger({
|
|
141
|
-
onSearch: (query: string): TriggerSuggestion[] =>
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
123
|
+
onSearch: (query: string): TriggerSuggestion[] =>
|
|
124
|
+
rankComposerRows(query, rows).map((row) =>
|
|
125
|
+
row.kind === 'skill'
|
|
126
|
+
? {
|
|
127
|
+
value: row.name,
|
|
128
|
+
label: row.label,
|
|
129
|
+
description: row.description ? `Skill · ${row.description}` : 'Skill · inserts a message you can edit',
|
|
130
|
+
icon: <Sparkles className="size-3.5 text-fg-3" />,
|
|
131
|
+
data: `${row.kind}:${row.name}`,
|
|
132
|
+
}
|
|
133
|
+
: {
|
|
134
|
+
value: row.name,
|
|
135
|
+
label: `/${row.name}${row.argumentHint ? ` ${row.argumentHint}` : ''}`,
|
|
136
|
+
description: row.description,
|
|
137
|
+
icon: row.kind === 'client' ? <SlidersHorizontal className="size-3.5 text-fg-3" /> : undefined,
|
|
138
|
+
data: `${row.kind}:${row.name}`,
|
|
139
|
+
},
|
|
140
|
+
),
|
|
141
|
+
insertAsText: (suggestion) => {
|
|
142
|
+
const row = byTag.get(String(suggestion.data))
|
|
143
|
+
if (row?.kind === 'skill') {
|
|
144
|
+
return skillPrompt(row.skill)
|
|
156
145
|
}
|
|
157
|
-
|
|
158
|
-
return scored.map(({ suggestion }) => suggestion)
|
|
146
|
+
return row?.kind === 'client' && row.command.requiresArgs ? `/${row.name} ` : undefined
|
|
159
147
|
},
|
|
160
148
|
onSelect: (suggestion) => suggestion.value,
|
|
161
149
|
chipClassName: 'font-mono',
|
|
162
150
|
}),
|
|
163
151
|
)
|
|
164
152
|
}
|
|
165
|
-
if (usableSkills.length > 0) {
|
|
166
|
-
configured.push(
|
|
167
|
-
commandTrigger({
|
|
168
|
-
char: '$',
|
|
169
|
-
accessibilityLabel: 'skill',
|
|
170
|
-
onSearch: (query: string): TriggerSuggestion[] => {
|
|
171
|
-
const scored: Array<{ score: number; suggestion: TriggerSuggestion }> = []
|
|
172
|
-
for (const skill of usableSkills) {
|
|
173
|
-
const score = matchScore(query, [skill.name, ...skill.name.split(/[-:_]/)])
|
|
174
|
-
if (score === 0) {
|
|
175
|
-
continue
|
|
176
|
-
}
|
|
177
|
-
const summary = skill.shortDescription ?? skill.description
|
|
178
|
-
scored.push({
|
|
179
|
-
score,
|
|
180
|
-
suggestion: {
|
|
181
|
-
value: skill.name,
|
|
182
|
-
label: skill.displayName ?? skill.name,
|
|
183
|
-
description: summary ? `Skill · ${summary}` : 'Skill · inserts a message you can edit',
|
|
184
|
-
icon: <Sparkles className="size-3.5 text-fg-3" />,
|
|
185
|
-
},
|
|
186
|
-
})
|
|
187
|
-
}
|
|
188
|
-
scored.sort((a, b) => b.score - a.score)
|
|
189
|
-
return scored.map(({ suggestion }) => suggestion)
|
|
190
|
-
},
|
|
191
|
-
insertAsText: (suggestion) => {
|
|
192
|
-
const skill = usableSkills.find((s) => s.name === suggestion.value)
|
|
193
|
-
return skill ? skillPrompt(skill) : `$${suggestion.value} `
|
|
194
|
-
},
|
|
195
|
-
}),
|
|
196
|
-
)
|
|
197
|
-
}
|
|
198
153
|
if (onSearchFiles) {
|
|
199
154
|
configured.push(
|
|
200
155
|
mentionTrigger({
|
|
@@ -224,7 +179,7 @@ export function Composer({
|
|
|
224
179
|
)
|
|
225
180
|
}
|
|
226
181
|
return configured.length > 0 ? configured : undefined
|
|
227
|
-
}, [commands, skills, onSearchFiles, onShellCommand])
|
|
182
|
+
}, [commands, skills, clientCommands, onSearchFiles, onShellCommand])
|
|
228
183
|
|
|
229
184
|
const saveDraft = draft?.save
|
|
230
185
|
useEffect(() => {
|
|
@@ -261,6 +216,13 @@ export function Composer({
|
|
|
261
216
|
setShellMode(false)
|
|
262
217
|
focus()
|
|
263
218
|
}
|
|
219
|
+
|
|
220
|
+
const hints = [
|
|
221
|
+
triggers?.some((t) => t.char === '/') ? { key: '/', what: 'commands and skills' } : undefined,
|
|
222
|
+
onSearchFiles ? { key: '@', what: 'mention a file' } : undefined,
|
|
223
|
+
onShellCommand ? { key: '!', what: 'run a shell command' } : undefined,
|
|
224
|
+
{ key: '?', what: 'this list, on an empty composer' },
|
|
225
|
+
].filter((h) => h !== undefined)
|
|
264
226
|
// Escape and backspace-on-empty both leave, because both are what a person reaches for when the
|
|
265
227
|
// pink frame was not what they meant. Backspace only when there is nothing left to delete.
|
|
266
228
|
const shellKeys = shellMode
|
|
@@ -273,7 +235,40 @@ export function Composer({
|
|
|
273
235
|
}
|
|
274
236
|
},
|
|
275
237
|
}
|
|
276
|
-
: {
|
|
238
|
+
: {
|
|
239
|
+
onEscape: helpOpen ? () => setHelpOpen(false) : undefined,
|
|
240
|
+
// Only on a genuinely empty composer, so a message that opens with a question mark still types.
|
|
241
|
+
onKeyDown: (e: KeyboardEvent<HTMLDivElement>) => {
|
|
242
|
+
if (e.key === '?' && isEmpty && !e.metaKey && !e.ctrlKey) {
|
|
243
|
+
e.preventDefault()
|
|
244
|
+
setHelpOpen(true)
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
if (helpOpen) {
|
|
248
|
+
setHelpOpen(false)
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const helpHints = helpOpen ? (
|
|
254
|
+
<>
|
|
255
|
+
{hints.map((hint) => (
|
|
256
|
+
<span key={hint.key} className="flex items-center gap-1">
|
|
257
|
+
<kbd className="font-mono text-text">{hint.key}</kbd>
|
|
258
|
+
{hint.what}
|
|
259
|
+
</span>
|
|
260
|
+
))}
|
|
261
|
+
</>
|
|
262
|
+
) : null
|
|
263
|
+
const helpRow = helpOpen ? (
|
|
264
|
+
<div
|
|
265
|
+
role="note"
|
|
266
|
+
aria-label="Composer shortcuts"
|
|
267
|
+
className="mx-auto mb-1 flex w-full max-w-[var(--wd-transcript-max-width)] flex-wrap items-center gap-x-3 gap-y-1 text-label text-fg-3"
|
|
268
|
+
>
|
|
269
|
+
{helpHints}
|
|
270
|
+
</div>
|
|
271
|
+
) : null
|
|
277
272
|
|
|
278
273
|
const pick = (files: FileList | null) => {
|
|
279
274
|
if (files && files.length > 0) {
|
|
@@ -405,6 +400,12 @@ export function Composer({
|
|
|
405
400
|
{submitButton}
|
|
406
401
|
</div>
|
|
407
402
|
</div>
|
|
403
|
+
{helpOpen ? (
|
|
404
|
+
<div role="note" aria-label="Composer shortcuts" className="term-row">
|
|
405
|
+
<span aria-hidden className="term-gutter" />
|
|
406
|
+
<div className="flex min-w-0 flex-wrap items-center gap-x-[2ch] text-fg-3">{helpHints}</div>
|
|
407
|
+
</div>
|
|
408
|
+
) : null}
|
|
408
409
|
{shellMode ? (
|
|
409
410
|
<div className="term-row">
|
|
410
411
|
<span aria-hidden className="term-gutter" />
|
|
@@ -425,6 +426,7 @@ export function Composer({
|
|
|
425
426
|
|
|
426
427
|
return (
|
|
427
428
|
<div data-slot="composer" className={cn('px-[var(--wd-composer-padding)] pb-[var(--wd-composer-padding)]', className)}>
|
|
429
|
+
{helpRow}
|
|
428
430
|
<div
|
|
429
431
|
{...dropHandlers}
|
|
430
432
|
className={cn(
|
|
@@ -544,7 +546,7 @@ function AttachmentChip({ item, onRetry, onRemove }: { item: StagedAttachment; o
|
|
|
544
546
|
const terminal = useTranscriptVariant() === 'terminal'
|
|
545
547
|
const round = terminal ? '' : 'rounded-md'
|
|
546
548
|
return (
|
|
547
|
-
<div className="group relative shrink-0" title={failed ? `${item.name}
|
|
549
|
+
<div className="group relative shrink-0" title={failed ? `${item.name} - ${item.error}` : `${item.name} · ${formatBytes(item.bytes)}`}>
|
|
548
550
|
<div
|
|
549
551
|
className={cn(
|
|
550
552
|
'flex size-14 items-center justify-center overflow-hidden bg-surface',
|
|
@@ -23,7 +23,7 @@ export function ContextDialog({ usage, engine, open, onOpenChange, className }:
|
|
|
23
23
|
<DialogBody>
|
|
24
24
|
{!usage ? (
|
|
25
25
|
<p className="py-6 text-center text-body-sm text-fg-4">
|
|
26
|
-
No reading yet
|
|
26
|
+
No reading yet - the context window is measured after a turn completes.
|
|
27
27
|
</p>
|
|
28
28
|
) : (
|
|
29
29
|
<>
|
|
@@ -2,7 +2,7 @@ import { File, Folder, Link2 } from 'lucide-react'
|
|
|
2
2
|
import type { HostDirEntry } from '@workerdeck/protocol'
|
|
3
3
|
|
|
4
4
|
// The icon for one host directory entry. A symlink is drawn as a link and never silently
|
|
5
|
-
// resolved
|
|
5
|
+
// resolved - the caller decides what a symlink means, the tree only says it is one.
|
|
6
6
|
export function EntryIcon({ type }: { type: HostDirEntry['type'] }) {
|
|
7
7
|
if (type === 'dir') {
|
|
8
8
|
return <Folder className="size-3.5 shrink-0 text-accent" />
|
|
@@ -147,7 +147,7 @@ export function FileTree({ tree, search, activePath, onOpenFile, onCollapse, sty
|
|
|
147
147
|
/>
|
|
148
148
|
{row.truncated ? (
|
|
149
149
|
<p className="truncate py-0.5 text-label text-fg-4" style={{ paddingLeft: (row.depth + 1) * INDENT + 22 }}>
|
|
150
|
-
More entries than the server will return
|
|
150
|
+
More entries than the server will return - use search.
|
|
151
151
|
</p>
|
|
152
152
|
) : null}
|
|
153
153
|
</li>
|
|
@@ -93,7 +93,7 @@ export function FileViewer({
|
|
|
93
93
|
<span className="text-label text-fg-4">Saving…</span>
|
|
94
94
|
</>
|
|
95
95
|
) : !canWrite ? (
|
|
96
|
-
<span className="text-label text-fg-4">Read-only
|
|
96
|
+
<span className="text-label text-fg-4">Read-only - this gateway doesn’t allow writes.</span>
|
|
97
97
|
) : (
|
|
98
98
|
<>
|
|
99
99
|
<span className="text-label text-fg-3">Unsaved changes</span>
|
|
@@ -84,7 +84,7 @@ export function HostFilesDialog({ client, cwd, open, onOpenChange }: HostFilesDi
|
|
|
84
84
|
setFile({
|
|
85
85
|
path: response.path,
|
|
86
86
|
bytes: response.bytes,
|
|
87
|
-
content: response.encoding === 'utf8' ? response.content : '(binary file
|
|
87
|
+
content: response.encoding === 'utf8' ? response.content : '(binary file - not shown)',
|
|
88
88
|
})
|
|
89
89
|
} catch (e) {
|
|
90
90
|
setError(e instanceof Error ? e.message : 'Could not read that file')
|
|
@@ -178,7 +178,7 @@ export function HostFilesDialog({ client, cwd, open, onOpenChange }: HostFilesDi
|
|
|
178
178
|
</ul>
|
|
179
179
|
)}
|
|
180
180
|
{truncated && !matches ? (
|
|
181
|
-
<p className="text-label text-fg-4">More entries than the server will return
|
|
181
|
+
<p className="text-label text-fg-4">More entries than the server will return - use the search box.</p>
|
|
182
182
|
) : null}
|
|
183
183
|
</>
|
|
184
184
|
)}
|
|
@@ -46,14 +46,14 @@ export const PERMISSION_MODES: PermissionModeMeta[] = [
|
|
|
46
46
|
value: 'dontAsk',
|
|
47
47
|
label: "Don't ask",
|
|
48
48
|
shortLabel: "Don't ask",
|
|
49
|
-
description: 'Never ask
|
|
49
|
+
description: 'Never ask - deny anything not pre-approved',
|
|
50
50
|
icon: ShieldCheck,
|
|
51
51
|
},
|
|
52
52
|
{
|
|
53
53
|
value: 'bypassPermissions',
|
|
54
54
|
label: 'Bypass permissions',
|
|
55
55
|
shortLabel: 'Bypass',
|
|
56
|
-
description: 'Skip every approval
|
|
56
|
+
description: 'Skip every approval - the agent is unsupervised',
|
|
57
57
|
icon: TriangleAlert,
|
|
58
58
|
dangerous: true,
|
|
59
59
|
},
|
|
@@ -96,8 +96,8 @@ export function PermissionPrompt({ request, onApprove, onDeny, className }: Perm
|
|
|
96
96
|
}}
|
|
97
97
|
placeholder={
|
|
98
98
|
plan
|
|
99
|
-
? 'What should change? (optional)
|
|
100
|
-
: 'Reason (optional)
|
|
99
|
+
? 'What should change? (optional) - the agent keeps planning and reads this'
|
|
100
|
+
: 'Reason (optional) - the agent reads this and can try something else'
|
|
101
101
|
}
|
|
102
102
|
className="h-7 flex-1"
|
|
103
103
|
/>
|
|
@@ -284,7 +284,7 @@ function SessionRowItem({
|
|
|
284
284
|
{onClearContext && info.capabilities?.clearContext ? (
|
|
285
285
|
<RowAction
|
|
286
286
|
label="Clear context"
|
|
287
|
-
title="Clear the conversation
|
|
287
|
+
title="Clear the conversation - the session keeps running and the old conversation stays resumable"
|
|
288
288
|
onClick={() => onClearContext(row)}
|
|
289
289
|
>
|
|
290
290
|
<Eraser className="size-3 text-fg-3" />
|
|
@@ -70,7 +70,7 @@ export function SessionItem({
|
|
|
70
70
|
const extras = [
|
|
71
71
|
showGateway ? row.hostName : undefined,
|
|
72
72
|
info.profile ? `@${info.profile}` : undefined,
|
|
73
|
-
cost === '
|
|
73
|
+
cost === '-' ? undefined : cost,
|
|
74
74
|
].filter((part): part is string => Boolean(part))
|
|
75
75
|
|
|
76
76
|
const model = friendlyModel(info.model)
|
|
@@ -162,7 +162,9 @@ export function SessionItem({
|
|
|
162
162
|
className={cn(
|
|
163
163
|
'flex h-4 min-w-6 shrink-0 items-center justify-center rounded-full px-2',
|
|
164
164
|
'text-[0.75rem] leading-none tracking-[-0.005em] tabular-nums',
|
|
165
|
-
|
|
165
|
+
// Grey while the turn is still producing, accent once it has stopped: the badge's colour
|
|
166
|
+
// answers "is this waiting for me", and a working session is not yet.
|
|
167
|
+
row.state === 'working' ? 'bg-badge text-badge-fg' : 'bg-accent text-accent-fg',
|
|
166
168
|
)}
|
|
167
169
|
>
|
|
168
170
|
{row.unseen}
|