v0.115.0: version-aware Supports (agent header channel) + DSM-validated NFS guidance — MinAgent: 0.81.0
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -17,7 +17,9 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -28,6 +30,36 @@ type Client struct {
|
||||
hc *http.Client
|
||||
// features caches capability-probe verdicts for Supports (features.go).
|
||||
features SupportCache
|
||||
// verMu guards lastAgentVersion — the most recent STRICTLY-VALIDATED X-Felhom-Agent-Version
|
||||
// seen on any agent response (v0.82.0 version channel). "" = never seen (pre-0.82 agent) →
|
||||
// Supports falls back to the route probe.
|
||||
verMu sync.Mutex
|
||||
lastAgentVersion string
|
||||
}
|
||||
|
||||
// reAgentVersion is the bare-semver shape the publish pipeline enforces (publish-agent.sh) — the
|
||||
// ONLY header values trusted for capability comparison. Anything else (garbage, "dev", suffixes)
|
||||
// is ignored and the probe fallback stays in charge.
|
||||
var reAgentVersion = regexp.MustCompile(`^[0-9]+\.[0-9]+\.[0-9]+$`)
|
||||
|
||||
// noteAgentVersion records a response's version header (passive capture — called on EVERY response
|
||||
// path). Invalid/absent headers never overwrite a previously-seen valid version.
|
||||
func (c *Client) noteAgentVersion(resp *http.Response) {
|
||||
v := strings.TrimSpace(resp.Header.Get("X-Felhom-Agent-Version"))
|
||||
if v == "" || !reAgentVersion.MatchString(v) {
|
||||
return
|
||||
}
|
||||
c.verMu.Lock()
|
||||
c.lastAgentVersion = v
|
||||
c.verMu.Unlock()
|
||||
}
|
||||
|
||||
// AgentVersion returns the last strictly-validated agent version seen on this client's traffic
|
||||
// ("" = unknown — header-less agent or no traffic yet). This is the Supports comparison source.
|
||||
func (c *Client) AgentVersion() string {
|
||||
c.verMu.Lock()
|
||||
defer c.verMu.Unlock()
|
||||
return c.lastAgentVersion
|
||||
}
|
||||
|
||||
// MountInfo mirrors the agent's GET /storage mount entry (doc 03 §6).
|
||||
@@ -491,6 +523,7 @@ func (c *Client) WipeStagedEscrowSecret(ctx context.Context) error {
|
||||
return fmt.Errorf("agentapi: DELETE /escrow/stage-secret: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
c.noteAgentVersion(resp) // v0.82.0 version channel: passive capture on EVERY response
|
||||
raw, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
||||
var env apiResponse
|
||||
if err := json.Unmarshal(raw, &env); err != nil {
|
||||
@@ -771,6 +804,7 @@ func (c *Client) postWithStatus(ctx context.Context, path string, body any) (api
|
||||
return env, 0, fmt.Errorf("agentapi: POST %s: %w", path, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
c.noteAgentVersion(resp) // v0.82.0 version channel: passive capture on EVERY response
|
||||
raw, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
||||
if err := json.Unmarshal(raw, &env); err != nil {
|
||||
return env, resp.StatusCode, fmt.Errorf("agentapi: POST %s: HTTP %d, bad envelope: %w", path, resp.StatusCode, err)
|
||||
@@ -879,6 +913,7 @@ func (c *Client) get(ctx context.Context, path string) (json.RawMessage, error)
|
||||
return nil, fmt.Errorf("agentapi: GET %s: %w", path, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
c.noteAgentVersion(resp) // v0.82.0 version channel: passive capture on EVERY response
|
||||
raw, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, &StatusError{Path: path, Code: resp.StatusCode}
|
||||
@@ -911,6 +946,7 @@ func (c *Client) post(ctx context.Context, path string, body any) (json.RawMessa
|
||||
return nil, fmt.Errorf("agentapi: POST %s: %w", path, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
c.noteAgentVersion(resp) // v0.82.0 version channel: passive capture on EVERY response
|
||||
raw, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted {
|
||||
return nil, fmt.Errorf("agentapi: POST %s: HTTP %d", path, resp.StatusCode)
|
||||
|
||||
Reference in New Issue
Block a user