feat: compact UI tree output and separate max_tokens from context_window

Reduce get_ui_tree verbosity by skipping invisible nodes, omitting
empty/default fields, and stripping common class prefixes. Lower
defaults to max_nodes=300, max_depth=15. Introduce a dedicated
context_window config field (default 128000) so summarization threshold
is independent of the API response token limit (max_tokens).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
KoheiYamashita 2026-02-22 14:21:27 +09:00
parent c90d2a138f
commit 0524dc7b65
6 changed files with 56 additions and 18 deletions

View file

@ -183,8 +183,8 @@ class ToolRequestHandler(
val index = request.params?.get("index")?.jsonPrimitive?.intOrNull ?: 0 val index = request.params?.get("index")?.jsonPrimitive?.intOrNull ?: 0
val boundsX = request.params?.get("bounds_x")?.jsonPrimitive?.doubleOrNull val boundsX = request.params?.get("bounds_x")?.jsonPrimitive?.doubleOrNull
val boundsY = request.params?.get("bounds_y")?.jsonPrimitive?.doubleOrNull val boundsY = request.params?.get("bounds_y")?.jsonPrimitive?.doubleOrNull
val maxDepth = request.params?.get("max_depth")?.jsonPrimitive?.intOrNull ?: 30 val maxDepth = request.params?.get("max_depth")?.jsonPrimitive?.intOrNull ?: 15
val maxNodes = request.params?.get("max_nodes")?.jsonPrimitive?.intOrNull ?: 2000 val maxNodes = request.params?.get("max_nodes")?.jsonPrimitive?.intOrNull ?: 300
return try { return try {
withContext(Dispatchers.Main) { setOverlayVisibility(false) } withContext(Dispatchers.Main) { setOverlayVisibility(false) }
@ -262,20 +262,32 @@ class ToolRequestHandler(
nodeCount: IntArray nodeCount: IntArray
) { ) {
if (nodeCount[0] >= maxNodes) return if (nodeCount[0] >= maxNodes) return
// Skip invisible nodes
if (!node.isVisibleToUser) return
nodeCount[0]++ nodeCount[0]++
val indent = " ".repeat(depth) val indent = " ".repeat(depth)
val bounds = Rect() val bounds = Rect()
node.getBoundsInScreen(bounds) node.getBoundsInScreen(bounds)
sb.appendLine(
"${indent}[${node.className}] " + // Strip common class name prefixes
"text=${node.text ?: ""} " + val className = node.className?.toString() ?: "View"
"desc=${node.contentDescription ?: ""} " + val shortClass = className
"bounds=${bounds} " + .removePrefix("android.widget.")
"clickable=${node.isClickable} " + .removePrefix("android.view.")
"enabled=${node.isEnabled} " +
"visible=${node.isVisibleToUser} " + sb.append("${indent}[${shortClass}]")
"id=${node.viewIdResourceName ?: ""}"
) // Only output non-empty fields
node.text?.takeIf { it.isNotEmpty() }?.let { sb.append(" text=$it") }
node.contentDescription?.takeIf { it.isNotEmpty() }?.let { sb.append(" desc=$it") }
sb.append(" bounds=$bounds")
// Only output non-default values: clickable=true (default is false), enabled=false (default is true)
if (node.isClickable) sb.append(" clickable")
if (!node.isEnabled) sb.append(" enabled=false")
node.viewIdResourceName?.let { sb.append(" id=$it") }
sb.appendLine()
if (depth >= maxDepth) { if (depth >= maxDepth) {
if (node.childCount > 0) { if (node.childCount > 0) {
sb.appendLine("${indent} [truncated: ${node.childCount} children at depth $depth]") sb.appendLine("${indent} [truncated: ${node.childCount} children at depth $depth]")

View file

@ -6,6 +6,7 @@
"restrict_to_workspace": true, "restrict_to_workspace": true,
"model": "glm-4.7", "model": "glm-4.7",
"max_tokens": 8192, "max_tokens": 8192,
"context_window": 128000,
"temperature": 0.7, "temperature": 0.7,
"max_tool_iterations": 20 "max_tool_iterations": 20
} }

View file

@ -36,7 +36,8 @@ type AgentLoop struct {
provider providers.LLMProvider provider providers.LLMProvider
workspace string workspace string
model string model string
contextWindow int // Maximum context window size in tokens maxTokens int // Maximum tokens for API response
contextWindow int // Maximum context window size in tokens (for summarization)
maxIterations int maxIterations int
sessions *session.SessionManager sessions *session.SessionManager
state *state.Manager state *state.Manager
@ -213,7 +214,8 @@ func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers
provider: provider, provider: provider,
workspace: workspace, workspace: workspace,
model: cfg.Agents.Defaults.Model, model: cfg.Agents.Defaults.Model,
contextWindow: cfg.Agents.Defaults.MaxTokens, // Restore context window for summarization maxTokens: cfg.Agents.Defaults.MaxTokens,
contextWindow: cfg.Agents.Defaults.ContextWindow,
maxIterations: cfg.Agents.Defaults.MaxToolIterations, maxIterations: cfg.Agents.Defaults.MaxToolIterations,
sessions: sessionsManager, sessions: sessionsManager,
state: stateManager, state: stateManager,
@ -672,7 +674,7 @@ func (al *AgentLoop) runLLMIteration(ctx context.Context, messages []providers.M
"model": al.model, "model": al.model,
"messages_count": len(messages), "messages_count": len(messages),
"tools_count": len(providerToolDefs), "tools_count": len(providerToolDefs),
"max_tokens": 8192, "max_tokens": al.maxTokens,
"temperature": 0.7, "temperature": 0.7,
"system_prompt_len": len(messages[0].Content), "system_prompt_len": len(messages[0].Content),
}) })
@ -692,7 +694,7 @@ func (al *AgentLoop) runLLMIteration(ctx context.Context, messages []providers.M
maxRetries := 2 maxRetries := 2
for retry := 0; retry <= maxRetries; retry++ { for retry := 0; retry <= maxRetries; retry++ {
response, err = al.provider.Chat(ctx, messages, providerToolDefs, al.model, map[string]interface{}{ response, err = al.provider.Chat(ctx, messages, providerToolDefs, al.model, map[string]interface{}{
"max_tokens": 8192, "max_tokens": al.maxTokens,
"temperature": 0.7, "temperature": 0.7,
}) })

View file

@ -45,6 +45,7 @@ func TestRecordLastChannel(t *testing.T) {
DataDir: tmpDir, DataDir: tmpDir,
Model: "test-model", Model: "test-model",
MaxTokens: 4096, MaxTokens: 4096,
ContextWindow: 128000,
MaxToolIterations: 10, MaxToolIterations: 10,
}, },
}, },
@ -91,6 +92,7 @@ func TestRecordLastChatID(t *testing.T) {
DataDir: tmpDir, DataDir: tmpDir,
Model: "test-model", Model: "test-model",
MaxTokens: 4096, MaxTokens: 4096,
ContextWindow: 128000,
MaxToolIterations: 10, MaxToolIterations: 10,
}, },
}, },
@ -137,6 +139,7 @@ func TestNewAgentLoop_StateInitialized(t *testing.T) {
DataDir: tmpDir, DataDir: tmpDir,
Model: "test-model", Model: "test-model",
MaxTokens: 4096, MaxTokens: 4096,
ContextWindow: 128000,
MaxToolIterations: 10, MaxToolIterations: 10,
}, },
}, },
@ -174,6 +177,7 @@ func TestToolRegistry_ToolRegistration(t *testing.T) {
DataDir: tmpDir, DataDir: tmpDir,
Model: "test-model", Model: "test-model",
MaxTokens: 4096, MaxTokens: 4096,
ContextWindow: 128000,
MaxToolIterations: 10, MaxToolIterations: 10,
}, },
}, },
@ -221,6 +225,7 @@ func TestToolContext_Updates(t *testing.T) {
DataDir: tmpDir, DataDir: tmpDir,
Model: "test-model", Model: "test-model",
MaxTokens: 4096, MaxTokens: 4096,
ContextWindow: 128000,
MaxToolIterations: 10, MaxToolIterations: 10,
}, },
}, },
@ -253,6 +258,7 @@ func TestToolRegistry_GetDefinitions(t *testing.T) {
DataDir: tmpDir, DataDir: tmpDir,
Model: "test-model", Model: "test-model",
MaxTokens: 4096, MaxTokens: 4096,
ContextWindow: 128000,
MaxToolIterations: 10, MaxToolIterations: 10,
}, },
}, },
@ -298,6 +304,7 @@ func TestAgentLoop_GetStartupInfo(t *testing.T) {
DataDir: tmpDir, DataDir: tmpDir,
Model: "test-model", Model: "test-model",
MaxTokens: 4096, MaxTokens: 4096,
ContextWindow: 128000,
MaxToolIterations: 10, MaxToolIterations: 10,
}, },
}, },
@ -346,6 +353,7 @@ func TestCreateToolRegistry_ExecDisabled(t *testing.T) {
DataDir: tmpDir, DataDir: tmpDir,
Model: "test-model", Model: "test-model",
MaxTokens: 4096, MaxTokens: 4096,
ContextWindow: 128000,
MaxToolIterations: 10, MaxToolIterations: 10,
}, },
}, },
@ -386,6 +394,7 @@ func TestCreateToolRegistry_ExecEnabled(t *testing.T) {
DataDir: tmpDir, DataDir: tmpDir,
Model: "test-model", Model: "test-model",
MaxTokens: 4096, MaxTokens: 4096,
ContextWindow: 128000,
MaxToolIterations: 10, MaxToolIterations: 10,
}, },
}, },
@ -431,6 +440,7 @@ func TestCreateToolRegistry_I2CDisabled(t *testing.T) {
DataDir: tmpDir, DataDir: tmpDir,
Model: "test-model", Model: "test-model",
MaxTokens: 4096, MaxTokens: 4096,
ContextWindow: 128000,
MaxToolIterations: 10, MaxToolIterations: 10,
}, },
}, },
@ -469,6 +479,7 @@ func TestCreateToolRegistry_I2CEnabled(t *testing.T) {
DataDir: tmpDir, DataDir: tmpDir,
Model: "test-model", Model: "test-model",
MaxTokens: 4096, MaxTokens: 4096,
ContextWindow: 128000,
MaxToolIterations: 10, MaxToolIterations: 10,
}, },
}, },
@ -512,6 +523,7 @@ func TestCreateToolRegistry_SPIDisabled(t *testing.T) {
DataDir: tmpDir, DataDir: tmpDir,
Model: "test-model", Model: "test-model",
MaxTokens: 4096, MaxTokens: 4096,
ContextWindow: 128000,
MaxToolIterations: 10, MaxToolIterations: 10,
}, },
}, },
@ -550,6 +562,7 @@ func TestCreateToolRegistry_SPIEnabled(t *testing.T) {
DataDir: tmpDir, DataDir: tmpDir,
Model: "test-model", Model: "test-model",
MaxTokens: 4096, MaxTokens: 4096,
ContextWindow: 128000,
MaxToolIterations: 10, MaxToolIterations: 10,
}, },
}, },
@ -593,6 +606,7 @@ func TestAgentLoop_Stop(t *testing.T) {
DataDir: tmpDir, DataDir: tmpDir,
Model: "test-model", Model: "test-model",
MaxTokens: 4096, MaxTokens: 4096,
ContextWindow: 128000,
MaxToolIterations: 10, MaxToolIterations: 10,
}, },
}, },
@ -716,6 +730,7 @@ func TestToolResult_SilentToolDoesNotSendUserMessage(t *testing.T) {
DataDir: tmpDir, DataDir: tmpDir,
Model: "test-model", Model: "test-model",
MaxTokens: 4096, MaxTokens: 4096,
ContextWindow: 128000,
MaxToolIterations: 10, MaxToolIterations: 10,
}, },
}, },
@ -759,6 +774,7 @@ func TestToolResult_UserFacingToolDoesSendMessage(t *testing.T) {
DataDir: tmpDir, DataDir: tmpDir,
Model: "test-model", Model: "test-model",
MaxTokens: 4096, MaxTokens: 4096,
ContextWindow: 128000,
MaxToolIterations: 10, MaxToolIterations: 10,
}, },
}, },
@ -825,6 +841,7 @@ func TestAgentLoop_ContextExhaustionRetry(t *testing.T) {
DataDir: tmpDir, DataDir: tmpDir,
Model: "test-model", Model: "test-model",
MaxTokens: 4096, MaxTokens: 4096,
ContextWindow: 128000,
MaxToolIterations: 10, MaxToolIterations: 10,
}, },
}, },
@ -921,6 +938,7 @@ func TestRetryLoop_CancelledContextSkipsCompression(t *testing.T) {
DataDir: tmpDir, DataDir: tmpDir,
Model: "test-model", Model: "test-model",
MaxTokens: 4096, MaxTokens: 4096,
ContextWindow: 128000,
MaxToolIterations: 10, MaxToolIterations: 10,
}, },
}, },
@ -983,6 +1001,7 @@ func TestForceCompression_ToolGroupBoundary(t *testing.T) {
DataDir: tmpDir, DataDir: tmpDir,
Model: "test-model", Model: "test-model",
MaxTokens: 4096, MaxTokens: 4096,
ContextWindow: 128000,
MaxToolIterations: 10, MaxToolIterations: 10,
}, },
}, },
@ -1069,6 +1088,7 @@ func TestForceCompression_MidOnAssistantWithToolCalls(t *testing.T) {
DataDir: tmpDir, DataDir: tmpDir,
Model: "test-model", Model: "test-model",
MaxTokens: 4096, MaxTokens: 4096,
ContextWindow: 128000,
MaxToolIterations: 10, MaxToolIterations: 10,
}, },
}, },
@ -1156,6 +1176,7 @@ func TestForceCompression_NoteUsesUserRole(t *testing.T) {
DataDir: tmpDir, DataDir: tmpDir,
Model: "test-model", Model: "test-model",
MaxTokens: 4096, MaxTokens: 4096,
ContextWindow: 128000,
MaxToolIterations: 10, MaxToolIterations: 10,
}, },
}, },

