Add bounded ranges to terminal reads
This commit is contained in:
@@ -20,16 +20,34 @@ const terminalArg = `"terminal": {"type": "string", "description": "The id of on
|
||||
|
||||
type terminalReadArgs struct {
|
||||
Terminal string `json:"terminal"`
|
||||
Offset *int `json:"offset"`
|
||||
Limit int `json:"limit"`
|
||||
}
|
||||
|
||||
func (m *Manager) terminalReadTool(l *live) mcp.Tool {
|
||||
return mcp.Tool{
|
||||
Name: "terminal_read",
|
||||
Description: "Read one of your task's terminals: the shells in this task's worktree that the human uses from the Terminal tab, and that terminal_type types into. " +
|
||||
"Returns its recent output as plain text, escape sequences removed, at most the last 64 KiB, then the task's terminals by id and name. " +
|
||||
"Returns the last 100 lines by default, as plain text with escape sequences removed, followed by the task's terminals by id and name. Use offset and limit to select a range from the retained output (up to 256 KiB of raw output); older output is discarded. Line numbers are relative to that retained output and shift as older output is discarded. " +
|
||||
"Full-screen programs (vim, top, less) come out garbled, since this is the output stream, not a picture of the screen.",
|
||||
InputSchema: json.RawMessage(`{"type": "object", "properties": {` + terminalArg + `}}`),
|
||||
InputSchema: json.RawMessage(`{"type": "object", "properties": {` + terminalArg + `,
|
||||
"offset": {"type": "integer", "description": "First line to return: 1 is the start of retained output, -1 is its last line, -100 starts 100 lines from the end. Defaults to minus limit."},
|
||||
"limit": {"type": "integer", "minimum": 1, "description": "Maximum lines to return, starting at offset. Defaults to 100."}
|
||||
}}`),
|
||||
Handler: mcp.Typed(func(ctx context.Context, args terminalReadArgs) (string, error) {
|
||||
if args.Limit < 0 {
|
||||
return "", fmt.Errorf("limit must be positive")
|
||||
}
|
||||
if args.Limit == 0 {
|
||||
args.Limit = 100
|
||||
}
|
||||
offset := -args.Limit
|
||||
if args.Offset != nil {
|
||||
offset = *args.Offset
|
||||
}
|
||||
if offset == 0 {
|
||||
return "", fmt.Errorf("offset must be positive (from start) or negative (from end)")
|
||||
}
|
||||
term := m.terminals.First(l.taskID)
|
||||
if args.Terminal != "" {
|
||||
term = m.terminals.Get(l.taskID, args.Terminal)
|
||||
@@ -40,7 +58,7 @@ func (m *Manager) terminalReadTool(l *live) mcp.Tool {
|
||||
if term == nil {
|
||||
return "No shell is running in this task's terminal.", nil
|
||||
}
|
||||
out := terminal.Text(term.Backlog())
|
||||
out := terminal.Lines(term.Backlog(), offset, args.Limit)
|
||||
exit, exited := term.ExitStatus()
|
||||
if exited {
|
||||
out += "\n[shell ended: " + exit + "]"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
@@ -8,6 +9,33 @@ import (
|
||||
const textLimit = 64 << 10
|
||||
|
||||
func Text(b []byte) string {
|
||||
s := plainText(b)
|
||||
if len(s) > textLimit {
|
||||
s = s[len(s)-textLimit:]
|
||||
s = s[strings.IndexByte(s, '\n')+1:]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Lines reads a range of the retained output. Positive offsets are one-based;
|
||||
// negative offsets count back from the last line.
|
||||
func Lines(b []byte, offset, limit int) string {
|
||||
lines := strings.Split(strings.TrimSuffix(plainText(b), "\n"), "\n")
|
||||
start := 0
|
||||
if offset < 0 {
|
||||
start = len(lines) + offset
|
||||
} else {
|
||||
start = offset - 1
|
||||
}
|
||||
start = min(max(0, start), len(lines))
|
||||
end := start + min(limit, len(lines)-start)
|
||||
if start == end {
|
||||
return fmt.Sprintf("[no lines in range; %d retained lines]", len(lines))
|
||||
}
|
||||
return fmt.Sprintf("[lines %d–%d of %d retained lines]\n", min(start+1, end), end, len(lines)) + strings.Join(lines[start:end], "\n")
|
||||
}
|
||||
|
||||
func plainText(b []byte) string {
|
||||
out := strings.Builder{}
|
||||
line := []rune{}
|
||||
col := 0
|
||||
@@ -49,12 +77,7 @@ func Text(b []byte) string {
|
||||
}
|
||||
i++
|
||||
}
|
||||
s := out.String() + strings.TrimRight(string(line), " ")
|
||||
if len(s) > textLimit {
|
||||
s = s[len(s)-textLimit:]
|
||||
s = s[strings.IndexByte(s, '\n')+1:]
|
||||
}
|
||||
return s
|
||||
return out.String() + strings.TrimRight(string(line), " ")
|
||||
}
|
||||
|
||||
func skipEscape(b []byte, i int) (int, byte) {
|
||||
|
||||
Reference in New Issue
Block a user