Dashboard › opencode › Distillation
714f6978-d652-4a74-a0db-aafbf202df1d["lore_tm_v1_6nvAmi8uUxmp8zeC09MJj7AoBgy9aiGldaI0w_qchhw","lore_tm_v1_4gveEAvxLYSFisWc60OM_N9o0JkYKH8wb1ArKLWajHU","lore_tm_v1_ZhtDb0l64kDDwBntI8uvoK7O5N-nDwfNii26FYQa52o","lore_tm_v1_4YFAyyObI472-K2PApzzZm8mavdMAq1RF03bArGnPNY","lore_tm_v1_bEHe_gJFMcG7PtbFvCgFKtyjL-YQRoyGjTFoQmIkSEU","lore_tm_v1_KeGl0HyHuNipiG7K-M7lBkW9G2NgVsinEvYB79gzZys","lore_tm_v1_aNLyN_lLJWB-2X3JxVUEODrw37SJjNGg7uryi8m8pqk"]
bun test v1.3.14 (0d9b296a) against /home/byk/.local/share/opencode-v2-pilot/config/opencode/test/pty.test.ts reported exactly 1 pass, 4 fail, 3 errors, and 3 expect() calls across 5 tests in 21.32s; Bun also reported killed 1 dangling process multiple times.pty > runs an interactive command with a fixed environment and pinned cwd timed out after 5000ms with runtime 7006.74ms.pty > preserves argv without shell evaluation failed after 48.05ms at /home/byk/.local/share/opencode-v2-pilot/config/opencode/test/pty.test.ts:39; read.lines.some((line: { text: string }) => line.text === JSON.stringify(values)) returned false.pty > kills the process group and reports a JSON notification timed out after 5000ms with runtime 7002.32ms.pty > removes active sessions when their parent Session is deleted timed out after 5000ms with runtime 7001.73ms.Error: PTY plugin is closing. at /home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts:297, where a post-spawn closing/deleted-session check calls await terminate(session) and throws; cleanup also logged [pty] plugin cleanup failed./usr/bin/script owns the process group. The PID handshake was corrected, and process-group cleanup was then being corrected before rerunning tests./home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts and /home/byk/.local/share/opencode-v2-pilot/config/opencode/test/pty.test.ts./home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts./home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts./home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts:600-631, PTY admission obtains [wrapperPID, pid] via Promise.all([spawned, target]); failure sends SIGKILL to child.pid, waits for exited, and rethrows. The returned PtyProcess exposes target pid, writes through child.stdin, buffers pre-listener output, and tracks an idempotent closing promise./home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts:613-622, close() signals the process group keyed by wrapperPID, first with SIGKILL and then SIGTERM; it waits up to TERMINATE_TIMEOUT_MS, checks processGroupExists(wrapperPID), sends another SIGKILL if needed, and throws PTY process ${pid} did not close. if the wrapper still does not close./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 from node:child_process, open and realpath from node:fs/promises, constants from node:fs, Readable from node:stream, and StringDecoder from node:string_decoder./home/byk/.local/share/opencode-v2-pilot/config/opencode/plugins/pty.ts defines exact limits: 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, 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, and TERMINATE_TIMEOUT_MS = 2_000.SCRIPT_PATH = "/usr/bin/script" and SHELL_PATH = "/bin/sh" and uses fixedEnvironment values HOME=/home/byk, LANG=C.UTF-8, LOGNAME=byk, PATH=/usr/bin:/bin, SHELL=/bin/sh, TERM=xterm-256color, and USER=byk.SpawnInput requires a nonempty command of at most 4_096 characters, args with at most 128 entries and at most 16_384 characters per argument, and a nonempty description of at most 2_000 characters. Optional fields are workdir up to 4_096 characters, title up to 500 characters, notifyOnExit, and integer timeoutSeconds from 1 through 3_600.WriteInput accepts id and data; data is schema-limited to 65_536 characters, then separately checked after escape decoding against a 65_536 UTF-8-byte limit. Supported escapes are \n, \r, \t, \xNN, \uNNNN, and \\.ReadInput accepts id, optional integer offset from 0 through 10_000, optional integer limit from 1 through 2_000 (default 500), optional extended-regex pattern up to 500 characters, and optional ignoreCase.KillInput accepts id and optional cleanup; cleanup removes the session and buffered output and defaults to false."running" | "killing" | "killed" | "exited". Session tracks id, title, description, command, args, workdir, status, notifyOnExit, optional timeoutSeconds, timedOut, optional exitCode, optional exitSignal, pid, createdAt, parentSessionID, process, optional timer, closed, optional terminate, and buffer.PtyProcess exposes target pid, asynchronous write(data: Buffer) and close(), exited: Promise<PtyExit>, and onOutput(listener). DirectoryBinding holds an authorized real directory and open FileHandle.createPtyPlugin(supervisor: PtyPluginOptions = {}) defines plugin ID local-pty; its setup tracks sessions, an event AbortController, deleted parent-session IDs, per-owner reservations, pending/owner spawns, pending notifications, total reserved slots, plugin-closing state, and active regex workers.info() include id, title, description, command, args, workdir, status, notifyOnExit, timeoutSeconds, timedOut, exitCode, exitSignal, pid, createdAt, and computed lineCount.remove(session) clears its timeout, calls terminate(session), then always deletes the session from the map and empties session.buffer.notifyOnExit is true and the plugin/parent session remains active, ctx.session.synthetic() sends a "steer" delivery to the parent with description PTY exited: ... and JSON text containing type: "pty.exit", id, truncated description, exitCode, exitSignal, timeoutSeconds, timedOut, outputLines, and a truncated lastLine. Notifications have a 5_000ms timeout and are tracked in pendingNotifications.pty_spawnβstarts a background interactive command, codemode: false, permission "shell"; 2. pty_writeβwrites decoded input/control characters, codemode: false, permission "shell"; 3. pty_readβreads buffered output by line range or regex, codemode: false; 4. pty_listβlists PTYs owned by the calling parent session, codemode: false; 5. pty_killβstops a PTY and optionally removes its buffer, codemode: false, permission "shell".pty_spawn validates command/arguments, rejects plugin-closing or deleted parent sessions, enforces 5 sessions per owner and 16 per Location including reservations, and may evict one inactive "exited" or "killed" owner session when the per-owner limit is reached. It reserves capacity before authorization/spawn and releases reservation bookkeeping in operation.finally().pty_spawn calls authorize(ctx, input.command, input.args, input.workdir ?? ctx.location.directory, context), then invokes injected supervisor.openProcess or openPtyProcess with the authorized directory handle. It closes the directory handle after spawning and closes the process if handle closure fails.pty_${crypto.randomUUID().replaceAll("-", "").slice(0, 8)}. Default titles are ${input.command} ${input.args.join(" ")} trimmed, and output is retained as only the final 1_000_000 characters."killing" state to "killed" and all others to "exited", records exitCode/exitSignal, and sends the optional notification. If the exit promise rejects, it records exitCode = 1, appends the error text to the buffer, and still notifies.pty_spawn timeout handling marks session.timedOut = true and calls terminate(session) after timeoutSeconds * 1_000; the timer is unreferenced. Immediately before insertion into sessions, it rechecks plugin-closing/deleted-parent state, terminates if invalidated, and throws either PTY plugin is closing. or Session has been deleted: ${context.sessionID}.pty_spawn returns a JSON envelope with type: "pty.spawn" and session: info(session). pty_write returns type: "pty.write", the PTY id, and exact decoded UTF-8 byte count.pty_write requires owner access and "running" status before permission authorization, calls assertShell(ctx, session, context, data), re-fetches/rechecks the session afterward to prevent authorization races, and writes Buffer.from(data).pty_read limits concurrent regex work to MAX_REGEX_WORKERS = 4; it uses matchingLines(...) for patterns and linePage(...) otherwise. Its JSON envelope contains type: "pty.read", id, status, pattern, offset, limit, lines as { line, text } with text truncated to 2_000 characters, totalLines, hasMore, and optional nextOffset.pty_list filters strictly by parentSessionID === context.sessionID, returning [] if none or pretty-printed JSON summaries otherwise.pty_kill performs owner lookup, assertShell, and a second owner lookup to prevent permission races. With cleanup: true, it calls remove() and returns { type: "pty.kill", id, removed: true }; otherwise it clears the timer, terminates "running" or "killing" sessions, and returns { type: "pty.kill", id, removed: false, status }.ctx.event; on session.deleted, it records the parent ID in deleted, waits up to 27_000ms for that ownerβs pending spawns using Promise.allSettled, suppresses expected "Session has been deleted" rejections, then removes all owned sessions with another 27_000ms bounded Promise.allSettled.closing = true, aborts event subscription, and waits up to 27_000ms on all pending spawns, removal of every session, and pending notifications. Timeout and individual rejected cleanup entries are logged as [pty] plugin cleanup failed.