feat(worker): add the terminal_session built-in tool #349
No reviewers
Labels
No labels
Epic 5
MCP
P1
PR-0
PR-1
api
api-layer
architecture
area:backend
area:frontend
arrow
authentication
backend
bug
chat
client
clipboard
common-module
compose
cross-cutting
cross-cutting-requirement
data-layer
data-models
database
desktop-client
documentation
duplicate
enhancement
epic
epic-1
epic-2
epic-3
epic-4
epic-5
epic-6
epic-7
epic-llm-config
error-handling
feature
frontend
good first issue
help wanted
implementation
infrastructure
installer
invalid
kotlin
ktor
llm
medium-priority
message
multiplatform
needs-review
needs-triage
packaging
performance
phase-1
phase-2
phase-3
phase-4
phase-5
phase3
phase:3
priority: high
question
refactor
refactoring
resource-sharing
security
server
server-module
services
session
session-management
settings
setup
shutdown
state
storage
streaming
sub-issue
tests
tooling
type:task
ui
ui/ux
user-accounts
user-management
ux
viewmodel
websocket
windows
windows-integration
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Torvian.eu/chatbot!349
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/worker-terminal-session"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Adds a new built-in worker tool,
terminal_session, that runs a long-lived interactive PTY session inside the worker workspace so the model can drive a shell, ansshsession or another line-oriented program across several tool calls instead of paying for a fresh process each time. Sessions are addressed by the id returned fromstart, read with a per-session cursor so eachpollreturns only new output, and bounded in memory, count, lifetime and blocking time by a newworker.terminalSessionsconfiguration group. The change also prepares the deployed worker image for pty4j and relaxes one assertion in the setuid privilege-drop helper, because pty4j's child is already a session leader when the helper runs.Changes
terminal_sessiontool withstart,send,pollandclose, plus the PTY runtime behind it: a byte-bounded output ring buffer, an output sanitizer, a child-environment builder, a process factory, the session itself and a manager that reaps idle, over-lifetime and closed sessions.worker.terminalSessionsconfiguration group with defaults, ranges and cross-field validation, documented in both dist templates, the bundled default config and the dev config sample.BuiltInToolCatalogso the server seeds it per worker, and marked it dangerous in the app's built-in tool settings.stopKoin()path.-Dpty4j.tmpdirand native-access JVM flags.deploy/worker/run-as-agent.cto accept an already-session-leader child instead of failing withEPERMonsetpgid(0,0).pollon a closed session a success that drains the remaining buffered output and then returns an emptyCLOSEDsnapshot, rather than an error that kept yielding more data.maxBytesof at least 4 so a chunk can always hold one maximum-width UTF-8 code point and a tombstone cannot stall.Implementation notes
run_commandchildren. Under command isolation that means the isolated account withPR_SET_NO_NEW_PRIVS, sosudo,suandpasswdcannot elevate inside a session, and secrets are not supported in v1: an interactivesshmust use keys or an already-authenticated agent.QUIETin the tool result means the process is alive but has been silent for the quiet window, which usually means it is waiting for input; it does not mean the process exited. Full-screen TUI programs are out of scope and the tool description says so.maxSessionslive sessions and at mostmaxClosedSessionstombstones ofmaxOutputBytesPerSessioneach, about 8 MiB with the defaults.reaperIntervalSecondsis validated to be shorter thandefaultIdleTimeoutSecondsso a session cannot outlive its TTL by more than one sweep.onClosecovers tests andstopKoin(), and the JVM shutdown hook is the only path that reaches live PTYs on SIGTERM. The manager close is idempotent, so either may win.setpgid(0,0)is still called whenever the helper is not already its own process-group leader, so the existingrun_commandisolation path keeps its previous behaviour and only the pty4j spawn shape is newly accepted.Validation
./gradlew :worker:classes,:worker:testClasses,:worker:test,:common:desktopTestClasses,:server:testClasses,:app:desktopTestClassesall pass; the terminal suites cover the ring buffer, sanitizer, environment builder, manager (slot accounting, id collisions, reaping, retention, cancellation, closed-session draining) and tool argument handling.agentwith norun-as-agent: setpgidfailure.cd /tmpthenpwdreturned/tmp),closereturnedCLOSED, output truncation and cursor continuation behaved, and the TUI hint no longer appears for a plain shell.Residual unverified items
docker stop/ SIGTERM reaping of live PTYs was not observed end to end; the shutdown-hook path is reviewed but not watched.Pty4jTerminalSessionIntegrationTestwas not run as a JUnit test in an image; the isolation behaviour was verified manually through the client instead.JAVA_OPTSmust keep-Dpty4j.tmpdirand--enable-native-access=ALL-UNNAMED, otherwise pty4j extracts to a location the worker cannot execute from.How to verify the isolation gate manually
deploy/worker/Dockerfile, which installs the setuid helper at/usr/local/bin/run-as-agentand theagentaccount with its home at/home/agent.CHATBOT_TERMINAL_ISOLATION_GATE=true ./gradlew :worker:test --tests '*Pty4jTerminalSessionIntegrationTest*'; a missing gate variable or helper shows up as a skip rather than a pass, so read the test report.id -un, and confirmagentwith norun-as-agent: setpgidin the output.docker stopthe container, and confirm the worker log shows the session closed and no orphaned child survives.