| author | |
| committer | |
| log | 719a76ca1dbf5490f1aa33ffb9966301e02be9a9 |
| tree | 153d573dcfbc5771f671a99386edb8bb7d6d3014 |
| parent | 96bddf81258090690bfcf4e6a6414aa0dcf1910a |
| signature | Signed by PGP key B5690EEEBB952194 |
10 files changed, 117 insertions(+), 7 deletions(-)
apps/web/src/components/ChatView.tsx+24-5| ... | @@ -6781,6 +6781,17 @@ export default function ChatView(props: ChatViewProps) { | ... | @@ -6781,6 +6781,17 @@ export default function ChatView(props: ChatViewProps) { |
| 6781 | return; | 6781 | return; |
| 6782 | } | 6782 | } |
| 6783 | 6783 | ||
| 6784 | if (command === "thread.steerQueuedMessage") { | ||
| 6785 | const message = activeThreadKey | ||
| 6786 | ? useQueuedMessageStore.getState().queuesByThreadKey[activeThreadKey]?.[0] | ||
| 6787 | : undefined; | ||
| 6788 | if (!message) return; | ||
| 6789 | event.preventDefault(); | ||
| 6790 | event.stopPropagation(); | ||
| 6791 | if (!event.repeat) queuedMessageActionsRef.current.steer(message.id); | ||
| 6792 | return; | ||
| 6793 | } | ||
| 6794 | |||
| 6784 | if (command === "thread.stop") { | 6795 | if (command === "thread.stop") { |
| 6785 | // An unavailable command should not shadow contextual shortcuts such as Escape to close a dialog. | 6796 | // An unavailable command should not shadow contextual shortcuts such as Escape to close a dialog. |
| 6786 | if (!canInterruptRunningThread) return; | 6797 | if (!canInterruptRunningThread) return; |
| ... | @@ -6810,6 +6821,7 @@ export default function ChatView(props: ChatViewProps) { | ... | @@ -6810,6 +6821,7 @@ export default function ChatView(props: ChatViewProps) { |
| 6810 | activeThreadPinned, | 6821 | activeThreadPinned, |
| 6811 | activeThreadSettled, | 6822 | activeThreadSettled, |
| 6812 | canInterruptRunningThread, | 6823 | canInterruptRunningThread, |
| 6824 | activeThreadKey, | ||
| 6813 | terminalUiState.terminalOpen, | 6825 | terminalUiState.terminalOpen, |
| 6814 | terminalUiState.activeTerminalId, | 6826 | terminalUiState.activeTerminalId, |
| 6815 | activeThreadId, | 6827 | activeThreadId, |
| ... | @@ -7497,11 +7509,13 @@ export default function ChatView(props: ChatViewProps) { | ... | @@ -7497,11 +7509,13 @@ export default function ChatView(props: ChatViewProps) { |
| 7497 | ); | 7509 | ); |
| 7498 | return; | 7510 | return; |
| 7499 | } | 7511 | } |
| 7500 | // A send during a running turn waits in the queue. It leaves on the next | 7512 | if ( |
| 7501 | // tool boundary, when the turn ends, or when the user clicks Steer. The | 7513 | !queuedMessage && |
| 7502 | // provider treats a mid-turn send as a steer of the active turn, so the | 7514 | !directAnnotation && |
| 7503 | // dispatch below is the same either way. | 7515 | phase === "running" && |
| 7504 | if (!queuedMessage && !directAnnotation && phase === "running" && activeThreadKey) { | 7516 | activeThreadKey && |
| 7517 | settings.followUpBehavior === "queue" | ||
| 7518 | ) { | ||
| 7505 | if (composerRef.current?.validateProviderInput(promptForSend) === false) { | 7519 | if (composerRef.current?.validateProviderInput(promptForSend) === false) { |
| 7506 | return; | 7520 | return; |
| 7507 | } | 7521 | } |
| ... | @@ -9501,6 +9515,11 @@ export default function ChatView(props: ChatViewProps) { | ... | @@ -9501,6 +9515,11 @@ export default function ChatView(props: ChatViewProps) { |
| 9501 | loadEarlier={paintOnlyDisplayedTimeline ? null : loadEarlierTurns} | 9515 | loadEarlier={paintOnlyDisplayedTimeline ? null : loadEarlierTurns} |
| 9502 | queuedMessages={paintOnlyDisplayedTimeline ? EMPTY_QUEUED_MESSAGES : queuedMessages} | 9516 | queuedMessages={paintOnlyDisplayedTimeline ? EMPTY_QUEUED_MESSAGES : queuedMessages} |
| 9503 | onSteerQueuedMessage={onSteerQueuedMessage} | 9517 | onSteerQueuedMessage={onSteerQueuedMessage} |
| 9518 | steerQueuedMessageShortcutLabel={shortcutLabelForCommand( | ||
| 9519 | keybindings, | ||
| 9520 | "thread.steerQueuedMessage", | ||
| 9521 | { context: { terminalFocus: false } }, | ||
| 9522 | )} | ||
| 9504 | onRemoveQueuedMessage={onRemoveQueuedMessage} | 9523 | onRemoveQueuedMessage={onRemoveQueuedMessage} |
| 9505 | /> | 9524 | /> |
| 9506 | 9525 |
apps/web/src/components/chat/MessagesTimeline.tsx+11-1| ... | @@ -285,6 +285,7 @@ interface TimelineRowSharedState { | ... | @@ -285,6 +285,7 @@ interface TimelineRowSharedState { |
| 285 | onWorktreeSetupWorkLocally: (() => void) | null; | 285 | onWorktreeSetupWorkLocally: (() => void) | null; |
| 286 | onOpenWorktreeSetupTerminal: ((terminalId: string) => void) | null; | 286 | onOpenWorktreeSetupTerminal: ((terminalId: string) => void) | null; |
| 287 | onSteerQueuedMessage: (id: string) => void; | 287 | onSteerQueuedMessage: (id: string) => void; |
| 288 | steerQueuedMessageShortcutLabel: string | null; | ||
| 288 | onRemoveQueuedMessage: (id: string) => void; | 289 | onRemoveQueuedMessage: (id: string) => void; |
| 289 | } | 290 | } |
| 290 | 291 | ||
| ... | @@ -441,6 +442,7 @@ interface MessagesTimelineProps { | ... | @@ -441,6 +442,7 @@ interface MessagesTimelineProps { |
| 441 | /** Messages sent during the running turn. They render as ghost bubbles after the live rows. */ | 442 | /** Messages sent during the running turn. They render as ghost bubbles after the live rows. */ |
| 442 | queuedMessages?: ReadonlyArray<QueuedComposerMessage>; | 443 | queuedMessages?: ReadonlyArray<QueuedComposerMessage>; |
| 443 | onSteerQueuedMessage?: (id: string) => void; | 444 | onSteerQueuedMessage?: (id: string) => void; |
| 445 | steerQueuedMessageShortcutLabel?: string | null; | ||
| 444 | onRemoveQueuedMessage?: (id: string) => void; | 446 | onRemoveQueuedMessage?: (id: string) => void; |
| 445 | } | 447 | } |
| 446 | 448 | ||
| ... | @@ -496,6 +498,7 @@ export const MessagesTimeline = memo(function MessagesTimeline({ | ... | @@ -496,6 +498,7 @@ export const MessagesTimeline = memo(function MessagesTimeline({ |
| 496 | loadEarlier = null, | 498 | loadEarlier = null, |
| 497 | queuedMessages = EMPTY_QUEUED_MESSAGES, | 499 | queuedMessages = EMPTY_QUEUED_MESSAGES, |
| 498 | onSteerQueuedMessage = NOOP_QUEUED_MESSAGE_ACTION, | 500 | onSteerQueuedMessage = NOOP_QUEUED_MESSAGE_ACTION, |
| 501 | steerQueuedMessageShortcutLabel = null, | ||
| 499 | onRemoveQueuedMessage = NOOP_QUEUED_MESSAGE_ACTION, | 502 | onRemoveQueuedMessage = NOOP_QUEUED_MESSAGE_ACTION, |
| 500 | }: MessagesTimelineProps) { | 503 | }: MessagesTimelineProps) { |
| 501 | const [expandedTurnIds, setExpandedTurnIds] = useState<ReadonlySet<TurnId>>(new Set()); | 504 | const [expandedTurnIds, setExpandedTurnIds] = useState<ReadonlySet<TurnId>>(new Set()); |
| ... | @@ -940,6 +943,7 @@ export const MessagesTimeline = memo(function MessagesTimeline({ | ... | @@ -940,6 +943,7 @@ export const MessagesTimeline = memo(function MessagesTimeline({ |
| 940 | onWorktreeSetupWorkLocally: onWorktreeSetupWorkLocally ?? null, | 943 | onWorktreeSetupWorkLocally: onWorktreeSetupWorkLocally ?? null, |
| 941 | onOpenWorktreeSetupTerminal: onOpenWorktreeSetupTerminal ?? null, | 944 | onOpenWorktreeSetupTerminal: onOpenWorktreeSetupTerminal ?? null, |
| 942 | onSteerQueuedMessage, | 945 | onSteerQueuedMessage, |
| 946 | steerQueuedMessageShortcutLabel, | ||
| 943 | onRemoveQueuedMessage, | 947 | onRemoveQueuedMessage, |
| 944 | }), | 948 | }), |
| 945 | [ | 949 | [ |
| ... | @@ -972,6 +976,7 @@ export const MessagesTimeline = memo(function MessagesTimeline({ | ... | @@ -972,6 +976,7 @@ export const MessagesTimeline = memo(function MessagesTimeline({ |
| 972 | onWorktreeSetupWorkLocally, | 976 | onWorktreeSetupWorkLocally, |
| 973 | onOpenWorktreeSetupTerminal, | 977 | onOpenWorktreeSetupTerminal, |
| 974 | onSteerQueuedMessage, | 978 | onSteerQueuedMessage, |
| 979 | steerQueuedMessageShortcutLabel, | ||
| 975 | onRemoveQueuedMessage, | 980 | onRemoveQueuedMessage, |
| 976 | ], | 981 | ], |
| 977 | ); | 982 | ); |
| ... | @@ -1564,7 +1569,12 @@ function QueuedMessageTimelineRow({ | ... | @@ -1564,7 +1569,12 @@ function QueuedMessageTimelineRow({ |
| 1564 | > | 1569 | > |
| 1565 | <ArrowUpIcon className="size-3.5" aria-hidden /> | 1570 | <ArrowUpIcon className="size-3.5" aria-hidden /> |
| 1566 | </TooltipTrigger> | 1571 | </TooltipTrigger> |
| 1567 | <TooltipPopup side="bottom">Send now</TooltipPopup> | 1572 | <TooltipPopup side="bottom"> |
| 1573 | Send now | ||
| 1574 | {row.isNext && ctx.steerQueuedMessageShortcutLabel | ||
| 1575 | ? ` (${ctx.steerQueuedMessageShortcutLabel})` | ||
| 1576 | : null} | ||
| 1577 | </TooltipPopup> | ||
| 1568 | </Tooltip> | 1578 | </Tooltip> |
| 1569 | <Tooltip> | 1579 | <Tooltip> |
| 1570 | <TooltipTrigger | 1580 | <TooltipTrigger |
apps/web/src/components/settings/SettingsPanels.tsx+46| ... | @@ -578,6 +578,9 @@ export function useSettingsRestore(onRestored?: () => void) { | ... | @@ -578,6 +578,9 @@ export function useSettingsRestore(onRestored?: () => void) { |
| 578 | ...(settings.composerCollapseOnScroll !== DEFAULT_UNIFIED_SETTINGS.composerCollapseOnScroll | 578 | ...(settings.composerCollapseOnScroll !== DEFAULT_UNIFIED_SETTINGS.composerCollapseOnScroll |
| 579 | ? ["Collapse composer on scroll"] | 579 | ? ["Collapse composer on scroll"] |
| 580 | : []), | 580 | : []), |
| 581 | ...(settings.followUpBehavior !== DEFAULT_UNIFIED_SETTINGS.followUpBehavior | ||
| 582 | ? ["Follow-up behavior"] | ||
| 583 | : []), | ||
| 581 | ...(settings.contextWindowMeterEnabled !== DEFAULT_UNIFIED_SETTINGS.contextWindowMeterEnabled | 584 | ...(settings.contextWindowMeterEnabled !== DEFAULT_UNIFIED_SETTINGS.contextWindowMeterEnabled |
| 582 | ? ["Context window indicator"] | 585 | ? ["Context window indicator"] |
| 583 | : []), | 586 | : []), |
| ... | @@ -636,6 +639,7 @@ export function useSettingsRestore(onRestored?: () => void) { | ... | @@ -636,6 +639,7 @@ export function useSettingsRestore(onRestored?: () => void) { |
| 636 | settings.confirmThreadDelete, | 639 | settings.confirmThreadDelete, |
| 637 | settings.confirmThreadUnpin, | 640 | settings.confirmThreadUnpin, |
| 638 | settings.composerCollapseOnScroll, | 641 | settings.composerCollapseOnScroll, |
| 642 | settings.followUpBehavior, | ||
| 639 | settings.addProjectBaseDirectory, | 643 | settings.addProjectBaseDirectory, |
| 640 | settings.defaultThreadEnvMode, | 644 | settings.defaultThreadEnvMode, |
| 641 | settings.newWorktreesStartFromOrigin, | 645 | settings.newWorktreesStartFromOrigin, |
| ... | @@ -748,6 +752,7 @@ export function useSettingsRestore(onRestored?: () => void) { | ... | @@ -748,6 +752,7 @@ export function useSettingsRestore(onRestored?: () => void) { |
| 748 | proactivePanelsEnabled: DEFAULT_UNIFIED_SETTINGS.proactivePanelsEnabled, | 752 | proactivePanelsEnabled: DEFAULT_UNIFIED_SETTINGS.proactivePanelsEnabled, |
| 749 | showSkillsInSlashMenu: DEFAULT_UNIFIED_SETTINGS.showSkillsInSlashMenu, | 753 | showSkillsInSlashMenu: DEFAULT_UNIFIED_SETTINGS.showSkillsInSlashMenu, |
| 750 | composerCollapseOnScroll: DEFAULT_UNIFIED_SETTINGS.composerCollapseOnScroll, | 754 | composerCollapseOnScroll: DEFAULT_UNIFIED_SETTINGS.composerCollapseOnScroll, |
| 755 | followUpBehavior: DEFAULT_UNIFIED_SETTINGS.followUpBehavior, | ||
| 751 | contextWindowMeterEnabled: DEFAULT_UNIFIED_SETTINGS.contextWindowMeterEnabled, | 756 | contextWindowMeterEnabled: DEFAULT_UNIFIED_SETTINGS.contextWindowMeterEnabled, |
| 752 | environmentIdentificationMode: DEFAULT_UNIFIED_SETTINGS.environmentIdentificationMode, | 757 | environmentIdentificationMode: DEFAULT_UNIFIED_SETTINGS.environmentIdentificationMode, |
| 753 | glassOpacity: DEFAULT_UNIFIED_SETTINGS.glassOpacity, | 758 | glassOpacity: DEFAULT_UNIFIED_SETTINGS.glassOpacity, |
| ... | @@ -2589,6 +2594,47 @@ export function GeneralSettingsPanel() { | ... | @@ -2589,6 +2594,47 @@ export function GeneralSettingsPanel() { |
| 2589 | } | 2594 | } |
| 2590 | /> | 2595 | /> |
| 2591 | 2596 | ||
| 2597 | <SettingsRow | ||
| 2598 | {...searchableSetting("follow-up-behavior")} | ||
| 2599 | description="Queue follow-ups while the agent runs or steer the current turn." | ||
| 2600 | resetAction={ | ||
| 2601 | settings.followUpBehavior !== DEFAULT_UNIFIED_SETTINGS.followUpBehavior ? ( | ||
| 2602 | <SettingResetButton | ||
| 2603 | label="follow-up behavior" | ||
| 2604 | onClick={() => | ||
| 2605 | updateSettings({ | ||
| 2606 | followUpBehavior: DEFAULT_UNIFIED_SETTINGS.followUpBehavior, | ||
| 2607 | }) | ||
| 2608 | } | ||
| 2609 | /> | ||
| 2610 | ) : null | ||
| 2611 | } | ||
| 2612 | control={ | ||
| 2613 | <Select | ||
| 2614 | value={settings.followUpBehavior} | ||
| 2615 | onValueChange={(value) => { | ||
| 2616 | if (value === "queue" || value === "steer") { | ||
| 2617 | updateSettings({ followUpBehavior: value }); | ||
| 2618 | } | ||
| 2619 | }} | ||
| 2620 | > | ||
| 2621 | <SelectTrigger size="sm" className="w-full sm:w-40" aria-label="Follow-up behavior"> | ||
| 2622 | <SelectValue> | ||
| 2623 | {settings.followUpBehavior === "queue" ? "Queue" : "Steer"} | ||
| 2624 | </SelectValue> | ||
| 2625 | </SelectTrigger> | ||
| 2626 | <SelectPopup align="end" alignItemWithTrigger={false}> | ||
| 2627 | <SelectItem hideIndicator value="queue"> | ||
| 2628 | Queue | ||
| 2629 | </SelectItem> | ||
| 2630 | <SelectItem hideIndicator value="steer"> | ||
| 2631 | Steer | ||
| 2632 | </SelectItem> | ||
| 2633 | </SelectPopup> | ||
| 2634 | </Select> | ||
| 2635 | } | ||
| 2636 | /> | ||
| 2637 | |||
| 2592 | <SettingsRow | 2638 | <SettingsRow |
| 2593 | serverScoped | 2639 | serverScoped |
| 2594 | settingKeys={["enableProviderUpdateChecks"]} | 2640 | settingKeys={["enableProviderUpdateChecks"]} |
apps/web/src/components/settings/settingsSearch.ts+6| ... | @@ -298,6 +298,12 @@ export const SETTINGS_SEARCH_ITEMS = [ | ... | @@ -298,6 +298,12 @@ export const SETTINGS_SEARCH_ITEMS = [ |
| 298 | to: "/settings/general", | 298 | to: "/settings/general", |
| 299 | searchTerms: ["composer rest resting scroll wheel conversation timeline shrink minimize"], | 299 | searchTerms: ["composer rest resting scroll wheel conversation timeline shrink minimize"], |
| 300 | }, | 300 | }, |
| 301 | { | ||
| 302 | id: "follow-up-behavior", | ||
| 303 | title: "Follow-up behavior", | ||
| 304 | to: "/settings/general", | ||
| 305 | searchTerms: ["queue steer running turn send default behavior composer"], | ||
| 306 | }, | ||
| 301 | { | 307 | { |
| 302 | id: "provider-update-checks", | 308 | id: "provider-update-checks", |
| 303 | title: "Provider update checks", | 309 | title: "Provider update checks", |
apps/web/src/keybindings.test.ts+1| ... | @@ -1119,6 +1119,7 @@ describe("composer and pull request shortcuts", () => { | ... | @@ -1119,6 +1119,7 @@ describe("composer and pull request shortcuts", () => { |
| 1119 | ["l", "composer.previousWorktree"], | 1119 | ["l", "composer.previousWorktree"], |
| 1120 | ["c", "thread.copyReference"], | 1120 | ["c", "thread.copyReference"], |
| 1121 | ["k", "pullRequest.copyNumber"], | 1121 | ["k", "pullRequest.copyNumber"], |
| 1122 | ["Enter", "thread.steerQueuedMessage"], | ||
| 1122 | ] as const; | 1123 | ] as const; |
| 1123 | 1124 | ||
| 1124 | for (const platform of ["MacIntel", "Win32", "Linux"]) { | 1125 | for (const platform of ["MacIntel", "Win32", "Linux"]) { |
docs/user/composer.md+10-1| ... | @@ -31,12 +31,21 @@ See [images and videos](#images-and-videos-in-messages) for previewing and savin | ... | @@ -31,12 +31,21 @@ See [images and videos](#images-and-videos-in-messages) for previewing and savin |
| 31 | 31 | ||
| 32 | ## Send while the agent is working | 32 | ## Send while the agent is working |
| 33 | 33 | ||
| 34 | A message sent during a running turn waits at the end of the conversation as a | 34 | On web and desktop, a message sent during a running turn waits at the end of the conversation as a |
| 35 | dashed bubble. It goes out on its own when the agent finishes its next tool | 35 | dashed bubble. It goes out on its own when the agent finishes its next tool |
| 36 | call, or when the turn ends. Use the arrow under the bubble to send it right | 36 | call, or when the turn ends. Use the arrow under the bubble to send it right |
| 37 | away, or the X to move it back into the composer. Stop returns every queued | 37 | away, or the X to move it back into the composer. Stop returns every queued |
| 38 | message to the composer. | 38 | message to the composer. |
| 39 | 39 | ||
| 40 | In **Settings → General → Follow-up behavior**, choose **Queue** to keep this | ||
| 41 | behavior or **Steer** to send new messages immediately. This setting applies to | ||
| 42 | the current client. Messages already queued keep their place. | ||
| 43 | |||
| 44 | Use `Cmd+Shift+Enter` on macOS or `Ctrl+Shift+Enter` on Windows and Linux to send | ||
| 45 | the oldest queued message now. Change `thread.steerQueuedMessage` in | ||
| 46 | **Settings → Keybindings** to use another shortcut. It leaves the current draft | ||
| 47 | in the composer and waits if the agent needs an approval or an answer. | ||
| 48 | |||
| 40 | ## Queue messages offline on mobile | 49 | ## Queue messages offline on mobile |
| 41 | 50 | ||
| 42 | Mobile keeps local copies of draft attachments, so you can preview them and queue | 51 | Mobile keeps local copies of draft attachments, so you can preview them and queue |
packages/contracts/src/keybindings.ts+1| ... | @@ -36,6 +36,7 @@ export type ModelPickerJumpKeybindingCommand = | ... | @@ -36,6 +36,7 @@ export type ModelPickerJumpKeybindingCommand = |
| 36 | 36 | ||
| 37 | const THREAD_KEYBINDING_COMMANDS = [ | 37 | const THREAD_KEYBINDING_COMMANDS = [ |
| 38 | "thread.stop", | 38 | "thread.stop", |
| 39 | "thread.steerQueuedMessage", | ||
| 39 | "thread.previous", | 40 | "thread.previous", |
| 40 | "thread.next", | 41 | "thread.next", |
| 41 | "thread.copyReference", | 42 | "thread.copyReference", |
packages/contracts/src/settings.test.ts+13| ... | @@ -529,6 +529,19 @@ describe("ClientSettings context window meter", () => { | ... | @@ -529,6 +529,19 @@ describe("ClientSettings context window meter", () => { |
| 529 | }); | 529 | }); |
| 530 | }); | 530 | }); |
| 531 | 531 | ||
| 532 | describe("ClientSettings follow-up behavior", () => { | ||
| 533 | it("defaults to queue and accepts either behavior", () => { | ||
| 534 | expect(decodeClientSettings({}).followUpBehavior).toBe("queue"); | ||
| 535 | for (const followUpBehavior of ["queue", "steer"]) { | ||
| 536 | expect(decodeClientSettings({ followUpBehavior }).followUpBehavior).toBe(followUpBehavior); | ||
| 537 | expect(decodeClientSettingsPatch({ followUpBehavior }).followUpBehavior).toBe( | ||
| 538 | followUpBehavior, | ||
| 539 | ); | ||
| 540 | } | ||
| 541 | expect(() => decodeClientSettingsPatch({ followUpBehavior: "invalid" })).toThrow(); | ||
| 542 | }); | ||
| 543 | }); | ||
| 544 | |||
| 532 | describe("ClientSettings composer collapse", () => { | 545 | describe("ClientSettings composer collapse", () => { |
| 533 | it("collapses on scroll by default and accepts opting out", () => { | 546 | it("collapses on scroll by default and accepts opting out", () => { |
| 534 | expect(decodeClientSettings({}).composerCollapseOnScroll).toBe(true); | 547 | expect(decodeClientSettings({}).composerCollapseOnScroll).toBe(true); |
packages/contracts/src/settings.ts+4| ... | @@ -430,6 +430,9 @@ export const ClientSettingsSchema = Schema.Struct({ | ... | @@ -430,6 +430,9 @@ export const ClientSettingsSchema = Schema.Struct({ |
| 430 | // Desktop resting composer: scrolling an existing thread's conversation | 430 | // Desktop resting composer: scrolling an existing thread's conversation |
| 431 | // settles the composer into its single-line layout. Losing focus never does. | 431 | // settles the composer into its single-line layout. Losing focus never does. |
| 432 | composerCollapseOnScroll: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))), | 432 | composerCollapseOnScroll: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))), |
| 433 | followUpBehavior: Schema.Literals(["queue", "steer"]).pipe( | ||
| 434 | Schema.withDecodingDefault(Effect.succeed("queue")), | ||
| 435 | ), | ||
| 433 | proactivePanelsEnabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), | 436 | proactivePanelsEnabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), |
| 434 | showSkillsInSlashMenu: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))), | 437 | showSkillsInSlashMenu: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))), |
| 435 | // Legacy sidebar (the original per-project tree). Deliberately a fresh key | 438 | // Legacy sidebar (the original per-project tree). Deliberately a fresh key |
| ... | @@ -1499,6 +1502,7 @@ export const ClientSettingsPatch = Schema.Struct({ | ... | @@ -1499,6 +1502,7 @@ export const ClientSettingsPatch = Schema.Struct({ |
| 1499 | planModeEnabled: Schema.optionalKey(Schema.Boolean), | 1502 | planModeEnabled: Schema.optionalKey(Schema.Boolean), |
| 1500 | contextWindowMeterEnabled: Schema.optionalKey(Schema.Boolean), | 1503 | contextWindowMeterEnabled: Schema.optionalKey(Schema.Boolean), |
| 1501 | composerCollapseOnScroll: Schema.optionalKey(Schema.Boolean), | 1504 | composerCollapseOnScroll: Schema.optionalKey(Schema.Boolean), |
| 1505 | followUpBehavior: Schema.optionalKey(Schema.Literals(["queue", "steer"])), | ||
| 1502 | proactivePanelsEnabled: Schema.optionalKey(Schema.Boolean), | 1506 | proactivePanelsEnabled: Schema.optionalKey(Schema.Boolean), |
| 1503 | showSkillsInSlashMenu: Schema.optionalKey(Schema.Boolean), | 1507 | showSkillsInSlashMenu: Schema.optionalKey(Schema.Boolean), |
| 1504 | legacySidebarEnabled: Schema.optionalKey(Schema.Boolean), | 1508 | legacySidebarEnabled: Schema.optionalKey(Schema.Boolean), |
packages/shared/src/keybindings.ts+1| ... | @@ -40,6 +40,7 @@ export const DEFAULT_KEYBINDINGS: ReadonlyArray<KeybindingRule> = [ | ... | @@ -40,6 +40,7 @@ export const DEFAULT_KEYBINDINGS: ReadonlyArray<KeybindingRule> = [ |
| 40 | { key: "mod+shift+f", command: "projectSearch.toggle", when: "!terminalFocus" }, | 40 | { key: "mod+shift+f", command: "projectSearch.toggle", when: "!terminalFocus" }, |
| 41 | { key: "mod+alt+shift+t", command: "themeEditor.toggle" }, | 41 | { key: "mod+alt+shift+t", command: "themeEditor.toggle" }, |
| 42 | { key: "mod+s", command: "composer.stash", when: "!terminalFocus" }, | 42 | { key: "mod+s", command: "composer.stash", when: "!terminalFocus" }, |
| 43 | { key: "mod+shift+enter", command: "thread.steerQueuedMessage", when: "!terminalFocus" }, | ||
| 43 | { key: "mod+n", command: "chat.new", when: "!terminalFocus" }, | 44 | { key: "mod+n", command: "chat.new", when: "!terminalFocus" }, |
| 44 | { key: "mod+shift+o", command: "chat.new", when: "!terminalFocus" }, | 45 | { key: "mod+shift+o", command: "chat.new", when: "!terminalFocus" }, |
| 45 | { key: "mod+shift+n", command: "chat.newLocal", when: "!terminalFocus" }, | 46 | { key: "mod+shift+n", command: "chat.newLocal", when: "!terminalFocus" }, |