style: fix remaining lint errors (misspell, shadow, unused, etc.)
- Fix British→American spelling (cancelled→canceled, honour→honor, etc.) - Remove unused loadSkills method and regex vars - Fix variable shadowing (err, max) - Add explicit returns for nakedret - Use net.JoinHostPort for nosprintfhostport - Preallocate slices, add nolint:gosmopolitan for CJK test data - Fix golines and trailing whitespace Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
dca46e6799
commit
674d491d37
15 changed files with 55 additions and 69 deletions
|
|
@ -4,11 +4,13 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -216,7 +218,8 @@ func gatewayCmd(debug bool, orchestration bool) error {
|
||||||
if certErr != nil {
|
if certErr != nil {
|
||||||
logger.ErrorCF("miniapp", "Failed to fetch TLS cert", map[string]any{"error": certErr.Error()})
|
logger.ErrorCF("miniapp", "Failed to fetch TLS cert", map[string]any{"error": certErr.Error()})
|
||||||
} else {
|
} else {
|
||||||
webAppURL = fmt.Sprintf("https://%s:%d/miniapp", hostname, cfg.Gateway.Port)
|
hostPort := net.JoinHostPort(hostname, strconv.Itoa(cfg.Gateway.Port))
|
||||||
|
webAppURL = "https://" + hostPort + "/miniapp"
|
||||||
cfg.Channels.Telegram.WebAppURL = webAppURL
|
cfg.Channels.Telegram.WebAppURL = webAppURL
|
||||||
tlsCert, tlsKey = certFile, keyFile
|
tlsCert, tlsKey = certFile, keyFile
|
||||||
useTLS = true
|
useTLS = true
|
||||||
|
|
@ -268,7 +271,10 @@ func gatewayCmd(debug bool, orchestration bool) error {
|
||||||
cfg.Gateway.Port,
|
cfg.Gateway.Port,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("✓ Health endpoints available at http://%s:%d/health and /ready\n", cfg.Gateway.Host, cfg.Gateway.Port)
|
fmt.Printf(
|
||||||
|
"✓ Health endpoints available at http://%s:%d/health and /ready\n",
|
||||||
|
cfg.Gateway.Host, cfg.Gateway.Port,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
go agentLoop.Run(ctx)
|
go agentLoop.Run(ctx)
|
||||||
|
|
|
||||||
|
|
@ -787,25 +787,6 @@ func (cb *ContextBuilder) AddAssistantMessage(
|
||||||
return messages
|
return messages
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cb *ContextBuilder) loadSkills() string {
|
|
||||||
allSkills := cb.skillsLoader.ListSkills()
|
|
||||||
if len(allSkills) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
var skillNames []string
|
|
||||||
for _, s := range allSkills {
|
|
||||||
skillNames = append(skillNames, s.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
content := cb.skillsLoader.LoadSkillsForContext(skillNames)
|
|
||||||
if content == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return "# Skill Definitions\n\n" + content
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadSkill loads a skill by name, returning its content (with frontmatter stripped) and whether it was found.
|
// LoadSkill loads a skill by name, returning its content (with frontmatter stripped) and whether it was found.
|
||||||
func (cb *ContextBuilder) LoadSkill(name string) (string, bool) {
|
func (cb *ContextBuilder) LoadSkill(name string) (string, bool) {
|
||||||
return cb.skillsLoader.LoadSkill(name)
|
return cb.skillsLoader.LoadSkill(name)
|
||||||
|
|
|
||||||
|
|
@ -645,7 +645,8 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage)
|
||||||
lower := strings.ToLower(content)
|
lower := strings.ToLower(content)
|
||||||
|
|
||||||
// Check for stop keywords
|
// Check for stop keywords
|
||||||
stopKeywords := []string{"stop", "cancel", "abort", "停止", "中止", "やめて"} //nolint:gosmopolitan // intentional CJK stop words
|
//nolint:gosmopolitan // intentional CJK stop words
|
||||||
|
stopKeywords := []string{"stop", "cancel", "abort", "停止", "中止", "やめて"}
|
||||||
isStop := false
|
isStop := false
|
||||||
for _, kw := range stopKeywords {
|
for _, kw := range stopKeywords {
|
||||||
if lower == kw {
|
if lower == kw {
|
||||||
|
|
@ -1999,13 +2000,13 @@ func (al *AgentLoop) runLLMIteration(
|
||||||
if sp, ok := p.(providers.StreamingProvider); ok && sp.CanStream() {
|
if sp, ok := p.(providers.StreamingProvider); ok && sp.CanStream() {
|
||||||
streamCtx, streamCancel := context.WithCancel(ctx)
|
streamCtx, streamCancel := context.WithCancel(ctx)
|
||||||
defer streamCancel()
|
defer streamCancel()
|
||||||
ch, err := sp.ChatStream(streamCtx, messages, providerToolDefs, model, opts_)
|
ch, sErr := sp.ChatStream(streamCtx, messages, providerToolDefs, model, opts_)
|
||||||
if err != nil {
|
if sErr != nil {
|
||||||
return nil, err
|
return nil, sErr
|
||||||
}
|
}
|
||||||
resp, repetition, err := consumeStreamWithRepetitionDetection(ch, streamCancel, 1000, onChunk)
|
resp, repetition, sErr := consumeStreamWithRepetitionDetection(ch, streamCancel, 1000, onChunk)
|
||||||
if err != nil {
|
if sErr != nil {
|
||||||
return nil, err
|
return nil, sErr
|
||||||
}
|
}
|
||||||
if repetition {
|
if repetition {
|
||||||
resp.FinishReason = "repetition_detected"
|
resp.FinishReason = "repetition_detected"
|
||||||
|
|
@ -2156,7 +2157,7 @@ func (al *AgentLoop) runLLMIteration(
|
||||||
// blocks so loops inside <think> are caught). Skip when the
|
// blocks so loops inside <think> are caught). Skip when the
|
||||||
// provider already returned native tool calls.
|
// provider already returned native tool calls.
|
||||||
// Streaming providers may have already flagged repetition via
|
// Streaming providers may have already flagged repetition via
|
||||||
// FinishReason="repetition_detected" — honour that too.
|
// FinishReason="repetition_detected" — honor that too.
|
||||||
if response.FinishReason == "repetition_detected" ||
|
if response.FinishReason == "repetition_detected" ||
|
||||||
(len(response.ToolCalls) == 0 && utils.DetectRepetitionLoop(response.Content)) {
|
(len(response.ToolCalls) == 0 && utils.DetectRepetitionLoop(response.Content)) {
|
||||||
logger.WarnCF("agent", "Repetition loop detected in LLM response, retrying",
|
logger.WarnCF("agent", "Repetition loop detected in LLM response, retrying",
|
||||||
|
|
@ -2603,7 +2604,6 @@ func (al *AgentLoop) runLLMIteration(
|
||||||
al.lastSystemPrompt.Store(newPrompt)
|
al.lastSystemPrompt.Store(newPrompt)
|
||||||
al.promptDirty.Store(false)
|
al.promptDirty.Store(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If max iterations exhausted with tool calls still pending,
|
// If max iterations exhausted with tool calls still pending,
|
||||||
|
|
@ -2783,7 +2783,7 @@ func (al *AgentLoop) GetPlanInfo() (hasPlan bool, status string, currentPhase, t
|
||||||
totalPhases = mem.GetTotalPhases()
|
totalPhases = mem.GetTotalPhases()
|
||||||
display = mem.FormatPlanDisplay()
|
display = mem.FormatPlanDisplay()
|
||||||
memory = mem.ReadLongTerm()
|
memory = mem.ReadLongTerm()
|
||||||
return
|
return hasPlan, status, currentPhase, totalPhases, display, memory
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPlanStatus returns the current plan status ("interviewing", "executing", "review", etc.) or "".
|
// GetPlanStatus returns the current plan status ("interviewing", "executing", "review", etc.) or "".
|
||||||
|
|
@ -2837,7 +2837,7 @@ func (al *AgentLoop) GetContextInfo() (workDir, planWorkDir, workspace string, b
|
||||||
workDir = agent.ContextBuilder.workDir
|
workDir = agent.ContextBuilder.workDir
|
||||||
}
|
}
|
||||||
bootstrap = agent.ContextBuilder.ResolveBootstrapPaths()
|
bootstrap = agent.ContextBuilder.ResolveBootstrapPaths()
|
||||||
return
|
return workDir, planWorkDir, workspace, bootstrap
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSystemPrompt returns the system prompt last sent to the LLM.
|
// GetSystemPrompt returns the system prompt last sent to the LLM.
|
||||||
|
|
|
||||||
|
|
@ -2513,8 +2513,8 @@ func TestAgentLoop_PlanModel_UsedDuringInterviewing(t *testing.T) {
|
||||||
os.MkdirAll(memoryDir, 0o755)
|
os.MkdirAll(memoryDir, 0o755)
|
||||||
memoryPath := filepath.Join(memoryDir, "MEMORY.md")
|
memoryPath := filepath.Join(memoryDir, "MEMORY.md")
|
||||||
memoryContent := "# Active Plan\n\n> Task: Test plan model\n> Status: interviewing\n> Phase: 1\n"
|
memoryContent := "# Active Plan\n\n> Task: Test plan model\n> Status: interviewing\n> Phase: 1\n"
|
||||||
if err := os.WriteFile(memoryPath, []byte(memoryContent), 0o644); err != nil {
|
if wErr := os.WriteFile(memoryPath, []byte(memoryContent), 0o644); wErr != nil {
|
||||||
t.Fatalf("Failed to write MEMORY.md: %v", err)
|
t.Fatalf("Failed to write MEMORY.md: %v", wErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = al.ProcessDirectWithChannel(
|
_, err = al.ProcessDirectWithChannel(
|
||||||
|
|
@ -2581,8 +2581,8 @@ func TestAgentLoop_PlanModel_NotUsedDuringExecuting(t *testing.T) {
|
||||||
## Phase 1: Build
|
## Phase 1: Build
|
||||||
- [ ] Run build
|
- [ ] Run build
|
||||||
`
|
`
|
||||||
if err := os.WriteFile(memoryPath, []byte(memoryContent), 0o644); err != nil {
|
if wErr := os.WriteFile(memoryPath, []byte(memoryContent), 0o644); wErr != nil {
|
||||||
t.Fatalf("Failed to write MEMORY.md: %v", err)
|
t.Fatalf("Failed to write MEMORY.md: %v", wErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = al.ProcessDirectWithChannel(
|
_, err = al.ProcessDirectWithChannel(
|
||||||
|
|
@ -2642,8 +2642,8 @@ func TestAgentLoop_PlanModel_ResolvesProviderForSingleCandidate(t *testing.T) {
|
||||||
os.MkdirAll(memoryDir, 0o755)
|
os.MkdirAll(memoryDir, 0o755)
|
||||||
memoryPath := filepath.Join(memoryDir, "MEMORY.md")
|
memoryPath := filepath.Join(memoryDir, "MEMORY.md")
|
||||||
memoryContent := "# Active Plan\n\n> Task: Test provider resolution\n> Status: interviewing\n> Phase: 1\n"
|
memoryContent := "# Active Plan\n\n> Task: Test provider resolution\n> Status: interviewing\n> Phase: 1\n"
|
||||||
if err := os.WriteFile(memoryPath, []byte(memoryContent), 0o644); err != nil {
|
if wErr := os.WriteFile(memoryPath, []byte(memoryContent), 0o644); wErr != nil {
|
||||||
t.Fatalf("Failed to write MEMORY.md: %v", err)
|
t.Fatalf("Failed to write MEMORY.md: %v", wErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = al.ProcessDirectWithChannel(
|
_, err = al.ProcessDirectWithChannel(
|
||||||
|
|
|
||||||
|
|
@ -146,8 +146,6 @@ var (
|
||||||
reStatus = regexp.MustCompile(`(?m)^> Status:\s*(.+)`)
|
reStatus = regexp.MustCompile(`(?m)^> Status:\s*(.+)`)
|
||||||
rePhase = regexp.MustCompile(`(?m)^> Phase:\s*(\d+)`)
|
rePhase = regexp.MustCompile(`(?m)^> Phase:\s*(\d+)`)
|
||||||
rePhaseHeader = regexp.MustCompile(`(?m)^## Phase (\d+):\s*(.*)`)
|
rePhaseHeader = regexp.MustCompile(`(?m)^## Phase (\d+):\s*(.*)`)
|
||||||
reStepDone = regexp.MustCompile(`(?m)^- \[x\] `)
|
|
||||||
reStepTodo = regexp.MustCompile(`(?m)^- \[ \] `)
|
|
||||||
reWorkDir = regexp.MustCompile(`(?m)^> WorkDir:\s*(.+)`)
|
reWorkDir = regexp.MustCompile(`(?m)^> WorkDir:\s*(.+)`)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -182,16 +180,16 @@ func (ms *MemoryStore) GetCurrentPhase() int {
|
||||||
func (ms *MemoryStore) GetTotalPhases() int {
|
func (ms *MemoryStore) GetTotalPhases() int {
|
||||||
content := ms.ReadLongTerm()
|
content := ms.ReadLongTerm()
|
||||||
matches := rePhaseHeader.FindAllStringSubmatch(content, -1)
|
matches := rePhaseHeader.FindAllStringSubmatch(content, -1)
|
||||||
max := 0
|
maxN := 0
|
||||||
for _, m := range matches {
|
for _, m := range matches {
|
||||||
if len(m) >= 2 {
|
if len(m) >= 2 {
|
||||||
n, _ := strconv.Atoi(m[1])
|
n, _ := strconv.Atoi(m[1])
|
||||||
if n > max {
|
if n > maxN {
|
||||||
max = n
|
maxN = n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return max
|
return maxN
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsPlanComplete returns true if all steps in all phases are [x].
|
// IsPlanComplete returns true if all steps in all phases are [x].
|
||||||
|
|
@ -638,16 +636,16 @@ func (ms *MemoryStore) getPlanContextFrom(content string) string {
|
||||||
// maxPhaseNumber returns the highest phase number found in content.
|
// maxPhaseNumber returns the highest phase number found in content.
|
||||||
func maxPhaseNumber(content string) int {
|
func maxPhaseNumber(content string) int {
|
||||||
matches := rePhaseHeader.FindAllStringSubmatch(content, -1)
|
matches := rePhaseHeader.FindAllStringSubmatch(content, -1)
|
||||||
max := 0
|
maxN := 0
|
||||||
for _, m := range matches {
|
for _, m := range matches {
|
||||||
if len(m) >= 2 {
|
if len(m) >= 2 {
|
||||||
n, _ := strconv.Atoi(m[1])
|
n, _ := strconv.Atoi(m[1])
|
||||||
if n > max {
|
if n > maxN {
|
||||||
max = n
|
maxN = n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return max
|
return maxN
|
||||||
}
|
}
|
||||||
|
|
||||||
// getPhaseTitle extracts the title of a phase from "## Phase N: Title".
|
// getPhaseTitle extracts the title of a phase from "## Phase N: Title".
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ import (
|
||||||
// buildInitData constructs a valid initData string from params and a bot token.
|
// buildInitData constructs a valid initData string from params and a bot token.
|
||||||
func buildInitData(params map[string]string, botToken string) string {
|
func buildInitData(params map[string]string, botToken string) string {
|
||||||
// Build data-check-string
|
// Build data-check-string
|
||||||
var pairs []string
|
pairs := make([]string, 0, len(params))
|
||||||
for k, v := range params {
|
for k, v := range params {
|
||||||
pairs = append(pairs, fmt.Sprintf("%s=%s", k, v))
|
pairs = append(pairs, fmt.Sprintf("%s=%s", k, v))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ type Event struct {
|
||||||
From string `json:"from,omitempty"`
|
From string `json:"from,omitempty"`
|
||||||
To string `json:"to,omitempty"`
|
To string `json:"to,omitempty"`
|
||||||
Text string `json:"text,omitempty"`
|
Text string `json:"text,omitempty"`
|
||||||
Reason string `json:"reason,omitempty"` // agent_gc: completed | failed | cancelled
|
Reason string `json:"reason,omitempty"` // agent_gc: completed | failed | canceled
|
||||||
Created int64 `json:"created,omitempty"`
|
Created int64 `json:"created,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1086,8 +1086,9 @@ func TestExtractXMLToolCalls_MismatchedCloseTag(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStripXMLToolCalls_MismatchedCloseTag(t *testing.T) { //nolint:gosmopolitan // CJK test data
|
func TestStripXMLToolCalls_MismatchedCloseTag(t *testing.T) {
|
||||||
text := `今テスト走らせるね。
|
text := `今テスト走らせるね。` + //nolint:gosmopolitan // CJK test data
|
||||||
|
`
|
||||||
<minimax:toolcall>
|
<minimax:toolcall>
|
||||||
<invoke name="exec">
|
<invoke name="exec">
|
||||||
<parameter name="command">cd /home/user && pytest</parameter>
|
<parameter name="command">cd /home/user && pytest</parameter>
|
||||||
|
|
@ -1098,7 +1099,7 @@ func TestStripXMLToolCalls_MismatchedCloseTag(t *testing.T) { //nolint:gosmopoli
|
||||||
if strings.Contains(got, "toolcall") || strings.Contains(got, "tool_call") {
|
if strings.Contains(got, "toolcall") || strings.Contains(got, "tool_call") {
|
||||||
t.Errorf("should remove XML block, got %q", got)
|
t.Errorf("should remove XML block, got %q", got)
|
||||||
}
|
}
|
||||||
if !strings.Contains(got, "今テスト走らせるね。") {
|
if !strings.Contains(got, "今テスト走らせるね。") { //nolint:gosmopolitan // CJK test data
|
||||||
t.Errorf("should keep text before, got %q", got)
|
t.Errorf("should keep text before, got %q", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1163,7 +1164,7 @@ Finished.`
|
||||||
|
|
||||||
func TestExtractXMLToolCalls_OrphanedClosingTag(t *testing.T) {
|
func TestExtractXMLToolCalls_OrphanedClosingTag(t *testing.T) {
|
||||||
// LLM emits [TOOLCALL] marker + <invoke> with orphaned closing tag (no opening tag)
|
// LLM emits [TOOLCALL] marker + <invoke> with orphaned closing tag (no opening tag)
|
||||||
text := "了解!確認するね。\n[TOOLCALL]\n<invoke name=\"listdir\">\n<parameter name=\"path\">/home/user/workspace</parameter>\n</invoke>\n</minimax:tool_call>"
|
text := "了解!確認するね。\n[TOOLCALL]\n<invoke name=\"listdir\">\n<parameter name=\"path\">/home/user/workspace</parameter>\n</invoke>\n</minimax:tool_call>" //nolint:gosmopolitan // CJK test data
|
||||||
|
|
||||||
calls := extractXMLToolCalls(text)
|
calls := extractXMLToolCalls(text)
|
||||||
if len(calls) != 1 {
|
if len(calls) != 1 {
|
||||||
|
|
@ -1178,12 +1179,12 @@ func TestExtractXMLToolCalls_OrphanedClosingTag(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStripXMLToolCalls_OrphanedClosingTag(t *testing.T) {
|
func TestStripXMLToolCalls_OrphanedClosingTag(t *testing.T) {
|
||||||
text := "了解!確認するね。\n[TOOLCALL]\n<invoke name=\"listdir\">\n<parameter name=\"path\">/home/user</parameter>\n</invoke>\n</minimax:tool_call>"
|
text := "了解!確認するね。\n[TOOLCALL]\n<invoke name=\"listdir\">\n<parameter name=\"path\">/home/user</parameter>\n</invoke>\n</minimax:tool_call>" //nolint:gosmopolitan // CJK test data
|
||||||
got := stripXMLToolCalls(text)
|
got := stripXMLToolCalls(text)
|
||||||
if strings.Contains(got, "invoke") || strings.Contains(got, "TOOLCALL") || strings.Contains(got, "minimax") {
|
if strings.Contains(got, "invoke") || strings.Contains(got, "TOOLCALL") || strings.Contains(got, "minimax") {
|
||||||
t.Errorf("should remove orphaned closing tag block, got %q", got)
|
t.Errorf("should remove orphaned closing tag block, got %q", got)
|
||||||
}
|
}
|
||||||
if !strings.Contains(got, "了解") {
|
if !strings.Contains(got, "了解") { //nolint:gosmopolitan // CJK test data
|
||||||
t.Errorf("should keep user-facing text, got %q", got)
|
t.Errorf("should keep user-facing text, got %q", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -259,7 +259,7 @@ func (p *Provider) CanStream() bool {
|
||||||
|
|
||||||
// ChatStream opens an SSE connection and returns a channel of StreamEvent.
|
// ChatStream opens an SSE connection and returns a channel of StreamEvent.
|
||||||
// The channel is closed when the stream ends or an error occurs.
|
// The channel is closed when the stream ends or an error occurs.
|
||||||
// Cancelling ctx will abort the HTTP request and close the channel.
|
// Canceling ctx will abort the HTTP request and close the channel.
|
||||||
func (p *Provider) ChatStream(
|
func (p *Provider) ChatStream(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
messages []Message,
|
messages []Message,
|
||||||
|
|
@ -294,7 +294,7 @@ func (p *Provider) ChatStream(
|
||||||
}
|
}
|
||||||
|
|
||||||
// readSSEIntoChannel reads SSE lines from r and sends StreamEvent values on ch.
|
// readSSEIntoChannel reads SSE lines from r and sends StreamEvent values on ch.
|
||||||
// It returns when the stream ends, an error occurs, or ctx is cancelled.
|
// It returns when the stream ends, an error occurs, or ctx is canceled.
|
||||||
func readSSEIntoChannel(ctx context.Context, r io.Reader, ch chan<- protocoltypes.StreamEvent) {
|
func readSSEIntoChannel(ctx context.Context, r io.Reader, ch chan<- protocoltypes.StreamEvent) {
|
||||||
scanner := bufio.NewScanner(r)
|
scanner := bufio.NewScanner(r)
|
||||||
scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
|
scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
|
||||||
|
|
|
||||||
|
|
@ -519,7 +519,7 @@ func TestReadSSEIntoChannel_TextAndToolCalls(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadSSEIntoChannel_ContextCancel(t *testing.T) {
|
func TestReadSSEIntoChannel_ContextCancel(t *testing.T) {
|
||||||
// Simulate a slow SSE stream that gets cancelled.
|
// Simulate a slow SSE stream that gets canceled.
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
// Create a reader that blocks after sending one chunk.
|
// Create a reader that blocks after sending one chunk.
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ func NewTracker(workspace string) *Tracker {
|
||||||
}
|
}
|
||||||
t.load()
|
t.load()
|
||||||
|
|
||||||
// Initialise Since if this is a fresh tracker
|
// Initialize Since if this is a fresh tracker
|
||||||
if t.stats.Since.IsZero() {
|
if t.stats.Since.IsZero() {
|
||||||
t.stats.Since = time.Now()
|
t.stats.Since = time.Now()
|
||||||
}
|
}
|
||||||
|
|
@ -104,7 +104,7 @@ func (t *Tracker) GetStats() Stats {
|
||||||
return t.stats
|
return t.stats
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset zeroes all counters and re-initialises Since.
|
// Reset zeroes all counters and re-initializes Since.
|
||||||
func (t *Tracker) Reset() {
|
func (t *Tracker) Reset() {
|
||||||
t.mu.Lock()
|
t.mu.Lock()
|
||||||
defer t.mu.Unlock()
|
defer t.mu.Unlock()
|
||||||
|
|
|
||||||
|
|
@ -191,7 +191,7 @@ func (t *BgMonitorTool) actionWatch(ctx context.Context, args map[string]any) *T
|
||||||
IsError: true,
|
IsError: true,
|
||||||
}
|
}
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return ErrorResult("watch cancelled")
|
return ErrorResult("watch canceled")
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
// Continue polling
|
// Continue polling
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ func (m *mockDevTargetManager) GetDevTarget() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockDevTargetManager) ListDevTargets() []miniapp.DevTarget {
|
func (m *mockDevTargetManager) ListDevTargets() []miniapp.DevTarget {
|
||||||
var out []miniapp.DevTarget
|
out := make([]miniapp.DevTarget, 0, len(m.targets))
|
||||||
for _, dt := range m.targets {
|
for _, dt := range m.targets {
|
||||||
out = append(out, *dt)
|
out = append(out, *dt)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,9 @@ import (
|
||||||
"github.com/sipeed/picoclaw/pkg/providers"
|
"github.com/sipeed/picoclaw/pkg/providers"
|
||||||
)
|
)
|
||||||
|
|
||||||
// blockingProvider blocks inside Chat until the context is cancelled.
|
// blockingProvider blocks inside Chat until the context is canceled.
|
||||||
// The ready channel is closed the moment Chat is entered, so callers can
|
// The ready channel is closed the moment Chat is entered, so callers can
|
||||||
// synchronise before cancelling the context.
|
// synchronize before canceling the context.
|
||||||
type blockingProvider struct {
|
type blockingProvider struct {
|
||||||
ready chan struct{}
|
ready chan struct{}
|
||||||
}
|
}
|
||||||
|
|
@ -179,14 +179,14 @@ func TestSubagentManager_Spawn_SnapshotLiveDuringExecution(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSubagentManager_Spawn_CancelledDuringExecution verifies that when the
|
// TestSubagentManager_Spawn_CancelledDuringExecution verifies that when the
|
||||||
// context is cancelled while a subagent's LLM call is in progress, the
|
// context is canceled while a subagent's LLM call is in progress, the
|
||||||
// Broadcaster receives agent_gc with reason="canceled" and the agent is
|
// Broadcaster receives agent_gc with reason="canceled" and the agent is
|
||||||
// removed from the snapshot.
|
// removed from the snapshot.
|
||||||
//
|
//
|
||||||
// Synchronisation:
|
// Synchronization:
|
||||||
// 1. blockingProvider.ready is closed when Chat() is entered (goroutine is
|
// 1. blockingProvider.ready is closed when Chat() is entered (goroutine is
|
||||||
// now blocked inside the LLM call).
|
// now blocked inside the LLM call).
|
||||||
// 2. Only then is the context cancelled, so there is no race between spawn
|
// 2. Only then is the context canceled, so there is no race between spawn
|
||||||
// and cancellation.
|
// and cancellation.
|
||||||
func TestSubagentManager_Spawn_CancelledDuringExecution(t *testing.T) {
|
func TestSubagentManager_Spawn_CancelledDuringExecution(t *testing.T) {
|
||||||
b := orch.NewBroadcaster()
|
b := orch.NewBroadcaster()
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ func TestStripThinkBlocks_ClosedThenUnclosed(t *testing.T) {
|
||||||
|
|
||||||
func TestDetectRepetitionLoop_HighRepetition(t *testing.T) {
|
func TestDetectRepetitionLoop_HighRepetition(t *testing.T) {
|
||||||
// Repeat a short phrase many times → should be detected
|
// Repeat a short phrase many times → should be detected
|
||||||
phrase := "結構本格的なコード"
|
phrase := "結構本格的なコード" //nolint:gosmopolitan // CJK test data
|
||||||
repeated := strings.Repeat(phrase, 300)
|
repeated := strings.Repeat(phrase, 300)
|
||||||
if !DetectRepetitionLoop(repeated) {
|
if !DetectRepetitionLoop(repeated) {
|
||||||
t.Fatal("DetectRepetitionLoop should return true for highly repetitive text")
|
t.Fatal("DetectRepetitionLoop should return true for highly repetitive text")
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue