@workerdeck/ui 2.0.0 → 2.2.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 +4 -4
- package/build/{SessionPanel-CE5AVkg6.mjs → SessionPanel-ErvQUyHe.mjs} +105 -92
- package/build/SessionPanel-ErvQUyHe.mjs.map +1 -0
- package/build/{SessionPanel-CUpq2yO-.d.mts → SessionPanel-My98j9RZ.d.mts} +2 -3
- package/build/index.d.mts +16 -23
- package/build/index.mjs +2 -2
- package/build/scoped.css +23 -4
- package/build/workspace.d.mts +2 -3
- package/build/workspace.mjs +2 -3
- package/build/workspace.mjs.map +1 -1
- package/package.json +4 -4
- package/src/components/agent/Composer.tsx +102 -17
- package/src/components/agent/SessionPanel.tsx +4 -9
- package/src/components/agent/SessionWorkspace.tsx +0 -3
- package/src/components/prompt-area/index.ts +1 -1
- package/src/components/prompt-area/trigger-presets.ts +24 -0
- package/src/index.ts +1 -1
- package/src/styles/terminal.css +11 -0
- package/src/styles/theme.css +5 -0
- package/build/SessionPanel-CE5AVkg6.mjs.map +0 -1
- package/src/components/agent/held-sends.tsx +0 -73
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
useEffect,
|
|
3
|
+
useImperativeHandle,
|
|
4
|
+
useMemo,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
type DragEvent,
|
|
8
|
+
type KeyboardEvent,
|
|
9
|
+
type ReactNode,
|
|
10
|
+
type Ref,
|
|
11
|
+
} from 'react'
|
|
2
12
|
import type { SkillInfo, SlashCommandInfo } from '@workerdeck/protocol'
|
|
3
13
|
import type { StagedAttachment, UseAttachmentsResult } from '@workerdeck/react'
|
|
4
14
|
import { ArrowUp, FileText, Paperclip, RotateCw, Sparkles, Square, TriangleAlert, X } from 'lucide-react'
|
|
@@ -7,7 +17,7 @@ import { Spinner } from '../ui/Spinner.tsx'
|
|
|
7
17
|
import { PromptArea } from '../prompt-area/prompt-area.tsx'
|
|
8
18
|
import { usePromptAreaState } from '../prompt-area/use-prompt-area-state.ts'
|
|
9
19
|
import { plainTextToSegments } from '../prompt-area/prompt-area-engine.ts'
|
|
10
|
-
import { commandTrigger, mentionTrigger } from '../prompt-area/trigger-presets.ts'
|
|
20
|
+
import { commandTrigger, launchTrigger, mentionTrigger } from '../prompt-area/trigger-presets.ts'
|
|
11
21
|
import { useTranscriptVariant } from './transcript-variant.tsx'
|
|
12
22
|
import type { TerminalAffordances } from '../terminal/affordances.tsx'
|
|
13
23
|
import { PROMPT_GLYPH } from '../terminal/items.tsx'
|
|
@@ -18,6 +28,9 @@ import { formatBytes } from '../../lib/format.ts'
|
|
|
18
28
|
|
|
19
29
|
export type ComposerFileMatch = { path: string; relative: string }
|
|
20
30
|
|
|
31
|
+
const SHELL_PLACEHOLDER = 'Run a command on the host…'
|
|
32
|
+
const SHELL_HINT = '! shell mode · esc to exit'
|
|
33
|
+
|
|
21
34
|
export type ComposerHandle = {
|
|
22
35
|
insertText: (text: string) => void
|
|
23
36
|
focus: () => void
|
|
@@ -32,6 +45,9 @@ export interface ComposerProps {
|
|
|
32
45
|
commands?: SlashCommandInfo[]
|
|
33
46
|
skills?: SkillInfo[]
|
|
34
47
|
onSearchFiles?: (query: string, options: { signal: AbortSignal }) => Promise<ComposerFileMatch[]>
|
|
48
|
+
// Shell mode: `!` as the first character turns the composer into a host shell prompt. Omit to leave the
|
|
49
|
+
// mode off entirely — the gateway only offers it to an operator on a session whose engine reaches a host cwd.
|
|
50
|
+
onShellCommand?: (command: string) => void
|
|
35
51
|
attachments?: UseAttachmentsResult
|
|
36
52
|
toolbar?: ReactNode
|
|
37
53
|
layout?: 'stacked' | 'inline'
|
|
@@ -72,6 +88,7 @@ export function Composer({
|
|
|
72
88
|
commands,
|
|
73
89
|
skills,
|
|
74
90
|
onSearchFiles,
|
|
91
|
+
onShellCommand,
|
|
75
92
|
attachments,
|
|
76
93
|
toolbar,
|
|
77
94
|
layout = 'stacked',
|
|
@@ -91,6 +108,7 @@ export function Composer({
|
|
|
91
108
|
)
|
|
92
109
|
const fileInput = useRef<HTMLInputElement>(null)
|
|
93
110
|
const [dragging, setDragging] = useState(false)
|
|
111
|
+
const [shellMode, setShellMode] = useState(false)
|
|
94
112
|
|
|
95
113
|
useImperativeHandle(
|
|
96
114
|
ref,
|
|
@@ -196,8 +214,17 @@ export function Composer({
|
|
|
196
214
|
}),
|
|
197
215
|
)
|
|
198
216
|
}
|
|
217
|
+
if (onShellCommand) {
|
|
218
|
+
configured.push(
|
|
219
|
+
launchTrigger({
|
|
220
|
+
char: '!',
|
|
221
|
+
accessibilityLabel: 'shell mode',
|
|
222
|
+
onActivate: () => setShellMode(true),
|
|
223
|
+
}),
|
|
224
|
+
)
|
|
225
|
+
}
|
|
199
226
|
return configured.length > 0 ? configured : undefined
|
|
200
|
-
}, [commands, skills, onSearchFiles])
|
|
227
|
+
}, [commands, skills, onSearchFiles, onShellCommand])
|
|
201
228
|
|
|
202
229
|
const saveDraft = draft?.save
|
|
203
230
|
useEffect(() => {
|
|
@@ -211,6 +238,18 @@ export function Composer({
|
|
|
211
238
|
if (!canSend) {
|
|
212
239
|
return
|
|
213
240
|
}
|
|
241
|
+
if (shellMode) {
|
|
242
|
+
const command = plainText.trim()
|
|
243
|
+
if (command === '') {
|
|
244
|
+
return
|
|
245
|
+
}
|
|
246
|
+
onShellCommand?.(command)
|
|
247
|
+
setShellMode(false)
|
|
248
|
+
draft?.clear()
|
|
249
|
+
clear()
|
|
250
|
+
focus()
|
|
251
|
+
return
|
|
252
|
+
}
|
|
214
253
|
onSend(plainText.trim(), attachments?.readyIds ?? [])
|
|
215
254
|
attachments?.clear()
|
|
216
255
|
draft?.clear()
|
|
@@ -218,6 +257,24 @@ export function Composer({
|
|
|
218
257
|
focus()
|
|
219
258
|
}
|
|
220
259
|
|
|
260
|
+
const leaveShellMode = () => {
|
|
261
|
+
setShellMode(false)
|
|
262
|
+
focus()
|
|
263
|
+
}
|
|
264
|
+
// Escape and backspace-on-empty both leave, because both are what a person reaches for when the
|
|
265
|
+
// pink frame was not what they meant. Backspace only when there is nothing left to delete.
|
|
266
|
+
const shellKeys = shellMode
|
|
267
|
+
? {
|
|
268
|
+
onEscape: leaveShellMode,
|
|
269
|
+
onKeyDown: (e: KeyboardEvent<HTMLDivElement>) => {
|
|
270
|
+
if (e.key === 'Backspace' && isEmpty) {
|
|
271
|
+
e.preventDefault()
|
|
272
|
+
leaveShellMode()
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
}
|
|
276
|
+
: {}
|
|
277
|
+
|
|
221
278
|
const pick = (files: FileList | null) => {
|
|
222
279
|
if (files && files.length > 0) {
|
|
223
280
|
attachments?.add(files)
|
|
@@ -250,7 +307,11 @@ export function Composer({
|
|
|
250
307
|
</>
|
|
251
308
|
) : null
|
|
252
309
|
|
|
253
|
-
const gutter =
|
|
310
|
+
const gutter = shellMode ? (
|
|
311
|
+
<GlyphButton gutter label="Leave shell mode" tone="magenta" onClick={leaveShellMode}>
|
|
312
|
+
!
|
|
313
|
+
</GlyphButton>
|
|
314
|
+
) : busy ? (
|
|
254
315
|
<GlyphButton gutter label="Interrupt" tone="yellow" onClick={onInterrupt}>
|
|
255
316
|
✕
|
|
256
317
|
</GlyphButton>
|
|
@@ -318,12 +379,12 @@ export function Composer({
|
|
|
318
379
|
affordances={affordances}
|
|
319
380
|
bleed="1ch"
|
|
320
381
|
data-dragging={dragging || undefined}
|
|
321
|
-
className={cn('term-composer', disabled && 'opacity-60')}
|
|
382
|
+
className={cn('term-composer', shellMode && 'term-composer-shell', disabled && 'opacity-60')}
|
|
322
383
|
>
|
|
323
384
|
<div className="term-composer-body">
|
|
324
385
|
{staged.length > 0 && attachments ? <AttachmentStrip attachments={attachments} /> : null}
|
|
325
386
|
<div className="term-row">
|
|
326
|
-
{canAttach ? fileField : null}
|
|
387
|
+
{canAttach && !shellMode ? fileField : null}
|
|
327
388
|
{gutter}
|
|
328
389
|
<div className="flex min-w-0 items-start">
|
|
329
390
|
<PromptArea
|
|
@@ -333,17 +394,23 @@ export function Composer({
|
|
|
333
394
|
normalizeBullets={false}
|
|
334
395
|
onSubmit={submit}
|
|
335
396
|
disabled={disabled}
|
|
336
|
-
placeholder={disabled ? 'Session ended' : placeholder}
|
|
397
|
+
placeholder={disabled ? 'Session ended' : shellMode ? SHELL_PLACEHOLDER : placeholder}
|
|
337
398
|
minHeight={line}
|
|
338
399
|
maxHeight={line * 10}
|
|
339
|
-
aria-label=
|
|
400
|
+
aria-label={shellMode ? 'Run a shell command' : 'Message the agent'}
|
|
340
401
|
className="term-composer-field min-w-0 flex-1"
|
|
341
402
|
onImagePaste={(file) => attachments?.add([file])}
|
|
403
|
+
{...shellKeys}
|
|
342
404
|
/>
|
|
343
405
|
{submitButton}
|
|
344
406
|
</div>
|
|
345
407
|
</div>
|
|
346
|
-
{
|
|
408
|
+
{shellMode ? (
|
|
409
|
+
<div className="term-row">
|
|
410
|
+
<span aria-hidden className="term-gutter" />
|
|
411
|
+
<span className="term-composer-shell-hint">{SHELL_HINT}</span>
|
|
412
|
+
</div>
|
|
413
|
+
) : toolbar ? (
|
|
347
414
|
<div className="term-row">
|
|
348
415
|
<span aria-hidden className="term-gutter" />
|
|
349
416
|
<div className="flex min-w-0 items-center gap-[1ch]">{toolbar}</div>
|
|
@@ -365,13 +432,15 @@ export function Composer({
|
|
|
365
432
|
'transition-colors rounded-lg shadow-(--shadow-xs)',
|
|
366
433
|
'focus-within:border-ring focus-within:ring-2 focus-within:ring-ring/30',
|
|
367
434
|
dragging && 'border-ring ring-2 ring-ring/30',
|
|
435
|
+
shellMode &&
|
|
436
|
+
'border-[var(--wd-shell-accent)] focus-within:border-[var(--wd-shell-accent)] focus-within:ring-[var(--wd-shell-accent)]/30',
|
|
368
437
|
disabled && 'opacity-60',
|
|
369
438
|
)}
|
|
370
439
|
>
|
|
371
440
|
{staged.length > 0 && attachments ? <AttachmentStrip attachments={attachments} /> : null}
|
|
372
441
|
{inline ? (
|
|
373
442
|
<div className="flex items-end gap-1 p-1">
|
|
374
|
-
{attach}
|
|
443
|
+
{shellMode ? <ShellBadge onLeave={leaveShellMode} /> : attach}
|
|
375
444
|
<PromptArea
|
|
376
445
|
{...bind}
|
|
377
446
|
triggers={triggers}
|
|
@@ -379,12 +448,13 @@ export function Composer({
|
|
|
379
448
|
normalizeBullets={false}
|
|
380
449
|
onSubmit={submit}
|
|
381
450
|
disabled={disabled}
|
|
382
|
-
placeholder={disabled ? 'Session ended' : placeholder}
|
|
451
|
+
placeholder={disabled ? 'Session ended' : shellMode ? SHELL_PLACEHOLDER : placeholder}
|
|
383
452
|
minHeight={20}
|
|
384
453
|
maxHeight={192}
|
|
385
|
-
aria-label=
|
|
454
|
+
aria-label={shellMode ? 'Run a shell command' : 'Message the agent'}
|
|
386
455
|
className="min-w-0 flex-1 py-1 text-body-sm text-text"
|
|
387
456
|
onImagePaste={(file) => attachments?.add([file])}
|
|
457
|
+
{...shellKeys}
|
|
388
458
|
/>
|
|
389
459
|
{submitButton}
|
|
390
460
|
</div>
|
|
@@ -397,17 +467,18 @@ export function Composer({
|
|
|
397
467
|
normalizeBullets={false}
|
|
398
468
|
onSubmit={submit}
|
|
399
469
|
disabled={disabled}
|
|
400
|
-
placeholder={disabled ? 'Session ended' : placeholder}
|
|
470
|
+
placeholder={disabled ? 'Session ended' : shellMode ? SHELL_PLACEHOLDER : placeholder}
|
|
401
471
|
minHeight={28}
|
|
402
472
|
maxHeight={192}
|
|
403
|
-
aria-label=
|
|
473
|
+
aria-label={shellMode ? 'Run a shell command' : 'Message the agent'}
|
|
404
474
|
className="px-3 pt-2.5 pb-0 text-body-sm text-text"
|
|
405
475
|
onImagePaste={(file) => attachments?.add([file])}
|
|
476
|
+
{...shellKeys}
|
|
406
477
|
/>
|
|
407
478
|
<div className="flex items-center justify-between gap-2 px-2 pb-2">
|
|
408
479
|
<div className="flex min-w-0 items-center gap-1">
|
|
409
|
-
{attach}
|
|
410
|
-
{toolbar}
|
|
480
|
+
{shellMode ? <ShellBadge onLeave={leaveShellMode} /> : attach}
|
|
481
|
+
{shellMode ? <span className="text-label text-text-muted">{SHELL_HINT}</span> : toolbar}
|
|
411
482
|
</div>
|
|
412
483
|
{submitButton}
|
|
413
484
|
</div>
|
|
@@ -428,7 +499,7 @@ function GlyphButton({
|
|
|
428
499
|
className,
|
|
429
500
|
children,
|
|
430
501
|
}: {
|
|
431
|
-
tone?: 'blue' | 'yellow'
|
|
502
|
+
tone?: 'blue' | 'yellow' | 'magenta'
|
|
432
503
|
label: string
|
|
433
504
|
disabled?: boolean
|
|
434
505
|
onClick: () => void
|
|
@@ -526,3 +597,17 @@ function extensionOf(name: string) {
|
|
|
526
597
|
const dot = name.lastIndexOf('.')
|
|
527
598
|
return dot > 0 ? name.slice(dot + 1).toUpperCase() : 'FILE'
|
|
528
599
|
}
|
|
600
|
+
|
|
601
|
+
function ShellBadge({ onLeave }: { onLeave: () => void }) {
|
|
602
|
+
return (
|
|
603
|
+
<button
|
|
604
|
+
type="button"
|
|
605
|
+
aria-label="Leave shell mode"
|
|
606
|
+
title="Leave shell mode"
|
|
607
|
+
onClick={onLeave}
|
|
608
|
+
className="shrink-0 rounded px-1.5 py-0.5 font-mono text-label font-semibold text-[var(--wd-shell-accent)]"
|
|
609
|
+
>
|
|
610
|
+
!
|
|
611
|
+
</button>
|
|
612
|
+
)
|
|
613
|
+
}
|
|
@@ -50,7 +50,6 @@ import { BookmarkProvider, type BookmarkHandle, type TerminalAffordances } from
|
|
|
50
50
|
import { SessionInfoDialog } from './SessionInfoDialog.tsx'
|
|
51
51
|
import { StatusBar } from './StatusBar.tsx'
|
|
52
52
|
import { Transcript } from './Transcript.tsx'
|
|
53
|
-
import { HeldSendsBar, useHeldSends } from './held-sends.tsx'
|
|
54
53
|
import { ToolResultFetchProvider } from './tool-result-fetch.tsx'
|
|
55
54
|
import { ToolTitleProvider } from './tool-titles.tsx'
|
|
56
55
|
import { ToolResultImageProvider, useToolResultImages } from './tool-result-image.tsx'
|
|
@@ -116,9 +115,6 @@ export interface SessionPanelProps {
|
|
|
116
115
|
stickyPrompt?: boolean
|
|
117
116
|
transcriptDensity?: TranscriptDensity
|
|
118
117
|
transcriptFont?: TranscriptFont
|
|
119
|
-
// 'fold' sends a mid-turn message straight through, and the engine folds it into the running
|
|
120
|
-
// turn (catch-up mode). 'hold' keeps it here until the turn ends.
|
|
121
|
-
midTurnSend?: 'fold' | 'hold'
|
|
122
118
|
controlsSurface?: 'internal' | 'external' | 'status'
|
|
123
119
|
onControls?: (controls: SessionControls | undefined) => void
|
|
124
120
|
focusComposerOnClick?: boolean
|
|
@@ -194,7 +190,6 @@ export function SessionPanel({
|
|
|
194
190
|
transcriptVariant = 'cards',
|
|
195
191
|
transcriptDensity = 'comfortable',
|
|
196
192
|
transcriptFont = 'sans',
|
|
197
|
-
midTurnSend = 'fold',
|
|
198
193
|
affordances,
|
|
199
194
|
terminalMetrics,
|
|
200
195
|
scrubber = false,
|
|
@@ -234,10 +229,12 @@ export function SessionPanel({
|
|
|
234
229
|
connection,
|
|
235
230
|
replaying,
|
|
236
231
|
protocolMismatch,
|
|
232
|
+
shell,
|
|
237
233
|
models,
|
|
238
234
|
effectiveModel,
|
|
239
235
|
handle,
|
|
240
236
|
send,
|
|
237
|
+
runShell,
|
|
241
238
|
approve,
|
|
242
239
|
deny,
|
|
243
240
|
interrupt,
|
|
@@ -416,8 +413,6 @@ export function SessionPanel({
|
|
|
416
413
|
return [{ name: 'model', description: 'Switch the model for this session', argumentHint: '<model>' }, ...state.commands]
|
|
417
414
|
}, [state.commands])
|
|
418
415
|
|
|
419
|
-
const heldSends = useHeldSends({ hold: midTurnSend === 'hold', busy: busy && !ended, send })
|
|
420
|
-
|
|
421
416
|
const handleSend = (text: string, attachmentIds: string[]) => {
|
|
422
417
|
if (attachmentIds.length === 0) {
|
|
423
418
|
const modelCommand = /^\/model\s+(\S+)$/.exec(text)
|
|
@@ -432,7 +427,7 @@ export function SessionPanel({
|
|
|
432
427
|
}
|
|
433
428
|
setCaughtUp(true)
|
|
434
429
|
repinTranscript.current?.()
|
|
435
|
-
|
|
430
|
+
send(text, attachmentIds)
|
|
436
431
|
}
|
|
437
432
|
|
|
438
433
|
const actionsMenu = (
|
|
@@ -691,7 +686,6 @@ export function SessionPanel({
|
|
|
691
686
|
) : null}
|
|
692
687
|
{readOnly || subagentId !== undefined ? null : (
|
|
693
688
|
<>
|
|
694
|
-
<HeldSendsBar held={heldSends.held} onSendNow={heldSends.flush} />
|
|
695
689
|
<Composer
|
|
696
690
|
ref={composerRef}
|
|
697
691
|
onSend={handleSend}
|
|
@@ -703,6 +697,7 @@ export function SessionPanel({
|
|
|
703
697
|
attachments={attachments}
|
|
704
698
|
draft={draft}
|
|
705
699
|
onSearchFiles={hostFiles.available ? searchComposerFiles : undefined}
|
|
700
|
+
onShellCommand={shell ? runShell : undefined}
|
|
706
701
|
layout={controlsExternal ? 'inline' : 'stacked'}
|
|
707
702
|
toolbar={controlsExternal ? undefined : sessionControls}
|
|
708
703
|
fontSize={effectiveTermFontSize}
|
|
@@ -19,7 +19,6 @@ export interface SessionWorkspaceProps {
|
|
|
19
19
|
transcriptVariant?: SessionPanelProps['transcriptVariant']
|
|
20
20
|
transcriptDensity?: SessionPanelProps['transcriptDensity']
|
|
21
21
|
transcriptFont?: SessionPanelProps['transcriptFont']
|
|
22
|
-
midTurnSend?: SessionPanelProps['midTurnSend']
|
|
23
22
|
scrubber?: SessionPanelProps['scrubber']
|
|
24
23
|
bookmarks?: SessionPanelProps['bookmarks']
|
|
25
24
|
onToggleBookmark?: SessionPanelProps['onToggleBookmark']
|
|
@@ -53,7 +52,6 @@ export function SessionWorkspace({
|
|
|
53
52
|
transcriptVariant,
|
|
54
53
|
transcriptDensity,
|
|
55
54
|
transcriptFont,
|
|
56
|
-
midTurnSend,
|
|
57
55
|
scrubber,
|
|
58
56
|
bookmarks,
|
|
59
57
|
onToggleBookmark,
|
|
@@ -213,7 +211,6 @@ export function SessionWorkspace({
|
|
|
213
211
|
transcriptVariant={transcriptVariant}
|
|
214
212
|
transcriptDensity={transcriptDensity}
|
|
215
213
|
transcriptFont={transcriptFont}
|
|
216
|
-
midTurnSend={midTurnSend}
|
|
217
214
|
scrubber={scrubber}
|
|
218
215
|
bookmarks={bookmarks}
|
|
219
216
|
onToggleBookmark={onToggleBookmark}
|
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export { PromptArea } from './prompt-area.tsx'
|
|
7
7
|
export { usePromptAreaState } from './use-prompt-area-state.ts'
|
|
8
|
-
export { commandTrigger, mentionTrigger, hashtagTrigger } from './trigger-presets.ts'
|
|
8
|
+
export { commandTrigger, mentionTrigger, hashtagTrigger, launchTrigger } from './trigger-presets.ts'
|
|
9
9
|
export { segmentsToPlainText, plainTextToSegments, isSegmentsEmpty, getChipsByTrigger } from './segment-helpers.ts'
|
|
10
10
|
export type { PromptAreaHandle, PromptAreaProps, Segment, ChipSegment, TextSegment, TriggerConfig, TriggerSuggestion } from './types.ts'
|
|
@@ -131,3 +131,27 @@ export function callbackTrigger(opts: CallbackTriggerOptions): TriggerConfig {
|
|
|
131
131
|
...rest,
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
|
+
|
|
135
|
+
// Launch trigger — fires onActivate and swallows the character
|
|
136
|
+
|
|
137
|
+
export type LaunchTriggerOptions = Omit<Partial<TriggerConfig>, 'mode'> & {
|
|
138
|
+
/** The trigger character. Required. */
|
|
139
|
+
char: string
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Creates a **launch** trigger: `onActivate` fires on keydown and the character
|
|
144
|
+
* never enters the editor. For flipping the composer into another mode, or
|
|
145
|
+
* opening a surface outside it.
|
|
146
|
+
*
|
|
147
|
+
* Defaults: `position: 'start'`, `mode: 'launch'`.
|
|
148
|
+
*/
|
|
149
|
+
export function launchTrigger(opts: LaunchTriggerOptions): TriggerConfig {
|
|
150
|
+
const { char, ...rest } = opts
|
|
151
|
+
return {
|
|
152
|
+
char,
|
|
153
|
+
position: 'start',
|
|
154
|
+
mode: 'launch',
|
|
155
|
+
...rest,
|
|
156
|
+
}
|
|
157
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -28,6 +28,7 @@ export {
|
|
|
28
28
|
commandTrigger,
|
|
29
29
|
mentionTrigger,
|
|
30
30
|
hashtagTrigger,
|
|
31
|
+
launchTrigger,
|
|
31
32
|
segmentsToPlainText,
|
|
32
33
|
plainTextToSegments,
|
|
33
34
|
isSegmentsEmpty,
|
|
@@ -75,7 +76,6 @@ export { Reasoning, type ReasoningProps } from './components/agent/Reasoning.tsx
|
|
|
75
76
|
export { Loader } from './components/agent/Loader.tsx'
|
|
76
77
|
export { ToolCallCard, type ToolCallCardProps, type ToolCallItem } from './components/agent/ToolCallCard.tsx'
|
|
77
78
|
export { ToolTitleProvider, useToolTitle } from './components/agent/tool-titles.tsx'
|
|
78
|
-
export { HeldSendsBar, useHeldSends, type HeldSend, type HeldSends } from './components/agent/held-sends.tsx'
|
|
79
79
|
export { FileCard, type FileCardProps, type FileDeliveredItem } from './components/agent/FileCard.tsx'
|
|
80
80
|
export { PermissionPrompt, type PermissionPromptProps } from './components/agent/PermissionPrompt.tsx'
|
|
81
81
|
export {
|
package/src/styles/terminal.css
CHANGED
|
@@ -643,6 +643,17 @@
|
|
|
643
643
|
border-color: var(--term-blue);
|
|
644
644
|
}
|
|
645
645
|
|
|
646
|
+
/* Shell mode. The frame and the gutter glyph move together and both go magenta, because the thing
|
|
647
|
+
the mode changes is where the text goes — a subtle cue would be the wrong kind of quiet. Listed
|
|
648
|
+
after the focus rule so it wins while the field is focused, which is the whole time it is on. */
|
|
649
|
+
[data-terminal].term-composer-shell,
|
|
650
|
+
[data-terminal].term-composer-shell:focus-within {
|
|
651
|
+
border-color: var(--term-magenta);
|
|
652
|
+
}
|
|
653
|
+
[data-terminal] .term-composer-shell-hint {
|
|
654
|
+
color: var(--term-dim);
|
|
655
|
+
}
|
|
656
|
+
|
|
646
657
|
/* The contentEditable field. `font: inherit` is what makes it the cell — the
|
|
647
658
|
surface above already carries the size and the line height, and anything that
|
|
648
659
|
restates them here can disagree with the rows. */
|
package/src/styles/theme.css
CHANGED
|
@@ -155,6 +155,10 @@
|
|
|
155
155
|
and every other colour on the rail is already spoken for. */
|
|
156
156
|
--wd-scrub-bookmark: #8b3a8b;
|
|
157
157
|
|
|
158
|
+
/* Shell mode's frame and glyph. The same magenta as the bookmark rail, for the same reason: the app
|
|
159
|
+
palette has no magenta, and shell mode must not read as focus (blue) or as an error (red). */
|
|
160
|
+
--wd-shell-accent: #8b3a8b;
|
|
161
|
+
|
|
158
162
|
/* ---------- aliases (bridge for the @theme utilities) ---------- */
|
|
159
163
|
--surface: var(--bg-surface);
|
|
160
164
|
--sidebar: #f4f4f4;
|
|
@@ -238,6 +242,7 @@
|
|
|
238
242
|
|
|
239
243
|
/* See the light block — lifted for the dark ground. */
|
|
240
244
|
--wd-scrub-bookmark: #c586c0;
|
|
245
|
+
--wd-shell-accent: #c586c0;
|
|
241
246
|
|
|
242
247
|
/* ---------- aliases ---------- */
|
|
243
248
|
--surface: var(--bg-surface);
|