View file

@ -66,6 +66,7 @@ type AgentDefaults struct {
Provider string `json:"provider" env:"PICOCLAW_AGENTS_DEFAULTS_PROVIDER"` Provider string `json:"provider" env:"PICOCLAW_AGENTS_DEFAULTS_PROVIDER"`
Model string `json:"model" env:"PICOCLAW_AGENTS_DEFAULTS_MODEL"` Model string `json:"model" env:"PICOCLAW_AGENTS_DEFAULTS_MODEL"`
MaxTokens int `json:"max_tokens" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOKENS"` MaxTokens int `json:"max_tokens" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOKENS"`
ContextWindow int `json:"context_window" env:"PICOCLAW_AGENTS_DEFAULTS_CONTEXT_WINDOW"`
Temperature float64 `json:"temperature" env:"PICOCLAW_AGENTS_DEFAULTS_TEMPERATURE"` Temperature float64 `json:"temperature" env:"PICOCLAW_AGENTS_DEFAULTS_TEMPERATURE"`
MaxToolIterations int `json:"max_tool_iterations" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOOL_ITERATIONS"` MaxToolIterations int `json:"max_tool_iterations" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOOL_ITERATIONS"`
} }
@ -281,6 +282,7 @@ func DefaultConfig() *Config {
Provider: "", Provider: "",
Model: "glm-4.7", Model: "glm-4.7",
MaxTokens: 8192, MaxTokens: 8192,
ContextWindow: 128000,
Temperature: 0.7, Temperature: 0.7,
MaxToolIterations: 20, MaxToolIterations: 20,
}, },

View file

@ -145,11 +145,11 @@ func (t *AndroidTool) Parameters() map[string]interface{} {
}, },
"max_depth": map[string]interface{}{ "max_depth": map[string]interface{}{
"type": "integer", "type": "integer",
"description": "Maximum traversal depth (for get_ui_tree, default 30)", "description": "Maximum traversal depth (for get_ui_tree, default 15)",
}, },
"max_nodes": map[string]interface{}{ "max_nodes": map[string]interface{}{
"type": "integer", "type": "integer",
"description": "Maximum number of nodes to output (for get_ui_tree, default 2000)", "description": "Maximum number of nodes to output (for get_ui_tree, default 300)",
}, },
}, },
"required": []string{"action"}, "required": []string{"action"},