Dashboard › opencode › Distillation
35a88d88-1b16-431d-93c7-09d11f334fba["lore_tm_v1_l8wDu14LVujrNN9_P176uKSgYJxSnIzLPYXSrfTDDgo"]
Date: Sep 8, 2026
notifyOnExit, wait for the future <pty_exited> message./home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts is 514 lines and defines plugin ID local-pty using Plugin.define./home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts imports Plugin from @opencode/plugin, Context from @opencode/plugin/plugin, ToolContext from @opencode/plugin/tool, Schema from effect, spawn and ChildProcessWithoutNullStreams from node:child_process, and realpath/stat from node:fs/promises.MAX_BUFFER_SIZE = 1_000_000, MAX_SESSIONS = 25, MAX_SESSIONS_PER_OWNER = 5, DEFAULT_READ_LIMIT = 500, MAX_READ_LIMIT = 2_000, MAX_READ_OFFSET = 10_000, MAX_LINE_LENGTH = 2_000, MAX_PATTERN_LENGTH = 500, MAX_WRITE_SIZE = 65_536, MAX_TIMEOUT_SECONDS = 2_147_483, and KILL_GRACE_MS = 1_000.HOME: process.env.HOME ?? "/home/byk", LANG: process.env.LANG ?? "C.UTF-8", PATH: "/usr/bin:/bin", SHELL: "/bin/sh", and TERM: "xterm-256color".pty_spawn starts a background interactive command and has shell permission; 2. pty_write writes escaped input/control characters and has shell permission; 3. pty_read reads buffered output by line range or extended-regex match; 4. pty_list lists sessions owned by the current parent session; 5. pty_kill stops a session and can optionally remove its output.SpawnInput requires command, args, and description; optionally accepts workdir, title, notifyOnExit, and integer timeoutSeconds constrained to 1..2_147_483.WriteInput.data is limited to 65_536 characters and supports \n, \r, \t, \xNN, \uNNNN, and \\; pty_write decodes these through decodeEscapes() and awaits the stdin.write callback.ReadInput accepts zero-based offset from 0..10_000, limit from 1..2_000 with default 500, optional extended-regex pattern up to 500 characters, and optional ignoreCase.Session records id, title, description, command, readonly args, canonical workdir, status ("running" | "killing" | "killed" | "exited"), notification and timeout fields, exit code/signal, PID, creation time, parentSessionID, child process, timer, close/termination promises, and output buffer.pty_spawn calls authorize(ctx, input.command, input.args, input.workdir ?? ctx.location.directory, context) before capacity checks or process creation.parentSessionID === context.sessionID; at 5 sessions it removes the first "exited" or "killed" session, otherwise throws PTY session limit reached for this Session (5). Global capacity is 25 and throws PTY Location session limit reached (25).pty_spawn generates IDs as pty_${crypto.randomUUID().replaceAll("-", "").slice(0, 8)} and launches /usr/bin/script with ["-qefc", shellCommand(input.command, input.args), "/dev/null"], cwd: workdir, detached: true, and env: FIXED_ENV."spawn", ignores stdin "error" events, rejects on pre-spawn process "error", requires a defined PID, then transfers accumulated initial output into a rolling buffer capped at MAX_BUFFER_SIZE.session.buffer and retain its final 1_000_000 characters; process "error" messages are appended with a newline under the same cap."close" event: the timer is cleared; "killing" becomes "killed" and all other states become "exited"; exitCode is code ?? (signal ? 1 : 0); and exitSignal is stored when present.notifyOnExit: true, close handling awaits settlement of ctx.session.synthetic(...).catch(...).finally(done). The synthetic prompt uses delivery: "steer" and includes <pty_exited>, ID, description truncated to 100 characters, exit code, timeout seconds or none, timed-out status, output-line count, and the last nonblank line truncated to 200 characters.<pty_spawned> with ID, title, command and arguments, workdir, PID, status, notification setting, and timeout. When notifications are enabled it also emits <system_reminder> stating: Completion signal for this session is the future <pty_exited> message. Never poll with sleep and pty_read.timedOut = true and status = "killing", calls terminate(session), logs termination failure, schedules at timeoutSeconds * 1_000, and calls unref() when the timer is an object.pty_read uses linePage(session.buffer, offset, limit + 1) for ordinary paging or matchingLines(session.buffer, pattern, ignoreCase, offset + limit + 1) for pattern searches; results include original line numbers, truncate each displayed line to MAX_LINE_LENGTH, and provide the next offset when more results exist.pty_read appends <system_reminder>Wait for <pty_exited>; never poll with sleep and pty_read.</system_reminder>.pty_list returns only sessions owned by context.sessionID, serialized as formatted JSON via info(), or No PTY sessions. when none exist.pty_kill enforces session ownership through requireSession(). With cleanup: true, remove() clears the timer, awaits termination, deletes the map entry, and empties the buffer; otherwise it clears the timer and awaits terminate() only if status is "running".ctx.event.subscribe({ signal: events.signal }); on session.deleted, it removes all sessions whose parentSessionID matches event.data.sessionID. Plugin cleanup aborts the subscription and awaits removal of every remaining session.requireSession() rejects absent or cross-owner sessions with PTY session not found: ${id}.shellCommand() single-quotes the command and each argument, replacing embedded ' with the shell sequence '\\'', then joins the parts with spaces.process.kill(-session.pid, value) and suppresses only ESRCH. terminate() is idempotent through session.terminate, sends SIGTERM, races session.closed against a 1_000 ms grace timer, sends SIGKILL if still open, and then awaits session.closed.authorize() canonicalizes the requested workdir and ctx.location.project.directory concurrently with realpath; if the directory is outside the project, it calls ctx.permission.assert for action external_directory and resource [directory].authorize() constructs permission source { type: "tool", messageID: context.messageID, id: context.id }, then calls canonical ctx.permission.assert for action shell, with resources: [shellCommand(command, args)], the same value in save, sessionID, agent, and source.authorize() calls realpath(workdir) again, rejects if it differs from the initially authorized canonical directory, verifies (await stat(current)).isDirectory(), and returns the authorized canonical directory.matchingLines() launches /usr/bin/grep with -a, -m ${limit}, -nE, optional -i, --, and the user pattern; it feeds the complete bounded PTY buffer through stdin.250 ms timer that sends SIGKILL, stdout storage capped at MAX_BUFFER_SIZE * 2, stderr storage capped at MAX_LINE_LENGTH, and grepβs -m match limit. EPIPE from stdin is ignored after settlement; exit code 1 means no matches, nonzero codes otherwise fail, and SIGKILL reports PTY output pattern timed out.matchingLines() parses grepβs line:text format into { line: Number(...), text: ... } and throws Unexpected grep output if no colon separator exists.