Skip to content

feat: add hidden integrated terminal#2094

Open
benjaminshafii wants to merge 1 commit into
devfrom
feat/hidden-integrated-terminal
Open

feat: add hidden integrated terminal#2094
benjaminshafii wants to merge 1 commit into
devfrom
feat/hidden-integrated-terminal

Conversation

@benjaminshafii

Copy link
Copy Markdown
Member

Summary

  • Adds a hidden integrated terminal available from the command palette and Cmd/Ctrl+J.
  • Backs the renderer with xterm + a narrow Electron IPC bridge to a real PTY.
  • Uses the @lydell/node-pty alias because node-pty@1.1.0 loaded but failed to fork locally with posix_spawnp failed.

Validation

  • pnpm --filter @openwork/app typecheck passed.
  • pnpm --filter @openwork/desktop typecheck:electron passed.
  • pnpm --filter @openwork/app build passed.
  • Desktop PTY smoke passed: node -e "import('node-pty').then(...)" printed PASS openwork-terminal-proof.

Daytona Proof

Sandbox: openwork-test-20260604-152359

Recording:

Frame-by-frame screenshots:

  1. Session loaded, terminal hidden: https://8090-mlzktgfcqzp4bmaf.daytonaproxy01.net/screenshots/daytona-screenshot-20260604-222719.png
  2. Command palette shows Show terminal: https://8090-mlzktgfcqzp4bmaf.daytonaproxy01.net/screenshots/daytona-screenshot-20260604-222740.png
  3. Terminal opened in /workspace/terminal-proof: https://8090-mlzktgfcqzp4bmaf.daytonaproxy01.net/screenshots/daytona-screenshot-20260604-222807.png
  4. Terminal command output visible: https://8090-mlzktgfcqzp4bmaf.daytonaproxy01.net/screenshots/daytona-screenshot-20260604-222904.png
  5. Terminal hidden again via Ctrl+J: https://8090-mlzktgfcqzp4bmaf.daytonaproxy01.net/screenshots/daytona-screenshot-20260604-222924.png
  6. Final assertion screenshot: https://8090-mlzktgfcqzp4bmaf.daytonaproxy01.net/screenshots/daytona-screenshot-20260604-223031.png

CDP assertions:

  • Real Electron app: navigator.userAgent.includes("Electron/") === true.
  • Terminal bridge exposed: Boolean(window.__OPENWORK_ELECTRON__?.terminal) === true.
  • Hidden state after Ctrl+J: !document.body.innerText.includes("Terminal ·") === true.
  • Reopened terminal and executed command: output included assert-terminal-ok:/workspace/terminal-proof.

Recording duration after finalization: 221.666667 seconds.

@vercel

vercel Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
openwork-app Ready Ready Preview, Comment Jun 4, 2026 10:32pm
openwork-den Ready Ready Preview, Comment Jun 4, 2026 10:32pm
openwork-den-worker-proxy Ready Ready Preview, Comment Jun 4, 2026 10:32pm
openwork-landing Ready Ready Preview, Comment, Open in v0 Jun 4, 2026 10:32pm

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 12 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/desktop/electron/main.mjs">

<violation number="1" location="apps/desktop/electron/main.mjs:3075">
P2: `destroyed` listener is registered once per terminal creation and never cleaned up. Repeated create/kill cycles can trigger MaxListeners warnings and unnecessary listener/memory growth.</violation>
</file>

<file name="apps/app/src/react-app/domains/session/chat/session-page.tsx">

<violation number="1" location="apps/app/src/react-app/domains/session/chat/session-page.tsx:1122">
P2: Hiding the panel unmounts and kills the PTY, so reopening loses the shell session instead of just hiding it.</violation>

<violation number="2" location="apps/app/src/react-app/domains/session/chat/session-page.tsx:1128">
P2: Terminal remote gating uses workspace display type instead of runtime remote state. This can incorrectly enable a local PTY on remote sessions or block terminal on local sessions when metadata differs.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

});

terminalProcesses.set(terminalId, { process: child, webContentsId: event.sender.id });
event.sender.once("destroyed", () => killTerminalsForWebContents(event.sender.id));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: destroyed listener is registered once per terminal creation and never cleaned up. Repeated create/kill cycles can trigger MaxListeners warnings and unnecessary listener/memory growth.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/electron/main.mjs, line 3075:

<comment>`destroyed` listener is registered once per terminal creation and never cleaned up. Repeated create/kill cycles can trigger MaxListeners warnings and unnecessary listener/memory growth.</comment>

<file context>
@@ -3015,6 +3051,56 @@ ipcMain.handle("openwork:system:askMicrophoneAccess", async () => {
+  });
+
+  terminalProcesses.set(terminalId, { process: child, webContentsId: event.sender.id });
+  event.sender.once("destroyed", () => killTerminalsForWebContents(event.sender.id));
+  child.onData((data) => {
+    if (event.sender.isDestroyed()) return;
</file context>
Suggested change
event.sender.once("destroyed", () => killTerminalsForWebContents(event.sender.id));
if (!event.sender.__openworkTerminalCleanupRegistered) {
event.sender.__openworkTerminalCleanupRegistered = true;
event.sender.once("destroyed", () => killTerminalsForWebContents(event.sender.id));
}

<ResizablePanel defaultSize="280px" minSize="160px" maxSize="55%" className="min-h-0">
<TerminalDock
workspaceRoot={props.selectedWorkspaceRoot}
isRemoteWorkspace={props.selectedWorkspaceDisplay.workspaceType === "remote"}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Terminal remote gating uses workspace display type instead of runtime remote state. This can incorrectly enable a local PTY on remote sessions or block terminal on local sessions when metadata differs.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/app/src/react-app/domains/session/chat/session-page.tsx, line 1128:

<comment>Terminal remote gating uses workspace display type instead of runtime remote state. This can incorrectly enable a local PTY on remote sessions or block terminal on local sessions when metadata differs.</comment>

<file context>
@@ -1114,7 +1118,20 @@ export function SessionPage(props: SessionPageProps) {
+                <ResizablePanel defaultSize="280px" minSize="160px" maxSize="55%" className="min-h-0">
+                  <TerminalDock
+                    workspaceRoot={props.selectedWorkspaceRoot}
+                    isRemoteWorkspace={props.selectedWorkspaceDisplay.workspaceType === "remote"}
+                    onClose={() => props.onTerminalOpenChange?.(false)}
+                  />
</file context>
Suggested change
isRemoteWorkspace={props.selectedWorkspaceDisplay.workspaceType === "remote"}
isRemoteWorkspace={props.surface?.isRemoteWorkspace ?? false}

</div>
</div>
</ResizablePanel>
{props.terminalOpen ? (

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Hiding the panel unmounts and kills the PTY, so reopening loses the shell session instead of just hiding it.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/app/src/react-app/domains/session/chat/session-page.tsx, line 1122:

<comment>Hiding the panel unmounts and kills the PTY, so reopening loses the shell session instead of just hiding it.</comment>

<file context>
@@ -1114,7 +1118,20 @@ export function SessionPage(props: SessionPageProps) {
             </div>
-          </div>
+            </ResizablePanel>
+            {props.terminalOpen ? (
+              <>
+                <ResizableHandle withHandle />
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant