mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-01 21:20:44 +00:00
feat(desktop): attach terminal output to composer
This commit is contained in:
parent
3f007a350a
commit
f640c4ea9d
9 changed files with 293 additions and 38 deletions
|
|
@ -15,8 +15,9 @@ Slice 13 basic scoped terminal.
|
|||
5. Copy the terminal transcript and verify the UI reports copy success.
|
||||
6. Start a command that waits for stdin, send input through the drawer, and
|
||||
verify the command output includes that stdin.
|
||||
7. Send the terminal output to the active AI thread and approve the fake ACP
|
||||
command request.
|
||||
7. Attach the terminal output to the composer, verify no AI turn starts until
|
||||
the composer Send action is clicked, then approve the fake ACP command
|
||||
request.
|
||||
8. Start a long-running command and click Kill.
|
||||
9. Click Clear and verify the drawer output resets.
|
||||
|
||||
|
|
@ -32,8 +33,9 @@ Slice 13 basic scoped terminal.
|
|||
are visible in the bottom drawer and do not use Node integration.
|
||||
- Copy output uses the preload-whitelisted Electron clipboard IPC, not renderer
|
||||
Node integration or an unbounded IPC channel.
|
||||
- Send to AI uses the existing authenticated WebSocket user-message path with
|
||||
a bounded terminal transcript.
|
||||
- Attach Output appends a bounded terminal transcript to the composer draft,
|
||||
does not touch the session WebSocket immediately, and requires the normal
|
||||
composer Send action before a new agent turn starts.
|
||||
|
||||
## Diagnostics on Failure
|
||||
|
||||
|
|
@ -97,6 +99,34 @@ Additional artifacts collected:
|
|||
- `completed-layout.json`
|
||||
- `completed-workspace.png`
|
||||
|
||||
## Automated Coverage Added In Codex Alignment Iteration 5
|
||||
|
||||
Iteration 5 changes terminal follow-up from direct AI send to explicit
|
||||
attach-to-composer and updates the real Electron CDP smoke to cover the safer
|
||||
workflow:
|
||||
|
||||
1. Launch real Electron with isolated HOME/runtime/user-data and fake ACP.
|
||||
2. Open the fake Git project, create a thread from the project composer, and
|
||||
complete the existing approval/review/commit path.
|
||||
3. Expand Terminal, run stdout and stdin commands, then click `Attach Output`.
|
||||
4. Assert the composer contains the bounded terminal transcript, the terminal
|
||||
action is labeled `Attach Output`, the legacy `Send to AI` action is absent,
|
||||
and no new `Approve Once` request appears before composer Send.
|
||||
5. Click composer `Send`, approve the fake ACP request, and verify the fake ACP
|
||||
response includes the terminal-output prompt.
|
||||
|
||||
Executable harness:
|
||||
|
||||
- `packages/desktop/scripts/e2e-cdp-smoke.mjs`
|
||||
|
||||
Additional artifacts collected:
|
||||
|
||||
- `terminal-attachment.json`
|
||||
- `terminal-expanded-layout.json`
|
||||
- `terminal-expanded.png`
|
||||
- `completed-layout.json`
|
||||
- `completed-workspace.png`
|
||||
|
||||
## Execution Results
|
||||
|
||||
Codex alignment iteration 4:
|
||||
|
|
@ -110,6 +140,17 @@ Codex alignment iteration 4:
|
|||
- Success artifacts:
|
||||
`.qwen/e2e-tests/electron-desktop/artifacts/2026-04-25T17-00-08-461Z/`.
|
||||
|
||||
Codex alignment iteration 5:
|
||||
|
||||
- `cd packages/desktop && SHELL=/bin/bash npx vitest run src/renderer/components/layout/WorkspacePage.test.tsx`
|
||||
passed: 5 tests.
|
||||
- `cd packages/desktop && npm run typecheck` passed.
|
||||
- `cd packages/desktop && npm run lint` passed.
|
||||
- `cd packages/desktop && npm run build` passed.
|
||||
- `cd packages/desktop && npm run e2e:cdp` passed.
|
||||
- Success artifacts:
|
||||
`.qwen/e2e-tests/electron-desktop/artifacts/2026-04-25T17-08-17-022Z/`.
|
||||
|
||||
Slice 16:
|
||||
|
||||
- `npm run test --workspace=packages/desktop` passed: 9 files, 55 tests.
|
||||
|
|
|
|||
|
|
@ -22,7 +22,101 @@ execution order, verification, decisions, and remaining work.
|
|||
|
||||
## Codex Alignment Progress
|
||||
|
||||
### Active Slice: Collapsed Terminal Status Strip Alignment
|
||||
### Completed Slice: Terminal Attach-to-Composer Workflow
|
||||
|
||||
Status: completed in iteration 5.
|
||||
|
||||
Goal: change terminal output follow-up from an immediate `Send to AI` action
|
||||
into an explicit attach-to-composer flow, so users can review and edit command
|
||||
output before deciding whether to send it to the agent.
|
||||
|
||||
User-visible value: terminal output becomes contextual material in the task
|
||||
composer rather than a hidden second send path that can unexpectedly trigger a
|
||||
new agent turn. This keeps the conversation-first workbench aligned with
|
||||
`home.jpg` while preserving the terminal as a supporting tool.
|
||||
|
||||
Expected files:
|
||||
|
||||
- `packages/desktop/src/renderer/App.tsx`
|
||||
- `packages/desktop/src/renderer/api/websocket.ts`
|
||||
- `packages/desktop/src/renderer/components/layout/WorkspacePage.tsx`
|
||||
- `packages/desktop/src/renderer/components/layout/TerminalDrawer.tsx`
|
||||
- `packages/desktop/src/renderer/components/layout/SidebarIcons.tsx`
|
||||
- `packages/desktop/src/renderer/components/layout/WorkspacePage.test.tsx`
|
||||
- `packages/desktop/scripts/e2e-cdp-smoke.mjs`
|
||||
- `.qwen/e2e-tests/electron-desktop/terminal-drawer.md`
|
||||
- `design/qwen-code-electron-desktop-implementation-plan.md`
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- The expanded Terminal action is labeled as attaching output to the composer,
|
||||
not sending directly to AI.
|
||||
- Attaching terminal output appends a bounded terminal transcript to the
|
||||
existing composer text and shows a clear success notice.
|
||||
- The attach action works whenever terminal output exists, including before a
|
||||
thread is selected, and does not require or write to the session WebSocket.
|
||||
- The user must still click Send from the composer before a new agent turn is
|
||||
created.
|
||||
- Copy, clear, kill, run command, stdin, expand, and collapse behavior is
|
||||
unchanged.
|
||||
|
||||
Verification:
|
||||
|
||||
- Unit/component test command:
|
||||
`cd packages/desktop && SHELL=/bin/bash npx vitest run src/renderer/components/layout/WorkspacePage.test.tsx`
|
||||
- Build/typecheck/lint commands:
|
||||
`cd packages/desktop && npm run typecheck && npm run lint && npm run build`
|
||||
- Real Electron harness:
|
||||
`cd packages/desktop && npm run e2e:cdp`
|
||||
- Harness path: `packages/desktop/scripts/e2e-cdp-smoke.mjs`
|
||||
- E2E scenario steps: launch real Electron with isolated HOME/runtime/user-data
|
||||
and fake ACP, open the fake Git project, create a composer-first thread,
|
||||
approve the fake command, review and commit changes, expand Terminal, run
|
||||
stdout and stdin commands, attach the resulting output to the composer,
|
||||
assert no fake ACP follow-up happens until Send is clicked, then send the
|
||||
composer text and approve the fake command request.
|
||||
- E2E assertions: attach button is present and `Send to AI` is absent; composer
|
||||
contains the terminal transcript after attach; terminal notice confirms the
|
||||
attachment; the output stays editable in the composer; console errors and
|
||||
failed local requests are absent.
|
||||
- Diagnostic artifacts: CDP screenshots, terminal layout JSON, composer attach
|
||||
JSON, Electron log, summary JSON under
|
||||
`.qwen/e2e-tests/electron-desktop/artifacts/`.
|
||||
- Required skills applied: `frontend-design` for prototype-constrained terminal
|
||||
action wording and compact composer-centric hierarchy; `electron-desktop-dev`
|
||||
for renderer changes and real Electron CDP verification.
|
||||
|
||||
Notes and decisions:
|
||||
|
||||
- The prototype keeps the composer as the task control center, so terminal
|
||||
output should land there for user review rather than bypassing it.
|
||||
- This slice intentionally preserves the transcript formatting and bounding
|
||||
logic from the existing send path, but changes the destination from WebSocket
|
||||
send to composer draft text.
|
||||
- The WebSocket helper no longer needs a separate terminal-output send method
|
||||
because the final send is the same explicit user-message path as any other
|
||||
composer submit.
|
||||
|
||||
Verification results:
|
||||
|
||||
- `cd packages/desktop && SHELL=/bin/bash npx vitest run src/renderer/components/layout/WorkspacePage.test.tsx`
|
||||
passed with 5 tests.
|
||||
- `cd packages/desktop && npm run typecheck` passed.
|
||||
- `cd packages/desktop && npm run lint` passed.
|
||||
- `cd packages/desktop && npm run build` passed.
|
||||
- `cd packages/desktop && npm run e2e:cdp` passed after launch through real
|
||||
Electron over CDP.
|
||||
- Passing artifacts:
|
||||
`.qwen/e2e-tests/electron-desktop/artifacts/2026-04-25T17-08-17-022Z/`.
|
||||
|
||||
Next work:
|
||||
|
||||
- Improve review safety by replacing `Accept`/`Revert` terminology with
|
||||
Stage/Unstage/Discard and confirming destructive discard paths.
|
||||
- Continue prototype fidelity work in the conversation timeline by hiding
|
||||
protocol/session noise and adding inline changed-file summaries.
|
||||
|
||||
### Completed Slice: Collapsed Terminal Status Strip Alignment
|
||||
|
||||
Status: completed in iteration 4.
|
||||
|
||||
|
|
|
|||
|
|
@ -136,8 +136,10 @@ async function main() {
|
|||
await clickButton('Send Input');
|
||||
await waitForText('Input sent.');
|
||||
await waitForText('stdin:desktop-e2e-stdin');
|
||||
await clickButton('Send to AI');
|
||||
await waitForText('Sent terminal output to AI.');
|
||||
await clickButton('Attach Output');
|
||||
await waitForText('Attached terminal output to composer.');
|
||||
await assertTerminalOutputAttached('terminal-attachment.json');
|
||||
await clickButton('Send');
|
||||
await waitForText('Approve Once');
|
||||
await clickButton('Approve Once');
|
||||
await waitForText(
|
||||
|
|
@ -747,6 +749,59 @@ async function assertTerminalExpandedLayout(fileName) {
|
|||
}
|
||||
}
|
||||
|
||||
async function assertTerminalOutputAttached(fileName) {
|
||||
const snapshot = await evaluate(`(() => {
|
||||
const textarea = document.querySelector('textarea[aria-label="Message"]');
|
||||
const terminalActions = document.querySelector('.terminal-actions');
|
||||
const text = textarea?.value ?? '';
|
||||
return {
|
||||
composerValue: text,
|
||||
hasTerminalPrompt: text.includes('Review this terminal output'),
|
||||
hasCommand: text.includes('$ node -e'),
|
||||
hasStdinOutput: text.includes('stdin:desktop-e2e-stdin'),
|
||||
hasAttachAction: Boolean(
|
||||
document.querySelector('button[aria-label="Attach Output"]')
|
||||
),
|
||||
hasLegacySendAction: Boolean(
|
||||
document.querySelector('button[aria-label="Send to AI"]')
|
||||
) || (terminalActions?.textContent ?? '').includes('Send to AI'),
|
||||
hasPendingApproval: document.body.innerText.includes('Approve Once')
|
||||
};
|
||||
})()`);
|
||||
|
||||
await writeFile(
|
||||
join(artifactDir, fileName),
|
||||
`${JSON.stringify(snapshot, null, 2)}\n`,
|
||||
'utf8',
|
||||
);
|
||||
|
||||
if (!snapshot.hasAttachAction) {
|
||||
throw new Error('Terminal attach action was not rendered.');
|
||||
}
|
||||
|
||||
if (snapshot.hasLegacySendAction) {
|
||||
throw new Error('Terminal should attach output, not show Send to AI.');
|
||||
}
|
||||
|
||||
if (
|
||||
!snapshot.hasTerminalPrompt ||
|
||||
!snapshot.hasCommand ||
|
||||
!snapshot.hasStdinOutput
|
||||
) {
|
||||
throw new Error(
|
||||
`Terminal output was not attached to composer: ${JSON.stringify(
|
||||
snapshot,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (snapshot.hasPendingApproval) {
|
||||
throw new Error(
|
||||
'Attaching terminal output should not create an agent approval request.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function assertSettingsPageLayout(fileName) {
|
||||
const metrics = await evaluate(`(() => {
|
||||
const rectFor = (selector) => {
|
||||
|
|
|
|||
|
|
@ -713,14 +713,9 @@ export function App() {
|
|||
}
|
||||
}, [terminal]);
|
||||
|
||||
const sendTerminalOutputToAi = useCallback(() => {
|
||||
if (
|
||||
!activeSessionId ||
|
||||
!socketRef.current ||
|
||||
chatState.connection !== 'connected' ||
|
||||
!terminal
|
||||
) {
|
||||
setTerminalError('Open a thread before sending terminal output to AI.');
|
||||
const attachTerminalOutputToComposer = useCallback(() => {
|
||||
if (!terminal) {
|
||||
setTerminalError('Run a terminal command before attaching output.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -730,12 +725,15 @@ export function App() {
|
|||
return;
|
||||
}
|
||||
|
||||
const content = buildTerminalOutputPrompt(terminal);
|
||||
dispatchChat({ type: 'append_user_message', content });
|
||||
socketRef.current.sendTerminalOutput(content);
|
||||
const content = buildTerminalAttachmentDraft(terminal);
|
||||
setMessageText((current) =>
|
||||
current.trim().length > 0
|
||||
? `${current.trimEnd()}\n\n${content}`
|
||||
: content,
|
||||
);
|
||||
setTerminalError(null);
|
||||
setTerminalNotice('Sent terminal output to AI.');
|
||||
}, [activeSessionId, chatState.connection, terminal]);
|
||||
setTerminalNotice('Attached terminal output to composer.');
|
||||
}, [terminal]);
|
||||
|
||||
useEffect(() => {
|
||||
if (loadState.state !== 'ready' || terminal?.status !== 'running') {
|
||||
|
|
@ -972,7 +970,7 @@ export function App() {
|
|||
onRevertReviewTarget={revertReviewTarget}
|
||||
onRunTerminalCommand={runTerminalCommand}
|
||||
onSaveSettings={saveSettings}
|
||||
onSendTerminalOutputToAi={sendTerminalOutputToAi}
|
||||
onAttachTerminalOutput={attachTerminalOutputToComposer}
|
||||
onSelectProject={selectProject}
|
||||
onSelectSession={selectSession}
|
||||
onSendMessage={sendMessage}
|
||||
|
|
@ -1041,7 +1039,7 @@ function formatTerminalTranscript(terminal: DesktopTerminal): string {
|
|||
return `$ ${terminal.command}\n[${terminal.status}]${exitText}\n${terminal.output}`;
|
||||
}
|
||||
|
||||
function buildTerminalOutputPrompt(terminal: DesktopTerminal): string {
|
||||
function buildTerminalAttachmentDraft(terminal: DesktopTerminal): string {
|
||||
const transcript = formatTerminalTranscript(terminal);
|
||||
const boundedTranscript =
|
||||
transcript.length > 12_000
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ export interface SessionSocketHandlers {
|
|||
|
||||
export interface SessionSocketClient {
|
||||
sendUserMessage(content: string): void;
|
||||
sendTerminalOutput(content: string): void;
|
||||
respondToPermission(requestId: string, optionId: string): void;
|
||||
respondToAskUserQuestion(
|
||||
requestId: string,
|
||||
|
|
@ -55,9 +54,6 @@ export function connectSessionSocket(
|
|||
sendUserMessage(content: string): void {
|
||||
sendClientMessage(socket, { type: 'user_message', content });
|
||||
},
|
||||
sendTerminalOutput(content: string): void {
|
||||
sendClientMessage(socket, { type: 'user_message', content });
|
||||
},
|
||||
respondToPermission(requestId: string, optionId: string): void {
|
||||
sendClientMessage(socket, {
|
||||
type: 'permission_response',
|
||||
|
|
|
|||
|
|
@ -312,6 +312,33 @@ export function CopyIcon(props: SidebarIconProps) {
|
|||
);
|
||||
}
|
||||
|
||||
export function PaperclipIcon(props: SidebarIconProps) {
|
||||
return (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="m8.1 12.1 6.2-6.2c1.4-1.4 3.6-1.4 5 0s1.4 3.6 0 5l-8.1 8.1c-1.9 1.9-5 1.9-6.9 0s-1.9-5 0-6.9l7.8-7.8"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.7"
|
||||
/>
|
||||
<path
|
||||
d="m8.3 15.8 7.3-7.3"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeWidth="1.7"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function SendIcon(props: SidebarIconProps) {
|
||||
return (
|
||||
<svg
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
ChevronDownIcon,
|
||||
ChevronUpIcon,
|
||||
CopyIcon,
|
||||
SendIcon,
|
||||
PaperclipIcon,
|
||||
StopIcon,
|
||||
TerminalIcon,
|
||||
TrashIcon,
|
||||
|
|
@ -21,13 +21,13 @@ export function TerminalDrawer({
|
|||
isExpanded,
|
||||
input,
|
||||
notice,
|
||||
onAttachOutput,
|
||||
onClear,
|
||||
onCommandChange,
|
||||
onCopyOutput,
|
||||
onInputChange,
|
||||
onKill,
|
||||
onRun,
|
||||
onSendOutputToAi,
|
||||
onToggleExpanded,
|
||||
onWriteInput,
|
||||
project,
|
||||
|
|
@ -38,13 +38,13 @@ export function TerminalDrawer({
|
|||
isExpanded: boolean;
|
||||
input: string;
|
||||
notice: string | null;
|
||||
onAttachOutput: () => void;
|
||||
onClear: () => void;
|
||||
onCommandChange: (command: string) => void;
|
||||
onCopyOutput: () => void;
|
||||
onInputChange: (input: string) => void;
|
||||
onKill: () => void;
|
||||
onRun: () => void;
|
||||
onSendOutputToAi: () => void;
|
||||
onToggleExpanded: () => void;
|
||||
onWriteInput: () => void;
|
||||
project: DesktopProject | null;
|
||||
|
|
@ -110,15 +110,15 @@ export function TerminalDrawer({
|
|||
<span className="sr-only">Copy Output</span>
|
||||
</button>
|
||||
<button
|
||||
aria-label="Send to AI"
|
||||
aria-label="Attach Output"
|
||||
className="terminal-icon-button"
|
||||
disabled={!hasOutput}
|
||||
title="Send to AI"
|
||||
title="Attach Output to Composer"
|
||||
type="button"
|
||||
onClick={onSendOutputToAi}
|
||||
onClick={onAttachOutput}
|
||||
>
|
||||
<SendIcon />
|
||||
<span className="sr-only">Send to AI</span>
|
||||
<PaperclipIcon />
|
||||
<span className="sr-only">Attach Output</span>
|
||||
</button>
|
||||
<button
|
||||
aria-label="Clear Terminal"
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import type {
|
|||
DesktopGitDiff,
|
||||
DesktopProject,
|
||||
DesktopSessionSummary,
|
||||
DesktopTerminal,
|
||||
} from '../../api/client.js';
|
||||
import { createInitialChatState } from '../../stores/chatStore.js';
|
||||
import { createInitialModelState } from '../../stores/modelStore.js';
|
||||
|
|
@ -95,6 +96,12 @@ describe('WorkspacePage', () => {
|
|||
expect(
|
||||
renderedContainer.querySelector('button[aria-label="Copy Output"]'),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
renderedContainer.querySelector('button[aria-label="Attach Output"]'),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
renderedContainer.querySelector('button[aria-label="Send to AI"]'),
|
||||
).toBeNull();
|
||||
|
||||
act(() => {
|
||||
clickButton(renderedContainer, 'Open Changes');
|
||||
|
|
@ -219,6 +226,30 @@ describe('WorkspacePage', () => {
|
|||
HTMLFormElement.prototype.requestSubmit = originalRequestSubmit;
|
||||
}
|
||||
});
|
||||
|
||||
it('routes terminal output through an attach action', () => {
|
||||
const onAttachTerminalOutput = vi.fn();
|
||||
const renderedContainer = renderWorkspace({
|
||||
onAttachTerminalOutput,
|
||||
terminal,
|
||||
});
|
||||
|
||||
act(() => {
|
||||
clickButton(renderedContainer, 'Expand Terminal');
|
||||
});
|
||||
|
||||
const attachButton = renderedContainer.querySelector(
|
||||
'button[aria-label="Attach Output"]',
|
||||
);
|
||||
expect(attachButton).toBeInstanceOf(HTMLButtonElement);
|
||||
expect((attachButton as HTMLButtonElement).disabled).toBe(false);
|
||||
|
||||
act(() => {
|
||||
clickButton(renderedContainer, 'Attach Output');
|
||||
});
|
||||
|
||||
expect(onAttachTerminalOutput).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
type WorkspacePageProps = Parameters<typeof WorkspacePage>[0];
|
||||
|
|
@ -270,7 +301,7 @@ function renderWorkspace(
|
|||
onRevertReviewTarget: vi.fn(),
|
||||
onRunTerminalCommand: vi.fn(),
|
||||
onSaveSettings: vi.fn(),
|
||||
onSendTerminalOutputToAi: vi.fn(),
|
||||
onAttachTerminalOutput: vi.fn(),
|
||||
onSelectProject: vi.fn(),
|
||||
onSelectSession: vi.fn(),
|
||||
onSendMessage: (event) => event.preventDefault(),
|
||||
|
|
@ -348,6 +379,19 @@ const session: DesktopSessionSummary = {
|
|||
cwd: project.path,
|
||||
};
|
||||
|
||||
const terminal: DesktopTerminal = {
|
||||
id: 'terminal-1',
|
||||
projectId: project.id,
|
||||
cwd: project.path,
|
||||
command: 'printf terminal-output',
|
||||
status: 'exited',
|
||||
output: 'terminal-output',
|
||||
exitCode: 0,
|
||||
signal: null,
|
||||
createdAt: '2026-04-25T00:00:00.000Z',
|
||||
updatedAt: '2026-04-25T00:00:01.000Z',
|
||||
};
|
||||
|
||||
const gitDiff: DesktopGitDiff = {
|
||||
ok: true,
|
||||
generatedAt: '2026-04-25T00:00:00.000Z',
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ export function WorkspacePage({
|
|||
onRevertReviewTarget,
|
||||
onRunTerminalCommand,
|
||||
onSaveSettings,
|
||||
onSendTerminalOutputToAi,
|
||||
onAttachTerminalOutput,
|
||||
onSelectProject,
|
||||
onSelectSession,
|
||||
onSendMessage,
|
||||
|
|
@ -119,7 +119,7 @@ export function WorkspacePage({
|
|||
onRevertReviewTarget: (target: DesktopGitReviewTarget) => void;
|
||||
onRunTerminalCommand: () => void;
|
||||
onSaveSettings: () => void;
|
||||
onSendTerminalOutputToAi: () => void;
|
||||
onAttachTerminalOutput: () => void;
|
||||
onSelectProject: (projectId: string) => void;
|
||||
onSelectSession: (sessionId: string) => void;
|
||||
onSendMessage: (event: FormEvent<HTMLFormElement>) => void;
|
||||
|
|
@ -259,7 +259,7 @@ export function WorkspacePage({
|
|||
onKill={onKillTerminal}
|
||||
onInputChange={onTerminalInputChange}
|
||||
onRun={onRunTerminalCommand}
|
||||
onSendOutputToAi={onSendTerminalOutputToAi}
|
||||
onAttachOutput={onAttachTerminalOutput}
|
||||
onToggleExpanded={() =>
|
||||
setIsTerminalExpanded((current) => !current)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue