aggressive token saving attempts
This commit is contained in:
@@ -68,6 +68,8 @@ type toolHost struct {
|
||||
const (
|
||||
defaultMCPContentBytes = 12_000
|
||||
maxMCPContentBytes = 65_536
|
||||
defaultMCPCanonicalLines = 120
|
||||
maxMCPCanonicalLines = 500
|
||||
defaultMCPTailBytes = 8_000
|
||||
defaultScratchpadReadBytes = 12_000
|
||||
defaultSearchLineBytes = 2_000
|
||||
@@ -380,22 +382,42 @@ func (h *toolHost) GetProcessOutput(callerID string, args mcp.ProcessOutputArgs)
|
||||
if c == nil {
|
||||
return mcp.ProcessOutput{}, mcp.Errorf(mcp.ErrorKindNotFound, "no such process %q", processID)
|
||||
}
|
||||
if mode == "" {
|
||||
mode = "grid"
|
||||
}
|
||||
if args.Raw {
|
||||
b, end := c.StreamRead(sinceOffset)
|
||||
content, contentBytes, truncated, truncatedBytes := capBytesTail(b, capLimit(args.MaxBytes, defaultMCPContentBytes))
|
||||
return mcp.ProcessOutput{
|
||||
Content: content,
|
||||
Mode: "stream",
|
||||
NewOffset: end,
|
||||
Status: string(c.Status()),
|
||||
ContentBytes: contentBytes,
|
||||
Truncated: truncated,
|
||||
TruncatedBytes: truncatedBytes,
|
||||
}, nil
|
||||
}
|
||||
out := mcp.ProcessOutput{
|
||||
Mode: mode,
|
||||
IdleMS: c.IdleMS(),
|
||||
Status: string(c.Status()),
|
||||
ScreenVersion: c.ScreenVersion(),
|
||||
Canonicalized: true,
|
||||
}
|
||||
if em := c.Emulator(); em != nil {
|
||||
if sc, err := em.ActiveScreen(); err == nil {
|
||||
out.ActiveScreen = activeScreenName(sc)
|
||||
if args.IncludeMeta {
|
||||
out.IdleMS = c.IdleMS()
|
||||
out.ScreenVersion = c.ScreenVersion()
|
||||
if em := c.Emulator(); em != nil {
|
||||
if sc, err := em.ActiveScreen(); err == nil {
|
||||
out.ActiveScreen = activeScreenName(sc)
|
||||
}
|
||||
if cur, err := em.Cursor(); err == nil {
|
||||
out.Cursor = &mcp.Cursor{X: int(cur.Col), Y: int(cur.Row)}
|
||||
}
|
||||
cols, rows := em.Size()
|
||||
out.Cols, out.Rows = int(cols), int(rows)
|
||||
}
|
||||
if cur, err := em.Cursor(); err == nil {
|
||||
out.Cursor = mcp.Cursor{X: int(cur.Col), Y: int(cur.Row)}
|
||||
}
|
||||
cols, rows := em.Size()
|
||||
out.Cols, out.Rows = int(cols), int(rows)
|
||||
}
|
||||
maxLines := canonicalLineLimit(args.MaxLines)
|
||||
switch mode {
|
||||
case "grid":
|
||||
em := c.Emulator()
|
||||
@@ -409,12 +431,21 @@ func (h *toolHost) GetProcessOutput(callerID string, args mcp.ProcessOutputArgs)
|
||||
if c.Kind == KindAgent {
|
||||
txt = applyChromeTrim(txt, h.chromeHintsFor(c.PresetRef))
|
||||
}
|
||||
content := normalizeGridText(txt)
|
||||
content, lineTruncated, lineDroppedBytes := canonicalizeTerminalText(txt, maxLines)
|
||||
out.Content, out.ContentBytes, out.Truncated, out.TruncatedBytes = capTextMiddle(content, capLimit(args.MaxBytes, defaultMCPContentBytes))
|
||||
if lineTruncated {
|
||||
out.Truncated = true
|
||||
out.TruncatedBytes += lineDroppedBytes
|
||||
}
|
||||
return out, nil
|
||||
case "stream":
|
||||
b, end := c.StreamRead(sinceOffset)
|
||||
out.Content, out.ContentBytes, out.Truncated, out.TruncatedBytes = capBytesTail(stripANSIBytes(nil, b), capLimit(args.MaxBytes, defaultMCPContentBytes))
|
||||
content, lineTruncated, lineDroppedBytes := canonicalizeTerminalText(string(b), maxLines)
|
||||
out.Content, out.ContentBytes, out.Truncated, out.TruncatedBytes = capTextTail(content, capLimit(args.MaxBytes, defaultMCPContentBytes))
|
||||
if lineTruncated {
|
||||
out.Truncated = true
|
||||
out.TruncatedBytes += lineDroppedBytes
|
||||
}
|
||||
out.NewOffset = end
|
||||
return out, nil
|
||||
default:
|
||||
@@ -1064,11 +1095,10 @@ func activeScreenName(s pkgvt.Screen) string {
|
||||
}
|
||||
}
|
||||
|
||||
// ansiRegexp strips CSI escape sequences and common single-character
|
||||
// controls (BEL, OSC terminators) from the stream. The vt emulator
|
||||
// already handles full rendering for grid mode; this is only for
|
||||
// stream-mode ANSI-stripped output.
|
||||
var ansiRegexp = regexp.MustCompile(`\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]|\x1b[\x40-\x5f]|\x07`)
|
||||
// ansiRegexp strips CSI/OSC escape sequences and common single-character
|
||||
// controls from the stream. The vt emulator already handles full
|
||||
// rendering for grid mode; this is only for stream-mode text output.
|
||||
var ansiRegexp = regexp.MustCompile(`\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]|\x1b[\x40-\x5f]|\x07`)
|
||||
|
||||
func stripANSI(s string) string {
|
||||
return ansiRegexp.ReplaceAllString(s, "")
|
||||
@@ -1111,6 +1141,16 @@ func capLimit(requested, def int) int {
|
||||
return requested
|
||||
}
|
||||
|
||||
func canonicalLineLimit(requested int) int {
|
||||
if requested <= 0 {
|
||||
return defaultMCPCanonicalLines
|
||||
}
|
||||
if requested > maxMCPCanonicalLines {
|
||||
return maxMCPCanonicalLines
|
||||
}
|
||||
return requested
|
||||
}
|
||||
|
||||
func capBytesTail(b []byte, limit int) (string, int, bool, int) {
|
||||
if limit <= 0 || len(b) <= limit {
|
||||
return string(b), len(b), false, 0
|
||||
@@ -1149,6 +1189,7 @@ func capTextMiddle(s string, limit int) (string, int, bool, int) {
|
||||
// pattern match (WaitForPattern scrollback). Recognises the same
|
||||
// shapes the regex did:
|
||||
// - `\x1b[ <params> <intermediate> <final-byte>` (CSI / SGR)
|
||||
// - `\x1b] ... (BEL|ST)` (OSC)
|
||||
// - `\x1b<final-byte>` for `@..._` (one-byte escapes)
|
||||
// - `\x07` (BEL)
|
||||
//
|
||||
@@ -1178,6 +1219,24 @@ func stripANSIBytes(dst, src []byte) []byte {
|
||||
continue
|
||||
}
|
||||
next := src[i+1]
|
||||
if next == ']' {
|
||||
j := i + 2
|
||||
for j < len(src) {
|
||||
if src[j] == 0x07 {
|
||||
i = j + 1
|
||||
break
|
||||
}
|
||||
if src[j] == 0x1b && j+1 < len(src) && src[j+1] == '\\' {
|
||||
i = j + 2
|
||||
break
|
||||
}
|
||||
j++
|
||||
}
|
||||
if j >= len(src) {
|
||||
i = len(src)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if next != '[' {
|
||||
// One-byte ESC sequence (`\x1b<final>` where final is
|
||||
// `@..._` per the regex; we drop anything that follows).
|
||||
@@ -1260,7 +1319,7 @@ func helpFor(topic string) mcp.HelpResponse {
|
||||
case "inspection":
|
||||
return mcp.HelpResponse{
|
||||
Topic: "inspection",
|
||||
Content: "get_process_output gives you the visible pane (grid mode) or a byte slice from since_offset (stream mode). list_processes is for the whole session. get_project_status batches everything you need to orient yourself.",
|
||||
Content: "get_process_output gives you canonical terminal text by default: the visible pane (grid mode) or recent stream text from since_offset (stream mode), with ANSI/control noise, borders, duplicate status churn, and volatile timers removed. Use raw:true only when you need diagnostic PTY bytes; include_meta:true restores cursor, geometry, and screen-version fields. list_processes is for the whole session. get_project_status batches everything you need to orient yourself.",
|
||||
RelatedTools: []string{"list_processes", "get_process_status", "get_process_output", "search_output", "wait_for_pattern", "get_project_status"},
|
||||
}
|
||||
case "io":
|
||||
|
||||
Reference in New Issue
Block a user