mirror of
https://github.com/Twigpine/zero.git
synced 2026-11-11 23:40:28 -10:00
agent.Options gains Provider (auto-injected in Run) + Depth. A 'task' tool is intercepted in executeToolCall (like ask_user): it spawns agent.Run as a child with Depth+1, a registry filtered via new Registry.Without("task","ask_user") (no infinite nesting, no interactive prompts), inherited Sandbox/PermissionMode/Autonomy but all interactive callbacks nil, sharing ctx for cancellation; the child's final answer becomes the tool result. Depth guard (maxTaskDepth=2) refuses deeper nesting with a clear message. Synchronous; async task output/stop deferred. Build/vet/-race/full-suite green.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
78 lines
2.4 KiB
Go
78 lines
2.4 KiB
Go
package tools
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestParseTaskRequestReadsPromptAndDescription(t *testing.T) {
|
|
request, err := ParseTaskRequest(map[string]any{
|
|
"description": "find bug",
|
|
"prompt": "locate the panic in the parser",
|
|
"subagent_type": "explorer",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if request.Prompt != "locate the panic in the parser" {
|
|
t.Fatalf("unexpected prompt: %q", request.Prompt)
|
|
}
|
|
if request.Description != "find bug" {
|
|
t.Fatalf("unexpected description: %q", request.Description)
|
|
}
|
|
if request.SubagentType != "explorer" {
|
|
t.Fatalf("unexpected subagent_type: %q", request.SubagentType)
|
|
}
|
|
}
|
|
|
|
func TestParseTaskRequestAcceptsAliases(t *testing.T) {
|
|
// instructions/title are the aliased spellings weaker models emit.
|
|
request, err := ParseTaskRequest(map[string]any{
|
|
"instructions": "do the thing",
|
|
"title": "the thing",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if request.Prompt != "do the thing" {
|
|
t.Fatalf("expected instructions to map to prompt, got %q", request.Prompt)
|
|
}
|
|
if request.Description != "the thing" {
|
|
t.Fatalf("expected title to map to description, got %q", request.Description)
|
|
}
|
|
}
|
|
|
|
func TestParseTaskRequestRequiresPrompt(t *testing.T) {
|
|
if _, err := ParseTaskRequest(map[string]any{"description": "no prompt here"}); err == nil {
|
|
t.Fatalf("expected error when prompt is missing")
|
|
}
|
|
if _, err := ParseTaskRequest(map[string]any{"prompt": " "}); err == nil {
|
|
t.Fatalf("expected error when prompt is blank")
|
|
}
|
|
}
|
|
|
|
func TestTaskToolRunIsGracefulFallback(t *testing.T) {
|
|
result := NewTaskTool().Run(context.Background(), map[string]any{"prompt": "do work"})
|
|
if result.Status != StatusOK {
|
|
t.Fatalf("expected ok fallback status, got %s", result.Status)
|
|
}
|
|
if !strings.Contains(strings.ToLower(result.Output), "unavailable") {
|
|
t.Fatalf("expected unavailable guidance, got %q", result.Output)
|
|
}
|
|
}
|
|
|
|
func TestTaskToolRunRejectsMissingPrompt(t *testing.T) {
|
|
result := NewTaskTool().Run(context.Background(), map[string]any{"description": "x"})
|
|
if result.Status != StatusError {
|
|
t.Fatalf("expected error status for missing prompt, got %s", result.Status)
|
|
}
|
|
}
|
|
|
|
func TestTaskToolIsReadOnlySafety(t *testing.T) {
|
|
safety := NewTaskTool().Safety()
|
|
if safety.SideEffect != SideEffectRead || safety.Permission != PermissionAllow {
|
|
t.Fatalf("expected read-only/allow safety, got %#v", safety)
|
|
}
|
|
}
|