You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[FEATURE]: Agent should use integrated terminal (PTY) instead of spawning new shell processes #23449
I have verified this feature hasn't been suggested before
I understand this issue needs status:approved before a PR can be opened
Problem Description
OpenCode has a built-in terminal panel powered by ghostty-web with full PTY support. The terminal panel is accessible via `Ctrl+`` and supports:
Multiple tabs
10,000 lines of scrollback
Themes (light/dark)
WebSocket bridge to /pty/{id}/connect
However, when the agent executes shell commands, it uses the bash tool which spawns a completely new shell process each time. This creates several problems:
No interactive command support - Commands like htop, vim, ssh, mysql that require a TTY can't run
State not shared - Environment variables, working directory changes, and shell state don't persist between commands
Timeout issues - Interactive commands often get killed after the timeout
Waste of resources - New process spawned for every command instead of reusing the PTY
Proposed Solution
Expose the integrated terminal (PTY) to the agent as a tool, so instead of:
Agent -> bash tool -> spawns new process -> returns output
It would be:
Agent -> terminal tool -> connects to existing PTY via WebSocket -> streams I/O
The proposed solution (PTY + WebSocket bridge to existing terminal panel) is technically feasible since the WebSocket endpoint at /pty/{id}/connect already exists in the codebase. This would enable truly interactive commands (ssh, vim, htop), persistent shell state, and shared PTY between agent and user.
constParameters=z.object({command: z.string().describe("The command to execute in the PTY session"),timeout: z.number().describe("Optional timeout in milliseconds").optional(),interactive: z.boolean().describe("Whether to wait for user input or auto-complete").optional(),pty_id: z.string().describe("Optional existing PTY session ID to reuse").optional(),})
3.2 Execution Flow
// Pseudocode — follows existing tool patterns from bash.tsexportconstTerminalTool=Tool.info({id: "terminal",init: ()=>Effect.gen(function*(){constpty=yield*Pty.Servicereturn{description: TERMINAL_DESCRIPTION,parameters: Parameters,execute: (params,ctx)=>Effect.gen(function*(){// 1. Get or create PTY sessionconstid=params.pty_id
? yield*pty.get(params.pty_id)
: yield*pty.create({cwd: Instance.directory,cols: 80,rows: 24,})// 2. Write command to PTYconstproc=yield*pty.proc(id)proc.write(params.command+"\n")// 3. Stream output until prompt returns or timeoutconstoutput=yield*streamUntilPrompt(proc,params.timeout)// 4. Return resultreturn{title: `Terminal: ${params.command}`,metadata: { output,pty_id: id},
output,}}),}}),})
3.3 PTY Output Streaming Strategy
The key challenge: detecting when a command finishes in a PTY.
Options (in order of preference):
Strategy
How
Trade-off
A. Shell marker
Wrap command with echo of unique sentinel: CMD; echo "__OPENCODE_DONE_$?"
Reliable, but messes with aliases/functions. Best for non-interactive.
B. Prompt detection
Track PS1/prompt pattern, detect when prompt reappears
Natural UX, but prompt detection is fragile across shells.
C. Timeout + manual
Run for timeout, then return output + let user continue
Simplest, but agent doesn't know if command finished. Good for truly interactive.
D. Hybrid
A for non-interactive, C for interactive
Recommended — best of both worlds.
Recommended: Strategy D (Hybrid)
// For commands the agent knows are non-interactive:// Wrap with sentinel markerproc.write(`${command}\necho "__OPENCODE_EXIT_$?"\n`)// For truly interactive commands:// Stream for timeout, return what we have, let user continue in terminal
4. Changes Required
4.1 New Files
File
Purpose
packages/opencode/src/tool/terminal.ts
Terminal tool implementation
packages/opencode/src/tool/terminal.txt
Tool description for LLM
4.2 Modified Files
File
Change
packages/opencode/src/tool/registry.ts
Register TerminalTool + add to tool map
packages/opencode/src/tool/bash.ts
Add hint: "For interactive commands, use the terminal tool instead"
Yield Pty.Service from Effect context (already available)
Create a PTY or reuse an existing one
Write the command via proc.write()
Stream output via proc.onData()
Detect completion (sentinel or timeout)
Return output to the agent
6. Security Considerations
Risk
Mitigation
Agent runs sudo without user knowing
Permission system: ask() for sudo/interactive commands before execution
Agent takes over terminal
Agent PTY sessions are marked with metadata, visible in terminal panel
Password leakage in output
Strip common password prompts from output before returning to LLM
Agent leaves zombie PTYs
Auto-cleanup: PTY sessions have TTL, cleaned up on session end
Permission Model
// Before executing interactive commands, request permissionyield*ctx.ask({id: "terminal.execute",sessionID: ctx.sessionID,tool: "terminal",title: `Execute interactive command: ${command}`,type: "command",// User sees this in the terminal panel in real-time})
7. ACP Integration
The ACP client (packages/opencode/src/acp/client.ts) currently has:
I dug into the current Desktop/agent architecture and I think this issue is pointing in the right direction, but it would help to tighten the implementation path a bit.
What already exists
OpenCode already seems to have most of the hard infrastructure:
PTY service in packages/opencode/src/pty/*
PTY HTTP/WS routes in packages/opencode/src/server/routes/instance/pty.ts
Desktop sidecar server in packages/desktop-electron/src/main/server.ts
integrated terminal UI already backed by PTY
So the core problem does not look like “Desktop has no PTY support”.
The actual gap seems to be that the current agent shell execution path still goes through the one-shot non-interactive bash flow:
packages/opencode/src/tool/bash.ts
That path is still fundamentally different from the integrated PTY terminal path, which explains why interactive flows like:
gh issue create --template
sudo
ssh
vim
other stdin/TTY-dependent CLIs
still fail or degrade in Desktop.
Suggested implementation direction
I would recommend not replacing the existing bash tool outright at first.
Instead, the safest path seems to be:
Phase 1 — add an explicit PTY-backed tool
Introduce a new tool (for example terminal) with persistent PTY session support, something like:
create
send
read
close
This would let the agent opt into the PTY path only when true interactivity is needed.
Phase 2 — wire it into Desktop UX
When the agent creates a PTY-backed session, Desktop should surface/focus that integrated terminal session so the user can observe or take over when needed.
Phase 3 — keep bash for simple commands
Keep the existing bash path for non-interactive commands:
git status
ls
npm test
etc.
This avoids forcing PTY/session overhead on every shell call and lowers regression risk.
Phase 4 — optional smart routing later
Only after the explicit PTY flow is stable, consider automatic heuristics for routing commands to PTY when they clearly require TTY behavior.
Why this might be safer than a full bash replacement
This separates two very different execution modes:
one-shot, output-oriented shell commands
stateful, interactive terminal sessions
Trying to collapse both into one tool immediately may make the implementation harder to reason about and harder to review.
A separate PTY-backed tool gives:
lower rollout risk
clearer semantics
easier testing
easier fallback if something breaks
Acceptance criteria I would use for this issue
I would consider this issue materially solved only if the agent can do all of the following through the PTY-backed path:
create a PTY session
send follow-up input to the same session
read incremental output
preserve shell state between calls
close/cleanup the session reliably
And ideally verify against real cases like:
gh issue create --template
sudo ...
ssh ...
vim / less
Bottom line
I agree with the direction of this issue.
My only refinement is:
use the existing PTY infrastructure through an explicit terminal/session tool first, instead of immediately trying to replace all bash execution.
That seems like the cleanest and lowest-risk path to getting interactive Desktop agent workflows working.
Thanks @egdev6 for the direction — this aligns well with what we've already implemented in PR #23794.
Phase 1 is done in that PR: the erminal tool with create, send,
ead, close (plus
un for backward-compat one-shot), backed by the existing Pty.Service. �ash is untouched for simple commands.
Still need to verify against the real interactive cases you listed (gh issue create --template, sudo, ssh, �im) — planning to do that next.
Would be great if you could review the PR when you have a chance. Happy to iterate on feedback.
+1, I've encountered this issue when using !yazi and !vim in opencode. The session hangs silently with no error message, requiring manual termination. My workaround is to run these commands directly in the terminal instead of using !.
Activity
github-actions commentedon Apr 19, 2026
This issue might be a duplicate of existing issues. Please check:
If your request is meaningfully different, please clarify what distinguishes it from the above.
herjarsa commentedon Apr 19, 2026
This issue consolidates the following related requests:
These issues have significant community interest. See also:
The proposed solution (PTY + WebSocket bridge to existing terminal panel) is technically feasible since the WebSocket endpoint at /pty/{id}/connect already exists in the codebase. This would enable truly interactive commands (ssh, vim, htop), persistent shell state, and shared PTY between agent and user.
herjarsa commentedon Apr 20, 2026
Technical Spec: Agent PTY Integration
Issue: #23449
Related: #8097, #9808, #18659, #652, #3212, #1180
Author: @herjarsa
Date: 2026-04-20
1. Problem Statement
The agent's
bashtool (packages/opencode/src/tool/bash.ts) spawns a new child process for every command viaChildProcessSpawner. This means:Meanwhile, OpenCode already has a fully functional terminal panel backed by:
packages/opencode/src/pty/) withPty.Service,Pty.Proc/pty/{id}/connect) atpackages/opencode/src/server/routes/instance/pty.tspackages/app/src/components/terminal.tsx)packages/app/src/context/terminal.tsx) withLocalPTYmanagementThe gap: the agent has NO access to the PTY infrastructure.
2. Proposed Architecture
2.1 Overview
2.2 Two Modes
bash(existing)terminal(new)The agent should choose the appropriate tool based on command analysis. For backward compatibility and simplicity,
bashremains the default.3. New Tool:
terminal3.1 Definition
File:
packages/opencode/src/tool/terminal.ts(NEW)Description file:
packages/opencode/src/tool/terminal.txt(NEW)Parameters:
3.2 Execution Flow
3.3 PTY Output Streaming Strategy
The key challenge: detecting when a command finishes in a PTY.
Options (in order of preference):
CMD; echo "__OPENCODE_DONE_$?"Recommended: Strategy D (Hybrid)
4. Changes Required
4.1 New Files
packages/opencode/src/tool/terminal.tspackages/opencode/src/tool/terminal.txt4.2 Modified Files
packages/opencode/src/tool/registry.tsTerminalTool+ add to tool mappackages/opencode/src/tool/bash.tspackages/opencode/src/acp/client.tspackages/sdk/openapi.json4.3 No Changes Needed
packages/opencode/src/pty/pty.tspackages/opencode/src/server/routes/instance/pty.tspackages/app/src/components/terminal.tsx5. PTY Service Integration
The existing
Pty.Servicealready exposes:The terminal tool simply needs to:
Pty.Servicefrom Effect context (already available)proc.write()proc.onData()6. Security Considerations
sudowithout user knowingask()for sudo/interactive commands before executionPermission Model
7. ACP Integration
The ACP client (
packages/opencode/src/acp/client.ts) currently has:This spec fills that stub:
8. Frontend Integration
The terminal panel already supports multiple tabs. Agent-created PTYs appear as new tabs:
Changes needed in
packages/app/:src/context/terminal.tsxcreateFromAgent(command)method that creates + auto-titlessrc/pages/session/terminal-panel.tsxsrc/components/terminal.tsx9. Implementation Priority
Phase 1: Core (MVP)
terminal.tstool with PTY-backed executionregistry.tsask()for interactive commandsPhase 2: UX
Phase 3: Advanced
pty_idparameter)10. Testing Strategy
terminal.test.tsterminal-pty.test.tsterminal-interactive.test.tsterminal-cleanup.test.tsterminal-existing.test.tspty_id)11. Alternative Approaches Considered
12. Dependencies
Pty.Service/pty/{id}/connectChildProcessSpawnerEffect.gen,InstanceState,Layerctx.ask()for approval flow13. Open Questions
interactive: true?14. References
packages/opencode/src/tool/bash.tspackages/opencode/src/pty/pty.tspackages/opencode/src/server/routes/instance/pty.tspackages/app/src/components/terminal.tsxpackages/app/src/context/terminal.tsxpackages/opencode/src/acp/client.tspackages/opencode/src/tool/registry.tspackages/opencode/src/tool/tool.ts21c52fd5cegdev6 commentedon Apr 23, 2026
I dug into the current Desktop/agent architecture and I think this issue is pointing in the right direction, but it would help to tighten the implementation path a bit.
What already exists
OpenCode already seems to have most of the hard infrastructure:
packages/opencode/src/pty/*packages/opencode/src/server/routes/instance/pty.tspackages/desktop-electron/src/main/server.tsSo the core problem does not look like “Desktop has no PTY support”.
The actual gap seems to be that the current agent shell execution path still goes through the one-shot non-interactive bash flow:
packages/opencode/src/tool/bash.tsThat path is still fundamentally different from the integrated PTY terminal path, which explains why interactive flows like:
gh issue create --templatesudosshvimstill fail or degrade in Desktop.
Suggested implementation direction
I would recommend not replacing the existing
bashtool outright at first.Instead, the safest path seems to be:
Phase 1 — add an explicit PTY-backed tool
Introduce a new tool (for example
terminal) with persistent PTY session support, something like:createsendreadcloseThis would let the agent opt into the PTY path only when true interactivity is needed.
Phase 2 — wire it into Desktop UX
When the agent creates a PTY-backed session, Desktop should surface/focus that integrated terminal session so the user can observe or take over when needed.
Phase 3 — keep
bashfor simple commandsKeep the existing
bashpath for non-interactive commands:git statuslsnpm testThis avoids forcing PTY/session overhead on every shell call and lowers regression risk.
Phase 4 — optional smart routing later
Only after the explicit PTY flow is stable, consider automatic heuristics for routing commands to PTY when they clearly require TTY behavior.
Why this might be safer than a full bash replacement
This separates two very different execution modes:
Trying to collapse both into one tool immediately may make the implementation harder to reason about and harder to review.
A separate PTY-backed tool gives:
Acceptance criteria I would use for this issue
I would consider this issue materially solved only if the agent can do all of the following through the PTY-backed path:
And ideally verify against real cases like:
gh issue create --templatesudo ...ssh ...vim/lessBottom line
I agree with the direction of this issue.
My only refinement is:
That seems like the cleanest and lowest-risk path to getting interactive Desktop agent workflows working.
egdev6 commentedon Apr 23, 2026
I can develop the issue if you reasign me
herjarsa commentedon Apr 23, 2026
Thanks @egdev6 for the direction — this aligns well with what we've already implemented in PR #23794.
Phase 1 is done in that PR: the erminal tool with create, send,
ead, close (plus
un for backward-compat one-shot), backed by the existing Pty.Service. �ash is untouched for simple commands.
Still need to verify against the real interactive cases you listed (gh issue create --template, sudo, ssh, �im) — planning to do that next.
Would be great if you could review the PR when you have a chance. Happy to iterate on feedback.
herjarsa commentedon Apr 23, 2026
@egdev6 — Phase 2 (Desktop UX) is now implemented and pushed to PR #23794:
Phase 1 ✅ (from before): erminal tool with create, send,
ead, close, backed by Pty.Service
Phase 2 ✅ (new): Agent-created PTY sessions now:
The implementation:
Both phases are in the same PR. Ready for review when you have a moment!
egdev6 commentedon Apr 23, 2026
@herjarsa you have the feedback in the PR :)
feat(terminal): surface agent-created PTY sessions in Desktop UI
Tofu-Xx commentedon May 30, 2026
+1, I've encountered this issue when using
!yaziand!vimin opencode. The session hangs silently with no error message, requiring manual termination. My workaround is to run these commands directly in the terminal instead of using!.Related: #29972
github-actions commentedon Jul 30, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.
docs: 补充 Q2.4「没有持久 REPL 具体意味着什么」对照例子