mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-07 02:36:01 -10:00
refactor(go): adopt modern stdlib idioms and gate them (#7831)
* refactor(go): adopt modern stdlib idioms across kernel and desktop Mechanical sweep with golangci-lint's modernize/intrange/copyloopvar/ usestdlibvars fixers plus hand-fixes for what they could not rewrite: slices.Contains, min/max, slices.Backward, WaitGroup.Go, reflect.TypeFor, maps.Copy, strings.Cut/CutPrefix/SplitSeq, strings.Builder, range-over-int. Net -549 lines with no behavior change. Three loops in session.go stay index-based: slices.Backward yields element copies, so the fixer's rewrite silently dropped writes to DecisionReceipts and ToolCalls (caught by TestDelivery* and TestDependentSameBatch*). All 49 slices.Backward sites were audited for that shape; only these three write through the index, and they carry a nolint with the reason. CaptureAfter's scan loop had an empty body and never ran any logic, so the fixer could not express it. Drop it and the accessor it was the only caller of. omitzero is deliberately untouched: turning omitempty into omitzero changes the JSON written for nested structs, which is a wire-format change to session files and the extension protocol. * build(lint): gate the modern-idiom linters Nothing stopped the codebase from drifting back to pre-1.22 idioms. Both modules are now clean for modernize, intrange, copyloopvar, and usestdlibvars, so enable them rather than leaving the sweep as a one-off. omitzero stays disabled with the reason inline; usetesting, perfsprint, and errorlint still have residue and land separately.
This commit is contained in:
428 files changed
+1558
-2248
No files matched your search
@@ -2,13 +2,23 @@ version: "2"
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- copyloopvar
|
||||
- errcheck
|
||||
- errorlint
|
||||
- govet
|
||||
- ineffassign
|
||||
- intrange
|
||||
- modernize
|
||||
- staticcheck
|
||||
- unused
|
||||
- usestdlibvars
|
||||
settings:
|
||||
modernize:
|
||||
# omitempty -> omitzero changes which fields are written for nested
|
||||
# structs. That is a wire-format change to session files and the
|
||||
# extension protocol, not a refactor, so it lands on its own.
|
||||
disable:
|
||||
- omitzero
|
||||
errcheck:
|
||||
check-type-assertions: false
|
||||
exclude-functions:
|
||||
|
||||
@@ -55,7 +55,7 @@ func fakeGoFile(nonce string, i, size int) string {
|
||||
func seedSession(nonce string) *agent.Session {
|
||||
s := agent.NewSession("You are a terse coding agent reviewing a Go codebase.")
|
||||
s.Add(provider.Message{Role: provider.RoleUser, Content: "Review every file in module " + nonce + " one by one. Keep notes short."})
|
||||
for i := 0; i < fatResults; i++ {
|
||||
for i := range fatResults {
|
||||
id := fmt.Sprintf("c%02d", i)
|
||||
name := fmt.Sprintf("src/file%02d.go", i)
|
||||
s.Add(provider.Message{Role: provider.RoleAssistant, ToolCalls: []provider.ToolCall{{ID: id, Name: "read_file", Arguments: fmt.Sprintf(`{"path":%q}`, name)}}})
|
||||
@@ -180,7 +180,7 @@ func resume(dir string) {
|
||||
func comprehension(trials int) {
|
||||
p := prov()
|
||||
pass := 0
|
||||
for t := 0; t < trials; t++ {
|
||||
for t := range trials {
|
||||
dir, err := os.MkdirTemp("", "cm-e2e-")
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
@@ -198,7 +198,7 @@ func comprehension(trials int) {
|
||||
s.Add(provider.Message{Role: provider.RoleAssistant, ToolCalls: []provider.ToolCall{{ID: "r1", Name: "read_file", Arguments: `{"path":"config.go"}`}}})
|
||||
s.Add(provider.Message{Role: provider.RoleTool, ToolCallID: "r1", Name: "read_file", Content: content})
|
||||
s.Add(provider.Message{Role: provider.RoleAssistant, Content: "Noted the constants in config.go."})
|
||||
for i := 0; i < 4; i++ {
|
||||
for i := range 4 {
|
||||
s.Add(provider.Message{Role: provider.RoleUser, Content: fmt.Sprintf("ack %d", i)})
|
||||
s.Add(provider.Message{Role: provider.RoleAssistant, Content: "ok"})
|
||||
}
|
||||
|
||||
+17
-20
@@ -48,10 +48,7 @@ func runDiff(o diffOpts) string {
|
||||
pkgs := packagesOf(srcFiles)
|
||||
prompt := buildDiffPrompt(srcFiles, pkgs, truncate(gitOut(o.repo, "diff", o.base+"...HEAD", "--")))
|
||||
|
||||
attempts := o.attempts
|
||||
if attempts < 1 {
|
||||
attempts = 1
|
||||
}
|
||||
attempts := max(o.attempts, 1)
|
||||
var best diffReport
|
||||
made := 0
|
||||
for i := 1; i <= attempts; i++ {
|
||||
@@ -406,7 +403,7 @@ func parseCoverProfile(repo, path string) map[string][]coverBlock {
|
||||
return nil
|
||||
}
|
||||
out := map[string][]coverBlock{}
|
||||
for _, ln := range strings.Split(string(data), "\n") {
|
||||
for ln := range strings.SplitSeq(string(data), "\n") {
|
||||
if ln == "" || strings.HasPrefix(ln, "mode:") {
|
||||
continue
|
||||
}
|
||||
@@ -433,8 +430,8 @@ func repoRelFromModulePath(p string) string {
|
||||
if strings.HasPrefix(p, prefix) {
|
||||
return p[len(prefix):]
|
||||
}
|
||||
if i := strings.IndexByte(p, '/'); i >= 0 {
|
||||
return p[i+1:]
|
||||
if _, after, ok := strings.Cut(p, "/"); ok {
|
||||
return after
|
||||
}
|
||||
return p
|
||||
}
|
||||
@@ -447,7 +444,7 @@ func changedLineSet(repo, base string, srcFiles []string) map[string]map[int]boo
|
||||
out := map[string]map[int]bool{}
|
||||
file := ""
|
||||
newLine := 0
|
||||
for _, ln := range strings.Split(diff, "\n") {
|
||||
for ln := range strings.SplitSeq(diff, "\n") {
|
||||
// '-' (deletion) lines are intentionally unhandled: they don't advance the
|
||||
// new-side line counter, so they fall through with no case.
|
||||
switch {
|
||||
@@ -457,10 +454,10 @@ func changedLineSet(repo, base string, srcFiles []string) map[string]map[int]boo
|
||||
case strings.HasPrefix(ln, "@@"):
|
||||
// @@ -a,b +c,d @@ — start collecting at new-side line c.
|
||||
// Digit-only cut: malformed headers (e.g. `@@ +abc @@`) fail closed.
|
||||
if plus := strings.Index(ln, "+"); plus >= 0 {
|
||||
num := ln[plus+1:]
|
||||
if _, after, ok := strings.Cut(ln, "+"); ok {
|
||||
num := after
|
||||
end := len(num)
|
||||
for i := 0; i < len(num); i++ {
|
||||
for i := range len(num) {
|
||||
if num[i] < '0' || num[i] > '9' {
|
||||
end = i
|
||||
break
|
||||
@@ -503,9 +500,9 @@ func countAssertionPins(ps []pinResult) int {
|
||||
func parseNewTests(diff string) []testRef {
|
||||
var refs []testRef
|
||||
pkg := ""
|
||||
for _, ln := range strings.Split(diff, "\n") {
|
||||
if strings.HasPrefix(ln, "+++ b/") {
|
||||
pkg = "./" + filepath.ToSlash(filepath.Dir(strings.TrimPrefix(ln, "+++ b/")))
|
||||
for ln := range strings.SplitSeq(diff, "\n") {
|
||||
if after, ok := strings.CutPrefix(ln, "+++ b/"); ok {
|
||||
pkg = "./" + filepath.ToSlash(filepath.Dir(after))
|
||||
continue
|
||||
}
|
||||
if !strings.HasPrefix(ln, "+") || strings.HasPrefix(ln, "+++") {
|
||||
@@ -519,11 +516,11 @@ func parseNewTests(diff string) []testRef {
|
||||
// Method form `(r T) Name(...)` starts with '('; parse the receiver out before the name.
|
||||
var name string
|
||||
if sig[0] == '(' {
|
||||
close := strings.IndexByte(sig, ')')
|
||||
if close < 0 {
|
||||
_, after, ok := strings.Cut(sig, ")")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
rest := strings.TrimSpace(sig[close+1:])
|
||||
rest := strings.TrimSpace(after)
|
||||
methodParen := strings.IndexByte(rest, '(')
|
||||
if methodParen <= 0 {
|
||||
continue
|
||||
@@ -545,7 +542,7 @@ func parseNewTests(diff string) []testRef {
|
||||
|
||||
func countAdded(diff string) int {
|
||||
n := 0
|
||||
for _, ln := range strings.Split(diff, "\n") {
|
||||
for ln := range strings.SplitSeq(diff, "\n") {
|
||||
if strings.HasPrefix(ln, "+") && !strings.HasPrefix(ln, "+++") {
|
||||
n++
|
||||
}
|
||||
@@ -557,7 +554,7 @@ func countAdded(diff string) int {
|
||||
func failingTestNames(out string) []string {
|
||||
var names []string
|
||||
seen := map[string]bool{}
|
||||
for _, ln := range strings.Split(out, "\n") {
|
||||
for ln := range strings.SplitSeq(out, "\n") {
|
||||
ln = strings.TrimSpace(ln)
|
||||
if !strings.HasPrefix(ln, "--- FAIL:") {
|
||||
continue
|
||||
@@ -603,7 +600,7 @@ func changedGoFilesWorktree(repo string, includeTests bool) []string {
|
||||
|
||||
func filterGo(out string, includeTests bool) []string {
|
||||
var keep []string
|
||||
for _, f := range strings.Fields(strings.ReplaceAll(out, "\n", " ")) {
|
||||
for f := range strings.FieldsSeq(strings.ReplaceAll(out, "\n", " ")) {
|
||||
if strings.HasSuffix(f, "_test.go") && !includeTests {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -526,7 +527,7 @@ func median(ms []int64) int64 {
|
||||
return 0
|
||||
}
|
||||
sorted := append([]int64(nil), ms...)
|
||||
sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] })
|
||||
slices.Sort(sorted)
|
||||
return sorted[len(sorted)/2]
|
||||
}
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ func mutantBody(fset *token.FileSet, results *ast.FieldList) string {
|
||||
if n == 0 {
|
||||
n = 1
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
for range n {
|
||||
rets = append(rets, t)
|
||||
}
|
||||
}
|
||||
|
||||
+11
-20
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"reasonix/internal/ablation"
|
||||
@@ -137,30 +138,20 @@ func swebenchReportPath(model, runID string) string {
|
||||
// taxonomy. An id the grader never mentions is reported as unknown rather than
|
||||
// silently counted as unsolved.
|
||||
func (r swebenchReport) gradedClass(instanceID string) string {
|
||||
for _, id := range r.ResolvedIDs {
|
||||
if id == instanceID {
|
||||
return "solved"
|
||||
}
|
||||
if slices.Contains(r.ResolvedIDs, instanceID) {
|
||||
return "solved"
|
||||
}
|
||||
for _, id := range r.EmptyPatchIDs {
|
||||
if id == instanceID {
|
||||
return "no_patch"
|
||||
}
|
||||
if slices.Contains(r.EmptyPatchIDs, instanceID) {
|
||||
return "no_patch"
|
||||
}
|
||||
for _, id := range r.ErrorIDs {
|
||||
if id == instanceID {
|
||||
return "grader_error"
|
||||
}
|
||||
if slices.Contains(r.ErrorIDs, instanceID) {
|
||||
return "grader_error"
|
||||
}
|
||||
for _, id := range r.IncompleteIDs {
|
||||
if id == instanceID {
|
||||
return "eval_timeout"
|
||||
}
|
||||
if slices.Contains(r.IncompleteIDs, instanceID) {
|
||||
return "eval_timeout"
|
||||
}
|
||||
for _, id := range r.UnresolvedIDs {
|
||||
if id == instanceID {
|
||||
return "wrong_patch"
|
||||
}
|
||||
if slices.Contains(r.UnresolvedIDs, instanceID) {
|
||||
return "wrong_patch"
|
||||
}
|
||||
return "ungraded"
|
||||
}
|
||||
@@ -296,8 +296,8 @@ func proxyEnv(url string) []string {
|
||||
}
|
||||
|
||||
func firstLine(s string) string {
|
||||
if i := strings.IndexByte(s, '\n'); i >= 0 {
|
||||
return strings.TrimSpace(s[:i])
|
||||
if before, _, ok := strings.Cut(s, "\n"); ok {
|
||||
return strings.TrimSpace(before)
|
||||
}
|
||||
return strings.TrimSpace(s)
|
||||
}
|
||||
|
||||
+12
-19
@@ -12,6 +12,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"maps"
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -20,6 +21,7 @@ import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
goruntime "runtime"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -2635,6 +2637,7 @@ func (a *App) CheckpointsForTab(tabID string) []CheckpointMeta {
|
||||
canCodeAfter := true
|
||||
codeFileSet := make(map[string]bool, len(metas)*2)
|
||||
codeFilePreview := []string{}
|
||||
//nolint:modernize // slices.Backward yields element copies; this body writes through the index.
|
||||
for i := len(out) - 1; i >= 0; i-- {
|
||||
if len(out[i].Files) > 0 {
|
||||
hasCodeAfter = true
|
||||
@@ -5681,10 +5684,7 @@ func historyPageFromMessages(messages []HistoryMessage, beforeTurn, limit int) H
|
||||
if beforeTurn <= 0 || beforeTurn > totalTurns {
|
||||
beforeTurn = totalTurns
|
||||
}
|
||||
startTurn := beforeTurn - limit
|
||||
if startTurn < 0 {
|
||||
startTurn = 0
|
||||
}
|
||||
startTurn := max(beforeTurn-limit, 0)
|
||||
page := HistoryPage{
|
||||
StartTurn: startTurn,
|
||||
EndTurn: beforeTurn,
|
||||
@@ -6033,10 +6033,7 @@ func historyPageFromProviderMessages(
|
||||
if beforeTurn <= 0 || beforeTurn > totalTurns {
|
||||
beforeTurn = totalTurns
|
||||
}
|
||||
startTurn := beforeTurn - limit
|
||||
if startTurn < 0 {
|
||||
startTurn = 0
|
||||
}
|
||||
startTurn := max(beforeTurn-limit, 0)
|
||||
page := HistoryPage{
|
||||
StartTurn: startTurn,
|
||||
EndTurn: beforeTurn,
|
||||
@@ -6323,7 +6320,7 @@ func historyLineCount(s string) int {
|
||||
|
||||
func historyNonEmptyLineCount(s string) int {
|
||||
count := 0
|
||||
for _, line := range strings.Split(s, "\n") {
|
||||
for line := range strings.SplitSeq(s, "\n") {
|
||||
if strings.TrimSpace(line) != "" {
|
||||
count++
|
||||
}
|
||||
@@ -6622,9 +6619,9 @@ func updateHistoryToolCallSummary(out []HistoryMessage, callID, output string) {
|
||||
if callID == "" {
|
||||
return
|
||||
}
|
||||
for i := len(out) - 1; i >= 0; i-- {
|
||||
for j := range out[i].ToolCalls {
|
||||
call := &out[i].ToolCalls[j]
|
||||
for _, v := range slices.Backward(out) {
|
||||
for j := range v.ToolCalls {
|
||||
call := &v.ToolCalls[j]
|
||||
if call.ID != callID {
|
||||
continue
|
||||
}
|
||||
@@ -8160,9 +8157,7 @@ func (a *App) mcpServersView() []ServerView {
|
||||
}
|
||||
ctrl := tab.Ctrl
|
||||
disabled := make(map[string]ServerView, len(tab.disabledMCP))
|
||||
for name, s := range tab.disabledMCP {
|
||||
disabled[name] = s
|
||||
}
|
||||
maps.Copy(disabled, tab.disabledMCP)
|
||||
order := append([]string(nil), tab.mcpOrder...)
|
||||
workspaceRoot := tab.WorkspaceRoot
|
||||
tabID := tab.ID
|
||||
@@ -9447,9 +9442,7 @@ func cloneStringIntMap(values map[string]int) map[string]int {
|
||||
return nil
|
||||
}
|
||||
out := make(map[string]int, len(values))
|
||||
for key, value := range values {
|
||||
out[key] = value
|
||||
}
|
||||
maps.Copy(out, values)
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -11327,7 +11320,7 @@ func saveExclusiveExportPayloads(targets []string, payloadCount int, payloadAt f
|
||||
// while bytes are staged; the caller restores finalMode only after the payload
|
||||
// has been completely written and synced.
|
||||
func createExportTempFile(dir string) (*os.File, os.FileMode, error) {
|
||||
for attempt := 0; attempt < exportTempCreateAttempts; attempt++ {
|
||||
for range exportTempCreateAttempts {
|
||||
var suffix [12]byte
|
||||
if _, err := rand.Read(suffix[:]); err != nil {
|
||||
return nil, 0, fmt.Errorf("generate export temp name: %w", err)
|
||||
|
||||
@@ -124,12 +124,10 @@ func TestScheduleSnapshotCoalesces(t *testing.T) {
|
||||
_ = a
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 64; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for range 64 {
|
||||
wg.Go(func() {
|
||||
tab.sink.Emit(event.Event{Kind: event.TurnDone})
|
||||
}()
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
@@ -335,11 +333,11 @@ func TestDesktopSnapshotConflictRecoveryRequiresRecoveryLease(t *testing.T) {
|
||||
detachedSessions: map[string]*WorkspaceTab{},
|
||||
activeTabID: "recovery_tab",
|
||||
}
|
||||
app.runtimeEvents.emit = func(ctx context.Context, name string, payload ...interface{}) {
|
||||
app.runtimeEvents.emit = func(ctx context.Context, name string, payload ...any) {
|
||||
runtimeEvents <- runtimeEventEnvelope{
|
||||
ctx: ctx,
|
||||
name: name,
|
||||
payload: append([]interface{}(nil), payload...),
|
||||
payload: append([]any(nil), payload...),
|
||||
}
|
||||
}
|
||||
tab := &WorkspaceTab{
|
||||
|
||||
@@ -35,7 +35,7 @@ func TestCarriedRebuildsKeepOneSession(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
for range 5 {
|
||||
prevPath := ctrl.SessionPath()
|
||||
carried := ctrl.History()
|
||||
ctrl.Close()
|
||||
|
||||
+36
-44
@@ -13,6 +13,7 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -1058,14 +1059,14 @@ func TestBackgroundRestorePlanAvoidsNormalWindowFlash(t *testing.T) {
|
||||
|
||||
func TestEmitReadyInvokesReadyHook(t *testing.T) {
|
||||
app := NewApp()
|
||||
var calls int32
|
||||
var calls atomic.Int32
|
||||
app.readyHook = func() {
|
||||
atomic.AddInt32(&calls, 1)
|
||||
calls.Add(1)
|
||||
}
|
||||
|
||||
app.emitReady(context.TODO())
|
||||
|
||||
if got := atomic.LoadInt32(&calls); got != 1 {
|
||||
if got := calls.Load(); got != 1 {
|
||||
t.Fatalf("ready hook calls = %d, want 1", got)
|
||||
}
|
||||
}
|
||||
@@ -1227,7 +1228,7 @@ func BenchmarkDesktopSettingsPayloads(b *testing.B) {
|
||||
b.Setenv("SHARED_PROVIDER_KEY", "sk-test")
|
||||
|
||||
cfg := config.LoadForEdit(config.UserConfigPath())
|
||||
for i := 0; i < 40; i++ {
|
||||
for i := range 40 {
|
||||
cfg.Providers = append(cfg.Providers, config.ProviderEntry{
|
||||
Name: fmt.Sprintf("custom-%02d", i),
|
||||
Kind: "openai",
|
||||
@@ -1243,12 +1244,12 @@ func BenchmarkDesktopSettingsPayloads(b *testing.B) {
|
||||
app := NewApp()
|
||||
|
||||
b.Run("Settings", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
_ = app.Settings()
|
||||
}
|
||||
})
|
||||
b.Run("DesktopStartupSettings", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
_ = app.DesktopStartupSettings()
|
||||
}
|
||||
})
|
||||
@@ -2073,7 +2074,6 @@ func TestResetProviderPresetAccessRejectsMissingSameNameProvider(t *testing.T) {
|
||||
|
||||
func TestAddEveryProviderPresetAccessInstallsTemplate(t *testing.T) {
|
||||
for _, preset := range config.CuratedProviderPresets() {
|
||||
preset := preset
|
||||
t.Run(preset.ID, func(t *testing.T) {
|
||||
isolateDesktopUserDirs(t)
|
||||
|
||||
@@ -2890,7 +2890,7 @@ func TestSetEffortForTabReanchorsDepthCapRecoveryBranch(t *testing.T) {
|
||||
|
||||
app := NewApp()
|
||||
app.ctx = context.Background()
|
||||
app.runtimeEvents.emit = func(context.Context, string, ...interface{}) {}
|
||||
app.runtimeEvents.emit = func(context.Context, string, ...any) {}
|
||||
tab := &WorkspaceTab{
|
||||
ID: "tab_depth_cap_effort",
|
||||
Scope: "global",
|
||||
@@ -3566,7 +3566,7 @@ func TestSetModelForTabContinuesRecoveryPathAfterSnapshotConflict(t *testing.T)
|
||||
|
||||
app := NewApp()
|
||||
app.ctx = context.Background()
|
||||
app.runtimeEvents.emit = func(context.Context, string, ...interface{}) {}
|
||||
app.runtimeEvents.emit = func(context.Context, string, ...any) {}
|
||||
tab := &WorkspaceTab{
|
||||
ID: "tab_recovery_model",
|
||||
Scope: "global",
|
||||
@@ -4339,16 +4339,13 @@ func runQuickClickWorkspaceReconcileTest(t *testing.T, layoutStyle string) {
|
||||
errs := make(chan error, len(actions))
|
||||
var wg sync.WaitGroup
|
||||
for _, action := range actions {
|
||||
action := action
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
wg.Go(func() {
|
||||
ready <- struct{}{}
|
||||
<-start
|
||||
if err := action.run(); err != nil {
|
||||
errs <- fmt.Errorf("%s: %w", action.name, err)
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
||||
for range actions {
|
||||
<-ready
|
||||
@@ -6793,7 +6790,7 @@ func assertSingleTeardownTimeoutNotice(t *testing.T, notices <-chan event.Event,
|
||||
t.Fatal("missing background-job teardown timeout notice")
|
||||
}
|
||||
var waited time.Duration
|
||||
for _, field := range strings.Fields(notice.Detail) {
|
||||
for field := range strings.FieldsSeq(notice.Detail) {
|
||||
if !strings.HasPrefix(field, "waited=") {
|
||||
continue
|
||||
}
|
||||
@@ -7297,7 +7294,7 @@ func BenchmarkDesktopListSessionsScoped(b *testing.B) {
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
b.Fatalf("mkdir %s: %v", dir, err)
|
||||
}
|
||||
for i := 0; i < 120; i++ {
|
||||
for i := range 120 {
|
||||
path := filepath.Join(dir, fmt.Sprintf("session-%03d.jsonl", i))
|
||||
body := fmt.Sprintf(`{"role":"user","content":"session %03d"}`+"\n", i)
|
||||
if err := os.WriteFile(path, []byte(body), 0o644); err != nil {
|
||||
@@ -7312,7 +7309,7 @@ func BenchmarkDesktopListSessionsScoped(b *testing.B) {
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
sessions := app.ListSessions()
|
||||
if len(sessions) != 120 {
|
||||
b.Fatalf("ListSessions len = %d, want 120", len(sessions))
|
||||
@@ -7563,12 +7560,12 @@ func TestDesktopSharedHostProjectMCPConnectsWithoutLaunchApproval(t *testing.T)
|
||||
|
||||
srv := desktopMCPHTTPServer(t)
|
||||
defer srv.Close()
|
||||
if err := os.WriteFile(filepath.Join(dir, "reasonix.toml"), []byte(fmt.Sprintf(`
|
||||
if err := os.WriteFile(filepath.Join(dir, "reasonix.toml"), fmt.Appendf(nil, `
|
||||
[[plugins]]
|
||||
name = "h"
|
||||
type = "http"
|
||||
url = %q
|
||||
`, srv.URL)), 0o644); err != nil {
|
||||
`, srv.URL), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -7759,12 +7756,12 @@ func TestSetMCPServerEnabledSharedHostPreservesSiblingTabs(t *testing.T) {
|
||||
|
||||
srv := desktopMCPHTTPServer(t)
|
||||
defer srv.Close()
|
||||
if err := os.WriteFile(filepath.Join(dir, "reasonix.toml"), []byte(fmt.Sprintf(`
|
||||
if err := os.WriteFile(filepath.Join(dir, "reasonix.toml"), fmt.Appendf(nil, `
|
||||
[[plugins]]
|
||||
name = "h"
|
||||
type = "http"
|
||||
url = %q
|
||||
`, srv.URL)), 0o644); err != nil {
|
||||
`, srv.URL), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -7909,12 +7906,12 @@ func TestReconnectMCPServerUsesEffectiveProjectConfigWhenUserNameIsShadowed(t *t
|
||||
if err := userCfg.SaveTo(config.UserConfigPath()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dir, "reasonix.toml"), []byte(fmt.Sprintf(`
|
||||
if err := os.WriteFile(filepath.Join(dir, "reasonix.toml"), fmt.Appendf(nil, `
|
||||
[[plugins]]
|
||||
name = "h"
|
||||
type = "http"
|
||||
url = %q
|
||||
`, projectServer.URL)), 0o644); err != nil {
|
||||
`, projectServer.URL), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -8055,7 +8052,7 @@ func newGatedDesktopMCPLaunchFixture(t *testing.T, startGateAddr string) gatedDe
|
||||
gateConfig = fmt.Sprintf("DESKTOP_MCP_START_GATE_ADDR = %q\n", startGateAddr)
|
||||
}
|
||||
helperArgs := []string{"-test.run=TestDesktopMCPHelperProcess", "--"}
|
||||
if err := os.WriteFile(filepath.Join(dir, "reasonix.toml"), []byte(fmt.Sprintf(`
|
||||
if err := os.WriteFile(filepath.Join(dir, "reasonix.toml"), fmt.Appendf(nil, `
|
||||
[[plugins]]
|
||||
name = "h"
|
||||
command = %q
|
||||
@@ -8067,7 +8064,7 @@ DESKTOP_MCP_SINGLE_INSTANCE_ADDR = %q
|
||||
%s
|
||||
[sandbox]
|
||||
network = true
|
||||
`, exe, singleInstanceAddr, gateConfig)), 0o644); err != nil {
|
||||
`, exe, singleInstanceAddr, gateConfig), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -8305,13 +8302,13 @@ func installGatedTestPluginPackage(t *testing.T, mcpServerName string) string {
|
||||
if err := os.MkdirAll(root, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(root, pluginpkg.NativeManifest), []byte(fmt.Sprintf(`{
|
||||
if err := os.WriteFile(filepath.Join(root, pluginpkg.NativeManifest), fmt.Appendf(nil, `{
|
||||
"name": "review-helper",
|
||||
"version": "1.0.0",
|
||||
"mcpServers": {
|
||||
%q: { "type": "stdio", "command": "helper" }
|
||||
}
|
||||
}`, mcpServerName)), 0o644); err != nil {
|
||||
}`, mcpServerName), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := pluginpkg.Upsert(reasonixHome, pluginpkg.InstalledPlugin{
|
||||
@@ -9082,13 +9079,13 @@ func TestRemoveMCPServerRejectsPluginManagedServerWithoutDisconnecting(t *testin
|
||||
if err := os.MkdirAll(root, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(root, pluginpkg.NativeManifest), []byte(fmt.Sprintf(`{
|
||||
if err := os.WriteFile(filepath.Join(root, pluginpkg.NativeManifest), fmt.Appendf(nil, `{
|
||||
"name": "superpowers",
|
||||
"version": "1.0.0",
|
||||
"mcpServers": {
|
||||
"helper": { "type": "http", "url": %q }
|
||||
}
|
||||
}`, srv.URL)), 0o644); err != nil {
|
||||
}`, srv.URL), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := pluginpkg.Upsert(reasonixHome, pluginpkg.InstalledPlugin{
|
||||
@@ -10127,7 +10124,7 @@ func TestRunShellForTabStaysBoundDuringRapidProjectTabSwitching(t *testing.T) {
|
||||
waitForShellDispatch(t, shellEvents, marker)
|
||||
waitForFile(t, filepath.Join(projectA, marker), "shell")
|
||||
|
||||
for i := 0; i < 8; i++ {
|
||||
for range 8 {
|
||||
if err := app.SetActiveTab("project-b"); err != nil {
|
||||
t.Fatalf("SetActiveTab(project-b): %v", err)
|
||||
}
|
||||
@@ -10281,12 +10278,7 @@ func newBackgroundJobController(t *testing.T, label string) *control.Controller
|
||||
}
|
||||
|
||||
func hasLevel(levels []string, want string) bool {
|
||||
for _, level := range levels {
|
||||
if level == want {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return slices.Contains(levels, want)
|
||||
}
|
||||
|
||||
func hasCommand(cmds []CommandInfo, name string) bool {
|
||||
@@ -10616,14 +10608,14 @@ func TestScanPromptHistoryFromDirUsesSessionActivityBeforeEventInterleaving(t *t
|
||||
early := filepath.Join(dir, "early.jsonl")
|
||||
late := filepath.Join(dir, "late.jsonl")
|
||||
|
||||
if err := os.WriteFile(early, []byte(fmt.Sprintf(`{"role":"user","content":"early first","time":%d}
|
||||
if err := os.WriteFile(early, fmt.Appendf(nil, `{"role":"user","content":"early first","time":%d}
|
||||
{"role":"assistant","content":"ok"}
|
||||
{"role":"user","content":"early second","time":%d}
|
||||
`, base.UnixMilli(), base.Add(time.Minute).UnixMilli())), 0o644); err != nil {
|
||||
`, base.UnixMilli(), base.Add(time.Minute).UnixMilli()), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(late, []byte(fmt.Sprintf(`{"role":"user","content":"late newest","time":%d}
|
||||
`, base.Add(2*time.Minute).UnixMilli())), 0o644); err != nil {
|
||||
if err := os.WriteFile(late, fmt.Appendf(nil, `{"role":"user","content":"late newest","time":%d}
|
||||
`, base.Add(2*time.Minute).UnixMilli()), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Invert file mtimes: session activity should keep each session grouped
|
||||
@@ -10792,8 +10784,8 @@ func TestScanPromptHistoryPaginatesCurrentSessionBeforeCrossSession(t *testing.T
|
||||
other := filepath.Join(dir, "other.jsonl")
|
||||
var lines []byte
|
||||
for i := range 55 {
|
||||
lines = append(lines, []byte(fmt.Sprintf(`{"role":"user","content":"current %d"}
|
||||
`, i))...)
|
||||
lines = append(lines, fmt.Appendf(nil, `{"role":"user","content":"current %d"}
|
||||
`, i)...)
|
||||
}
|
||||
if err := os.WriteFile(current, lines, 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -10867,8 +10859,8 @@ func TestScanPromptHistoryFromDirReadsAllEntriesForInternalHelper(t *testing.T)
|
||||
dir := t.TempDir()
|
||||
var lines []byte
|
||||
for i := range 250 {
|
||||
lines = append(lines, []byte(fmt.Sprintf(`{"role":"user","content":"prompt %d"}
|
||||
`, i))...)
|
||||
lines = append(lines, fmt.Appendf(nil, `{"role":"user","content":"prompt %d"}
|
||||
`, i)...)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dir, "many.jsonl"), lines, 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -155,7 +155,7 @@ func TestBridgeTakeoverSwitchAnnouncesReleaseToOldTab(t *testing.T) {
|
||||
t.Fatalf("switch to B: %v", err)
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
for i := 0; i < 2; i++ {
|
||||
for range 2 {
|
||||
select {
|
||||
case got := <-env.announced:
|
||||
seen[got[0]] = true
|
||||
|
||||
@@ -693,7 +693,7 @@ func postFeishuInstallFormResult(base string, body map[string]string) (map[strin
|
||||
for k, v := range body {
|
||||
reqBody.Set(k, v)
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", strings.TrimRight(base, "/")+"/oauth/v1/app/registration", strings.NewReader(reqBody.Encode()))
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, strings.TrimRight(base, "/")+"/oauth/v1/app/registration", strings.NewReader(reqBody.Encode()))
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
@@ -114,12 +114,12 @@ func assertRequiredJSONSlicesNonNil(t *testing.T, path string, value reflect.Val
|
||||
if value.Kind() == reflect.Slice && value.IsNil() {
|
||||
t.Fatalf("%s is a nil slice; JSON contract requires []", path)
|
||||
}
|
||||
for i := 0; i < value.Len(); i++ {
|
||||
for i := range value.Len() {
|
||||
assertRequiredJSONSlicesNonNil(t, path, value.Index(i))
|
||||
}
|
||||
case reflect.Struct:
|
||||
typ := value.Type()
|
||||
for i := 0; i < value.NumField(); i++ {
|
||||
for i := range value.NumField() {
|
||||
fieldType := typ.Field(i)
|
||||
if fieldType.PkgPath != "" {
|
||||
continue
|
||||
|
||||
@@ -230,7 +230,7 @@ func TestCheckpointsForTabLimitsCumulativeFilePreview(t *testing.T) {
|
||||
}
|
||||
content := "old"
|
||||
files := make([]checkpoint.FileSnap, 0, checkpointFilePreviewLimit+5)
|
||||
for i := 0; i < checkpointFilePreviewLimit+5; i++ {
|
||||
for i := range checkpointFilePreviewLimit + 5 {
|
||||
files = append(files, checkpoint.FileSnap{Path: "file-" + strconv.Itoa(1000+i) + ".txt", Content: &content})
|
||||
}
|
||||
now := time.Now()
|
||||
|
||||
@@ -534,7 +534,7 @@ func cleanupOwnedWindowsUpdateDirectory(path string, owner os.FileInfo) error {
|
||||
if path == "" || owner == nil || !owner.IsDir() {
|
||||
return fmt.Errorf("Windows update cleanup identity is incomplete")
|
||||
}
|
||||
for attempt := 0; attempt < 16; attempt++ {
|
||||
for attempt := range 16 {
|
||||
cleanup := fmt.Sprintf("%s.reasonix-cleanup-%d-%d", path, time.Now().UTC().UnixNano(), attempt)
|
||||
from, err := windows.UTF16PtrFromString(path)
|
||||
if err != nil {
|
||||
|
||||
@@ -155,7 +155,7 @@ func baseCrashReport(kind string) crashReport {
|
||||
}
|
||||
|
||||
func topFrameFromStack(stack string) string {
|
||||
for _, line := range strings.Split(stack, "\n") {
|
||||
for line := range strings.SplitSeq(stack, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
|
||||
@@ -107,11 +107,9 @@ func TestWritePendingReportQueueIsBoundedUnderConcurrentWriters(t *testing.T) {
|
||||
var done sync.WaitGroup
|
||||
var successes atomic.Int32
|
||||
|
||||
for i := 0; i < writers; i++ {
|
||||
for range writers {
|
||||
ready.Add(1)
|
||||
done.Add(1)
|
||||
go func() {
|
||||
defer done.Done()
|
||||
done.Go(func() {
|
||||
report := baseCrashReport("performance")
|
||||
report.Source = "native.watchdog"
|
||||
report.Label = "mac.main_thread.hang"
|
||||
@@ -121,7 +119,7 @@ func TestWritePendingReportQueueIsBoundedUnderConcurrentWriters(t *testing.T) {
|
||||
if writePendingReport(report, false) {
|
||||
successes.Add(1)
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
||||
ready.Wait()
|
||||
close(start)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"maps"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -163,9 +164,7 @@ func (a *App) deferredRebuildTick(markIdle bool) bool {
|
||||
return true
|
||||
}
|
||||
pending := make(map[string]string, len(d.pending))
|
||||
for id, setting := range d.pending {
|
||||
pending[id] = setting
|
||||
}
|
||||
maps.Copy(pending, d.pending)
|
||||
d.mu.Unlock()
|
||||
|
||||
for tabID, setting := range pending {
|
||||
|
||||
+3
-3
@@ -28,7 +28,7 @@ func collectDeviceInfo() deviceInfo {
|
||||
}
|
||||
|
||||
func parseCPUModel(cpuinfo string) string {
|
||||
for _, line := range strings.Split(cpuinfo, "\n") {
|
||||
for line := range strings.SplitSeq(cpuinfo, "\n") {
|
||||
if name, ok := strings.CutPrefix(line, "model name"); ok {
|
||||
if _, v, ok := strings.Cut(name, ":"); ok {
|
||||
return strings.TrimSpace(v)
|
||||
@@ -39,7 +39,7 @@ func parseCPUModel(cpuinfo string) string {
|
||||
}
|
||||
|
||||
func parseMemTotalBytes(meminfo string) uint64 {
|
||||
for _, line := range strings.Split(meminfo, "\n") {
|
||||
for line := range strings.SplitSeq(meminfo, "\n") {
|
||||
if rest, ok := strings.CutPrefix(line, "MemTotal:"); ok {
|
||||
kb, err := strconv.ParseUint(strings.TrimSuffix(strings.TrimSpace(rest), " kB"), 10, 64)
|
||||
if err != nil {
|
||||
@@ -52,7 +52,7 @@ func parseMemTotalBytes(meminfo string) uint64 {
|
||||
}
|
||||
|
||||
func parseOSReleasePrettyName(osRelease string) string {
|
||||
for _, line := range strings.Split(osRelease, "\n") {
|
||||
for line := range strings.SplitSeq(osRelease, "\n") {
|
||||
if v, ok := strings.CutPrefix(line, "PRETTY_NAME="); ok {
|
||||
return strings.Trim(strings.TrimSpace(v), `"`)
|
||||
}
|
||||
|
||||
@@ -81,7 +81,6 @@ func TestExportErrorsDoNotExposeSelectedDirectory(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
err := test.run(test.path)
|
||||
if err == nil {
|
||||
@@ -290,7 +289,6 @@ func TestConcurrentMultipartExportsHaveSingleCompleteWinner(t *testing.T) {
|
||||
var ready sync.WaitGroup
|
||||
ready.Add(len(batches))
|
||||
for _, batch := range batches {
|
||||
batch := batch
|
||||
go func() {
|
||||
ready.Done()
|
||||
<-start
|
||||
|
||||
@@ -48,7 +48,7 @@ func darwinInstalledApplicationIndex() map[string]string {
|
||||
defer cancel()
|
||||
out, err := exec.CommandContext(ctx, "/usr/bin/mdfind", "kMDItemContentType == 'com.apple.application-bundle'").Output()
|
||||
if err == nil {
|
||||
for _, line := range strings.Split(string(out), "\n") {
|
||||
for line := range strings.SplitSeq(string(out), "\n") {
|
||||
add(line)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,10 +24,7 @@ var (
|
||||
)
|
||||
|
||||
func recordMainThreadHeartbeat(t time.Time) {
|
||||
elapsed := t.Sub(mainThreadClockBase)
|
||||
if elapsed < 0 {
|
||||
elapsed = 0
|
||||
}
|
||||
elapsed := max(t.Sub(mainThreadClockBase), 0)
|
||||
mainThreadLastHeartbeatElapsed.Store(int64(elapsed))
|
||||
mainThreadLastHeartbeatWall.Store(t.UnixNano())
|
||||
}
|
||||
@@ -38,10 +35,7 @@ func mainThreadHeartbeatAge(now time.Time) (time.Duration, time.Time, bool) {
|
||||
if lastWall <= 0 {
|
||||
return 0, time.Time{}, false
|
||||
}
|
||||
age := now.Sub(mainThreadClockBase) - lastElapsed
|
||||
if age < 0 {
|
||||
age = 0
|
||||
}
|
||||
age := max(now.Sub(mainThreadClockBase)-lastElapsed, 0)
|
||||
return age, time.Unix(0, lastWall), true
|
||||
}
|
||||
|
||||
|
||||
@@ -113,10 +113,7 @@ func windowClassName(hwnd uintptr) string {
|
||||
}
|
||||
|
||||
func windowMessageLoopResponsive(hwnd uintptr, timeout time.Duration) bool {
|
||||
timeoutMS := timeout.Milliseconds()
|
||||
if timeoutMS < 250 {
|
||||
timeoutMS = 250
|
||||
}
|
||||
timeoutMS := max(timeout.Milliseconds(), 250)
|
||||
var result uintptr
|
||||
ok, _, _ := sendMessageTimeoutProc.Call(
|
||||
hwnd,
|
||||
|
||||
@@ -12,7 +12,7 @@ func TestCurrentProcessTopLevelWindowReusesEnumCallback(t *testing.T) {
|
||||
// Go's Windows runtime currently supports a finite callback table. The old
|
||||
// implementation allocated one callback per poll and exhausted that table
|
||||
// during a normal long-running session.
|
||||
for i := 0; i < 2100; i++ {
|
||||
for range 2100 {
|
||||
_ = currentProcessTopLevelWindow()
|
||||
}
|
||||
}
|
||||
@@ -319,7 +319,7 @@ func (e *HeartbeatEngine) executeTask(t HeartbeatTask) HeartbeatTask {
|
||||
// Wait for the tab's controller to be built (it's started
|
||||
// asynchronously in a goroutine by openTopicTab).
|
||||
var ctrl heartbeatRuntimeStatus
|
||||
for i := 0; i < 40; i++ {
|
||||
for range 40 {
|
||||
if candidate := e.app.ctrlByTabID(tabMeta.ID); candidate != nil {
|
||||
ctrl = candidate
|
||||
break
|
||||
@@ -597,11 +597,11 @@ type heartbeatSchedule struct {
|
||||
}
|
||||
|
||||
func parseHeartbeatSchedule(interval string) (heartbeatSchedule, bool) {
|
||||
idx := strings.Index(interval, "|")
|
||||
if idx < 0 {
|
||||
_, after, ok0 := strings.Cut(interval, "|")
|
||||
if !ok0 {
|
||||
return heartbeatSchedule{}, false
|
||||
}
|
||||
raw := strings.TrimSpace(interval[idx+1:])
|
||||
raw := strings.TrimSpace(after)
|
||||
if raw == "" {
|
||||
return heartbeatSchedule{}, false
|
||||
}
|
||||
@@ -625,7 +625,7 @@ func parseHeartbeatSchedule(interval string) (heartbeatSchedule, bool) {
|
||||
case "daily":
|
||||
return s, true
|
||||
case "weekly", "biweekly":
|
||||
for _, part := range strings.Split(rule, ",") {
|
||||
for part := range strings.SplitSeq(rule, ",") {
|
||||
if wd, ok := parseHeartbeatWeekday(part); ok {
|
||||
s.days = append(s.days, wd)
|
||||
}
|
||||
@@ -681,7 +681,7 @@ func previousHeartbeatScheduleAt(t HeartbeatTask, now time.Time) (time.Time, boo
|
||||
|
||||
func previousHeartbeatWeeklyAt(s heartbeatSchedule, now time.Time, windowDays int, anchor time.Time) (time.Time, bool) {
|
||||
var best time.Time
|
||||
for offset := 0; offset < windowDays; offset++ {
|
||||
for offset := range windowDays {
|
||||
day := now.AddDate(0, 0, -offset)
|
||||
for _, wd := range s.days {
|
||||
if day.Weekday() != wd {
|
||||
|
||||
@@ -155,7 +155,7 @@ func TestHeartbeatExecuteTaskPersistsFreshConversationTopicID(t *testing.T) {
|
||||
app := NewApp()
|
||||
app.ctx = context.Background()
|
||||
app.readyHook = func() {}
|
||||
app.runtimeEvents.emit = func(context.Context, string, ...interface{}) {}
|
||||
app.runtimeEvents.emit = func(context.Context, string, ...any) {}
|
||||
engine := &HeartbeatEngine{
|
||||
app: app,
|
||||
pendingTopics: map[string]heartbeatPendingTopic{},
|
||||
@@ -236,7 +236,7 @@ func TestHeartbeatExecuteTaskSkipsPendingPrompt(t *testing.T) {
|
||||
app := NewApp()
|
||||
app.ctx = context.Background()
|
||||
app.readyHook = func() {}
|
||||
app.runtimeEvents.emit = func(context.Context, string, ...interface{}) {}
|
||||
app.runtimeEvents.emit = func(context.Context, string, ...any) {}
|
||||
engine := &HeartbeatEngine{
|
||||
app: app,
|
||||
pendingTopics: map[string]heartbeatPendingTopic{},
|
||||
|
||||
@@ -23,7 +23,7 @@ func syntheticHistoryMessages(turns, outputSize int, toolName string, failed boo
|
||||
if failed {
|
||||
output = "error: " + output
|
||||
}
|
||||
for i := 0; i < turns; i++ {
|
||||
for i := range turns {
|
||||
callID := fmt.Sprintf("call_%d", i)
|
||||
msgs = append(msgs,
|
||||
provider.Message{Role: provider.RoleUser, Content: fmt.Sprintf("prompt %d", i)},
|
||||
@@ -66,7 +66,7 @@ func BenchmarkHistoryMessagesSynthetic(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.SetBytes(inputBytes)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
got := historyMessages(msgs, func(content string) string { return content })
|
||||
if len(got) == 0 {
|
||||
b.Fatal("empty history")
|
||||
@@ -84,7 +84,7 @@ func BenchmarkHistoryMessagesMarshalSynthetic(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.SetBytes(inputBytes)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
got := historyMessages(msgs, func(content string) string { return content })
|
||||
encoded, err := json.Marshal(got)
|
||||
if err != nil {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
fileencoding "reasonix/internal/fileutil/encoding"
|
||||
@@ -127,12 +128,7 @@ func hookEventNames() []string {
|
||||
}
|
||||
|
||||
func validHookEvent(event hook.Event) bool {
|
||||
for _, e := range hook.Events {
|
||||
if event == e {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return slices.Contains(hook.Events, event)
|
||||
}
|
||||
|
||||
func hookConfigView(event hook.Event, cfg hook.HookConfig) HookConfigView {
|
||||
|
||||
@@ -64,7 +64,7 @@ func TestMain(m *testing.M) {
|
||||
// contexts tests use, killing the process from any emitting code path.
|
||||
// Tests that assert on runtime events install their own capture through
|
||||
// the per-instance runtimeEvents.emit hook, which takes precedence.
|
||||
runtimeEventsEmitFallback = func(context.Context, string, ...interface{}) {}
|
||||
runtimeEventsEmitFallback = func(context.Context, string, ...any) {}
|
||||
code := m.Run()
|
||||
os.RemoveAll(dir)
|
||||
os.Exit(code)
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -102,10 +103,8 @@ func countBucket(n int) string {
|
||||
|
||||
func knownBucket(value string, allowed ...string) string {
|
||||
value = strings.ToLower(strings.TrimSpace(value))
|
||||
for _, ok := range allowed {
|
||||
if value == ok {
|
||||
return value
|
||||
}
|
||||
if slices.Contains(allowed, value) {
|
||||
return value
|
||||
}
|
||||
return "other"
|
||||
}
|
||||
@@ -433,7 +432,7 @@ func (m *metricsAggregator) observeRecoveryMetrics(stats recovery.Metrics) {
|
||||
return
|
||||
}
|
||||
add := func(signal string, n int64) {
|
||||
for i := int64(0); i < n; i++ {
|
||||
for range n {
|
||||
m.inc(signal, "total")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,14 +36,14 @@ func TestSessionLeaseHelpersConcurrentAccess(t *testing.T) {
|
||||
wg.Add(3)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < iterations; i++ {
|
||||
for range iterations {
|
||||
_ = tabA.ensureSessionLease(path)
|
||||
_ = tabA.sessionLeaseRuntimeKey()
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < iterations; i++ {
|
||||
for range iterations {
|
||||
// The applyRuntimeTab transfer shape: move A's lease to B and back.
|
||||
tabB.adoptSessionLease(tabA.takeSessionLease())
|
||||
tabA.adoptSessionLease(tabB.takeSessionLease())
|
||||
@@ -51,7 +51,7 @@ func TestSessionLeaseHelpersConcurrentAccess(t *testing.T) {
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < iterations; i++ {
|
||||
for range iterations {
|
||||
tabA.releaseSessionLease()
|
||||
}
|
||||
}()
|
||||
@@ -142,13 +142,13 @@ func TestTabEventSinkEmitConcurrentRebind(t *testing.T) {
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < iterations; i++ {
|
||||
for range iterations {
|
||||
sink.Emit(event.Event{Kind: event.Notice, Text: "hammer"})
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < iterations; i++ {
|
||||
for i := range iterations {
|
||||
sink.setBinding(fmt.Sprintf("tab-%d", i%2), nil)
|
||||
sink.clearContext()
|
||||
}
|
||||
@@ -170,7 +170,7 @@ func TestModeRebuildAndABANavigationFenceLeaseFailureAndOldEpochEvent(t *testing
|
||||
continueCommit := make(chan struct{})
|
||||
oldEvent := make(chan wireEventTab, 1)
|
||||
var capturedOldEvent wireEventTab
|
||||
oldSink.runtimeEvents.emit = func(_ context.Context, name string, payload ...interface{}) {
|
||||
oldSink.runtimeEvents.emit = func(_ context.Context, name string, payload ...any) {
|
||||
if name != eventChannel || len(payload) != 1 {
|
||||
return
|
||||
}
|
||||
@@ -573,7 +573,7 @@ func TestMetaForTabConcurrentWithBuildSwap(t *testing.T) {
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
for i := 0; i < iterations; i++ {
|
||||
for i := range iterations {
|
||||
app.mu.Lock()
|
||||
tab.Ready = !tab.Ready
|
||||
tab.Label = fmt.Sprintf("model-%d", i)
|
||||
@@ -584,7 +584,7 @@ func TestMetaForTabConcurrentWithBuildSwap(t *testing.T) {
|
||||
app.mu.Unlock()
|
||||
}
|
||||
}()
|
||||
for i := 0; i < iterations; i++ {
|
||||
for range iterations {
|
||||
meta := app.MetaForTab("tab")
|
||||
if meta.EventChannel == "" {
|
||||
t.Fatal("MetaForTab returned zero meta for a live tab")
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@@ -106,8 +105,7 @@ func TestRecoveryGCFirstSweepWaitsForTabRestore(t *testing.T) {
|
||||
}
|
||||
_, branchPath := forkCoveredRecoveryBranch(t, dir, "startup")
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
ctx := t.Context()
|
||||
app := &App{
|
||||
ctx: ctx,
|
||||
tabs: map[string]*WorkspaceTab{},
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -77,7 +78,7 @@ func TestReloadRuntimeSwapsAndClosesOldAfterSwap(t *testing.T) {
|
||||
app.readyHook = func() {}
|
||||
var fenceMu sync.Mutex
|
||||
var fenceNames []string
|
||||
app.runtimeEvents.emit = func(_ context.Context, name string, _ ...interface{}) {
|
||||
app.runtimeEvents.emit = func(_ context.Context, name string, _ ...any) {
|
||||
fenceMu.Lock()
|
||||
fenceNames = append(fenceNames, name)
|
||||
fenceMu.Unlock()
|
||||
@@ -131,13 +132,7 @@ func TestReloadRuntimeSwapsAndClosesOldAfterSwap(t *testing.T) {
|
||||
deadline := time.Now().Add(2 * time.Second)
|
||||
for {
|
||||
fenceMu.Lock()
|
||||
found := false
|
||||
for _, name := range fenceNames {
|
||||
if name == "runtime:rebuilt" {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
found := slices.Contains(fenceNames, "runtime:rebuilt")
|
||||
fenceMu.Unlock()
|
||||
if found {
|
||||
break
|
||||
|
||||
@@ -165,7 +165,7 @@ func TestConfirmRemoteSecretDelegatesWithoutPersisting(t *testing.T) {
|
||||
func TestRemoteStatusBridgesToAsyncEmitter(t *testing.T) {
|
||||
a := &App{ctx: context.Background()}
|
||||
events := make(chan runtimeEventEnvelope, 4)
|
||||
a.runtimeEvents.emit = func(ctx context.Context, name string, payload ...interface{}) {
|
||||
a.runtimeEvents.emit = func(ctx context.Context, name string, payload ...any) {
|
||||
events <- runtimeEventEnvelope{ctx: ctx, name: name, payload: payload}
|
||||
}
|
||||
a.onStatus(RemoteConnectionStatusView{HostID: "box", State: "connected"})
|
||||
|
||||
@@ -194,7 +194,7 @@ func canonicalRemoteSSHHost(host string) (string, error) {
|
||||
if trimmed == "" || len(trimmed) > 253 {
|
||||
return "", errors.New("remote SSH host is invalid")
|
||||
}
|
||||
for _, label := range strings.Split(trimmed, ".") {
|
||||
for label := range strings.SplitSeq(trimmed, ".") {
|
||||
if !remoteDNSLabelPattern.MatchString(label) {
|
||||
return "", errors.New("remote SSH host is invalid")
|
||||
}
|
||||
|
||||
@@ -100,10 +100,10 @@ func TestRemoteHostStoreLoadsV1AsConfigAndWritesV2OnMutation(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
raw := []byte(fmt.Sprintf(
|
||||
raw := fmt.Appendf(nil,
|
||||
`{"version":1,"hosts":[{"id":%q,"alias":%q,"label":%q,"sshConfigPath":%q,"clientInstanceId":%q,"resumeLeaseId":"lease_legacy","layoutRef":"layout_legacy"}]}`,
|
||||
entry.ID, entry.Alias, entry.Label, configPath, entry.ClientInstanceID,
|
||||
))
|
||||
)
|
||||
if err := os.WriteFile(path, raw, 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -213,7 +213,7 @@ func TestRemoteHostStoreConcurrentInstancesDoNotLoseUpdates(t *testing.T) {
|
||||
const count = 48
|
||||
var wg sync.WaitGroup
|
||||
errorsCh := make(chan error, count)
|
||||
for i := 0; i < count; i++ {
|
||||
for i := range count {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
|
||||
@@ -373,15 +373,13 @@ func TestHostKeyPromptsAreSerializedForGlobalDialog(t *testing.T) {
|
||||
prompts = append(prompts, pendingPrompt{hostID: hostID, prompt: mgr.hostKeyPrompt(hostID, mh)})
|
||||
}
|
||||
for _, pending := range prompts {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
wg.Go(func() {
|
||||
_, _ = pending.prompt(ctx, remote.HostKeyQuestion{
|
||||
Address: pending.hostID + ":22",
|
||||
KeyType: "ssh-ed25519",
|
||||
Fingerprint: pending.hostID,
|
||||
})
|
||||
}()
|
||||
})
|
||||
}
|
||||
|
||||
first := <-sink.statuses
|
||||
|
||||
@@ -574,8 +574,8 @@ func (a *App) domReadyRemoteWindow() {
|
||||
func (a *App) secondInstanceRemoteWindow(data options.SecondInstanceData) {
|
||||
ticket := ""
|
||||
for _, arg := range data.Args {
|
||||
if strings.HasPrefix(arg, remoteWindowTicketArgPrefix) {
|
||||
ticket = strings.TrimPrefix(arg, remoteWindowTicketArgPrefix)
|
||||
if after, ok := strings.CutPrefix(arg, remoteWindowTicketArgPrefix); ok {
|
||||
ticket = after
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,8 +289,7 @@ func TestRemoteWindowOwnerWaitDetectsParentExit(t *testing.T) {
|
||||
if err := cmd.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
ctx := t.Context()
|
||||
exited := make(chan bool, 1)
|
||||
go func() { exited <- waitForRemoteWindowOwnerExit(ctx, cmd.Process.Pid) }()
|
||||
if err := cmd.Process.Kill(); err != nil {
|
||||
|
||||
@@ -59,14 +59,14 @@ func TestRuntimeRebuildsEmitRuntimeRebuiltForTab(t *testing.T) {
|
||||
// The App-level queue must stay silent: ordering against the tab's agent
|
||||
// events only holds when the notice rides the tab sink's own queue, so a
|
||||
// notice showing up here means the routing regressed to the fallback.
|
||||
app.runtimeEvents.emit = func(_ context.Context, name string, _ ...interface{}) {
|
||||
app.runtimeEvents.emit = func(_ context.Context, name string, _ ...any) {
|
||||
if name == "runtime:rebuilt" {
|
||||
mu.Lock()
|
||||
rebuilt = append(rebuilt, "VIA-APP-QUEUE")
|
||||
mu.Unlock()
|
||||
}
|
||||
}
|
||||
sinkEmit := func(_ context.Context, name string, payload ...interface{}) {
|
||||
sinkEmit := func(_ context.Context, name string, payload ...any) {
|
||||
if name != "runtime:rebuilt" {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ func TestParallelDesktopTabsPersistCompleteTranscriptsAcrossReload(t *testing.T)
|
||||
tabOrder := make([]string, 0, tabCount)
|
||||
controllers := make([]*control.Controller, 0, tabCount)
|
||||
|
||||
for tabIndex := 0; tabIndex < tabCount; tabIndex++ {
|
||||
for tabIndex := range tabCount {
|
||||
id := fmt.Sprintf("parallel-%02d", tabIndex)
|
||||
path := filepath.Join(dir, id+".jsonl")
|
||||
prov := &capturingProvider{}
|
||||
@@ -207,25 +207,20 @@ func TestParallelDesktopTabsPersistCompleteTranscriptsAcrossReload(t *testing.T)
|
||||
errs := make(chan error, tabCount+1)
|
||||
var wg sync.WaitGroup
|
||||
for tabIndex, ctrl := range controllers {
|
||||
tabIndex, ctrl := tabIndex, ctrl
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
wg.Go(func() {
|
||||
<-start
|
||||
for turn := 0; turn < turnsPerTab; turn++ {
|
||||
for turn := range turnsPerTab {
|
||||
input := fmt.Sprintf("tab-%02d-turn-%02d", tabIndex, turn)
|
||||
if err := ctrl.RunTurn(context.Background(), input); err != nil {
|
||||
errs <- fmt.Errorf("%s: %w", input, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
wg.Go(func() {
|
||||
<-start
|
||||
for round := 0; round < 3; round++ {
|
||||
for range 3 {
|
||||
for _, id := range tabOrder {
|
||||
if err := app.SetActiveTab(id); err != nil {
|
||||
errs <- fmt.Errorf("switch to %s: %w", id, err)
|
||||
@@ -233,7 +228,7 @@ func TestParallelDesktopTabsPersistCompleteTranscriptsAcrossReload(t *testing.T)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
close(start)
|
||||
wg.Wait()
|
||||
@@ -269,7 +264,7 @@ func TestParallelDesktopTabsPersistCompleteTranscriptsAcrossReload(t *testing.T)
|
||||
if msgs[0].Role != provider.RoleSystem || msgs[0].Content != systemText {
|
||||
t.Fatalf("%s system message = %+v", id, msgs[0])
|
||||
}
|
||||
for turn := 0; turn < turnsPerTab; turn++ {
|
||||
for turn := range turnsPerTab {
|
||||
user := msgs[1+turn*2]
|
||||
assistant := msgs[2+turn*2]
|
||||
wantUser := fmt.Sprintf("tab-%02d-turn-%02d", tabIndex, turn)
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
func saveSnapshotTurns(t *testing.T, path string, turns int) *agent.Session {
|
||||
t.Helper()
|
||||
s := agent.NewSession("sys")
|
||||
for i := 0; i < turns; i++ {
|
||||
for i := range turns {
|
||||
s.Add(provider.Message{Role: provider.RoleUser, Content: "prompt " + string(rune('a'+i))})
|
||||
s.Add(provider.Message{Role: provider.RoleAssistant, Content: "reply"})
|
||||
if err := s.SaveSnapshot(path); err != nil {
|
||||
|
||||
+1
-1
@@ -354,7 +354,7 @@ func reserveUniqueSessionTrashItemDir(dir, key string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
stem := strings.TrimSuffix(key, ".jsonl")
|
||||
for i := 0; i < 100; i++ {
|
||||
for i := range 100 {
|
||||
name := fmt.Sprintf("%s.jsonl-deleted-%d-%02d", stem, time.Now().UnixNano(), i)
|
||||
itemDir := filepath.Join(root, name)
|
||||
if err := os.Mkdir(itemDir, 0o755); err == nil {
|
||||
|
||||
@@ -30,7 +30,7 @@ func occupyReadFileWithTimeoutSlots(t *testing.T) func() {
|
||||
return
|
||||
}
|
||||
released = true
|
||||
for i := 0; i < filled; i++ {
|
||||
for range filled {
|
||||
<-readFileWithTimeoutSlots
|
||||
}
|
||||
}
|
||||
@@ -1208,7 +1208,7 @@ func TestRecordSessionPlannerDisplayConcurrentPreservesEverySession(t *testing.T
|
||||
start := make(chan struct{})
|
||||
errs := make(chan error, writers)
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < writers; i++ {
|
||||
for i := range writers {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
@@ -1232,7 +1232,7 @@ func TestRecordSessionPlannerDisplayConcurrentPreservesEverySession(t *testing.T
|
||||
if len(got) != writers {
|
||||
t.Fatalf("planner display sessions = %d, want %d", len(got), writers)
|
||||
}
|
||||
for i := 0; i < writers; i++ {
|
||||
for i := range writers {
|
||||
key := fmt.Sprintf("session-%02d.jsonl", i)
|
||||
if len(got[key]) != 1 || len(got[key][0].Messages) != 1 || got[key][0].Messages[0].Content != fmt.Sprintf("answer-%02d", i) {
|
||||
t.Fatalf("planner display %s = %+v", key, got[key])
|
||||
@@ -1504,7 +1504,7 @@ func TestRecordSessionDisplaySerializesConcurrentTabs(t *testing.T) {
|
||||
const tabs = 32
|
||||
errs := make(chan error, tabs)
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < tabs; i++ {
|
||||
for i := range tabs {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
@@ -1523,7 +1523,7 @@ func TestRecordSessionDisplaySerializesConcurrentTabs(t *testing.T) {
|
||||
}
|
||||
|
||||
got := loadSessionDisplays(dir)
|
||||
for i := 0; i < tabs; i++ {
|
||||
for i := range tabs {
|
||||
key := fmt.Sprintf("tab-%02d.jsonl", i)
|
||||
content := fmt.Sprintf("expanded-%02d", i)
|
||||
if display := got[key][messageDisplayKey(content)]; display != fmt.Sprintf("display-%02d", i) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -1687,13 +1688,13 @@ func configDeclaresProviderAccess(path string) bool {
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, line := range strings.Split(string(body), "\n") {
|
||||
for line := range strings.SplitSeq(string(body), "\n") {
|
||||
if before, _, ok := strings.Cut(line, "#"); ok {
|
||||
line = before
|
||||
}
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, "provider_access") {
|
||||
rest := strings.TrimSpace(strings.TrimPrefix(line, "provider_access"))
|
||||
if after, ok := strings.CutPrefix(line, "provider_access"); ok {
|
||||
rest := strings.TrimSpace(after)
|
||||
return strings.HasPrefix(rest, "=")
|
||||
}
|
||||
}
|
||||
@@ -2321,10 +2322,8 @@ func providerVisionModels(models, visionModels []string) []string {
|
||||
func providerDefaultForModels(currentDefault string, models []string) string {
|
||||
currentDefault = strings.TrimSpace(currentDefault)
|
||||
if currentDefault != "" {
|
||||
for _, model := range models {
|
||||
if model == currentDefault {
|
||||
return currentDefault
|
||||
}
|
||||
if slices.Contains(models, currentDefault) {
|
||||
return currentDefault
|
||||
}
|
||||
}
|
||||
if len(models) > 0 {
|
||||
|
||||
@@ -73,7 +73,7 @@ func (a *App) reapOrphanCodeGraph() {
|
||||
ours := map[int]bool{}
|
||||
out, err := exec.Command("pgrep", "-P", strconv.Itoa(myPID)).Output()
|
||||
if err == nil {
|
||||
for _, f := range strings.Fields(string(out)) {
|
||||
for f := range strings.FieldsSeq(string(out)) {
|
||||
if pid, err := strconv.Atoi(f); err == nil {
|
||||
ours[pid] = true
|
||||
}
|
||||
@@ -85,7 +85,7 @@ func (a *App) reapOrphanCodeGraph() {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, f := range strings.Fields(string(out)) {
|
||||
for f := range strings.FieldsSeq(string(out)) {
|
||||
pid, err := strconv.Atoi(f)
|
||||
if err != nil || pid == myPID || ours[pid] {
|
||||
continue
|
||||
|
||||
@@ -110,7 +110,7 @@ func TestTabEventSinkDoesNotBlockOnRuntimeEventsEmit(t *testing.T) {
|
||||
var calls atomic.Int32
|
||||
|
||||
sink := &tabEventSink{tabID: "tab", ctx: context.Background()}
|
||||
sink.runtimeEvents.emit = func(_ context.Context, name string, payload ...interface{}) {
|
||||
sink.runtimeEvents.emit = func(_ context.Context, name string, payload ...any) {
|
||||
if name != eventChannel {
|
||||
t.Errorf("event name = %q, want %q", name, eventChannel)
|
||||
}
|
||||
@@ -170,7 +170,7 @@ func TestEmitProjectTreeChangedDoesNotBlockOnRuntimeEventsEmit(t *testing.T) {
|
||||
var calls atomic.Int32
|
||||
|
||||
app := &App{ctx: context.Background()}
|
||||
app.runtimeEvents.emit = func(_ context.Context, name string, payload ...interface{}) {
|
||||
app.runtimeEvents.emit = func(_ context.Context, name string, payload ...any) {
|
||||
if name != "project-tree:changed" {
|
||||
t.Errorf("event name = %q, want project-tree:changed", name)
|
||||
}
|
||||
@@ -221,7 +221,7 @@ func TestAsyncRuntimeEmitterDrainsBacklogInOrder(t *testing.T) {
|
||||
var calls atomic.Int32
|
||||
|
||||
emitter := &asyncRuntimeEmitter{}
|
||||
emitter.emit = func(_ context.Context, _ string, payload ...interface{}) {
|
||||
emitter.emit = func(_ context.Context, _ string, payload ...any) {
|
||||
if len(payload) != 1 {
|
||||
t.Errorf("payload count = %d, want 1", len(payload))
|
||||
return
|
||||
@@ -239,7 +239,7 @@ func TestAsyncRuntimeEmitterDrainsBacklogInOrder(t *testing.T) {
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
for i := 0; i < backlog; i++ {
|
||||
for i := range backlog {
|
||||
emitter.Emit(ctx, "agent:event", strconv.Itoa(i))
|
||||
}
|
||||
|
||||
@@ -250,7 +250,7 @@ func TestAsyncRuntimeEmitterDrainsBacklogInOrder(t *testing.T) {
|
||||
}
|
||||
close(release)
|
||||
|
||||
for i := 0; i < backlog; i++ {
|
||||
for i := range backlog {
|
||||
select {
|
||||
case got := <-delivered:
|
||||
if want := strconv.Itoa(i); got != want {
|
||||
|
||||
+27
-45
@@ -9,8 +9,10 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"maps"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -308,15 +310,11 @@ func cloneSessionUsageStats(in sessionUsageStats) sessionUsageStats {
|
||||
out := in
|
||||
if len(in.Sources) > 0 {
|
||||
out.Sources = make(map[string]usageSourceStats, len(in.Sources))
|
||||
for source, stats := range in.Sources {
|
||||
out.Sources[source] = stats
|
||||
}
|
||||
maps.Copy(out.Sources, in.Sources)
|
||||
}
|
||||
if len(in.sourceSessionCache) > 0 {
|
||||
out.sourceSessionCache = make(map[string]sourceSessionCacheCounters, len(in.sourceSessionCache))
|
||||
for source, counters := range in.sourceSessionCache {
|
||||
out.sourceSessionCache[source] = counters
|
||||
}
|
||||
maps.Copy(out.sourceSessionCache, in.sourceSessionCache)
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -1197,9 +1195,7 @@ func (t *WorkspaceTab) telemetrySnapshot() tabTelemetrySnapshot {
|
||||
}
|
||||
if len(t.usageTelemetry.Sources) > 0 {
|
||||
usage.Sources = make(map[string]usageSourceStats, len(t.usageTelemetry.Sources))
|
||||
for source, stats := range t.usageTelemetry.Sources {
|
||||
usage.Sources[source] = stats
|
||||
}
|
||||
maps.Copy(usage.Sources, t.usageTelemetry.Sources)
|
||||
}
|
||||
usage.activeTurnStartedAt = 0
|
||||
usage.sourceSessionCache = nil
|
||||
@@ -1408,9 +1404,9 @@ func updateBufferedHistoryToolCallSummary(messages []*bufferedHistoryMessage, ca
|
||||
if callID == "" {
|
||||
return
|
||||
}
|
||||
for i := len(messages) - 1; i >= 0; i-- {
|
||||
for j := range messages[i].message.ToolCalls {
|
||||
call := &messages[i].message.ToolCalls[j]
|
||||
for _, v := range slices.Backward(messages) {
|
||||
for j := range v.message.ToolCalls {
|
||||
call := &v.message.ToolCalls[j]
|
||||
if call.ID != callID {
|
||||
continue
|
||||
}
|
||||
@@ -1737,7 +1733,7 @@ func (s *tabEventSink) context() context.Context {
|
||||
return s.ctx
|
||||
}
|
||||
|
||||
func (s *tabEventSink) emitRuntimeEvent(name string, payload ...interface{}) {
|
||||
func (s *tabEventSink) emitRuntimeEvent(name string, payload ...any) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
@@ -1748,12 +1744,12 @@ func (s *tabEventSink) emitRuntimeEvent(name string, payload ...interface{}) {
|
||||
s.runtimeEvents.Emit(ctx, name, payload...)
|
||||
}
|
||||
|
||||
type runtimeEventEmitFunc func(context.Context, string, ...interface{})
|
||||
type runtimeEventEmitFunc func(context.Context, string, ...any)
|
||||
|
||||
type runtimeEventEnvelope struct {
|
||||
ctx context.Context
|
||||
name string
|
||||
payload []interface{}
|
||||
payload []any
|
||||
}
|
||||
|
||||
// asyncRuntimeEmitter decouples Wails' runtime event bridge from agent
|
||||
@@ -1775,14 +1771,14 @@ type asyncRuntimeEmitter struct {
|
||||
running bool
|
||||
}
|
||||
|
||||
func (e *asyncRuntimeEmitter) Emit(ctx context.Context, name string, payload ...interface{}) {
|
||||
func (e *asyncRuntimeEmitter) Emit(ctx context.Context, name string, payload ...any) {
|
||||
if ctx == nil {
|
||||
return
|
||||
}
|
||||
item := runtimeEventEnvelope{
|
||||
ctx: ctx,
|
||||
name: name,
|
||||
payload: append([]interface{}(nil), payload...),
|
||||
payload: append([]any(nil), payload...),
|
||||
}
|
||||
e.mu.Lock()
|
||||
e.queue = append(e.queue, item)
|
||||
@@ -2077,8 +2073,8 @@ func lastHistoryMessageIsUser(history []provider.Message) bool {
|
||||
}
|
||||
|
||||
func hasPendingInterruptedRecovery(history []provider.Message) bool {
|
||||
for i := len(history) - 1; i >= 0; i-- {
|
||||
m := history[i]
|
||||
for _, v := range slices.Backward(history) {
|
||||
m := v
|
||||
if m.LocalOnly && m.InterruptedTurn != nil {
|
||||
return m.InterruptedTurn.Pending
|
||||
}
|
||||
@@ -2104,9 +2100,9 @@ func (s *tabEventSink) eventTabAndController() (*WorkspaceTab, control.SessionAP
|
||||
}
|
||||
|
||||
func lastUserMessageContent(msgs []provider.Message) string {
|
||||
for i := len(msgs) - 1; i >= 0; i-- {
|
||||
if msgs[i].Role == provider.RoleUser {
|
||||
return agent.UserMessageText(msgs[i])
|
||||
for _, v := range slices.Backward(msgs) {
|
||||
if v.Role == provider.RoleUser {
|
||||
return agent.UserMessageText(v)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
@@ -2892,7 +2888,7 @@ func createEmptySessionFile(dir, model string) (string, error) {
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
for i := 0; i < 3; i++ {
|
||||
for range 3 {
|
||||
path := agent.NewSessionPath(dir, model)
|
||||
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o644)
|
||||
if err == nil {
|
||||
@@ -3232,10 +3228,7 @@ func (a *App) closeTab(tabID string, allowDetach bool) error {
|
||||
if wasActive {
|
||||
a.activeTabID = ""
|
||||
if len(a.tabOrder) > 0 {
|
||||
nextIndex := closedIndex
|
||||
if nextIndex < 0 {
|
||||
nextIndex = 0
|
||||
}
|
||||
nextIndex := max(closedIndex, 0)
|
||||
if nextIndex >= len(a.tabOrder) {
|
||||
nextIndex = len(a.tabOrder) - 1
|
||||
}
|
||||
@@ -4673,7 +4666,7 @@ func autoTopicTitleProposalFromSession(path string) autoTopicTitleProposal {
|
||||
if title == "" {
|
||||
return autoTopicTitleProposal{}
|
||||
}
|
||||
sum := sha256.Sum256([]byte(fmt.Sprintf("%d\x00%s", stage, strings.Join(basis, "\x00"))))
|
||||
sum := sha256.Sum256(fmt.Appendf(nil, "%d\x00%s", stage, strings.Join(basis, "\x00")))
|
||||
return autoTopicTitleProposal{
|
||||
Title: title,
|
||||
Stage: stage,
|
||||
@@ -4799,10 +4792,7 @@ func topicTitleFromUserTurns(users []string) string {
|
||||
continue
|
||||
}
|
||||
runes := len([]rune(title))
|
||||
score := runes
|
||||
if score > 24 {
|
||||
score = 24
|
||||
}
|
||||
score := min(runes, 24)
|
||||
if i == 0 {
|
||||
score += 3
|
||||
}
|
||||
@@ -5443,12 +5433,7 @@ func containsDesktopString(values []string, value string) bool {
|
||||
if value == "" {
|
||||
return false
|
||||
}
|
||||
for _, item := range uniqueStrings(values) {
|
||||
if item == value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return slices.Contains(uniqueStrings(values), value)
|
||||
}
|
||||
|
||||
func pinnedTopicIDs(topicIDs []string, pinned []string) []string {
|
||||
@@ -7484,7 +7469,7 @@ func (a *App) emitProjectTreeChangedEvent() {
|
||||
a.emitRuntimeEvent("project-tree:changed")
|
||||
}
|
||||
|
||||
func (a *App) emitRuntimeEvent(name string, payload ...interface{}) {
|
||||
func (a *App) emitRuntimeEvent(name string, payload ...any) {
|
||||
if a == nil || a.ctx == nil {
|
||||
return
|
||||
}
|
||||
@@ -7875,7 +7860,7 @@ func (a *App) ListProjectTree() []ProjectNode {
|
||||
continue
|
||||
}
|
||||
pendingLoads++
|
||||
dir := dir // capture
|
||||
// capture
|
||||
cacheToken := projectSessionCache.versionToken(dir)
|
||||
go func() {
|
||||
result := sessionDirLoadResult{dir: dir}
|
||||
@@ -8078,17 +8063,14 @@ func (a *App) ListProjectTree() []ProjectNode {
|
||||
projectTopicResults := make([]projectTopics, len(f.Projects))
|
||||
var topicLoadWg sync.WaitGroup
|
||||
for i, p := range f.Projects {
|
||||
i, p := i, p
|
||||
topicLoadWg.Add(1)
|
||||
go func() {
|
||||
defer topicLoadWg.Done()
|
||||
topicLoadWg.Go(func() {
|
||||
projectTopicResults[i] = projectTopics{
|
||||
project: p,
|
||||
titles: loadTopicTitles(p.Root),
|
||||
sources: loadTopicTitleSources(p.Root),
|
||||
createdAts: loadTopicCreatedAts(p.Root),
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
||||
topicLoadWg.Wait()
|
||||
for _, loaded := range projectTopicResults {
|
||||
|
||||
@@ -57,9 +57,9 @@ func TestDisplayTurnBufferStreamingAllocationsStayNearLinear(t *testing.T) {
|
||||
chunk := strings.Repeat("x", chunkSize)
|
||||
result := testing.Benchmark(func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
var buffer displayTurnBuffer
|
||||
for part := 0; part < chunks; part++ {
|
||||
for range chunks {
|
||||
recordHistoryDisplayEvent(&buffer, event.Event{Kind: event.Text, Text: chunk})
|
||||
}
|
||||
messages := buffer.materialize()
|
||||
|
||||
@@ -33,7 +33,7 @@ func testAppWithOrderedTabs(t *testing.T, active string, ids ...string) *App {
|
||||
}
|
||||
|
||||
func installNoopRuntimeEvents(app *App, sinks ...*tabEventSink) {
|
||||
emit := func(context.Context, string, ...interface{}) {}
|
||||
emit := func(context.Context, string, ...any) {}
|
||||
if app != nil {
|
||||
app.runtimeEvents.emit = emit
|
||||
}
|
||||
@@ -664,18 +664,16 @@ func TestListTabsRepairsStaleOrderWithoutRacing(t *testing.T) {
|
||||
if testing.Short() {
|
||||
iterations = 5
|
||||
}
|
||||
for i := 0; i < 8; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for range 8 {
|
||||
wg.Go(func() {
|
||||
<-start
|
||||
for j := 0; j < iterations; j++ {
|
||||
for range iterations {
|
||||
if got := strings.Join(tabIDs(app.ListTabs()), ","); got != "a,b,c" {
|
||||
errs <- got
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
||||
close(start)
|
||||
wg.Wait()
|
||||
|
||||
@@ -224,10 +224,10 @@ func TestProjectTreeShowsBackgroundJobStatus(t *testing.T) {
|
||||
func TestBackgroundJobNoticeForcesProjectTreeRefresh(t *testing.T) {
|
||||
isolateDesktopUserDirs(t)
|
||||
|
||||
var refreshes int32
|
||||
var refreshes atomic.Int32
|
||||
app := NewApp()
|
||||
app.projectTreeChangedHook = func() {
|
||||
atomic.AddInt32(&refreshes, 1)
|
||||
refreshes.Add(1)
|
||||
}
|
||||
app.tabs["job"] = &WorkspaceTab{
|
||||
ID: "job",
|
||||
@@ -239,7 +239,7 @@ func TestBackgroundJobNoticeForcesProjectTreeRefresh(t *testing.T) {
|
||||
sink := &tabEventSink{tabID: "job", app: app}
|
||||
|
||||
sink.Emit(event.Event{Kind: event.Notice, Level: event.LevelInfo, Text: "background bash finished: bash-1"})
|
||||
if got := atomic.LoadInt32(&refreshes); got != 1 {
|
||||
if got := refreshes.Load(); got != 1 {
|
||||
t.Fatalf("project-tree refreshes = %d, want 1 for background job finish notice", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ func TestTabEventSinkClearContextStopsEmission(t *testing.T) {
|
||||
var mu sync.Mutex
|
||||
var emitted int
|
||||
s := &tabEventSink{tabID: "t"}
|
||||
s.runtimeEvents.emit = func(context.Context, string, ...interface{}) {
|
||||
s.runtimeEvents.emit = func(context.Context, string, ...any) {
|
||||
mu.Lock()
|
||||
emitted++
|
||||
mu.Unlock()
|
||||
|
||||
@@ -3971,18 +3971,16 @@ func TestLegacyMigrationConcurrentRunsHaveNoLostUpdates(t *testing.T) {
|
||||
}
|
||||
const n = 8
|
||||
want := make(map[string]bool, n)
|
||||
for i := 0; i < n; i++ {
|
||||
for i := range n {
|
||||
p := writeLegacySession(t, dir, fmt.Sprintf("legacy-%d.jsonl", i), "hi", time.Now())
|
||||
want[legacySessionTopicID(p)] = true
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < n; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for range n {
|
||||
wg.Go(func() {
|
||||
migrateLegacySessionsIntoGlobalTopics(dir)
|
||||
}()
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
@@ -4148,17 +4146,15 @@ func TestEnsureTopicIndexedConcurrentRunsHaveNoLostProjectUpdates(t *testing.T)
|
||||
const n = 12
|
||||
start := make(chan struct{})
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < n; i++ {
|
||||
i := i
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := range n {
|
||||
|
||||
wg.Go(func() {
|
||||
<-start
|
||||
topicID := fmt.Sprintf("topic_recovered_%02d", i)
|
||||
if err := ensureTopicIndexed("project", projectRoot, topicID, fmt.Sprintf("Recovered %02d", i), topicTitleSourceManual); err != nil {
|
||||
t.Errorf("ensure topic indexed: %v", err)
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
||||
close(start)
|
||||
wg.Wait()
|
||||
@@ -4171,7 +4167,7 @@ func TestEnsureTopicIndexedConcurrentRunsHaveNoLostProjectUpdates(t *testing.T)
|
||||
for _, child := range nodes[0].Children {
|
||||
got[child.TopicID] = true
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
for i := range n {
|
||||
topicID := fmt.Sprintf("topic_recovered_%02d", i)
|
||||
if !got[topicID] {
|
||||
t.Fatalf("concurrent topic index recovery lost %q; children=%#v", topicID, nodes[0].Children)
|
||||
|
||||
@@ -43,12 +43,10 @@ func TestTaskControlConcurrentInitializationReturnsOneService(t *testing.T) {
|
||||
const callers = 16
|
||||
services := make(chan *taskmonitor.ControlService, callers)
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < callers; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for range callers {
|
||||
wg.Go(func() {
|
||||
services <- app.taskControl()
|
||||
}()
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
close(services)
|
||||
|
||||
@@ -23,7 +23,7 @@ func robustTempDir(t *testing.T) string {
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
var rmErr error
|
||||
for i := 0; i < 100; i++ {
|
||||
for range 100 {
|
||||
if rmErr = os.RemoveAll(dir); rmErr == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -291,13 +291,13 @@ func TestTerminalManagerCountsConcurrentStartsTowardLimit(t *testing.T) {
|
||||
|
||||
type result struct{ err error }
|
||||
results := make(chan result, maxTerminalsPerWorkspace+1)
|
||||
for i := 0; i < maxTerminalsPerWorkspace+1; i++ {
|
||||
for range maxTerminalsPerWorkspace + 1 {
|
||||
go func() {
|
||||
_, err := manager.create("tab", "workspace", ".", terminalCommand{path: "shell", label: "shell"})
|
||||
results <- result{err: err}
|
||||
}()
|
||||
}
|
||||
for i := 0; i < maxTerminalsPerWorkspace; i++ {
|
||||
for range maxTerminalsPerWorkspace {
|
||||
select {
|
||||
case <-entered:
|
||||
case <-time.After(time.Second):
|
||||
@@ -307,7 +307,7 @@ func TestTerminalManagerCountsConcurrentStartsTowardLimit(t *testing.T) {
|
||||
close(release)
|
||||
|
||||
succeeded, limited := 0, 0
|
||||
for i := 0; i < maxTerminalsPerWorkspace+1; i++ {
|
||||
for range maxTerminalsPerWorkspace + 1 {
|
||||
result := <-results
|
||||
switch {
|
||||
case result.err == nil:
|
||||
|
||||
@@ -131,12 +131,12 @@ func decodeDataURLImage(dataURL string) (filename string, data []byte, err error
|
||||
}
|
||||
// data:[<mediatype>][;base64],<data>
|
||||
rest := dataURL[len(prefix):]
|
||||
comma := strings.IndexByte(rest, ',')
|
||||
if comma < 0 {
|
||||
before, after, ok := strings.Cut(rest, ",")
|
||||
if !ok {
|
||||
return "", nil, fmt.Errorf("invalid data URL")
|
||||
}
|
||||
meta := rest[:comma]
|
||||
payload := rest[comma+1:]
|
||||
meta := before
|
||||
payload := after
|
||||
parts := strings.Split(meta, ";")
|
||||
mime := strings.TrimSpace(parts[0])
|
||||
base64Enc := false
|
||||
|
||||
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"maps"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -84,7 +85,7 @@ type ThemePackManifest struct {
|
||||
Recipes ThemePackRecipes `json:"recipes"`
|
||||
Background *ThemePackBackground `json:"background,omitempty"`
|
||||
TaskBackground *ThemePackSceneBackground `json:"taskBackground,omitempty"`
|
||||
Extra map[string]interface{} `json:"-"` // rejected on parse when present as unknown top-level
|
||||
Extra map[string]any `json:"-"` // rejected on parse when present as unknown top-level
|
||||
}
|
||||
|
||||
// ThemePackTokens holds optional light/dark semantic color overrides.
|
||||
@@ -603,9 +604,7 @@ func copyStringMap(in map[string]string) map[string]string {
|
||||
return nil
|
||||
}
|
||||
out := make(map[string]string, len(in))
|
||||
for k, v := range in {
|
||||
out[k] = v
|
||||
}
|
||||
maps.Copy(out, in)
|
||||
return out
|
||||
}
|
||||
|
||||
|
||||
@@ -631,8 +631,8 @@ func TestManifestJSONRoundTrip(t *testing.T) {
|
||||
func writeTestPNG(t *testing.T, path string, w, h int) string {
|
||||
t.Helper()
|
||||
img := image.NewRGBA(image.Rect(0, 0, w, h))
|
||||
for y := 0; y < h; y++ {
|
||||
for x := 0; x < w; x++ {
|
||||
for y := range h {
|
||||
for x := range w {
|
||||
img.Set(x, y, color.RGBA{R: uint8(x), G: uint8(y), B: 40, A: 255})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,8 +45,8 @@ func testPluginThemeManifest(id, name string) *ThemePackManifest {
|
||||
func testPNGBytes(t *testing.T) []byte {
|
||||
t.Helper()
|
||||
img := image.NewRGBA(image.Rect(0, 0, 8, 8))
|
||||
for y := 0; y < 8; y++ {
|
||||
for x := 0; x < 8; x++ {
|
||||
for y := range 8 {
|
||||
for x := range 8 {
|
||||
img.Set(x, y, color.RGBA{R: uint8(x * 30), G: uint8(y * 30), B: 40, A: 255})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ func TestDesktopTrayLoopRunsOnLockedOSThread(t *testing.T) {
|
||||
done := make(chan struct{})
|
||||
runDesktopTrayLoop(func() {
|
||||
first := windows.GetCurrentThreadId()
|
||||
for i := 0; i < 100; i++ {
|
||||
for range 100 {
|
||||
runtime.Gosched()
|
||||
}
|
||||
if got := windows.GetCurrentThreadId(); got != first {
|
||||
|
||||
@@ -145,7 +145,7 @@ func helperErrorMessage(stdout, stderr []byte) string {
|
||||
}
|
||||
// Strip protocol lines from stderr diagnostics.
|
||||
var kept []string
|
||||
for _, line := range strings.Split(string(stderr), "\n") {
|
||||
for line := range strings.SplitSeq(string(stderr), "\n") {
|
||||
if _, ok := parseHelperPhaseLine(line); ok {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ func isDpkgOwnedReasonix(absPath string) bool {
|
||||
return false
|
||||
}
|
||||
// dpkg-query may return multiple lines for diversions; require an exact package hit.
|
||||
for _, raw := range strings.Split(line, "\n") {
|
||||
for raw := range strings.SplitSeq(line, "\n") {
|
||||
raw = strings.TrimSpace(raw)
|
||||
pkg, path, ok := strings.Cut(raw, ":")
|
||||
if !ok {
|
||||
|
||||
@@ -611,7 +611,7 @@ func completeMacHandoffHandshake(cfg macUpdateHandoffConfig) error {
|
||||
}
|
||||
|
||||
func retainMacHandoffNode(path, suffix string) (string, error) {
|
||||
for attempt := 0; attempt < 16; attempt++ {
|
||||
for attempt := range 16 {
|
||||
retained := fmt.Sprintf(
|
||||
"%s.%s-%d-%d",
|
||||
path,
|
||||
@@ -636,7 +636,7 @@ func cleanupOwnedMacUpdateDirectory(path string, owner os.FileInfo) error {
|
||||
if strings.TrimSpace(path) == "" || owner == nil || !owner.IsDir() {
|
||||
return fmt.Errorf("macOS update cleanup identity is incomplete")
|
||||
}
|
||||
for attempt := 0; attempt < 16; attempt++ {
|
||||
for attempt := range 16 {
|
||||
cleanup := fmt.Sprintf("%s.reasonix-cleanup-%d-%d", path, time.Now().UTC().UnixNano(), attempt)
|
||||
err := unix.RenameatxNp(unix.AT_FDCWD, path, unix.AT_FDCWD, cleanup, unix.RENAME_EXCL)
|
||||
if err != nil {
|
||||
|
||||
+18
-18
@@ -93,7 +93,7 @@ func TestValidateAssetInstallLayout(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUpdaterWailsMethodContracts(t *testing.T) {
|
||||
appType := reflect.TypeOf((*App)(nil))
|
||||
appType := reflect.TypeFor[*App]()
|
||||
tests := []struct {
|
||||
name string
|
||||
numIn int
|
||||
@@ -1256,9 +1256,9 @@ func fastRetry(t *testing.T) {
|
||||
func TestDownloadRecoversFromMidStreamReset(t *testing.T) {
|
||||
fastRetry(t)
|
||||
const body = "complete-installer-bytes"
|
||||
var calls int32
|
||||
var calls atomic.Int32
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
if atomic.AddInt32(&calls, 1) < int32(downloadAttempts) {
|
||||
if calls.Add(1) < int32(downloadAttempts) {
|
||||
// Mid-stream reset: promise 100 bytes, send a few, drop the socket —
|
||||
// the client's body read fails with unexpected EOF, exactly the CN-IPv6
|
||||
// "forcibly closed" case the retry exists for.
|
||||
@@ -1283,16 +1283,16 @@ func TestDownloadRecoversFromMidStreamReset(t *testing.T) {
|
||||
if string(data) != body {
|
||||
t.Fatalf("got %q, want %q", data, body)
|
||||
}
|
||||
if n := atomic.LoadInt32(&calls); n != int32(downloadAttempts) {
|
||||
if n := calls.Load(); n != int32(downloadAttempts) {
|
||||
t.Fatalf("made %d attempts, want %d", n, downloadAttempts)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDownloadGivesUpAfterCap(t *testing.T) {
|
||||
fastRetry(t)
|
||||
var calls int32
|
||||
var calls atomic.Int32
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
atomic.AddInt32(&calls, 1)
|
||||
calls.Add(1)
|
||||
conn, _, err := w.(http.Hijacker).Hijack()
|
||||
if err != nil {
|
||||
t.Errorf("hijack: %v", err)
|
||||
@@ -1305,7 +1305,7 @@ func TestDownloadGivesUpAfterCap(t *testing.T) {
|
||||
if _, err := download(context.Background(), srv.Client(), nil, srv.URL, 0, nil); err == nil {
|
||||
t.Fatal("download should fail after exhausting retries")
|
||||
}
|
||||
if n := atomic.LoadInt32(&calls); n != int32(downloadAttempts) {
|
||||
if n := calls.Load(); n != int32(downloadAttempts) {
|
||||
t.Fatalf("made %d attempts, want %d", n, downloadAttempts)
|
||||
}
|
||||
}
|
||||
@@ -1329,10 +1329,10 @@ func TestDownloadResumesWithRange(t *testing.T) {
|
||||
fastRetry(t)
|
||||
full := bytes.Repeat([]byte("0123456789"), 50) // 500 bytes
|
||||
const cut = 200
|
||||
var calls int32
|
||||
var calls atomic.Int32
|
||||
rangeCh := make(chan string, 4)
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if atomic.AddInt32(&calls, 1) == 1 {
|
||||
if calls.Add(1) == 1 {
|
||||
// First attempt: promise the whole file, send a prefix, drop the socket.
|
||||
conn, bw, err := w.(http.Hijacker).Hijack()
|
||||
if err != nil {
|
||||
@@ -1379,9 +1379,9 @@ func TestDownloadFallsBackToSecondClient(t *testing.T) {
|
||||
primary := &http.Client{Transport: rtFunc(func(*http.Request) (*http.Response, error) {
|
||||
return nil, errors.New("connection reset (ipv6)")
|
||||
})}
|
||||
var fbCalls int32
|
||||
var fbCalls atomic.Int32
|
||||
fallback := &http.Client{Transport: rtFunc(func(*http.Request) (*http.Response, error) {
|
||||
atomic.AddInt32(&fbCalls, 1)
|
||||
fbCalls.Add(1)
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: io.NopCloser(strings.NewReader(body)),
|
||||
@@ -1397,7 +1397,7 @@ func TestDownloadFallsBackToSecondClient(t *testing.T) {
|
||||
if string(data) != body {
|
||||
t.Fatalf("got %q, want %q", data, body)
|
||||
}
|
||||
if atomic.LoadInt32(&fbCalls) == 0 {
|
||||
if fbCalls.Load() == 0 {
|
||||
t.Fatal("fallback client was never used after the primary failed")
|
||||
}
|
||||
}
|
||||
@@ -1480,9 +1480,9 @@ func TestFetchBytesFallbackEscapesStalledPrimary(t *testing.T) {
|
||||
|
||||
func TestFetchBytesDoesNotRetryPermanentHTTPStatus(t *testing.T) {
|
||||
fastRetry(t)
|
||||
var calls int32
|
||||
var calls atomic.Int32
|
||||
client := &http.Client{Transport: rtFunc(func(*http.Request) (*http.Response, error) {
|
||||
atomic.AddInt32(&calls, 1)
|
||||
calls.Add(1)
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusForbidden,
|
||||
Status: "403 Forbidden",
|
||||
@@ -1494,7 +1494,7 @@ func TestFetchBytesDoesNotRetryPermanentHTTPStatus(t *testing.T) {
|
||||
if _, err := fetchBytes(context.Background(), client, "https://example.invalid/latest.json"); err == nil {
|
||||
t.Fatal("fetchBytes should return a permanent HTTP error")
|
||||
}
|
||||
if got := atomic.LoadInt32(&calls); got != 1 {
|
||||
if got := calls.Load(); got != 1 {
|
||||
t.Fatalf("permanent HTTP error made %d requests, want 1", got)
|
||||
}
|
||||
}
|
||||
@@ -1502,9 +1502,9 @@ func TestFetchBytesDoesNotRetryPermanentHTTPStatus(t *testing.T) {
|
||||
func TestFetchBytesRejectsOversizeResponsesWithoutRetry(t *testing.T) {
|
||||
fastRetry(t)
|
||||
t.Run("declared content length", func(t *testing.T) {
|
||||
var calls int32
|
||||
var calls atomic.Int32
|
||||
client := &http.Client{Transport: rtFunc(func(*http.Request) (*http.Response, error) {
|
||||
atomic.AddInt32(&calls, 1)
|
||||
calls.Add(1)
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Status: "200 OK",
|
||||
@@ -1523,7 +1523,7 @@ func TestFetchBytesRejectsOversizeResponsesWithoutRetry(t *testing.T) {
|
||||
); !errors.Is(err, errUpdateResponseTooLarge) {
|
||||
t.Fatalf("declared oversize error = %v, want errUpdateResponseTooLarge", err)
|
||||
}
|
||||
if got := atomic.LoadInt32(&calls); got != 1 {
|
||||
if got := calls.Load(); got != 1 {
|
||||
t.Fatalf("declared oversize response made %d requests, want 1", got)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -187,7 +187,7 @@ func TestSaveWindowStateConcurrentReports(t *testing.T) {
|
||||
|
||||
app := NewApp()
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 32; i++ {
|
||||
for i := range 32 {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
|
||||
@@ -351,7 +351,7 @@ func workspaceCurrentText(path string) (string, bool, bool, error) {
|
||||
|
||||
func tallyUnifiedPatch(patch string) (added, removed int) {
|
||||
inHunk := false
|
||||
for _, line := range strings.Split(patch, "\n") {
|
||||
for line := range strings.SplitSeq(patch, "\n") {
|
||||
switch {
|
||||
case strings.HasPrefix(line, "@@"):
|
||||
inHunk = true
|
||||
|
||||
@@ -568,7 +568,7 @@ func BenchmarkDesktopSessionDir(b *testing.B) {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
if desktopSessionDir(root) == "" {
|
||||
b.Fatal("empty session dir")
|
||||
}
|
||||
@@ -950,7 +950,7 @@ func TestMediaTokenMaxEviction(t *testing.T) {
|
||||
|
||||
// Fill beyond max to trigger eviction of oldest.
|
||||
var oldestToken string
|
||||
for i := 0; i < mediaTokenMax+1; i++ {
|
||||
for i := range mediaTokenMax + 1 {
|
||||
tok := store.create(dir+"/test.png", "test.png", "image/png", "image", 4, time.Time{})
|
||||
if i == 0 {
|
||||
oldestToken = tok
|
||||
|
||||
@@ -116,7 +116,7 @@ func TestNewSessionID(t *testing.T) {
|
||||
|
||||
func TestNewSessionIDUnique(t *testing.T) {
|
||||
seen := map[string]bool{}
|
||||
for i := 0; i < 100; i++ {
|
||||
for range 100 {
|
||||
id, err := newSessionID()
|
||||
if err != nil {
|
||||
t.Fatalf("newSessionID: %v", err)
|
||||
|
||||
@@ -208,18 +208,14 @@ func (c *Conn) dispatch(ctx context.Context, line []byte) {
|
||||
hasID := len(in.ID) > 0
|
||||
switch {
|
||||
case in.Method != "" && hasID:
|
||||
c.wg.Add(1)
|
||||
go func() {
|
||||
defer c.wg.Done()
|
||||
c.wg.Go(func() {
|
||||
c.serveRequest(ctx, in.ID, in.Method, in.Params)
|
||||
}()
|
||||
})
|
||||
case in.Method != "" && !hasID:
|
||||
if h := c.notH[in.Method]; h != nil {
|
||||
c.wg.Add(1)
|
||||
go func() {
|
||||
defer c.wg.Done()
|
||||
c.wg.Go(func() {
|
||||
h(ctx, in.Params)
|
||||
}()
|
||||
})
|
||||
}
|
||||
case in.Method == "" && hasID:
|
||||
c.resolve(in)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"maps"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
@@ -2955,9 +2956,7 @@ func mapString(in map[string]string) map[string]string {
|
||||
return nil
|
||||
}
|
||||
out := make(map[string]string, len(in))
|
||||
for k, v := range in {
|
||||
out[k] = v
|
||||
}
|
||||
maps.Copy(out, in)
|
||||
return out
|
||||
}
|
||||
|
||||
|
||||
@@ -411,7 +411,7 @@ func TestACPCtrlReadPathsDoNotRaceWithRebuild(t *testing.T) {
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
for i := 0; i < rebuilds; i++ {
|
||||
for i := range rebuilds {
|
||||
if err := svc.rebuildSession(context.Background(), sess, SessionConfigState{Model: models[i%len(models)]}, []sessionConfigDelta{{axis: "model", model: models[i%len(models)]}}); err != nil {
|
||||
t.Errorf("rebuildSession %d: %v", i, err)
|
||||
return
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"math"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -710,9 +711,9 @@ func finalAssistantSummary(ctrl acpController) string {
|
||||
return ""
|
||||
}
|
||||
history := ctrl.History()
|
||||
for i := len(history) - 1; i >= 0; i-- {
|
||||
if history[i].Role == provider.RoleAssistant && strings.TrimSpace(history[i].Content) != "" {
|
||||
return history[i].Content
|
||||
for _, v := range slices.Backward(history) {
|
||||
if v.Role == provider.RoleAssistant && strings.TrimSpace(v.Content) != "" {
|
||||
return v.Content
|
||||
}
|
||||
}
|
||||
return ""
|
||||
|
||||
@@ -1187,10 +1187,7 @@ func New(prov provider.Provider, tools *tool.Registry, session *Session, opts Op
|
||||
} else {
|
||||
maxSubagentDepth = NormalizeMaxSubagentDepth(maxSubagentDepth)
|
||||
}
|
||||
subagentDepth := opts.SubagentDepth
|
||||
if subagentDepth < 0 {
|
||||
subagentDepth = 0
|
||||
}
|
||||
subagentDepth := max(opts.SubagentDepth, 0)
|
||||
reasoningByteLimit := opts.ReasoningByteLimit
|
||||
if reasoningByteLimit == 0 {
|
||||
reasoningByteLimit = defaultReasoningByteLimit
|
||||
@@ -3297,7 +3294,7 @@ launch:
|
||||
<-sem
|
||||
break
|
||||
}
|
||||
i := i
|
||||
|
||||
wg.Add(1)
|
||||
ranUntil = i + 1
|
||||
go func() {
|
||||
@@ -4016,8 +4013,8 @@ func (a *Agent) toolReadOnly(name string) bool {
|
||||
// firstLine returns s up to its first newline — a one-line failure summary for
|
||||
// the display Err, while the full error stays in the model-facing output.
|
||||
func firstLine(s string) string {
|
||||
if i := strings.IndexByte(s, '\n'); i >= 0 {
|
||||
return s[:i]
|
||||
if before, _, ok := strings.Cut(s, "\n"); ok {
|
||||
return before
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -302,10 +302,7 @@ func MarkSessionInFlightTurn(sessionPath string, startMessageIndex int, preserve
|
||||
// what lets crash recovery relocate the turn after an in-turn compaction has
|
||||
// rewritten its original message index.
|
||||
func SetSessionInFlightTurn(sessionPath string, marker InFlightTurnMeta) error {
|
||||
startMessageIndex := marker.StartMessageIndex
|
||||
if startMessageIndex < 0 {
|
||||
startMessageIndex = 0
|
||||
}
|
||||
startMessageIndex := max(marker.StartMessageIndex, 0)
|
||||
// The sidecar is read-modify-write; the per-path save lock keeps concurrent
|
||||
// writers (autosave's UpdateSessionMeta, listing backfill) from dropping
|
||||
// each other's fields.
|
||||
|
||||
@@ -26,7 +26,7 @@ type PrefixShape struct {
|
||||
// every call site, while still assigning to event.Event.CacheDiagnostics.
|
||||
type CacheDiagnostics = event.CacheDiagnostics
|
||||
|
||||
func shortHash(v interface{}) string {
|
||||
func shortHash(v any) string {
|
||||
b, _ := json.Marshal(v)
|
||||
h := sha256.Sum256(b)
|
||||
return fmt.Sprintf("%x", h[:8])
|
||||
@@ -39,7 +39,7 @@ func CaptureShape(systemPrompt string, schemas []provider.ToolSchema, rewriteVer
|
||||
return PrefixShape{
|
||||
SystemHash: shortHash(systemPrompt),
|
||||
ToolsHash: shortHash(string(toolsJSON)),
|
||||
PrefixHash: shortHash(map[string]interface{}{
|
||||
PrefixHash: shortHash(map[string]any{
|
||||
"system": systemPrompt,
|
||||
"tools": string(toolsJSON),
|
||||
}),
|
||||
|
||||
@@ -199,7 +199,7 @@ func TestCacheHitClimbsWithoutCompaction(t *testing.T) {
|
||||
a, sink := newAgent(t, srv.URL, mock.tools(), 0 /*no compaction*/, 0)
|
||||
|
||||
const turns = 14
|
||||
for i := 0; i < turns; i++ {
|
||||
for i := range turns {
|
||||
userMsg := "Turn " + fmt.Sprint(i) + ": " + strings.Repeat("please consider this requirement. ", 6)
|
||||
if err := a.Run(context.Background(), userMsg); err != nil {
|
||||
t.Fatalf("Run %d: %v", i, err)
|
||||
@@ -286,7 +286,7 @@ func TestReasoningRoundTripCost(t *testing.T) {
|
||||
defer srv.Close()
|
||||
a, sink := newAgent(t, srv.URL, mock.tools(), 0, 0)
|
||||
const turns = 12
|
||||
for i := 0; i < turns; i++ {
|
||||
for i := range turns {
|
||||
if err := a.Run(context.Background(), strings.Repeat("please consider this requirement. ", 6)); err != nil {
|
||||
t.Fatalf("Run %d: %v", i, err)
|
||||
}
|
||||
@@ -329,7 +329,7 @@ func TestSessionAggregateCacheRate(t *testing.T) {
|
||||
|
||||
a, sink := newAgent(t, srv.URL, mock.tools(), 0, 0)
|
||||
const turns = 8
|
||||
for i := 0; i < turns; i++ {
|
||||
for i := range turns {
|
||||
if err := a.Run(context.Background(), strings.Repeat("please consider this requirement. ", 6)); err != nil {
|
||||
t.Fatalf("Run %d: %v", i, err)
|
||||
}
|
||||
@@ -409,7 +409,7 @@ func TestReleaseCacheHitGuard(t *testing.T) {
|
||||
name: "mixed-message-sizes",
|
||||
run: func(t *testing.T) []int {
|
||||
msgs := make([]string, 0, 20)
|
||||
for i := 0; i < 20; i++ {
|
||||
for i := range 20 {
|
||||
repeats := 4
|
||||
if i%3 == 2 {
|
||||
repeats = 20
|
||||
@@ -482,7 +482,7 @@ func cacheCurve(t *testing.T, mock *mockDeepSeek, turns int) []int {
|
||||
|
||||
func repeatedMessages(turns, repeats int) []string {
|
||||
msgs := make([]string, 0, turns)
|
||||
for i := 0; i < turns; i++ {
|
||||
for i := range turns {
|
||||
msgs = append(msgs, "Turn "+fmt.Sprint(i)+": "+strings.Repeat("please consider this requirement. ", repeats))
|
||||
}
|
||||
return msgs
|
||||
|
||||
@@ -130,7 +130,7 @@ func (closedStreamProvider) Stream(context.Context, provider.Request) (<-chan pr
|
||||
}
|
||||
|
||||
func TestCanceledContextClosedProviderStreamReturnsCancel(t *testing.T) {
|
||||
for i := 0; i < 100; i++ {
|
||||
for i := range 100 {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
@@ -574,7 +574,7 @@ func TestCancelInsideLargeParallelBatchStopsSchedulingNewTools(t *testing.T) {
|
||||
reg.Add(trackingTool{name: "readonly_tracking", readOnly: true})
|
||||
|
||||
var calls []provider.ToolCall
|
||||
for i := 0; i < 12; i++ {
|
||||
for i := range 12 {
|
||||
calls = append(calls, provider.ToolCall{
|
||||
ID: fmt.Sprintf("call-%02d", i),
|
||||
Name: "readonly_tracking",
|
||||
|
||||
@@ -109,7 +109,7 @@ func compactionsPerTurn(t *testing.T, windowTok int, blob, finalText string, tur
|
||||
})
|
||||
|
||||
perTurn = make([]int, turns)
|
||||
for i := 0; i < turns; i++ {
|
||||
for i := range turns {
|
||||
before := started
|
||||
if err := a.Run(context.Background(), fmt.Sprintf("turn %d: keep going, continue the work", i)); err != nil {
|
||||
t.Fatalf("Run %d: %v", i, err)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -521,17 +522,17 @@ func plannerPlanRequestsApproval(plan string) bool {
|
||||
}
|
||||
// Match per line so a nearby negation ("无需等待用户批准", "no need to wait
|
||||
// for approval") exempts only its own phrase, not the whole plan.
|
||||
for _, rawLine := range strings.Split(lower, "\n") {
|
||||
for rawLine := range strings.SplitSeq(lower, "\n") {
|
||||
line := strings.TrimSpace(rawLine)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
for _, phrase := range plannerApprovalPhrases {
|
||||
idx := strings.Index(line, phrase)
|
||||
if idx < 0 {
|
||||
before, _, ok := strings.Cut(line, phrase)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if approvalMentionNegated(line[:idx]) {
|
||||
if approvalMentionNegated(before) {
|
||||
continue
|
||||
}
|
||||
return true
|
||||
@@ -633,7 +634,7 @@ func parsePlannerAskBlock(plan string) (event.AskQuestion, bool) {
|
||||
block := plan[start+len(plannerAskStartMarker) : end]
|
||||
var question string
|
||||
var options []event.AskOption
|
||||
for _, raw := range strings.Split(block, "\n") {
|
||||
for raw := range strings.SplitSeq(block, "\n") {
|
||||
line := strings.TrimSpace(raw)
|
||||
if line == "" {
|
||||
continue
|
||||
@@ -671,8 +672,8 @@ func parsePlannerAskBlock(plan string) (event.AskQuestion, bool) {
|
||||
|
||||
func plannerQuestionPrompt(plan string) string {
|
||||
lines := strings.Split(plan, "\n")
|
||||
for i := len(lines) - 1; i >= 0; i-- {
|
||||
line := strings.TrimSpace(strings.Trim(lines[i], "-* \t"))
|
||||
for _, v := range slices.Backward(lines) {
|
||||
line := strings.TrimSpace(strings.Trim(v, "-* \t"))
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
@@ -780,8 +781,8 @@ func isNoOpPlan(plan string) bool {
|
||||
|
||||
func lastNonEmptyLine(s string) string {
|
||||
lines := strings.Split(s, "\n")
|
||||
for i := len(lines) - 1; i >= 0; i-- {
|
||||
if t := strings.TrimSpace(lines[i]); t != "" {
|
||||
for _, v := range slices.Backward(lines) {
|
||||
if t := strings.TrimSpace(v); t != "" {
|
||||
return t
|
||||
}
|
||||
}
|
||||
@@ -1076,11 +1077,11 @@ func HandoffTask(s string) string {
|
||||
return s
|
||||
}
|
||||
const header = "Original task:\n"
|
||||
i := strings.Index(trimmed, header)
|
||||
if i < 0 {
|
||||
_, after, ok := strings.Cut(trimmed, header)
|
||||
if !ok {
|
||||
return s
|
||||
}
|
||||
rest := trimmed[i+len(header):]
|
||||
rest := after
|
||||
if j := strings.Index(rest, "\n\nPlanner output:"); j >= 0 {
|
||||
rest = rest[:j]
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reasonix/internal/event"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -43,9 +44,9 @@ func (m *mockProvider) Stream(ctx context.Context, req provider.Request) (<-chan
|
||||
}
|
||||
|
||||
func lastUser(req provider.Request) string {
|
||||
for i := len(req.Messages) - 1; i >= 0; i-- {
|
||||
if req.Messages[i].Role == provider.RoleUser {
|
||||
return req.Messages[i].Content
|
||||
for _, v := range slices.Backward(req.Messages) {
|
||||
if v.Role == provider.RoleUser {
|
||||
return v.Content
|
||||
}
|
||||
}
|
||||
return ""
|
||||
@@ -1274,17 +1275,12 @@ func toolSchemaNames(schemas []provider.ToolSchema) []string {
|
||||
}
|
||||
|
||||
func contains(items []string, want string) bool {
|
||||
for _, item := range items {
|
||||
if item == want {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return slices.Contains(items, want)
|
||||
}
|
||||
|
||||
func BenchmarkPlannerToolRegistry(b *testing.B) {
|
||||
parentReg := tool.NewRegistry()
|
||||
for i := 0; i < 200; i++ {
|
||||
for i := range 200 {
|
||||
parentReg.Add(coordinatorTestTool{
|
||||
name: fmt.Sprintf("tool_%03d", i),
|
||||
readOnly: i%3 != 0,
|
||||
@@ -1294,7 +1290,7 @@ func BenchmarkPlannerToolRegistry(b *testing.B) {
|
||||
parentReg.Add(coordinatorTestTool{name: "write_file", readOnly: false})
|
||||
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
reg := PlannerToolRegistry(parentReg)
|
||||
if reg.Len() == 0 {
|
||||
b.Fatal("planner registry should retain read-only research tools")
|
||||
@@ -1727,7 +1723,7 @@ func TestCoordinatorHandoffSurvivesPlannerCompaction(t *testing.T) {
|
||||
// below) its pre-turn length, which is what strands a boundary based on
|
||||
// the pre-turn message count.
|
||||
filler := strings.Repeat("planner history filler. ", 150)
|
||||
for i := 0; i < 3; i++ {
|
||||
for range 3 {
|
||||
plannerSess.Add(provider.Message{Role: provider.RoleUser, Content: filler})
|
||||
plannerSess.Add(provider.Message{Role: provider.RoleAssistant, Content: filler})
|
||||
}
|
||||
@@ -1873,7 +1869,7 @@ func TestCoordinatorFailedTurnRollbackKeepsCompaction(t *testing.T) {
|
||||
executor := New(exec, tool.NewRegistry(), NewSession("exec-sys"), Options{}, event.Discard)
|
||||
plannerSess := NewSession("planner-sys")
|
||||
filler := strings.Repeat("planner history filler. ", 150)
|
||||
for i := 0; i < 3; i++ {
|
||||
for range 3 {
|
||||
plannerSess.Add(provider.Message{Role: provider.RoleUser, Content: filler})
|
||||
plannerSess.Add(provider.Message{Role: provider.RoleAssistant, Content: filler})
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ func BenchmarkHasVisibleFinalAnswer(b *testing.B) {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
var got bool
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
got = hasVisibleFinalAnswer(tc.text)
|
||||
}
|
||||
_ = got
|
||||
|
||||
@@ -323,9 +323,7 @@ func (f *FleetTool) runFleet(ctx context.Context, sink event.Sink, specs []Profi
|
||||
},
|
||||
})
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
wg.Go(func() {
|
||||
// Each fleet item runs as its own task-shaped execution so
|
||||
// transcripts, evidence, and scheduler claims stay independent.
|
||||
itemCtx := withCallContext(ctx, subID, subSinkFor(subID, sink), nil, false)
|
||||
@@ -350,7 +348,7 @@ func (f *FleetTool) runFleet(ctx context.Context, sink event.Sink, specs []Profi
|
||||
})
|
||||
}
|
||||
doneCh <- res
|
||||
}()
|
||||
})
|
||||
}
|
||||
|
||||
// Mark only genuinely unstarted items skipped. Started items always publish
|
||||
|
||||
@@ -356,7 +356,7 @@ func TestFleetParallelDisjointWriters(t *testing.T) {
|
||||
f := NewFleetTool(task)
|
||||
|
||||
tasks := make([]map[string]any, 0, 4)
|
||||
for i := 0; i < 4; i++ {
|
||||
for i := range 4 {
|
||||
path := filepath.Join("docs", "f"+string(rune('0'+i))+".md")
|
||||
tasks = append(tasks, map[string]any{
|
||||
"prompt": "handle " + path,
|
||||
|
||||
@@ -56,11 +56,11 @@ func StripAutoResearchEvidenceBlocks(text string) string {
|
||||
}
|
||||
b.WriteString(rest[:start])
|
||||
afterOpen := rest[start+len(autoResearchEvidenceOpen):]
|
||||
end := strings.Index(afterOpen, autoResearchEvidenceClose)
|
||||
if end < 0 {
|
||||
_, after, ok := strings.Cut(afterOpen, autoResearchEvidenceClose)
|
||||
if !ok {
|
||||
return strings.TrimSpace(b.String())
|
||||
}
|
||||
rest = afterOpen[end+len(autoResearchEvidenceClose):]
|
||||
rest = after
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package agent
|
||||
import (
|
||||
"fmt"
|
||||
"html"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"reasonix/internal/provider"
|
||||
@@ -24,8 +25,8 @@ func (a *Agent) pendingInterruptedRecovery() *provider.InterruptedTurnRecovery {
|
||||
return nil
|
||||
}
|
||||
msgs := a.session.Snapshot()
|
||||
for i := len(msgs) - 1; i >= 0; i-- {
|
||||
m := msgs[i]
|
||||
for _, v := range slices.Backward(msgs) {
|
||||
m := v
|
||||
if m.LocalOnly && m.InterruptedTurn != nil && m.InterruptedTurn.Pending {
|
||||
copy := *m.InterruptedTurn
|
||||
copy.CompletedTools = append([]provider.InterruptedToolSummary(nil), copy.CompletedTools...)
|
||||
|
||||
@@ -417,7 +417,7 @@ func TestRunStreamRetryRequestCountIsLinearNotTriangular(t *testing.T) {
|
||||
func TestRunExhaustedStreamRetriesPersistPendingLocalOnly(t *testing.T) {
|
||||
interrupted := &provider.StreamInterruptedError{Err: errors.New("eof"), Reason: provider.StreamInterruptPrematureEOF}
|
||||
turns := make([]testutil.Turn, 0, maxSamplingAttempts)
|
||||
for i := 0; i < maxSamplingAttempts; i++ {
|
||||
for range maxSamplingAttempts {
|
||||
turns = append(turns, testutil.Turn{Text: "half", ChunkError: interrupted})
|
||||
}
|
||||
mp := testutil.NewMock("m", turns...)
|
||||
@@ -1026,7 +1026,7 @@ func TestHealthyToolCallReasoningRetriesTransientStateWriteFailure(t *testing.T)
|
||||
t.Fatal(err)
|
||||
}
|
||||
permissionsRestored = true
|
||||
for healthy := 0; healthy < missingReasoningHealthyResolveStreak-1; healthy++ {
|
||||
for healthy := range missingReasoningHealthyResolveStreak - 1 {
|
||||
if missing, retry := a.observeMissingToolCallReasoning(calls, "healthy reasoning"); missing || retry {
|
||||
t.Fatalf("healthy recovery observation %d = missing:%v retry:%v, want false/false", healthy+1, missing, retry)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -177,9 +178,7 @@ func (p *ParallelTasksTool) Execute(ctx context.Context, args json.RawMessage) (
|
||||
},
|
||||
})
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
wg.Go(func() {
|
||||
modelRef, effortRef := p.taskTool.effectiveProfile(t.Model, t.Effort)
|
||||
itemCtx := withCallContext(ctx, subID, subSinkFor(subID, sink), nil, PlanModeFromContext(ctx))
|
||||
// Route through TaskTool's unified runner so persisted parent sessions
|
||||
@@ -221,7 +220,7 @@ func (p *ParallelTasksTool) Execute(ctx context.Context, args json.RawMessage) (
|
||||
})
|
||||
answer, ref := splitSubagentRunResult(output)
|
||||
doneCh <- subResult{index: idx, output: answer, ref: ref}
|
||||
}()
|
||||
})
|
||||
}
|
||||
|
||||
markCancelled := func(err error) {
|
||||
@@ -300,10 +299,8 @@ func parallelGroupTerminalPhase(ctx context.Context, err error, statuses []paral
|
||||
if ctx.Err() != nil {
|
||||
return subagentPhaseCancelled
|
||||
}
|
||||
for _, st := range statuses {
|
||||
if st == parallelTaskFailed {
|
||||
return subagentPhaseFailed
|
||||
}
|
||||
if slices.Contains(statuses, parallelTaskFailed) {
|
||||
return subagentPhaseFailed
|
||||
}
|
||||
if err != nil {
|
||||
return subagentPhaseFailed
|
||||
|
||||
@@ -377,9 +377,9 @@ func (parallelLongResultProvider) Stream(_ context.Context, req provider.Request
|
||||
|
||||
func subagentRefsFromText(text string) []string {
|
||||
var refs []string
|
||||
for _, line := range strings.Split(text, "\n") {
|
||||
if strings.HasPrefix(line, "Subagent reference: ") {
|
||||
refs = append(refs, strings.TrimSpace(strings.TrimPrefix(line, "Subagent reference: ")))
|
||||
for line := range strings.SplitSeq(text, "\n") {
|
||||
if after, ok := strings.CutPrefix(line, "Subagent reference: "); ok {
|
||||
refs = append(refs, strings.TrimSpace(after))
|
||||
}
|
||||
}
|
||||
return refs
|
||||
|
||||
@@ -98,7 +98,7 @@ func TestPruneNeverRewritesLocalInterruptedDisplay(t *testing.T) {
|
||||
|
||||
func TestSnipStaleToolResults(t *testing.T) {
|
||||
var lines []string
|
||||
for i := 0; i < 1000; i++ {
|
||||
for range 1000 {
|
||||
lines = append(lines, "line")
|
||||
}
|
||||
big := strings.Join(lines, "\n")
|
||||
@@ -376,7 +376,7 @@ func TestSnipUsesRegisteredToolHint(t *testing.T) {
|
||||
// 600 numbered lines; a SnipHinter keeping head=3, tail=2 must yield exactly
|
||||
// those boundary lines, which no default geometry would produce.
|
||||
var lines []string
|
||||
for i := 0; i < 600; i++ {
|
||||
for i := range 600 {
|
||||
lines = append(lines, fmt.Sprintf("L%d", i))
|
||||
}
|
||||
content := strings.Join(lines, "\n")
|
||||
@@ -403,7 +403,7 @@ func TestSnipUsesRegisteredToolHint(t *testing.T) {
|
||||
|
||||
func TestSnipFallsBackByReadOnlyTier(t *testing.T) {
|
||||
var lines []string
|
||||
for i := 0; i < 600; i++ {
|
||||
for i := range 600 {
|
||||
lines = append(lines, fmt.Sprintf("L%d", i))
|
||||
}
|
||||
content := strings.Join(lines, "\n")
|
||||
|
||||
@@ -242,11 +242,11 @@ func hasOpenTag(s, tag string) bool {
|
||||
|
||||
func trimLeadingTransientBlock(content, tag string) (string, bool) {
|
||||
closeTag := "</" + tag + ">"
|
||||
i := strings.Index(content, closeTag)
|
||||
if i < 0 {
|
||||
_, after, ok := strings.Cut(content, closeTag)
|
||||
if !ok {
|
||||
return content, false
|
||||
}
|
||||
return strings.TrimLeft(content[i+len(closeTag):], " \t\r\n"), true
|
||||
return strings.TrimLeft(after, " \t\r\n"), true
|
||||
}
|
||||
|
||||
// WithResponseLanguagePreference carries the runtime final-answer language
|
||||
|
||||
@@ -214,13 +214,7 @@ func normalizeMissingReasoningIncident(incident missingReasoningIncident, now ti
|
||||
}
|
||||
|
||||
func (incident missingReasoningIncident) lastEventUnixNano() int64 {
|
||||
lastEvent := incident.LastMissingUnixNano
|
||||
if incident.LastResolvedAtUnixNano > lastEvent {
|
||||
lastEvent = incident.LastResolvedAtUnixNano
|
||||
}
|
||||
if incident.LastHealthyAtUnixNano > lastEvent {
|
||||
lastEvent = incident.LastHealthyAtUnixNano
|
||||
}
|
||||
lastEvent := max(incident.LastHealthyAtUnixNano, max(incident.LastResolvedAtUnixNano, incident.LastMissingUnixNano))
|
||||
return lastEvent
|
||||
}
|
||||
|
||||
|
||||
@@ -406,14 +406,12 @@ func TestMissingReasoningWarnStateConcurrentSameIncidentWarnsOnce(t *testing.T)
|
||||
var warned atomic.Int64
|
||||
var wg sync.WaitGroup
|
||||
for range 8 {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
wg.Go(func() {
|
||||
<-start
|
||||
if newMissingReasoningWarnState(dir).claimAt(fingerprint, now) {
|
||||
warned.Add(1)
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
||||
close(start)
|
||||
wg.Wait()
|
||||
@@ -490,14 +488,12 @@ func TestMissingReasoningWarnStateConcurrentClaimsKeepEveryConfiguration(t *test
|
||||
var wg sync.WaitGroup
|
||||
for _, label := range labels {
|
||||
fingerprint := warningFingerprint(label)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
wg.Go(func() {
|
||||
<-start
|
||||
if !newMissingReasoningWarnState(dir).claimAt(fingerprint, now) {
|
||||
t.Errorf("fresh configuration %q did not claim its notice", label)
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
||||
close(start)
|
||||
wg.Wait()
|
||||
|
||||
@@ -457,7 +457,7 @@ func publishRecoveryTrashStage(dir, key, stageDir string) (string, error) {
|
||||
root := filepath.Join(dir, recoveryTrashDir)
|
||||
stem := strings.TrimSuffix(key, filepath.Ext(key))
|
||||
stamp := time.Now().UTC().UnixMilli()
|
||||
for i := 0; i < 1000; i++ {
|
||||
for i := range 1000 {
|
||||
name := key
|
||||
if i > 0 {
|
||||
name = fmt.Sprintf("%s-recovery-%d-%d", stem, stamp, i)
|
||||
@@ -523,7 +523,7 @@ func reserveRecoveryTrashItemDir(dir, key string) (string, string, error) {
|
||||
return "", "", err
|
||||
}
|
||||
stem := strings.TrimSuffix(key, filepath.Ext(key))
|
||||
for i := 0; i < 1000; i++ {
|
||||
for i := range 1000 {
|
||||
name := key
|
||||
if i > 0 {
|
||||
name = fmt.Sprintf("%s-recovery-%d-%d", stem, time.Now().UTC().UnixMilli(), i)
|
||||
|
||||
@@ -303,7 +303,7 @@ func TestRepeatGuardRetainsStaleEditFailuresAcrossGoalScope(t *testing.T) {
|
||||
TaskText: "fix prompt.txt",
|
||||
})
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
for i := range 3 {
|
||||
if err := a.Run(ctx, "continue goal"); err != nil {
|
||||
t.Fatalf("Run %d: %v", i+1, err)
|
||||
}
|
||||
@@ -335,7 +335,7 @@ func TestRepeatGuardClearsOrdinaryWriteFailureAcrossGoalRuns(t *testing.T) {
|
||||
TaskText: "fix prompt.txt",
|
||||
})
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
for i := range 3 {
|
||||
if err := a.Run(ctx, "continue goal"); err != nil {
|
||||
t.Fatalf("Run %d: %v", i+1, err)
|
||||
}
|
||||
|
||||
@@ -435,10 +435,7 @@ func (a *Agent) streamWithSamplingRecovery(ctx context.Context, turn int) stream
|
||||
before := provider.RequestAttemptCount(ctx)
|
||||
result := a.streamWithFrozen(ctx, turn, sink, &frozen, attemptID)
|
||||
after := provider.RequestAttemptCount(ctx)
|
||||
delta := after - before
|
||||
if delta < 0 {
|
||||
delta = 0
|
||||
}
|
||||
delta := max(after-before, 0)
|
||||
// httpRequests=0 means the provider does not use SendWithRetry
|
||||
// (extension/custom), or it failed before issuing an HTTP request.
|
||||
// Only overwrite RequestCount when the built-in counter observed POSTs;
|
||||
@@ -581,13 +578,7 @@ var streamRetrySleep = sleepStreamRetryBackoff
|
||||
// Returns false when ctx is cancelled during the wait.
|
||||
func sleepStreamRetryBackoff(ctx context.Context, attempt int) bool {
|
||||
// attempt is 1-based for the failed attempt about to be retried.
|
||||
shift := attempt - 1
|
||||
if shift < 0 {
|
||||
shift = 0
|
||||
}
|
||||
if shift > 4 {
|
||||
shift = 4
|
||||
}
|
||||
shift := min(max(attempt-1, 0), 4)
|
||||
base := time.Duration(1<<shift) * 500 * time.Millisecond
|
||||
jitter := time.Duration(rand.Intn(250)) * time.Millisecond
|
||||
timer := time.NewTimer(base + jitter)
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -679,14 +680,12 @@ func (s *Session) saveRecoveryBranch(opts RecoveryBranchOptions, shutdown bool)
|
||||
return RecoveryBranchInfo{}, fmt.Errorf("%w: %s is already %d recovery forks deep",
|
||||
ErrSessionRecoveryDepthExceeded, originalPath, parentDepth)
|
||||
}
|
||||
recoveryDepth := parentDepth + 1
|
||||
if recoveryDepth > SessionRecoveryMaxDepth {
|
||||
recoveryDepth := min(parentDepth+1,
|
||||
// A shutdown copy is allowed even when the ordinary conflict chain is
|
||||
// capped because losing the only in-memory transcript is worse than one
|
||||
// additional branch. Keep the saturated depth so later ordinary saves
|
||||
// still enforce the existing anti-cascade policy.
|
||||
recoveryDepth = SessionRecoveryMaxDepth
|
||||
}
|
||||
SessionRecoveryMaxDepth)
|
||||
|
||||
recoveryPath := recoverySessionPath(originalPath, digest)
|
||||
if shutdown {
|
||||
@@ -808,8 +807,8 @@ func recoveryParentStem(parent string) string {
|
||||
return "session"
|
||||
}
|
||||
sum := sha256.Sum256([]byte(parent))
|
||||
if idx := strings.Index(parent, "-recovery-"); idx >= 0 {
|
||||
base := strings.Trim(parent[:idx], "-_. ")
|
||||
if before, _, ok := strings.Cut(parent, "-recovery-"); ok {
|
||||
base := strings.Trim(before, "-_. ")
|
||||
if base == "" {
|
||||
base = "session"
|
||||
}
|
||||
@@ -1265,8 +1264,8 @@ func resolvePathThroughExistingAncestor(path string) string {
|
||||
missing := make([]string, 0, 4)
|
||||
for {
|
||||
if resolved, err := filepath.EvalSymlinks(current); err == nil {
|
||||
for i := len(missing) - 1; i >= 0; i-- {
|
||||
resolved = filepath.Join(resolved, missing[i])
|
||||
for _, v := range slices.Backward(missing) {
|
||||
resolved = filepath.Join(resolved, v)
|
||||
}
|
||||
return resolved
|
||||
}
|
||||
|
||||
@@ -2117,7 +2117,7 @@ func TestSaveSnapshotSameRevisionAllowsOwnedNonPrefixAppend(t *testing.T) {
|
||||
// Simulate recovery: load the saved transcript into a new session, then
|
||||
// add more messages. Every subsequent SaveSnapshot must succeed without a
|
||||
// diverged conflict while the persisted digest still proves ownership.
|
||||
for turn := 0; turn < 5; turn++ {
|
||||
for turn := range 5 {
|
||||
s, err := LoadSession(path)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadSession turn %d: %v", turn, err)
|
||||
|
||||
@@ -16,10 +16,8 @@ func TestSchedulerTotalConcurrencyQueues(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
barrier := make(chan struct{})
|
||||
|
||||
for i := 0; i < 4; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for range 4 {
|
||||
wg.Go(func() {
|
||||
release, err := s.Acquire(context.Background(), AcquireRequest{Writer: false})
|
||||
if err != nil {
|
||||
t.Errorf("acquire: %v", err)
|
||||
@@ -35,7 +33,7 @@ func TestSchedulerTotalConcurrencyQueues(t *testing.T) {
|
||||
<-barrier
|
||||
started.Add(-1)
|
||||
release()
|
||||
}()
|
||||
})
|
||||
}
|
||||
|
||||
// Wait until at least 2 are running, then release them.
|
||||
|
||||
Loaded 100 of 428 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user