feat(agent): hf agent status-of <agent-id> — read another agent's status
The backend has GET /calendar/agent/status?agent_id=<id> (read an agent's
runtime status, no side effects) but the CLI only exposed `hf agent status
--set` (sets the CALLER's own status). So delegate-task / on-call-handoff
status gates — which need to check whether the RECEIVER/incoming agent is
idle before pinging/handing off — had no CLI path; agents had to guess from
is_active.
Add `hf agent status-of <agent-id>` wrapping the GET endpoint
(X-Claw-Identifier header, no token). Prints {agent_id, status}; surfaces the
backend's 404 for unknown agents so callers can fail-open/closed.
Verified on sim: `hf agent status-of plxrec2` → idle; --json → {"agent_id",
"status"}; unknown agent → 404 "Agent not found".
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
neturl "net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -138,3 +139,69 @@ func RunAgentStatus(args []string) {
|
||||
"ok", "true",
|
||||
)
|
||||
}
|
||||
|
||||
// RunAgentStatusOf implements `hf agent status-of <agent-id>` — read ANOTHER
|
||||
// agent's current runtime status without side effects, wrapping
|
||||
// `GET /calendar/agent/status?agent_id=<id>`. Used by the delegate-task and
|
||||
// on-call-handoff status gates (which previously referenced this endpoint with
|
||||
// no CLI to reach it). Like RunAgentStatus this is header-identified, not
|
||||
// token-authed; the X-Claw-Identifier comes from CLAW_IDENTIFIER env or
|
||||
// hostname. Prints `{agent_id, status}`; backend returns 404 for unknown
|
||||
// agents so callers can fail-open or fail-closed.
|
||||
func RunAgentStatusOf(targetAgentID string) {
|
||||
targetAgentID = strings.TrimSpace(targetAgentID)
|
||||
if targetAgentID == "" {
|
||||
output.Error("usage: hf agent status-of <agent-id>")
|
||||
}
|
||||
|
||||
clawID := strings.TrimSpace(os.Getenv("CLAW_IDENTIFIER"))
|
||||
if clawID == "" {
|
||||
if h, err := os.Hostname(); err == nil {
|
||||
clawID = h
|
||||
}
|
||||
}
|
||||
if clawID == "" {
|
||||
output.Error("CLAW_IDENTIFIER env not set and hostname() failed — set CLAW_IDENTIFIER explicitly")
|
||||
}
|
||||
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
output.Errorf("config error: %v", err)
|
||||
}
|
||||
|
||||
url := strings.TrimRight(cfg.BaseURL, "/") + "/calendar/agent/status?agent_id=" + neturl.QueryEscape(targetAgentID)
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
output.Errorf("cannot build request: %v", err)
|
||||
}
|
||||
req.Header.Set("X-Claw-Identifier", clawID)
|
||||
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
output.Errorf("request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
_, _ = buf.ReadFrom(resp.Body)
|
||||
if resp.StatusCode/100 != 2 {
|
||||
output.Errorf("backend returned %d: %s", resp.StatusCode, buf.String())
|
||||
}
|
||||
|
||||
if output.JSONMode {
|
||||
fmt.Println(buf.String())
|
||||
return
|
||||
}
|
||||
var parsed struct {
|
||||
AgentID string `json:"agent_id"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
if err := json.Unmarshal(buf.Bytes(), &parsed); err != nil {
|
||||
output.Errorf("cannot parse response: %v", err)
|
||||
}
|
||||
output.PrintKeyValue(
|
||||
"agent_id", parsed.AgentID,
|
||||
"status", parsed.Status,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user