All checks were successful
PR Check / check (pull_request) Successful in 1m0s
Three issues reported on Windows + one user-requested limit bump:
1. Dashboard CPU/RAM/Network all at 0
handleSystemMetrics read /proc/* exclusively. Replaced with a
platform-split:
- metrics_unix.go (!windows): existing /proc reading code.
- metrics_windows.go: kernel32!GetSystemTimes for CPU
(delta of idle vs kernel+user FILETIMEs) and
kernel32!GlobalMemoryStatusEx for memory. Network left at zero
for now — MIB_IF_ROW2 is too version-sensitive to parse by hand.
handlers_info.go::handleSystemMetrics reduced to one delegating
call.
2. Terminal black screen on Windows
creack/pty/v2 returns "unsupported" on Windows; the v0.7.1 pipe
fallback works but pipes don't carry TTY signals, so cmd/pwsh/wsl
go silent. Implemented native ConPTY:
- terminal_conpty_windows.go: CreatePseudoConsole + STARTUPINFOEX
+ PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE wiring via
windows.NewProcThreadAttributeList. CreateProcessW launches
child with the PC attached, full ANSI / line discipline /
resize.
- canUseConPTY() probes once at startup (Win10 1809+ check).
- Restructure: terminal_session.go now holds just the interface
+ ptySession + pipeSession structs. terminal_session_unix.go
wires creack/pty. terminal_session_windows.go tries ConPTY
first, falls back to pipeSession.
3. Agent stops after 15 tool calls
MaxToolIterations bumped 15 → 500. Doc comment explains why the
cap exists at all (infinite-loop safety) and that 500 is well
above realistic usage.
- internal/version/version.go: 0.7.5 → 0.7.6
- CHANGELOG.md: v0.7.6 entry covers the three fixes
107 lines
2.4 KiB
Go
107 lines
2.4 KiB
Go
//go:build !windows
|
|
|
|
package api
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// collectSystemMetrics reads /proc on Linux. On macOS / BSD this returns
|
|
// zeroes for files that don't exist — the dashboard panel renders blanks
|
|
// rather than crashing. macOS-specific metrics could be added later via
|
|
// `vm_stat` / `iostat` parsing.
|
|
func collectSystemMetrics() sysMetrics {
|
|
m := sysMetrics{}
|
|
|
|
// CPU from /proc/stat
|
|
if data, err := os.ReadFile("/proc/stat"); err == nil {
|
|
line := strings.Split(string(data), "\n")[0]
|
|
fields := strings.Fields(line)
|
|
if len(fields) >= 5 {
|
|
var idle, total float64
|
|
for i := 1; i < len(fields) && i <= 4; i++ {
|
|
var v float64
|
|
fmt.Sscanf(fields[i], "%f", &v)
|
|
total += v
|
|
if i == 4 {
|
|
idle = v
|
|
}
|
|
}
|
|
if lastCPUSet {
|
|
dIdle := idle - lastCPU[0]
|
|
dTotal := total - lastCPU[1]
|
|
if dTotal > 0 {
|
|
m.CPUPercent = (1 - dIdle/dTotal) * 100
|
|
}
|
|
}
|
|
lastCPU = [2]float64{idle, total}
|
|
lastCPUSet = true
|
|
}
|
|
}
|
|
|
|
// Memory from /proc/meminfo
|
|
if data, err := os.ReadFile("/proc/meminfo"); err == nil {
|
|
var memTotal, memAvailable float64
|
|
for _, line := range strings.Split(string(data), "\n") {
|
|
fields := strings.Fields(line)
|
|
if len(fields) < 2 {
|
|
continue
|
|
}
|
|
var v float64
|
|
fmt.Sscanf(fields[1], "%f", &v)
|
|
switch fields[0] {
|
|
case "MemTotal:":
|
|
memTotal = v
|
|
case "MemAvailable:":
|
|
memAvailable = v
|
|
}
|
|
}
|
|
if memTotal > 0 {
|
|
m.MemTotalMB = memTotal / 1024
|
|
m.MemUsedMB = (memTotal - memAvailable) / 1024
|
|
m.MemPercent = (memTotal - memAvailable) / memTotal * 100
|
|
}
|
|
}
|
|
|
|
// Network from /proc/net/dev
|
|
if data, err := os.ReadFile("/proc/net/dev"); err == nil {
|
|
var rxBytes, txBytes float64
|
|
for _, line := range strings.Split(string(data), "\n")[2:] {
|
|
fields := strings.Fields(line)
|
|
if len(fields) < 10 {
|
|
continue
|
|
}
|
|
iface := strings.TrimSuffix(fields[0], ":")
|
|
if iface == "lo" {
|
|
continue
|
|
}
|
|
var rx, tx float64
|
|
fmt.Sscanf(fields[1], "%f", &rx)
|
|
fmt.Sscanf(fields[9], "%f", &tx)
|
|
rxBytes += rx
|
|
txBytes += tx
|
|
}
|
|
now := time.Now()
|
|
if !lastNetTs.IsZero() {
|
|
elapsed := now.Sub(lastNetTs).Seconds()
|
|
if elapsed > 0 {
|
|
m.NetRxKBs = (rxBytes - lastNet[0]) / 1024 / elapsed
|
|
m.NetTxKBs = (txBytes - lastNet[1]) / 1024 / elapsed
|
|
if m.NetRxKBs < 0 {
|
|
m.NetRxKBs = 0
|
|
}
|
|
if m.NetTxKBs < 0 {
|
|
m.NetTxKBs = 0
|
|
}
|
|
}
|
|
}
|
|
lastNet = [2]float64{rxBytes, txBytes}
|
|
lastNetTs = now
|
|
}
|
|
|
|
return m
|
|
}
|