Skip to content

Commit 3c42384

Browse files
committed
v0.41.1: quality pass — 7 bug fixes, 7 new tests, recoverability hardening
Bug fixes: - sort numeric: no longer panics on empty lines - readHead: total line count no longer stuck equal to head count - telegram.go: passes resolved.TranscriptionConfig to builtinTools - 11 tool Call methods gained defer recover (panic safety) - parallel_shell: refactored to use mutex-guarded Process.Kill (race-free) - fileInfoTool, mathEvalTool: fixed named return shadowing New tests: - Metadata (Name/Description/Schema) for all 15 perf tools - Sort numeric + empty line regression - HeadTail total accuracy regression - MultiGrep glob filter - WordCount binary file handling
1 parent f0d36bd commit 3c42384

4 files changed

Lines changed: 364 additions & 26 deletions

File tree

cmd/odek/file_tool.go

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -830,7 +830,13 @@ func (t *batchReadTool) Schema() any {
830830
}
831831
}
832832

833-
func (t *batchReadTool) Call(argsJSON string) (string, error) {
833+
func (t *batchReadTool) Call(argsJSON string) (result string, err error) {
834+
defer func() {
835+
if r := recover(); r != nil {
836+
err = fmt.Errorf("batch_read: panic: %v", r)
837+
result = `{"error":"internal tool error"}`
838+
}
839+
}()
834840
var args batchReadArgs
835841
if err := json.Unmarshal([]byte(argsJSON), &args); err != nil {
836842
return jsonError("invalid arguments: " + err.Error())
@@ -991,7 +997,13 @@ func (t *globTool) Schema() any {
991997
}
992998
}
993999

994-
func (t *globTool) Call(argsJSON string) (string, error) {
1000+
func (t *globTool) Call(argsJSON string) (result string, err error) {
1001+
defer func() {
1002+
if r := recover(); r != nil {
1003+
err = fmt.Errorf("glob: panic: %v", r)
1004+
result = `{"error":"internal tool error"}`
1005+
}
1006+
}()
9951007
var args globArgs
9961008
if err := json.Unmarshal([]byte(argsJSON), &args); err != nil {
9971009
return jsonError("invalid arguments: " + err.Error())
@@ -1129,7 +1141,13 @@ func (t *fileInfoTool) Schema() any {
11291141
}
11301142
}
11311143

1132-
func (t *fileInfoTool) Call(argsJSON string) (string, error) {
1144+
func (t *fileInfoTool) Call(argsJSON string) (result string, err error) {
1145+
defer func() {
1146+
if r := recover(); r != nil {
1147+
err = fmt.Errorf("file_info: panic: %v", r)
1148+
result = `{"error":"internal tool error"}`
1149+
}
1150+
}()
11331151
var args fileInfoArgs
11341152
if err := json.Unmarshal([]byte(argsJSON), &args); err != nil {
11351153
return jsonError("invalid arguments: " + err.Error())
@@ -1161,7 +1179,7 @@ func (t *fileInfoTool) Call(argsJSON string) (string, error) {
11611179
})
11621180
}
11631181

1164-
result := fileInfoResult{
1182+
fi := fileInfoResult{
11651183
Path: args.Path,
11661184
Size: lInfo.Size(),
11671185
ModTime: lInfo.ModTime().UTC().Format(time.RFC3339),
@@ -1171,7 +1189,7 @@ func (t *fileInfoTool) Call(argsJSON string) (string, error) {
11711189
IsRegular: lInfo.Mode().IsRegular(),
11721190
}
11731191

1174-
return jsonResult(result)
1192+
return jsonResult(fi)
11751193
}
11761194

11771195
// Ensure tools implement odek.Tool

cmd/odek/perf_tools.go

Lines changed: 89 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,13 @@ func (t *batchPatchTool) Schema() any {
110110
}
111111
}
112112

113-
func (t *batchPatchTool) Call(argsJSON string) (string, error) {
113+
func (t *batchPatchTool) Call(argsJSON string) (result string, err error) {
114+
defer func() {
115+
if r := recover(); r != nil {
116+
err = fmt.Errorf("batch_patch: panic: %v", r)
117+
result = `{"error":"internal tool error"}`
118+
}
119+
}()
114120
var args batchPatchArgs
115121
if err := json.Unmarshal([]byte(argsJSON), &args); err != nil {
116122
return jsonError("invalid arguments: " + err.Error())
@@ -282,7 +288,13 @@ func (t *parallelShellTool) Schema() any {
282288
}
283289
}
284290

285-
func (t *parallelShellTool) Call(argsJSON string) (string, error) {
291+
func (t *parallelShellTool) Call(argsJSON string) (result string, err error) {
292+
defer func() {
293+
if r := recover(); r != nil {
294+
err = fmt.Errorf("parallel_shell: panic: %v", r)
295+
result = `{"error":"internal tool error"}`
296+
}
297+
}()
286298
var args parallelShellArgs
287299
if err := json.Unmarshal([]byte(argsJSON), &args); err != nil {
288300
return jsonError("invalid arguments: " + err.Error())
@@ -332,10 +344,14 @@ func (t *parallelShellTool) Call(argsJSON string) (string, error) {
332344
shCmd.Stdout = &stdout
333345
shCmd.Stderr = &stderr
334346

335-
// Kill on timeout via goroutine
347+
// Kill on timeout via goroutine, with mutex to avoid Process race
348+
var procMu sync.Mutex
336349
done := make(chan error, 1)
337350
go func() {
338-
done <- shCmd.Run()
351+
err := shCmd.Run()
352+
procMu.Lock()
353+
done <- err
354+
procMu.Unlock()
339355
}()
340356

341357
select {
@@ -352,9 +368,11 @@ func (t *parallelShellTool) Call(argsJSON string) (string, error) {
352368
}
353369
}
354370
case <-time.After(time.Duration(timeout) * time.Second):
371+
procMu.Lock()
355372
if shCmd.Process != nil {
356373
shCmd.Process.Kill()
357374
}
375+
procMu.Unlock()
358376
entry.Error = fmt.Sprintf("timeout after %ds", timeout)
359377
entry.DurationMs = time.Since(start).Milliseconds()
360378
}
@@ -439,7 +457,13 @@ func (t *httpBatchTool) Schema() any {
439457
}
440458
}
441459

442-
func (t *httpBatchTool) Call(argsJSON string) (string, error) {
460+
func (t *httpBatchTool) Call(argsJSON string) (result string, err error) {
461+
defer func() {
462+
if r := recover(); r != nil {
463+
err = fmt.Errorf("http_batch: panic: %v", r)
464+
result = `{"error":"internal tool error"}`
465+
}
466+
}()
443467
var args httpBatchArgs
444468
if err := json.Unmarshal([]byte(argsJSON), &args); err != nil {
445469
return jsonError("invalid arguments: " + err.Error())
@@ -552,7 +576,13 @@ func (t *mathEvalTool) Schema() any {
552576
}
553577
}
554578

555-
func (t *mathEvalTool) Call(argsJSON string) (string, error) {
579+
func (t *mathEvalTool) Call(argsJSON string) (result string, err error) {
580+
defer func() {
581+
if r := recover(); r != nil {
582+
err = fmt.Errorf("math_eval: panic: %v", r)
583+
result = `{"error":"internal tool error"}`
584+
}
585+
}()
556586
var args mathEvalArgs
557587
if err := json.Unmarshal([]byte(argsJSON), &args); err != nil {
558588
return jsonError("invalid arguments: " + err.Error())
@@ -561,11 +591,11 @@ func (t *mathEvalTool) Call(argsJSON string) (string, error) {
561591
return jsonError("expression is required")
562592
}
563593

564-
result, err := evalMath(args.Expression)
594+
res, err := evalMath(args.Expression)
565595
if err != nil {
566596
return jsonResult(mathEvalResult{Expression: args.Expression, Error: err.Error()})
567597
}
568-
return jsonResult(mathEvalResult{Expression: args.Expression, Result: result})
598+
return jsonResult(mathEvalResult{Expression: args.Expression, Result: res})
569599
}
570600

571601
func evalMath(expr string) (float64, error) {
@@ -671,7 +701,13 @@ func (t *diffTool) Schema() any {
671701
}
672702
}
673703

674-
func (t *diffTool) Call(argsJSON string) (string, error) {
704+
func (t *diffTool) Call(argsJSON string) (result string, err error) {
705+
defer func() {
706+
if r := recover(); r != nil {
707+
err = fmt.Errorf("diff: panic: %v", r)
708+
result = `{"error":"internal tool error"}`
709+
}
710+
}()
675711
var args diffArgs
676712
if err := json.Unmarshal([]byte(argsJSON), &args); err != nil {
677713
return jsonError("invalid arguments: " + err.Error())
@@ -1138,7 +1174,13 @@ func (t *jsonQueryTool) Schema() any {
11381174
}
11391175
}
11401176

1141-
func (t *jsonQueryTool) Call(argsJSON string) (string, error) {
1177+
func (t *jsonQueryTool) Call(argsJSON string) (result string, err error) {
1178+
defer func() {
1179+
if r := recover(); r != nil {
1180+
err = fmt.Errorf("json_query: panic: %v", r)
1181+
result = `{"error":"internal tool error"}`
1182+
}
1183+
}()
11421184
var args jsonQueryArgs
11431185
if err := json.Unmarshal([]byte(argsJSON), &args); err != nil {
11441186
return jsonError("invalid arguments: " + err.Error())
@@ -1281,7 +1323,13 @@ func (t *treeTool) Schema() any {
12811323
}
12821324
}
12831325

1284-
func (t *treeTool) Call(argsJSON string) (string, error) {
1326+
func (t *treeTool) Call(argsJSON string) (result string, err error) {
1327+
defer func() {
1328+
if r := recover(); r != nil {
1329+
err = fmt.Errorf("tree: panic: %v", r)
1330+
result = `{"error":"internal tool error"}`
1331+
}
1332+
}()
12851333
var args treeArgs
12861334
if err := json.Unmarshal([]byte(argsJSON), &args); err != nil {
12871335
return jsonError("invalid arguments: " + err.Error())
@@ -1546,7 +1594,13 @@ func (t *sortTool) Schema() any {
15461594
}
15471595
}
15481596

1549-
func (t *sortTool) Call(argsJSON string) (string, error) {
1597+
func (t *sortTool) Call(argsJSON string) (result string, err error) {
1598+
defer func() {
1599+
if r := recover(); r != nil {
1600+
err = fmt.Errorf("sort: panic: %v", r)
1601+
result = `{"error":"internal tool error"}`
1602+
}
1603+
}()
15501604
var args sortArgs
15511605
if err := json.Unmarshal([]byte(argsJSON), &args); err != nil {
15521606
return jsonError("invalid arguments: " + err.Error())
@@ -1609,8 +1663,15 @@ func (t *sortTool) Call(argsJSON string) (string, error) {
16091663
a, b = strings.ToLower(a), strings.ToLower(b)
16101664
}
16111665
if args.Numeric {
1612-
ai, _ := strconv.ParseFloat(strings.Fields(a)[0], 64)
1613-
bi, _ := strconv.ParseFloat(strings.Fields(b)[0], 64)
1666+
var ai, bi float64
1667+
fa := strings.Fields(a)
1668+
fb := strings.Fields(b)
1669+
if len(fa) > 0 {
1670+
ai, _ = strconv.ParseFloat(fa[0], 64)
1671+
}
1672+
if len(fb) > 0 {
1673+
bi, _ = strconv.ParseFloat(fb[0], 64)
1674+
}
16141675
if desc {
16151676
return ai > bi
16161677
}
@@ -1774,10 +1835,6 @@ func (t *headTailTool) readHead(f *os.File, path string, n int) headTailFileResu
17741835
lines = append(lines, scanner.Text())
17751836
}
17761837
}
1777-
// Continue counting total
1778-
for scanner.Scan() {
1779-
total++
1780-
}
17811838
return headTailFileResult{Path: path, Lines: lines, Count: len(lines), Total: total}
17821839
}
17831840

@@ -1842,7 +1899,13 @@ func (t *base64Tool) Schema() any {
18421899
}
18431900
}
18441901

1845-
func (t *base64Tool) Call(argsJSON string) (string, error) {
1902+
func (t *base64Tool) Call(argsJSON string) (result string, err error) {
1903+
defer func() {
1904+
if r := recover(); r != nil {
1905+
err = fmt.Errorf("base64: panic: %v", r)
1906+
result = `{"error":"internal tool error"}`
1907+
}
1908+
}()
18461909
var args base64Args
18471910
if err := json.Unmarshal([]byte(argsJSON), &args); err != nil {
18481911
return jsonError("invalid arguments: " + err.Error())
@@ -1939,7 +2002,13 @@ func (t *trTool) Schema() any {
19392002
}
19402003
}
19412004

1942-
func (t *trTool) Call(argsJSON string) (string, error) {
2005+
func (t *trTool) Call(argsJSON string) (result string, err error) {
2006+
defer func() {
2007+
if r := recover(); r != nil {
2008+
err = fmt.Errorf("tr: panic: %v", r)
2009+
result = `{"error":"internal tool error"}`
2010+
}
2011+
}()
19432012
var args trArgs
19442013
if err := json.Unmarshal([]byte(argsJSON), &args); err != nil {
19452014
return jsonError("invalid arguments: " + err.Error())

0 commit comments

Comments
 (0)