@workerdeck/ui 2.8.0 → 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/build/{SessionPanel-Dg4xf-ql.mjs → SessionPanel-BClL7zUS.mjs} +649 -368
- package/build/SessionPanel-BClL7zUS.mjs.map +1 -0
- package/build/{SessionPanel-_lQLhWOw.d.mts → SessionPanel-BVbau2eR.d.mts} +68 -4
- package/build/index.d.mts +11 -4
- package/build/index.mjs +3 -3
- package/build/index.mjs.map +1 -1
- package/build/scoped.css +24 -0
- package/build/workspace.d.mts +3 -2
- package/build/workspace.mjs +4 -5
- package/build/workspace.mjs.map +1 -1
- package/package.json +4 -4
- package/src/components/agent/Composer.tsx +84 -82
- package/src/components/agent/SessionItem.tsx +3 -1
- package/src/components/agent/SessionPanel.tsx +304 -224
- package/src/components/agent/SessionWorkspace.tsx +3 -0
- 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/terminal/file-link.tsx +20 -0
- package/src/components/terminal/markdown.tsx +31 -5
- package/src/index.ts +11 -1
- package/src/lib/file-link.ts +70 -0
- package/src/styles/terminal.css +15 -0
- package/build/SessionPanel-Dg4xf-ql.mjs.map +0 -1
|
@@ -40,7 +40,15 @@ import { ChartPie, FolderTree, Gauge, Info, MoreHorizontal, Plug, Sparkles, Tria
|
|
|
40
40
|
import { cn } from '../../lib/utils.ts'
|
|
41
41
|
import { Button } from '../ui/Button.tsx'
|
|
42
42
|
import { Menu, MenuContent, MenuItem, MenuTrigger } from '../ui/Menu.tsx'
|
|
43
|
-
import { Composer,
|
|
43
|
+
import { Composer, type ComposerHandle } from './Composer.tsx'
|
|
44
|
+
import {
|
|
45
|
+
composerCommandRows,
|
|
46
|
+
matchClientCommand,
|
|
47
|
+
mergeComposerRows,
|
|
48
|
+
skillPrompt,
|
|
49
|
+
type ClientCommand,
|
|
50
|
+
type ComposerCommandRow,
|
|
51
|
+
} from './composer-commands.ts'
|
|
44
52
|
import { ContextDialog } from './ContextDialog.tsx'
|
|
45
53
|
import { TasksDialog } from './TasksDialog.tsx'
|
|
46
54
|
import { HostFilesDialog } from './HostFilesDialog.tsx'
|
|
@@ -56,6 +64,7 @@ import { TerminalPermissionPrompt } from '../terminal/PermissionPrompt.tsx'
|
|
|
56
64
|
import { TerminalQuestionPrompt } from '../terminal/QuestionPrompt.tsx'
|
|
57
65
|
import { TerminalSurface } from '../terminal/surface.tsx'
|
|
58
66
|
import { BookmarkProvider, type BookmarkHandle, type TerminalAffordances } from '../terminal/affordances.tsx'
|
|
67
|
+
import { FileLinkProvider, type FileLinkOpener } from '../terminal/file-link.tsx'
|
|
59
68
|
|
|
60
69
|
import { SessionInfoDialog } from './SessionInfoDialog.tsx'
|
|
61
70
|
import { StatusBar } from './StatusBar.tsx'
|
|
@@ -125,6 +134,8 @@ export interface SessionPanelProps {
|
|
|
125
134
|
// Bookmarked transcript item ids - the host owns membership and persistence.
|
|
126
135
|
bookmarks?: readonly string[]
|
|
127
136
|
onToggleBookmark?: (itemId: string) => void
|
|
137
|
+
// Where a file link in the transcript goes. Without it, such a link stays an ordinary anchor.
|
|
138
|
+
onOpenFile?: FileLinkOpener
|
|
128
139
|
reveal?: { toolUseId: string; nonce: number }
|
|
129
140
|
openSubagent?: { toolUseId: string; nonce: number }
|
|
130
141
|
onSubagentChange?: (toolUseId: string | undefined) => void
|
|
@@ -185,6 +196,8 @@ export type SessionVitals = {
|
|
|
185
196
|
permissionMode: TranscriptState['permissionMode']
|
|
186
197
|
permissionModes: PermissionModeChoice[]
|
|
187
198
|
skills: SkillInfo[] | undefined
|
|
199
|
+
// Everything `/` offers this session, flattened for a native host that cannot run the composer's own merge.
|
|
200
|
+
composerCommands: ComposerCommandRow[]
|
|
188
201
|
cwd: TranscriptState['cwd']
|
|
189
202
|
contextUsage: TranscriptState['contextUsage']
|
|
190
203
|
tasks: SessionTask[]
|
|
@@ -230,6 +243,7 @@ export function SessionPanel({
|
|
|
230
243
|
cacheTranscript,
|
|
231
244
|
emptyState,
|
|
232
245
|
onLinkClick,
|
|
246
|
+
onOpenFile,
|
|
233
247
|
fontSize,
|
|
234
248
|
className,
|
|
235
249
|
}: SessionPanelProps) {
|
|
@@ -258,6 +272,7 @@ export function SessionPanel({
|
|
|
258
272
|
approve,
|
|
259
273
|
deny,
|
|
260
274
|
interrupt,
|
|
275
|
+
clearContext,
|
|
261
276
|
setModel,
|
|
262
277
|
setPermissionMode,
|
|
263
278
|
reconnectNow,
|
|
@@ -334,6 +349,79 @@ export function SessionPanel({
|
|
|
334
349
|
[state.checklist, subagents, state.session?.subagents],
|
|
335
350
|
)
|
|
336
351
|
|
|
352
|
+
const clientCommands = useMemo((): ClientCommand[] => {
|
|
353
|
+
const modes = capabilities.permissionModes
|
|
354
|
+
const built: ClientCommand[] = []
|
|
355
|
+
if (models.length > 0) {
|
|
356
|
+
built.push({
|
|
357
|
+
name: 'model',
|
|
358
|
+
description: 'Switch the model for this session',
|
|
359
|
+
argumentHint: '<model>',
|
|
360
|
+
requiresArgs: true,
|
|
361
|
+
run: (args) => {
|
|
362
|
+
const wanted = args.split(/\s+/)[0]
|
|
363
|
+
if (!wanted) {
|
|
364
|
+
return false
|
|
365
|
+
}
|
|
366
|
+
setModel(wanted)
|
|
367
|
+
return true
|
|
368
|
+
},
|
|
369
|
+
})
|
|
370
|
+
}
|
|
371
|
+
if (modes.length > 1) {
|
|
372
|
+
built.push({
|
|
373
|
+
name: 'permissions',
|
|
374
|
+
description: `Set the permission mode (${modes.join(', ')})`,
|
|
375
|
+
argumentHint: '<mode>',
|
|
376
|
+
requiresArgs: true,
|
|
377
|
+
run: (args) => {
|
|
378
|
+
const wanted = args.split(/\s+/)[0] as PermissionMode
|
|
379
|
+
if (!modes.includes(wanted)) {
|
|
380
|
+
return false
|
|
381
|
+
}
|
|
382
|
+
setPermissionMode(wanted)
|
|
383
|
+
return true
|
|
384
|
+
},
|
|
385
|
+
})
|
|
386
|
+
}
|
|
387
|
+
if (capabilities.clearContext) {
|
|
388
|
+
built.push({
|
|
389
|
+
name: 'clear',
|
|
390
|
+
description: 'Clear the conversation - the session keeps running and the old one stays resumable',
|
|
391
|
+
run: () => {
|
|
392
|
+
clearContext()
|
|
393
|
+
return true
|
|
394
|
+
},
|
|
395
|
+
})
|
|
396
|
+
}
|
|
397
|
+
if (capabilities.mcpStatus) {
|
|
398
|
+
built.push({ name: 'mcp', description: 'MCP servers and their status', run: () => (openPanel('mcp'), true) })
|
|
399
|
+
}
|
|
400
|
+
if (capabilities.contextUsage) {
|
|
401
|
+
built.push({ name: 'context', description: 'Context window usage', run: () => (openPanel('context'), true) })
|
|
402
|
+
}
|
|
403
|
+
built.push({ name: 'status', description: 'Session details', run: () => (openPanel('info'), true) })
|
|
404
|
+
if (capabilities.rateLimits) {
|
|
405
|
+
built.push({ name: 'usage', description: 'Rate limits and spend', run: () => (openPanel('usage'), true) })
|
|
406
|
+
}
|
|
407
|
+
if (capabilities.skillsList) {
|
|
408
|
+
built.push({ name: 'skills', description: 'Browse the skills this session can use', run: () => (openPanel('skills'), true) })
|
|
409
|
+
}
|
|
410
|
+
return built
|
|
411
|
+
}, [capabilities, models.length, openPanel, clearContext, setModel, setPermissionMode])
|
|
412
|
+
|
|
413
|
+
const composerCommands = useMemo(
|
|
414
|
+
() =>
|
|
415
|
+
composerCommandRows(
|
|
416
|
+
mergeComposerRows({
|
|
417
|
+
commands: capabilities.slashCommands ? state.commands : undefined,
|
|
418
|
+
clientCommands,
|
|
419
|
+
skills: capabilities.skillsList ? state.skills : undefined,
|
|
420
|
+
}),
|
|
421
|
+
),
|
|
422
|
+
[capabilities.slashCommands, capabilities.skillsList, state.commands, state.skills, clientCommands],
|
|
423
|
+
)
|
|
424
|
+
|
|
337
425
|
const onVitalsRef = useRef(onVitals)
|
|
338
426
|
onVitalsRef.current = onVitals
|
|
339
427
|
const vitalsModel = effectiveModel ?? state.model
|
|
@@ -352,6 +440,7 @@ export function SessionPanel({
|
|
|
352
440
|
permissionMode: state.permissionMode,
|
|
353
441
|
permissionModes,
|
|
354
442
|
skills: state.skills,
|
|
443
|
+
composerCommands,
|
|
355
444
|
cwd: state.cwd,
|
|
356
445
|
contextUsage: state.contextUsage,
|
|
357
446
|
tasks,
|
|
@@ -370,6 +459,7 @@ export function SessionPanel({
|
|
|
370
459
|
state.permissionMode,
|
|
371
460
|
permissionModes,
|
|
372
461
|
state.skills,
|
|
462
|
+
composerCommands,
|
|
373
463
|
state.cwd,
|
|
374
464
|
state.contextUsage,
|
|
375
465
|
tasks,
|
|
@@ -423,25 +513,10 @@ export function SessionPanel({
|
|
|
423
513
|
const jumpToRecap = useRef<(() => void) | null>(null)
|
|
424
514
|
const repinTranscript = useRef<(() => void) | null>(null)
|
|
425
515
|
|
|
426
|
-
const commands = useMemo(() => {
|
|
427
|
-
if (!state.commands) {
|
|
428
|
-
return undefined
|
|
429
|
-
}
|
|
430
|
-
if (state.commands.some((c) => c.name === 'model')) {
|
|
431
|
-
return state.commands
|
|
432
|
-
}
|
|
433
|
-
return [{ name: 'model', description: 'Switch the model for this session', argumentHint: '<model>' }, ...state.commands]
|
|
434
|
-
}, [state.commands])
|
|
435
|
-
|
|
436
516
|
const handleSend = (text: string, attachmentIds: string[]) => {
|
|
437
517
|
if (attachmentIds.length === 0) {
|
|
438
|
-
const
|
|
439
|
-
if (
|
|
440
|
-
setModel(modelCommand[1])
|
|
441
|
-
return
|
|
442
|
-
}
|
|
443
|
-
if (capabilities.mcpStatus && text.trim() === '/mcp') {
|
|
444
|
-
openPanel('mcp')
|
|
518
|
+
const local = matchClientCommand(text, clientCommands)
|
|
519
|
+
if (local && local.command.run(local.args)) {
|
|
445
520
|
return
|
|
446
521
|
}
|
|
447
522
|
}
|
|
@@ -535,6 +610,8 @@ export function SessionPanel({
|
|
|
535
610
|
/>
|
|
536
611
|
)
|
|
537
612
|
|
|
613
|
+
const fileLinks = useMemo(() => (onOpenFile ? { cwd: state.cwd, open: onOpenFile } : undefined), [onOpenFile, state.cwd])
|
|
614
|
+
|
|
538
615
|
const panelRef = useRef<HTMLDivElement>(null)
|
|
539
616
|
useEffect(() => {
|
|
540
617
|
if (!onLinkClick) {
|
|
@@ -579,222 +656,225 @@ export function SessionPanel({
|
|
|
579
656
|
return (
|
|
580
657
|
<TranscriptVariantProvider value={transcriptVariant}>
|
|
581
658
|
<TranscriptDensityProvider value={transcriptDensity}>
|
|
582
|
-
<
|
|
583
|
-
<
|
|
584
|
-
<
|
|
585
|
-
<
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
{
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
fontSize={effectiveTermFontSize}
|
|
613
|
-
lineHeight={effectiveTermLineHeight}
|
|
614
|
-
/>
|
|
615
|
-
) : null}
|
|
616
|
-
<BookmarkProvider value={bookmarkHandle}>
|
|
617
|
-
<Transcript
|
|
618
|
-
key={subagentId ?? 'session'}
|
|
619
|
-
state={state}
|
|
620
|
-
fileUrl={sessionId ? (path) => client.sessionFileUrl(sessionId, path) : undefined}
|
|
621
|
-
attachmentUrl={sessionId ? (id) => client.attachmentUrl(sessionId, id) : undefined}
|
|
622
|
-
canBrowseFiles={hostFiles.available}
|
|
623
|
-
hostImage={hostImage}
|
|
624
|
-
variant={transcriptVariant}
|
|
625
|
-
density={transcriptDensity}
|
|
626
|
-
fontSize={effectiveTermFontSize}
|
|
627
|
-
lineHeight={effectiveTermLineHeight}
|
|
628
|
-
affordances={affordances}
|
|
629
|
-
stickyPrompt={stickyPrompt}
|
|
630
|
-
scrubber={scrubber}
|
|
631
|
-
bookmarks={bookmarks}
|
|
632
|
-
replaying={replaying}
|
|
633
|
-
catchUp={catchUp && newCount > 0 ? { from: catchUp.itemCount, since: catchUp.since } : undefined}
|
|
634
|
-
reveal={taskReveal ?? returnReveal ?? reveal}
|
|
635
|
-
frame={subagentId === undefined ? undefined : { parentToolUseId: subagentId }}
|
|
636
|
-
onOpenSubagent={enterSubagent}
|
|
637
|
-
emptyState={emptyState}
|
|
638
|
-
jumpToRecapRef={jumpToRecap}
|
|
639
|
-
repinRef={repinTranscript}
|
|
640
|
-
/>
|
|
641
|
-
</BookmarkProvider>
|
|
642
|
-
{catchUp && newCount > 0 && !replaying && subagentId === undefined ? (
|
|
643
|
-
<div className="px-3 pb-1">
|
|
644
|
-
<div
|
|
645
|
-
data-slot="catch-up"
|
|
646
|
-
className="mx-auto flex w-full max-w-[var(--wd-transcript-max-width)] items-center gap-2 text-label text-fg-3"
|
|
647
|
-
>
|
|
648
|
-
<span aria-hidden className={cn('select-none', terminal ? 'text-fg-3' : 'text-accent')}>
|
|
649
|
-
※
|
|
650
|
-
</span>
|
|
651
|
-
<span className="min-w-0 flex-1 truncate">
|
|
652
|
-
{newCount} new {newCount === 1 ? 'row' : 'rows'}
|
|
653
|
-
{catchUp.since !== undefined ? ` since you were last here` : ''}
|
|
654
|
-
</span>
|
|
655
|
-
<button
|
|
656
|
-
type="button"
|
|
657
|
-
onClick={() => jumpToRecap.current?.()}
|
|
658
|
-
className="shrink-0 underline-offset-2 hover:text-fg-1 hover:underline"
|
|
659
|
-
>
|
|
660
|
-
jump
|
|
661
|
-
</button>
|
|
662
|
-
<button
|
|
663
|
-
type="button"
|
|
664
|
-
onClick={() => setCaughtUp(true)}
|
|
665
|
-
className="shrink-0 underline-offset-2 hover:text-fg-1 hover:underline"
|
|
666
|
-
>
|
|
667
|
-
dismiss
|
|
668
|
-
</button>
|
|
669
|
-
</div>
|
|
670
|
-
</div>
|
|
671
|
-
) : null}
|
|
672
|
-
{!readOnly && capabilities.interactiveApprovals && state.pendingApprovals.length > 0 ? (
|
|
673
|
-
<div className={cn(terminal ? 'pb-2' : 'px-3 pb-2')}>
|
|
674
|
-
<PromptSurface
|
|
659
|
+
<FileLinkProvider value={fileLinks}>
|
|
660
|
+
<ToolResultFetchProvider value={loadFullResult}>
|
|
661
|
+
<ToolTitleProvider value={state.toolTitles}>
|
|
662
|
+
<ToolResultImageProvider value={resultImages}>
|
|
663
|
+
<div
|
|
664
|
+
ref={panelRef}
|
|
665
|
+
data-slot="session-panel"
|
|
666
|
+
data-agent-font={transcriptFont}
|
|
667
|
+
onClick={handleClick}
|
|
668
|
+
className={cn('flex h-full min-h-0 flex-col overflow-hidden bg-bg', className)}
|
|
669
|
+
style={fontSize !== undefined ? ({ '--wd-font-size': `${Math.round(fontSize)}px` } as React.CSSProperties) : undefined}
|
|
670
|
+
>
|
|
671
|
+
{headerTakesActions ? header({ actions: menu }) : header}
|
|
672
|
+
{statusPlacement === 'top' ? statusBar : null}
|
|
673
|
+
{protocolMismatch !== undefined ? (
|
|
674
|
+
<Notice level="warning">
|
|
675
|
+
Server speaks protocol v{protocolMismatch}, this build renders v{PROTOCOL_VERSION}. Some events may not render.
|
|
676
|
+
</Notice>
|
|
677
|
+
) : null}
|
|
678
|
+
{protocolError ? (
|
|
679
|
+
<Notice level="error" onDismiss={() => setProtocolError(undefined)}>
|
|
680
|
+
{protocolError}
|
|
681
|
+
</Notice>
|
|
682
|
+
) : null}
|
|
683
|
+
{subagentId !== undefined ? (
|
|
684
|
+
<SubagentStrip
|
|
685
|
+
task={subagentTask}
|
|
686
|
+
items={subagentFrameItems}
|
|
687
|
+
label={subagentFallbackLabel}
|
|
688
|
+
onBack={leaveSubagent}
|
|
675
689
|
terminal={terminal}
|
|
676
|
-
|
|
690
|
+
fontSize={effectiveTermFontSize}
|
|
691
|
+
lineHeight={effectiveTermLineHeight}
|
|
692
|
+
/>
|
|
693
|
+
) : null}
|
|
694
|
+
<BookmarkProvider value={bookmarkHandle}>
|
|
695
|
+
<Transcript
|
|
696
|
+
key={subagentId ?? 'session'}
|
|
697
|
+
state={state}
|
|
698
|
+
fileUrl={sessionId ? (path) => client.sessionFileUrl(sessionId, path) : undefined}
|
|
699
|
+
attachmentUrl={sessionId ? (id) => client.attachmentUrl(sessionId, id) : undefined}
|
|
700
|
+
canBrowseFiles={hostFiles.available}
|
|
701
|
+
hostImage={hostImage}
|
|
702
|
+
variant={transcriptVariant}
|
|
703
|
+
density={transcriptDensity}
|
|
704
|
+
fontSize={effectiveTermFontSize}
|
|
705
|
+
lineHeight={effectiveTermLineHeight}
|
|
677
706
|
affordances={affordances}
|
|
678
|
-
|
|
679
|
-
{
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
707
|
+
stickyPrompt={stickyPrompt}
|
|
708
|
+
scrubber={scrubber}
|
|
709
|
+
bookmarks={bookmarks}
|
|
710
|
+
replaying={replaying}
|
|
711
|
+
catchUp={catchUp && newCount > 0 ? { from: catchUp.itemCount, since: catchUp.since } : undefined}
|
|
712
|
+
reveal={taskReveal ?? returnReveal ?? reveal}
|
|
713
|
+
frame={subagentId === undefined ? undefined : { parentToolUseId: subagentId }}
|
|
714
|
+
onOpenSubagent={enterSubagent}
|
|
715
|
+
emptyState={emptyState}
|
|
716
|
+
jumpToRecapRef={jumpToRecap}
|
|
717
|
+
repinRef={repinTranscript}
|
|
718
|
+
/>
|
|
719
|
+
</BookmarkProvider>
|
|
720
|
+
{catchUp && newCount > 0 && !replaying && subagentId === undefined ? (
|
|
721
|
+
<div className="px-3 pb-1">
|
|
722
|
+
<div
|
|
723
|
+
data-slot="catch-up"
|
|
724
|
+
className="mx-auto flex w-full max-w-[var(--wd-transcript-max-width)] items-center gap-2 text-label text-fg-3"
|
|
725
|
+
>
|
|
726
|
+
<span aria-hidden className={cn('select-none', terminal ? 'text-fg-3' : 'text-accent')}>
|
|
727
|
+
※
|
|
728
|
+
</span>
|
|
729
|
+
<span className="min-w-0 flex-1 truncate">
|
|
730
|
+
{newCount} new {newCount === 1 ? 'row' : 'rows'}
|
|
731
|
+
{catchUp.since !== undefined ? ` since you were last here` : ''}
|
|
732
|
+
</span>
|
|
733
|
+
<button
|
|
734
|
+
type="button"
|
|
735
|
+
onClick={() => jumpToRecap.current?.()}
|
|
736
|
+
className="shrink-0 underline-offset-2 hover:text-fg-1 hover:underline"
|
|
737
|
+
>
|
|
738
|
+
jump
|
|
739
|
+
</button>
|
|
740
|
+
<button
|
|
741
|
+
type="button"
|
|
742
|
+
onClick={() => setCaughtUp(true)}
|
|
743
|
+
className="shrink-0 underline-offset-2 hover:text-fg-1 hover:underline"
|
|
744
|
+
>
|
|
745
|
+
dismiss
|
|
746
|
+
</button>
|
|
747
|
+
</div>
|
|
748
|
+
</div>
|
|
749
|
+
) : null}
|
|
750
|
+
{!readOnly && capabilities.interactiveApprovals && state.pendingApprovals.length > 0 ? (
|
|
751
|
+
<div className={cn(terminal ? 'pb-2' : 'px-3 pb-2')}>
|
|
752
|
+
<PromptSurface
|
|
753
|
+
terminal={terminal}
|
|
754
|
+
metrics={{ fontSize: effectiveTermFontSize, lineHeight: effectiveTermLineHeight }}
|
|
755
|
+
affordances={affordances}
|
|
756
|
+
>
|
|
757
|
+
{state.pendingApprovals.map((request) => {
|
|
758
|
+
// Before the variant split: a host's entry is the renderer for its tool in
|
|
759
|
+
// both themes, and it overrides the built-in entries rather than racing them.
|
|
760
|
+
const HostPrompt = approvalPrompts?.[request.toolName]
|
|
761
|
+
if (HostPrompt) {
|
|
762
|
+
return <HostPrompt key={request.id} request={request} onApprove={approve} onDeny={deny} />
|
|
763
|
+
}
|
|
764
|
+
const isQuestion = request.toolName === 'AskUserQuestion' && parseUserQuestions(request.input).length > 0
|
|
765
|
+
if (terminal) {
|
|
766
|
+
return isQuestion ? (
|
|
767
|
+
<TerminalQuestionPrompt
|
|
768
|
+
key={request.id}
|
|
769
|
+
request={request}
|
|
770
|
+
onAnswer={approve}
|
|
771
|
+
onDismiss={(id) => deny(id, 'Question dismissed by user')}
|
|
772
|
+
/>
|
|
773
|
+
) : (
|
|
774
|
+
<TerminalPermissionPrompt key={request.id} request={request} onApprove={approve} onDeny={deny} />
|
|
775
|
+
)
|
|
776
|
+
}
|
|
688
777
|
return isQuestion ? (
|
|
689
|
-
<
|
|
778
|
+
<QuestionPrompt
|
|
690
779
|
key={request.id}
|
|
691
780
|
request={request}
|
|
692
781
|
onAnswer={approve}
|
|
693
782
|
onDismiss={(id) => deny(id, 'Question dismissed by user')}
|
|
694
783
|
/>
|
|
695
784
|
) : (
|
|
696
|
-
<
|
|
785
|
+
<PermissionPrompt key={request.id} request={request} onApprove={approve} onDeny={deny} />
|
|
697
786
|
)
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
open={panel === 'files'}
|
|
790
|
-
onOpenChange={(next) => setPanel(next ? 'files' : undefined)}
|
|
791
|
-
/>
|
|
792
|
-
</>
|
|
793
|
-
) : null}
|
|
794
|
-
</div>
|
|
795
|
-
</ToolResultImageProvider>
|
|
796
|
-
</ToolTitleProvider>
|
|
797
|
-
</ToolResultFetchProvider>
|
|
787
|
+
})}
|
|
788
|
+
</PromptSurface>
|
|
789
|
+
</div>
|
|
790
|
+
) : null}
|
|
791
|
+
{readOnly || subagentId !== undefined ? null : (
|
|
792
|
+
<>
|
|
793
|
+
<Composer
|
|
794
|
+
ref={composerRef}
|
|
795
|
+
onSend={handleSend}
|
|
796
|
+
onInterrupt={interrupt}
|
|
797
|
+
busy={busy}
|
|
798
|
+
disabled={ended || !sessionId}
|
|
799
|
+
commands={capabilities.slashCommands ? state.commands : undefined}
|
|
800
|
+
skills={capabilities.skillsList ? state.skills : undefined}
|
|
801
|
+
clientCommands={clientCommands}
|
|
802
|
+
attachments={attachments}
|
|
803
|
+
draft={draft}
|
|
804
|
+
onSearchFiles={hostFiles.available ? searchComposerFiles : undefined}
|
|
805
|
+
onShellCommand={shell ? runShell : undefined}
|
|
806
|
+
layout={controlsExternal ? 'inline' : 'stacked'}
|
|
807
|
+
toolbar={controlsExternal ? undefined : sessionControls}
|
|
808
|
+
fontSize={effectiveTermFontSize}
|
|
809
|
+
lineHeight={effectiveTermLineHeight}
|
|
810
|
+
affordances={affordances}
|
|
811
|
+
/>
|
|
812
|
+
</>
|
|
813
|
+
)}
|
|
814
|
+
{statusPlacement === 'bottom' ? statusBar : null}
|
|
815
|
+
|
|
816
|
+
{!external ? (
|
|
817
|
+
<>
|
|
818
|
+
<SessionInfoDialog
|
|
819
|
+
state={state}
|
|
820
|
+
client={client}
|
|
821
|
+
sessionId={sessionId}
|
|
822
|
+
open={panel === 'info'}
|
|
823
|
+
onOpenChange={(next) => setPanel(next ? 'info' : undefined)}
|
|
824
|
+
/>
|
|
825
|
+
<ContextDialog
|
|
826
|
+
usage={state.contextUsage}
|
|
827
|
+
engine={state.engine ?? 'claude'}
|
|
828
|
+
open={panel === 'context'}
|
|
829
|
+
onOpenChange={(next) => setPanel(next ? 'context' : undefined)}
|
|
830
|
+
/>
|
|
831
|
+
<UsageDialog
|
|
832
|
+
rateLimits={windows}
|
|
833
|
+
subscriptionType={state.subscriptionType}
|
|
834
|
+
engine={state.engine ?? 'claude'}
|
|
835
|
+
totalCostUsd={state.totalCostUsd}
|
|
836
|
+
updatedAt={usageUpdatedAt}
|
|
837
|
+
open={panel === 'usage'}
|
|
838
|
+
onOpenChange={(next) => setPanel(next ? 'usage' : undefined)}
|
|
839
|
+
/>
|
|
840
|
+
<McpDialog
|
|
841
|
+
client={client}
|
|
842
|
+
sessionId={sessionId}
|
|
843
|
+
canManageServers={capabilities.mcpServerActions}
|
|
844
|
+
open={panel === 'mcp'}
|
|
845
|
+
onOpenChange={(next) => setPanel(next ? 'mcp' : undefined)}
|
|
846
|
+
/>
|
|
847
|
+
<TasksDialog
|
|
848
|
+
tasks={tasks}
|
|
849
|
+
showCompleted={showCompletedTasks}
|
|
850
|
+
onShowCompletedChange={setShowCompletedTasks}
|
|
851
|
+
onSelectTask={(task) => {
|
|
852
|
+
if (task.toolUseId) {
|
|
853
|
+
setTaskReveal({ toolUseId: task.toolUseId, nonce: Date.now() })
|
|
854
|
+
}
|
|
855
|
+
}}
|
|
856
|
+
open={panel === 'tasks'}
|
|
857
|
+
onOpenChange={(next) => setPanel(next ? 'tasks' : undefined)}
|
|
858
|
+
/>
|
|
859
|
+
<SkillsDialog
|
|
860
|
+
skills={state.skills}
|
|
861
|
+
open={panel === 'skills'}
|
|
862
|
+
onOpenChange={(next) => setPanel(next ? 'skills' : undefined)}
|
|
863
|
+
onUse={(skill) => composerRef.current?.insertText(skillPrompt(skill))}
|
|
864
|
+
/>
|
|
865
|
+
<HostFilesDialog
|
|
866
|
+
client={client}
|
|
867
|
+
cwd={state.cwd}
|
|
868
|
+
open={panel === 'files'}
|
|
869
|
+
onOpenChange={(next) => setPanel(next ? 'files' : undefined)}
|
|
870
|
+
/>
|
|
871
|
+
</>
|
|
872
|
+
) : null}
|
|
873
|
+
</div>
|
|
874
|
+
</ToolResultImageProvider>
|
|
875
|
+
</ToolTitleProvider>
|
|
876
|
+
</ToolResultFetchProvider>
|
|
877
|
+
</FileLinkProvider>
|
|
798
878
|
</TranscriptDensityProvider>
|
|
799
879
|
</TranscriptVariantProvider>
|
|
800
880
|
)
|
|
@@ -31,6 +31,7 @@ export interface SessionWorkspaceProps {
|
|
|
31
31
|
controlsSurface?: SessionPanelProps['controlsSurface']
|
|
32
32
|
fontSize?: SessionPanelProps['fontSize']
|
|
33
33
|
onLinkClick?: SessionPanelProps['onLinkClick']
|
|
34
|
+
onOpenFile?: SessionPanelProps['onOpenFile']
|
|
34
35
|
unseen?: SessionPanelProps['unseen']
|
|
35
36
|
readOnly?: SessionPanelProps['readOnly']
|
|
36
37
|
onVitals?: SessionPanelProps['onVitals']
|
|
@@ -64,6 +65,7 @@ export function SessionWorkspace({
|
|
|
64
65
|
controlsSurface,
|
|
65
66
|
fontSize,
|
|
66
67
|
onLinkClick,
|
|
68
|
+
onOpenFile,
|
|
67
69
|
unseen,
|
|
68
70
|
readOnly,
|
|
69
71
|
onVitals,
|
|
@@ -222,6 +224,7 @@ export function SessionWorkspace({
|
|
|
222
224
|
controlsSurface={controlsSurface}
|
|
223
225
|
fontSize={fontSize}
|
|
224
226
|
onLinkClick={onLinkClick}
|
|
227
|
+
onOpenFile={onOpenFile ?? files.open}
|
|
225
228
|
statusPlacement={statusPlacement}
|
|
226
229
|
unseen={unseen}
|
|
227
230
|
readOnly={readOnly}
|