Dashboard › opencode › Distillation
c952320b-7970-432f-97bc-039988047246["lore_tm_v1_Jg18lkIZjiRI8invAUyfBTYjyOncxCNwPHvUK10BFdM"]
π΄ (19:18) External plugin file /home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts is 852 lines and exports createPtyPlugin(options: PtyPluginOptions = {}), with default export createPtyPlugin() and plugin ID "local-pty".
π΄ (19:18) PtyPluginOptions supports openProcess?: typeof openPtyProcess and spawnTimeoutMs?: number; the plugin registers 5 tools through ctx.tool.transform: pty_spawn, pty_write, pty_read, pty_list, and pty_kill.
π΄ (19:18) PTY limits/constants in plugins/pty.ts: MAX_SESSIONS=16, 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=3_600, CLOSE_TIMEOUT_MS=27_000, NOTIFICATION_TIMEOUT_MS=5_000, IO_TIMEOUT_MS=NOTIFICATION_TIMEOUT_MS, MAX_COMMAND_LENGTH=4_096, MAX_ARGUMENTS=128, MAX_ARGUMENT_LENGTH=16_384, MAX_ARGUMENT_BYTES=65_536, MAX_PATH_LENGTH=4_096, MAX_TITLE_LENGTH=500, MAX_DESCRIPTION_LENGTH=2_000, MAX_REGEX_WORKERS=4, MAX_BUFFER_SIZE=1_000_000, TERMINATE_TIMEOUT_MS=2_000, and SPAWN_TIMEOUT_MS=5_000.
π΄ (19:18) PTY execution uses SCRIPT_PATH="/usr/bin/script" and SHELL_PATH="/bin/sh" with fixed environment HOME=/home/byk, LANG=C.UTF-8, LOGNAME=byk, PATH=/usr/bin:/bin, SHELL=/bin/sh, TERM=xterm-256color, and USER=byk.
π΄ (19:18) pty_spawn accepts a nonempty command, up to 128 arguments, optional workdir, optional title, required nonempty description, optional notifyOnExit, and optional positive timeoutSeconds up to 3,600; its tool options are { codemode: false, permission: "shell" }.
π΄ (19:18) pty_spawn maintains Location-wide and owner-specific concurrency state using sessions, reservations, pendingSpawns, and ownerSpawns. An owner may have at most 5 sessions including reservations; when that limit is reached, one exited/killed session may be evicted, otherwise spawning fails. The Location-wide maximum is 16 sessions including reservations.
π΄ (19:18) pty_spawn calls validateCommand(...), rejects while the plugin is closing or after the parent Session was deleted, authorizes the command and workdir, creates IDs in the form pty_${crypto.randomUUID().replaceAll("-", "").slice(0, 8)}, opens the process under a startup timeout, closes the directory handle, attaches output and exit handlers, and only then inserts the new Session into sessions.
π΄ (19:18) authorize(ctx, command, args, workdir, context) resolves both the requested directory and ctx.location.project.directory with realpath, opens the requested directory using constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW, verifies the handle still resolves to the approved directory, and returns { directory, handle }.
π΄ (19:18) authorize(...) requests ctx.permission.assert with action "external_directory" and resource [directory] when the resolved workdir is outside the project directory, followed by action "shell" and resource [shellCommand(command, args)]; both assertions use save: [], sessionID: context.sessionID, agent: context.agent, and source { type: "tool", messageID: context.messageID, id: context.id }.
π΄ (19:18) After permission approval, authorize(...) re-resolves both workdir and /proc/self/fd/${handle.fd} and throws Working directory changed during permission approval: ${workdir} if either differs from the originally authorized directory; on any authorization error it closes the directory handle before rethrowing.
π΄ (19:18) shellCommand(command, args) constructs a shell-quoted command by wrapping every command/argument in single quotes and replacing embedded ' with the sequence '\\''.
π΄ (19:18) pty_write requires ownership of the target PTY and "running" status, decodes \n, \r, \t, \xNN, \uNNNN, and \\, rejects malformed Unicode or decoded content above 65,536 UTF-8 bytes, obtains a canonical shell permission assertion through assertShell(...), rechecks ownership/status after approval, and applies a 5,000 ms write timeout.
π΄ (19:18) assertShell(...) authorizes action "shell" with the original command resource for kill operations; for writes it authorizes a resource formatted as ${shellCommand(session.command, session.args)} <stdin> ${shellCommand(input, [])}. It passes save: [], Session ID, agent, and tool source message/call IDs.
π΄ (19:18) pty_read supports zero-based offset up to 10,000, limit up to 2,000 with default 500, optional extended regular expression up to 500 characters, and optional case-insensitive matching. It caps concurrent regex workers at 4 and returns line numbers, text truncated to 2,000 characters, total line count, hasMore, and conditional nextOffset.
π΄ (19:18) Pattern-based pty_read delegates matching to /usr/bin/grep with ["-a", "-m", String(limit), "-nE", ...(ignoreCase ? ["-i"] : []), "--", pattern]; grep is killed after 250 ms, stdout is limited to MAX_BUFFER_SIZE * 2, stderr to MAX_LINE_LENGTH, exit code 1 means no matches, and SIGKILL is reported as "PTY output pattern timed out.".
π΄ (19:18) pty_list exposes only PTYs whose parentSessionID equals the calling context.sessionID; requireSession(...) likewise hides nonexistent and cross-owner PTYs behind PTY session not found: ${id}.
π΄ (19:18) pty_kill has { codemode: false, permission: "shell" }, performs assertShell(...), then re-fetches the session after permission approval. With cleanup: true it terminates and removes the session/output; otherwise it stops running/killing sessions but retains their metadata and buffer.
π΄ (19:18) PTY process output is retained as the last 1,000,000 characters. Exit handlers populate exitCode or exitSignal, set status to "exited" unless already "killing", append process errors to the buffer with fallback exitCode=1, clear timeout timers, and invoke exit notification.
π΄ (19:18) When notifyOnExit is true, notify(...) sends exactly one ctx.session.synthetic item with delivery: "steer", description PTY exited: ${truncate(session.description, 100)}, and JSON text containing type: "pty.exit", PTY ID, description truncated to 100 characters, exitCode, exitSignal, timeoutSeconds, timedOut, outputLines, and the last nonblank line truncated to 200 characters. Notifications are skipped during plugin closing or after parent Session deletion and time out after 5,000 ms.
π΄ (19:18) openPtyProcess(...) spawns /usr/bin/script detached with cwd: /proc/self/fd/${directory.fd}, fixed environment, and four pipes. Its command is printf '%s\\n' "$$" >&3; exec 3>&-; exec ${shellCommand(command, args)}, allowing pipe 3 to report the command PID separately from the script wrapper PID.
π΄ (19:18) openPtyProcess(...) validates the command PID as a newline-terminated positive decimal string no longer than 32 characters. Startup waits for both wrapper and command PIDs under the configured timeout; failure SIGKILLs the wrapper process group and waits up to 2,000 ms for close.
π΄ (19:18) PTY shutdown signals both command and wrapper process groups with SIGTERM, waits up to 2,000 ms, then uses SIGKILL and waits for both child close and process-group disappearance; failure throws PTY process ${pid} did not close.. terminate(session) additionally wraps close and session-close waits in 27,000 ms timeouts and sets status from "killing" to "killed".
π΄ (19:18) The plugin subscribes to ctx.event.subscribe(...) and handles session.deleted by recording the parent Session ID in deleted, waiting for that ownerβs pending spawns, then removing all owned PTYs, each cleanup stage bounded by CLOSE_TIMEOUT_MS=27_000.
π΄ (19:18) Plugin teardown sets closing=true, aborts event subscription, and waits together for all pendingSpawns, all session removals, and all pendingNotifications, bounded by 27,000 ms; rejected cleanup entries are logged.
π΄ (19:18) validateCommand(...) separately enforces well-formed Unicode, no NUL bytes, command UTF-8 size up to 4,096 bytes, at most 128 arguments, each argument up to 16,384 UTF-8 bytes, and aggregate argument size up to 65,536 UTF-8 bytes.