style: fix golines, gofmt, and gosmopolitan lint errors

- Run golines (max 120 chars) on long lines
- Run gofmt with interface{} -> any rewrite rule
- Add nolint:gosmopolitan for intentional CJK strings

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
dj-oyu 2026-02-28 23:53:23 +09:00
parent 5801176dfe
commit dca46e6799
16 changed files with 235 additions and 93 deletions

View file

@ -205,7 +205,11 @@ func gatewayCmd(debug bool, orchestration bool) error {
// Auto-detect Tailscale hostname and fetch TLS cert
hostname, tsErr := tailscale.DetectHostname()
if tsErr != nil {
logger.InfoCF("miniapp", "Tailscale not available, Mini App disabled", map[string]any{"error": tsErr.Error()})
logger.InfoCF(
"miniapp",
"Tailscale not available, Mini App disabled",
map[string]any{"error": tsErr.Error()},
)
} else {
certDir := filepath.Join(cfg.WorkspacePath(), "state", "certs")
certFile, keyFile, certErr := tailscale.FetchCert(hostname, certDir)
@ -224,7 +228,14 @@ func gatewayCmd(debug bool, orchestration bool) error {
dataProvider := &agentLoopDataProvider{loop: agentLoop, workspace: cfg.WorkspacePath()}
sender := &telegramCommandSender{bus: msgBus}
miniappNotifier = miniapp.NewStateNotifier()
handler := miniapp.NewHandler(dataProvider, sender, cfg.Channels.Telegram.Token, miniappNotifier, cfg.Channels.Telegram.AllowFrom, cfg.WorkspacePath())
handler := miniapp.NewHandler(
dataProvider,
sender,
cfg.Channels.Telegram.Token,
miniappNotifier,
cfg.Channels.Telegram.AllowFrom,
cfg.WorkspacePath(),
)
agentLoop.OnStateChange = miniappNotifier.Notify
if b := agentLoop.GetOrchBroadcaster(); b != nil {
handler.SetOrchBroadcaster(b)
@ -251,7 +262,11 @@ func gatewayCmd(debug bool, orchestration bool) error {
}
}()
if useTLS {
fmt.Printf("✓ Health endpoints available at https://%s:%d/health and /ready (TLS)\n", cfg.Gateway.Host, cfg.Gateway.Port)
fmt.Printf(
"✓ Health endpoints available at https://%s:%d/health and /ready (TLS)\n",
cfg.Gateway.Host,
cfg.Gateway.Port,
)
} else {
fmt.Printf("✓ Health endpoints available at http://%s:%d/health and /ready\n", cfg.Gateway.Host, cfg.Gateway.Port)
}

View file

@ -119,7 +119,12 @@ type processOptions struct {
const defaultResponse = "I've completed processing but have no response to give. Increase `max_tool_iterations` in config.json."
func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers.LLMProvider, enableStats ...bool) *AgentLoop {
func NewAgentLoop(
cfg *config.Config,
msgBus *bus.MessageBus,
provider providers.LLMProvider,
enableStats ...bool,
) *AgentLoop {
registry := NewAgentRegistry(cfg, provider)
// Set up shared fallback chain
@ -282,7 +287,14 @@ func registerSharedTools(
PerplexityMaxResults: cfg.Tools.Web.Perplexity.MaxResults,
PerplexityEnabled: cfg.Tools.Web.Perplexity.Enabled,
}
subagentManager := tools.NewSubagentManager(provider, agent.Model, agent.Workspace, msgBus, al.reporter(), webSearchOpts)
subagentManager := tools.NewSubagentManager(
provider,
agent.Model,
agent.Workspace,
msgBus,
al.reporter(),
webSearchOpts,
)
subagentManager.SetLLMOptions(agent.MaxTokens, agent.Temperature)
spawnTool := tools.NewSpawnTool(subagentManager)
currentAgentID := agentID
@ -480,7 +492,10 @@ func (al *AgentLoop) SetChannelManager(cm *channels.Manager) {
// It caches created providers by "provider/model" key so each combination is
// only resolved once. Looks up model_list first (new format), then falls back
// to the legacy providers section via CreateProviderByName.
func (al *AgentLoop) resolveProvider(providerName, modelName string, fallback providers.LLMProvider) providers.LLMProvider {
func (al *AgentLoop) resolveProvider(
providerName, modelName string,
fallback providers.LLMProvider,
) providers.LLMProvider {
key := strings.ToLower(providerName + "/" + modelName)
if key == "/" {
return fallback
@ -497,14 +512,14 @@ func (al *AgentLoop) resolveProvider(providerName, modelName string, fallback pr
return p
}
logger.WarnCF("agent", "Failed to create provider from model_list, trying legacy",
map[string]interface{}{"provider": providerName, "model": modelName, "error": err.Error()})
map[string]any{"provider": providerName, "model": modelName, "error": err.Error()})
}
// Fall back to legacy providers section.
p, err := providers.CreateProviderByName(al.cfg, providerName)
if err != nil {
logger.WarnCF("agent", "Failed to create provider for fallback, using primary",
map[string]interface{}{"provider": providerName, "error": err.Error()})
map[string]any{"provider": providerName, "error": err.Error()})
return fallback
}
al.providerCache[key] = p
@ -630,7 +645,7 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage)
lower := strings.ToLower(content)
// Check for stop keywords
stopKeywords := []string{"stop", "cancel", "abort", "停止", "中止", "やめて"}
stopKeywords := []string{"stop", "cancel", "abort", "停止", "中止", "やめて"} //nolint:gosmopolitan // intentional CJK stop words
isStop := false
for _, kw := range stopKeywords {
if lower == kw {
@ -939,7 +954,8 @@ func (al *AgentLoop) runAgentLoop(ctx context.Context, agent *AgentInstance, opt
// 1a. Set session-specific working directory for bootstrap file lookup.
// Prefer the tool-detected project directory (touch_dir) from the session tracker,
// resolved as an absolute path under workspace. Fall back to worktree or workspace.
if active := al.sessions.ListActive(); len(active) > 0 && active[0].SessionKey == opts.SessionKey && active[0].TouchDir != "" {
if active := al.sessions.ListActive(); len(active) > 0 && active[0].SessionKey == opts.SessionKey &&
active[0].TouchDir != "" {
agent.ContextBuilder.SetWorkDir(filepath.Join(agent.Workspace, active[0].TouchDir))
} else {
agent.ContextBuilder.SetWorkDir(agent.EffectiveWorkspace(opts.SessionKey))
@ -1015,7 +1031,9 @@ func (al *AgentLoop) runAgentLoop(ctx context.Context, agent *AgentInstance, opt
sb.WriteString("\n\n## Background Execution\n")
sb.WriteString("You are running as a background heartbeat with no conversation history. ")
sb.WriteString("MEMORY.md is the only shared state between heartbeats. ")
sb.WriteString("After completing each plan step, immediately use edit_file to mark it [x] in memory/MEMORY.md.")
sb.WriteString(
"After completing each plan step, immediately use edit_file to mark it [x] in memory/MEMORY.md.",
)
messages[0].Content = sb.String()
}
}
@ -1053,14 +1071,15 @@ func (al *AgentLoop) runAgentLoop(ctx context.Context, agent *AgentInstance, opt
// 5a. Auto-advance plan phases after LLM iteration
postStatus := agent.ContextBuilder.GetPlanStatus()
if agent.ContextBuilder.HasActivePlan() && (postStatus == "executing" || postStatus == "review" || postStatus == "completed") {
if agent.ContextBuilder.HasActivePlan() &&
(postStatus == "executing" || postStatus == "review" || postStatus == "completed") {
// Intercept: if AI changed status to executing or review without user approval
// (from interviewing or review), validate and hold at "review".
if preStatus == "interviewing" || (preStatus == "review" && postStatus == "executing") {
if err := agent.ContextBuilder.ValidatePlanStructure(); err != nil {
_ = agent.ContextBuilder.SetPlanStatus("interviewing")
logger.WarnCF("agent", "Reverted plan to interviewing: "+err.Error(),
map[string]interface{}{"agent_id": agent.ID})
map[string]any{"agent_id": agent.ID})
// Inject rejection into session history so LLM sees it next iteration
rejectionMsg := "[System] Plan rejected: " + err.Error() + ". Fix and try again."
agent.Sessions.AddMessage(opts.SessionKey, "user", rejectionMsg)
@ -1080,7 +1099,7 @@ func (al *AgentLoop) runAgentLoop(ctx context.Context, agent *AgentInstance, opt
// Safeguard: executing but no phases (shouldn't happen, but be safe).
_ = agent.ContextBuilder.SetPlanStatus("interviewing")
logger.WarnCF("agent", "Reverted plan to interviewing: no phases defined",
map[string]interface{}{"agent_id": agent.ID})
map[string]any{"agent_id": agent.ID})
} else if agent.ContextBuilder.IsPlanComplete() {
// Mark plan as completed (keep memory for review; user can /plan clear)
total := agent.ContextBuilder.GetTotalPhases()
@ -1194,7 +1213,8 @@ func buildTaskReminder(userMessage string, lastBlocker string) providers.Message
truncatedBlocker := utils.Truncate(lastBlocker, blockerMaxChars)
content = fmt.Sprintf(
"[TASK REMINDER]\nOriginal task:\n---\n%s\n---\nLast blocker:\n---\n%s\n---\nFix the blocker if essential, or find an alternative. If all steps are complete, move on.",
truncatedTask, truncatedBlocker,
truncatedTask,
truncatedBlocker,
)
} else {
content = fmt.Sprintf(
@ -1247,7 +1267,7 @@ var optFlagPattern = regexp.MustCompile(`\s+--?\w[\w-]*(=\S*)?`)
// extractExecProjectDir extracts the basename of an exec cd target.
// Returns "" if the command has no cd prefix.
func extractExecProjectDir(args map[string]interface{}) string {
func extractExecProjectDir(args map[string]any) string {
cmd, _ := args["command"].(string)
if cmd == "" {
return ""
@ -1338,7 +1358,7 @@ func (al *AgentLoop) targetReasoningChannelID(channelName string) (chatID string
// For exec: extracts the command and strips the leading "cd <workspace> && ".
// For file tools: extracts the path and strips the workspace prefix.
// Falls back to raw JSON truncation.
func buildArgsSnippet(toolName string, args map[string]interface{}, workspace string) string {
func buildArgsSnippet(toolName string, args map[string]any, workspace string) string {
switch toolName {
case "exec":
cmd, _ := args["command"].(string)
@ -1829,7 +1849,12 @@ type streamToolCallAcc struct {
}
// buildAccumulatedResponse constructs an LLMResponse from accumulated stream data.
func buildAccumulatedResponse(content, reasoning string, toolCalls []streamToolCallAcc, finishReason string, usage *providers.UsageInfo) *providers.LLMResponse {
func buildAccumulatedResponse(
content, reasoning string,
toolCalls []streamToolCallAcc,
finishReason string,
usage *providers.UsageInfo,
) *providers.LLMResponse {
resp := &providers.LLMResponse{
Content: content,
Reasoning: reasoning,
@ -2426,7 +2451,14 @@ func (al *AgentLoop) runLLMIteration(
if wt := agent.GetWorktree(opts.SessionKey); wt != nil {
toolCtx = tools.WithWorkspaceOverride(toolCtx, wt.Path)
}
toolResult := agent.Tools.ExecuteWithContext(toolCtx, tc.Name, tc.Arguments, opts.Channel, opts.ChatID, asyncCallback)
toolResult := agent.Tools.ExecuteWithContext(
toolCtx,
tc.Name,
tc.Arguments,
opts.Channel,
opts.ChatID,
asyncCallback,
)
toolDuration := time.Since(toolStart)
// Update tool log entry with result
@ -2539,7 +2571,7 @@ func (al *AgentLoop) runLLMIteration(
messages = append(messages, reminderMsg)
lastReminderIdx = len(messages) - 1
logger.DebugCF("agent", "Injected task reminder",
map[string]interface{}{
map[string]any{
"agent_id": agent.ID,
"iteration": iteration,
"has_blocker": lastBlocker != "",
@ -2551,7 +2583,7 @@ func (al *AgentLoop) runLLMIteration(
if reminder, ok := buildPlanReminder(planSnapshot); ok {
messages = append(messages, reminder)
logger.DebugCF("agent", "Injected plan reminder",
map[string]interface{}{
map[string]any{
"agent_id": agent.ID,
"iteration": iteration,
"plan_status": planSnapshot,
@ -2565,7 +2597,8 @@ func (al *AgentLoop) runLLMIteration(
if touchDir := al.sessions.GetTouchDir(opts.SessionKey); touchDir != "" {
agent.ContextBuilder.SetWorkDir(filepath.Join(agent.Workspace, touchDir))
}
if newPrompt := agent.ContextBuilder.BuildSystemPrompt(); len(messages) > 0 && messages[0].Content != newPrompt {
if newPrompt := agent.ContextBuilder.BuildSystemPrompt(); len(messages) > 0 &&
messages[0].Content != newPrompt {
messages[0].Content = newPrompt
al.lastSystemPrompt.Store(newPrompt)
al.promptDirty.Store(false)
@ -2577,11 +2610,11 @@ func (al *AgentLoop) runLLMIteration(
// make one final LLM call without tools to force a text response.
if finalContent == "" && iteration >= maxIter {
logger.WarnCF("agent", "Max iterations reached, forcing final response without tools",
map[string]interface{}{
map[string]any{
"agent_id": agent.ID,
"iteration": iteration,
})
forceResp, forceErr := agent.Provider.Chat(ctx, messages, nil, agent.Model, map[string]interface{}{
forceResp, forceErr := agent.Provider.Chat(ctx, messages, nil, agent.Model, map[string]any{
"max_tokens": agent.MaxTokens,
"temperature": agent.Temperature,
"prompt_cache_key": agent.ID,
@ -2633,7 +2666,7 @@ func (al *AgentLoop) maybeSummarize(agent *AgentInstance, sessionKey, channel, c
go func() {
defer al.summarizing.Delete(summarizeKey)
logger.InfoCF("agent", "Memory threshold reached, optimizing conversation history",
map[string]interface{}{
map[string]any{
"session_key": sessionKey,
"history_len": len(newHistory),
"token_estimate": tokenEstimate,
@ -3127,7 +3160,8 @@ func (al *AgentLoop) handleSessionCommand(args []string) string {
}
s := al.stats.GetStats()
return fmt.Sprintf("Session Statistics\n\nToday (%s):\n Prompts: %d\n LLM calls: %d\n Tokens: %s (in: %s, out: %s)\n\nAll time (since %s):\n Prompts: %d\n LLM calls: %d\n Tokens: %s (in: %s, out: %s)",
return fmt.Sprintf(
"Session Statistics\n\nToday (%s):\n Prompts: %d\n LLM calls: %d\n Tokens: %s (in: %s, out: %s)\n\nAll time (since %s):\n Prompts: %d\n LLM calls: %d\n Tokens: %s (in: %s, out: %s)",
s.Today.Date,
s.Today.Prompts,
s.Today.Requests,
@ -3369,7 +3403,7 @@ func filterInterviewTools(defs []providers.ToolDefinition) []providers.ToolDefin
// plan is in a pre-execution state. Uses the shared interviewAllowedTools map for
// name-level gating, then applies argument-level constraints for write-type tools
// (MEMORY.md only) and exec (read-only commands only).
func isToolAllowedDuringInterview(toolName string, args map[string]interface{}) bool {
func isToolAllowedDuringInterview(toolName string, args map[string]any) bool {
norm := tools.NormalizeToolName(toolName)
if !interviewAllowedTools[norm] {
return false

View file

@ -1466,7 +1466,7 @@ Test
func TestIsToolAllowedDuringInterview_FuzzyNames(t *testing.T) {
tests := []struct {
name string
args map[string]interface{}
args map[string]any
want bool
}{
// Exact names — read tools allowed
@ -1484,38 +1484,38 @@ func TestIsToolAllowedDuringInterview_FuzzyNames(t *testing.T) {
{"message", nil, true},
{"Message", nil, true},
// Write to MEMORY.md — allowed
{"edit_file", map[string]interface{}{"path": "/ws/memory/MEMORY.md"}, true},
{"editfile", map[string]interface{}{"path": "/ws/memory/MEMORY.md"}, true},
{"EditFile", map[string]interface{}{"path": "/ws/memory/MEMORY.md"}, true},
{"edit_file", map[string]any{"path": "/ws/memory/MEMORY.md"}, true},
{"editfile", map[string]any{"path": "/ws/memory/MEMORY.md"}, true},
{"EditFile", map[string]any{"path": "/ws/memory/MEMORY.md"}, true},
// Write to non-MEMORY.md — blocked
{"edit_file", map[string]interface{}{"path": "/ws/main.go"}, false},
{"editfile", map[string]interface{}{"path": "/ws/main.go"}, false},
{"edit_file", map[string]any{"path": "/ws/main.go"}, false},
{"editfile", map[string]any{"path": "/ws/main.go"}, false},
// exec — read-only commands allowed
{"exec", map[string]interface{}{"command": "find . -name '*.py'"}, true},
{"exec", map[string]interface{}{"command": "ls -la"}, true},
{"exec", map[string]interface{}{"command": "grep -r TODO ."}, true},
{"exec", map[string]interface{}{"command": "cat README.md"}, true},
{"exec", map[string]any{"command": "find . -name '*.py'"}, true},
{"exec", map[string]any{"command": "ls -la"}, true},
{"exec", map[string]any{"command": "grep -r TODO ."}, true},
{"exec", map[string]any{"command": "cat README.md"}, true},
// exec — cd prefix stripped
{"exec", map[string]interface{}{"command": "cd /home/user/project && find . -type f"}, true},
{"exec", map[string]interface{}{"command": "cd /tmp && rm -rf *"}, false},
{"exec", map[string]any{"command": "cd /home/user/project && find . -type f"}, true},
{"exec", map[string]any{"command": "cd /tmp && rm -rf *"}, false},
// exec — write operators blocked
{"exec", map[string]interface{}{"command": "find . > output.txt"}, false},
{"exec", map[string]interface{}{"command": "ls -la >> log.txt"}, false},
{"exec", map[string]interface{}{"command": "cat foo | tee bar.txt"}, false},
{"exec", map[string]any{"command": "find . > output.txt"}, false},
{"exec", map[string]any{"command": "ls -la >> log.txt"}, false},
{"exec", map[string]any{"command": "cat foo | tee bar.txt"}, false},
// exec — path traversal blocked
{"exec", map[string]interface{}{"command": "cat ../../etc/passwd"}, false},
{"exec", map[string]interface{}{"command": "find ../../"}, false},
{"exec", map[string]interface{}{"command": "ls ../secret"}, false},
{"exec", map[string]any{"command": "cat ../../etc/passwd"}, false},
{"exec", map[string]any{"command": "find ../../"}, false},
{"exec", map[string]any{"command": "ls ../secret"}, false},
// exec — absolute paths blocked
{"exec", map[string]interface{}{"command": "cat /etc/passwd"}, false},
{"exec", map[string]interface{}{"command": "find /etc -name '*.conf'"}, false},
{"exec", map[string]interface{}{"command": "ls /root"}, false},
{"exec", map[string]any{"command": "cat /etc/passwd"}, false},
{"exec", map[string]any{"command": "find /etc -name '*.conf'"}, false},
{"exec", map[string]any{"command": "ls /root"}, false},
// exec — write commands blocked
{"exec", map[string]interface{}{"command": "rm -rf /"}, false},
{"exec", map[string]interface{}{"command": "mv a b"}, false},
{"exec", map[string]any{"command": "rm -rf /"}, false},
{"exec", map[string]any{"command": "mv a b"}, false},
// exec — no args / empty command blocked
{"exec", nil, false},
{"exec", map[string]interface{}{"command": ""}, false},
{"exec", map[string]any{"command": ""}, false},
{"Exec", nil, false},
}
for _, tt := range tests {
@ -1530,56 +1530,60 @@ func TestBuildArgsSnippet_ExecStripsCD(t *testing.T) {
tests := []struct {
name string
tool string
args map[string]interface{}
args map[string]any
workspace string
wantSnip string
}{
{
name: "exec strips cd prefix",
tool: "exec",
args: map[string]interface{}{"command": "cd /home/user/workspace/project/my-projects && pytest tests/test_integration.py"},
name: "exec strips cd prefix",
tool: "exec",
args: map[string]any{
"command": "cd /home/user/workspace/project/my-projects && pytest tests/test_integration.py",
},
workspace: "/home/user/workspace",
wantSnip: "pytest tests/test_integration.py",
},
{
name: "exec no cd prefix, flags stripped",
tool: "exec",
args: map[string]interface{}{"command": "ls -la"},
args: map[string]any{"command": "ls -la"},
workspace: "/ws",
wantSnip: "ls",
},
{
name: "exec empty command",
tool: "exec",
args: map[string]interface{}{},
args: map[string]any{},
workspace: "/ws",
wantSnip: "{}",
},
{
name: "read_file strips workspace",
tool: "read_file",
args: map[string]interface{}{"path": "/home/user/workspace/src/main.go"},
args: map[string]any{"path": "/home/user/workspace/src/main.go"},
workspace: "/home/user/workspace",
wantSnip: "src/main.go",
},
{
name: "edit_file shows path",
tool: "edit_file",
args: map[string]interface{}{"path": "/ws/config.json", "old_text": "old value here"},
args: map[string]any{"path": "/ws/config.json", "old_text": "old value here"},
workspace: "/ws",
wantSnip: "config.json",
},
{
name: "file tool long path prioritizes filename",
tool: "read_file",
args: map[string]interface{}{"path": "/ws/projects/terra-py-form/src/terra_py_form/hot/state/backend.py"},
name: "file tool long path prioritizes filename",
tool: "read_file",
args: map[string]any{
"path": "/ws/projects/terra-py-form/src/terra_py_form/hot/state/backend.py",
},
workspace: "/ws",
wantSnip: "projects/terra-py-form/src/terra_py_form/hot/sta\u2026/backend.py",
},
{
name: "unknown tool shows raw JSON",
tool: "web_search",
args: map[string]interface{}{"query": "hello"},
args: map[string]any{"query": "hello"},
workspace: "/ws",
wantSnip: `{"query":"hello"}`,
},
@ -1609,20 +1613,32 @@ func TestFormatCompactEntry(t *testing.T) {
wantMark: "✓ 1.0s", // exec keeps duration
},
{
name: "exec long entry truncated from end",
entry: toolLogEntry{Name: "[2] exec", ArgsSnip: "pytest tests/integration/test_very_long_name.py", Result: "✗ 3.0s"},
name: "exec long entry truncated from end",
entry: toolLogEntry{
Name: "[2] exec",
ArgsSnip: "pytest tests/integration/test_very_long_name.py",
Result: "✗ 3.0s",
},
wantMark: "✗",
},
{
name: "file tool omits duration, shows filename",
entry: toolLogEntry{Name: "[3] edit_file", ArgsSnip: "projects/terra/src/deep/nested/backend.py", Result: "✓ 0.0s"},
name: "file tool omits duration, shows filename",
entry: toolLogEntry{
Name: "[3] edit_file",
ArgsSnip: "projects/terra/src/deep/nested/backend.py",
Result: "✓ 0.0s",
},
wantSub: "backend.py",
wantMark: "✓",
noTime: true,
},
{
name: "file tool path truncates from start",
entry: toolLogEntry{Name: "[4] read_file", ArgsSnip: "projects/terra-py-form/src/terra_py_form/hot/state/backend.py", Result: "✓ 0.1s"},
name: "file tool path truncates from start",
entry: toolLogEntry{
Name: "[4] read_file",
ArgsSnip: "projects/terra-py-form/src/terra_py_form/hot/state/backend.py",
Result: "✓ 0.1s",
},
wantSub: "backend.py",
wantMark: "✓",
noTime: true,
@ -1745,7 +1761,7 @@ func TestExtractExecProjectDir(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
args := map[string]interface{}{"command": tt.cmd}
args := map[string]any{"command": tt.cmd}
got := extractExecProjectDir(args)
if got != tt.want {
t.Errorf("extractExecProjectDir(%q) = %q, want %q", tt.cmd, got, tt.want)

View file

@ -520,12 +520,22 @@ func (ms *MemoryStore) getInterviewContextFrom(content string) string {
sb.WriteString("- Tooling preferences (test framework, linter, formatter, CI)\n")
sb.WriteString("- Key commands the user already runs (build, test, deploy)\n")
sb.WriteString("\n### Rules\n")
sb.WriteString("- NEVER remove or overwrite the header block (`# Active Plan`, `> Task:`, `> Status:`, `> Phase:` lines). The system parses these to track state.\n")
sb.WriteString("- After each answer, use edit_file to append findings to the ## Context section of memory/MEMORY.md.\n")
sb.WriteString("- When you have enough information, use edit_file to add ## Phase, ## Commands, and ## Context sections BELOW the header block.\n")
sb.WriteString("- Each step MUST use checkbox syntax: `- [ ] description`. The system parses checkboxes to track progress.\n")
sb.WriteString(
"- NEVER remove or overwrite the header block (`# Active Plan`, `> Task:`, `> Status:`, `> Phase:` lines). The system parses these to track state.\n",
)
sb.WriteString(
"- After each answer, use edit_file to append findings to the ## Context section of memory/MEMORY.md.\n",
)
sb.WriteString(
"- When you have enough information, use edit_file to add ## Phase, ## Commands, and ## Context sections BELOW the header block.\n",
)
sb.WriteString(
"- Each step MUST use checkbox syntax: `- [ ] description`. The system parses checkboxes to track progress.\n",
)
sb.WriteString("- Organize into 2-5 phases with 3-5 steps each.\n")
sb.WriteString("- After writing Phases, change `> Status: interviewing` to `> Status: review` via edit_file. The user must approve with /plan start before execution begins.\n")
sb.WriteString(
"- After writing Phases, change `> Status: interviewing` to `> Status: review` via edit_file. The user must approve with /plan start before execution begins.\n",
)
sb.WriteString("\n### Target Format (MANDATORY — system parses this exact structure)\n")
sb.WriteString("\n")
sb.WriteString("# Active Plan\n")

View file

@ -435,7 +435,8 @@ func TestAgentDefaults_PlanModel_StringParse(t *testing.T) {
if cfg.Agents.Defaults.PlanModel != "anthropic/claude-sonnet-4-6" {
t.Errorf("PlanModel = %q, want 'anthropic/claude-sonnet-4-6'", cfg.Agents.Defaults.PlanModel)
}
if len(cfg.Agents.Defaults.PlanModelFallbacks) != 1 || cfg.Agents.Defaults.PlanModelFallbacks[0] != "openai/gpt-4o" {
if len(cfg.Agents.Defaults.PlanModelFallbacks) != 1 ||
cfg.Agents.Defaults.PlanModelFallbacks[0] != "openai/gpt-4o" {
t.Errorf("PlanModelFallbacks = %v, want [openai/gpt-4o]", cfg.Agents.Defaults.PlanModelFallbacks)
}
}

View file

@ -17,7 +17,10 @@ func TestSanitizeBranchName(t *testing.T) {
{" spaces ", "plan/spaces"},
{"UPPER-case_Mix", "plan/upper-case-mix"},
{"a/b/c", "plan/a-b-c"},
{"very long task name that exceeds the forty character limit for safety", "plan/very-long-task-name-that-exceeds-the-for"},
{
"very long task name that exceeds the forty character limit for safety",
"plan/very-long-task-name-that-exceeds-the-for",
},
{"---leading-trailing---", "plan/leading-trailing"},
{"special!@#$%chars", "plan/special-chars"},
}

View file

@ -310,13 +310,26 @@ func TestSanitizeFields(t *testing.T) {
maskedK: nil,
},
{
name: "sensitive keys masked",
input: map[string]any{"token": "abc123", "api_key": "sk-xxx", "secret": "s3cr3t", "password": "pass", "authorization": "Bearer tok"},
name: "sensitive keys masked",
input: map[string]any{
"token": "abc123",
"api_key": "sk-xxx",
"secret": "s3cr3t",
"password": "pass",
"authorization": "Bearer tok",
},
maskedK: []string{"token", "api_key", "secret", "password", "authorization"},
},
{
name: "case insensitive",
input: map[string]any{"Token": "abc", "API_KEY": "xyz", "Secret": "s", "PASSWORD": "p", "Authorization": "a", "Credential": "c"},
name: "case insensitive",
input: map[string]any{
"Token": "abc",
"API_KEY": "xyz",
"Secret": "s",
"PASSWORD": "p",
"Authorization": "a",
"Credential": "c",
},
maskedK: []string{"Token", "API_KEY", "Secret", "PASSWORD", "Authorization", "Credential"},
},
{
@ -325,8 +338,13 @@ func TestSanitizeFields(t *testing.T) {
safeK: []string{"error", "count", "user_id", "component"},
},
{
name: "mixed keys",
input: map[string]any{"token": "sensitive", "msg_signature": "safe", "corp_secret": "sensitive2", "nonce": "safe2"},
name: "mixed keys",
input: map[string]any{
"token": "sensitive",
"msg_signature": "safe",
"corp_secret": "sensitive2",
"nonce": "safe2",
},
maskedK: []string{"token", "corp_secret"},
safeK: []string{"msg_signature", "nonce"},
},

View file

@ -134,7 +134,13 @@ func (h *Handler) apiEvents(w http.ResponseWriter, r *http.Request) {
sendSSEIfChanged(w, flusher, "skills", h.provider.ListSkills(), &lastSkills)
sendSSEIfChanged(w, flusher, "dev", h.devStatus(), &lastDev)
sendSSEIfChanged(w, flusher, "context", h.provider.GetContextInfo(), &lastContext)
sendSSEIfChanged(w, flusher, "prompt", map[string]string{"prompt": h.provider.GetSystemPrompt()}, &lastPrompt)
sendSSEIfChanged(
w,
flusher,
"prompt",
map[string]string{"prompt": h.provider.GetSystemPrompt()},
&lastPrompt,
)
}
}
}

View file

@ -40,7 +40,14 @@ type Handler struct {
}
// NewHandler creates a new Mini App handler.
func NewHandler(provider DataProvider, sender CommandSender, botToken string, notifier *StateNotifier, allowList []string, workspace string) *Handler {
func NewHandler(
provider DataProvider,
sender CommandSender,
botToken string,
notifier *StateNotifier,
allowList []string,
workspace string,
) *Handler {
return &Handler{
provider: provider,
sender: sender,

View file

@ -1086,7 +1086,7 @@ func TestExtractXMLToolCalls_MismatchedCloseTag(t *testing.T) {
}
}
func TestStripXMLToolCalls_MismatchedCloseTag(t *testing.T) {
func TestStripXMLToolCalls_MismatchedCloseTag(t *testing.T) { //nolint:gosmopolitan // CJK test data
text := `今テスト走らせるね
<minimax:toolcall>
<invoke name="exec">

View file

@ -48,7 +48,13 @@ func NewHTTPProviderWithOptions(apiKey, apiBase, proxy string, opts ...openai_co
}
}
func (p *HTTPProvider) Chat(ctx context.Context, messages []Message, tools []ToolDefinition, model string, options map[string]interface{}) (*LLMResponse, error) {
func (p *HTTPProvider) Chat(
ctx context.Context,
messages []Message,
tools []ToolDefinition,
model string,
options map[string]any,
) (*LLMResponse, error) {
resp, err := p.delegate.Chat(ctx, messages, tools, model, options)
if err != nil {
return nil, err

View file

@ -554,7 +554,17 @@ func normalizeModel(model, apiBase string) string {
prefix := strings.ToLower(model[:idx])
switch prefix {
case "openai", "moonshot", "nvidia", "groq", "ollama", "deepseek", "google", "openrouter", "zhipu", "minimax", "mistral":
case "openai",
"moonshot",
"nvidia",
"groq",
"ollama",
"deepseek",
"google",
"openrouter",
"zhipu",
"minimax",
"mistral":
return model[idx+1:]
default:
return model

View file

@ -177,7 +177,8 @@ func findToolCallBlock(text string) (blockStart, blockEnd int, content string, f
}
// Also consume a preceding [TOOLCALL] marker if present.
start := invokePos
if loc := reBracketMarker.FindStringIndex(before[:start]); loc != nil && strings.TrimSpace(before[loc[1]:start]) == "" {
if loc := reBracketMarker.FindStringIndex(before[:start]); loc != nil &&
strings.TrimSpace(before[loc[1]:start]) == "" {
start = loc[0]
}
return start, cm[1], text[invokePos:cm[0]], true
@ -245,7 +246,7 @@ func parseInvokeElements(text string, callIdx *int) []ToolCall {
toolName := invokeBody[nameStart : nameStart+nameEnd]
// Extract parameters
args := make(map[string]interface{})
args := make(map[string]any)
paramRemaining := invokeBody
for {
pStart := strings.Index(paramRemaining, "<parameter")

View file

@ -74,7 +74,14 @@ func (t *DevPreviewTool) Execute(ctx context.Context, args map[string]any) *Tool
if err := t.manager.ActivateDevTarget(id); err != nil {
return ErrorResult(fmt.Sprintf("failed to activate dev target: %v", err))
}
return SilentResult(fmt.Sprintf("Dev preview started (id=%s, name=%s). Target: %s\nUsers can view it in the Mini App Dev tab.", id, name, target))
return SilentResult(
fmt.Sprintf(
"Dev preview started (id=%s, name=%s). Target: %s\nUsers can view it in the Mini App Dev tab.",
id,
name,
target,
),
)
case "stop":
if err := t.manager.DeactivateDevTarget(); err != nil {

View file

@ -418,7 +418,9 @@ func (t *ExecTool) executeBg(command, cwd string) *ToolResult {
}
if running >= bgMaxProcesses {
t.bgMu.Unlock()
return ErrorResult(fmt.Sprintf("maximum background processes reached (%d). Kill an existing one first.", bgMaxProcesses))
return ErrorResult(
fmt.Sprintf("maximum background processes reached (%d). Kill an existing one first.", bgMaxProcesses),
)
}
t.bgNextID++

View file

@ -21,7 +21,13 @@ func newBlockingProvider() *blockingProvider {
return &blockingProvider{ready: make(chan struct{})}
}
func (p *blockingProvider) Chat(ctx context.Context, _ []providers.Message, _ []providers.ToolDefinition, _ string, _ map[string]any) (*providers.LLMResponse, error) {
func (p *blockingProvider) Chat(
ctx context.Context,
_ []providers.Message,
_ []providers.ToolDefinition,
_ string,
_ map[string]any,
) (*providers.LLMResponse, error) {
close(p.ready) // signal: we are now blocking
<-ctx.Done()
return nil, ctx.Err()