Agents workspace: roster, runs, and a readable run log; layout now eight workspaces

This commit is contained in:
Sam
2026-11-10 16:58:59 +11:00
parent 7f18c548fe
commit b15216aa7b
14 changed files with 1339 additions and 2 deletions

View File

@@ -373,3 +373,71 @@ func (c *Client) CostSummary(issueID string) (Cost, error) {
func (c *Client) Health() error {
return c.do("GET", "/api/health", nil, nil)
}
// AgentRun is one heartbeat run in the company, whichever agent ran it.
type AgentRun struct {
ID string `json:"id"`
AgentID string `json:"agentId"`
Status string `json:"status"`
Source string `json:"invocationSource"`
StartedAt string `json:"createdAt"`
FinishedAt string `json:"finishedAt"`
ErrorCode string `json:"errorCode"`
LastOutputAt string `json:"lastOutputAt"`
LastOutputBytes int `json:"lastOutputBytes"`
// The task a run worked on lives inside the run's context snapshot, not on
// the run itself.
Snapshot struct {
IssueID string `json:"issueId"`
TaskKey string `json:"taskKey"`
} `json:"contextSnapshot"`
}
// Runs returns every run in the company, newest first.
func (c *Client) CompanyRuns() ([]AgentRun, error) {
var out []AgentRun
err := c.do("GET", "/api/companies/"+c.Company+"/heartbeat-runs", nil, &out)
return out, err
}
// RunLog returns the recorded output of one run.
func (c *Client) RunLog(runID string, maxBytes int) (string, error) {
var raw []byte
req, err := http.NewRequest("GET", c.BaseURL+"/api/heartbeat-runs/"+runID+"/log", nil)
if err != nil {
return "", err
}
req.Header.Set("Authorization", "Bearer "+c.token)
resp, err := c.http.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode > 299 {
b, _ := io.ReadAll(io.LimitReader(resp.Body, 400))
return "", fmt.Errorf("run log returned %d: %s", resp.StatusCode, snippet(b))
}
raw, err = io.ReadAll(io.LimitReader(resp.Body, int64(maxBytes)))
if err != nil {
return "", err
}
if len(raw) == maxBytes {
return string(raw) + "\n… (only the last " + fmt.Sprint(maxBytes) + " bytes are shown)", nil
}
return string(raw), nil
}
// PauseAgent and ResumeAgent are the two ways to stop and start an agent.
func (c *Client) PauseAgent(agentID string) error {
return c.do("POST", "/api/agents/"+agentID+"/pause", map[string]any{}, nil)
}
func (c *Client) ResumeAgent(agentID string) error {
return c.do("POST", "/api/agents/"+agentID+"/resume", map[string]any{}, nil)
}
// WakeAgent asks Paperclip to start a run for this agent now.
func (c *Client) WakeAgent(agentID string) error {
return c.do("POST", "/api/agents/"+agentID+"/wakeup",
map[string]any{"source": "on_demand", "triggerDetail": "manual", "forceFreshSession": false}, nil)
}