feat(daemon): agent-driven repo checkout with bare clone cache
Agents now decide which repo to use based on issue context and check out repos on demand via `multica repo checkout <url>`. Workspace repos are cached locally as bare clones for fast worktree creation. Key changes: - Add repocache package for bare clone management (clone, fetch, worktree) - Add `multica repo checkout` CLI command that talks to local daemon - Add POST /repo/checkout endpoint on daemon health server - Pass workspace repos metadata through register + task claim responses - Remove pre-created worktrees from execenv (workdir starts empty) - Update CLAUDE.md template to instruct agents to use `multica repo checkout` - Pass MULTICA_DAEMON_PORT, WORKSPACE_ID, AGENT_NAME, TASK_ID env vars to agent
This commit is contained in:
parent
ab4058b1e4
commit
cdc1ac708e
15 changed files with 1064 additions and 255 deletions
|
|
@ -81,6 +81,13 @@ func agentToResponse(a db.Agent) AgentResponse {
|
|||
}
|
||||
}
|
||||
|
||||
// RepoData holds repository information included in claim responses so the
|
||||
// daemon can set up worktrees for each workspace repo.
|
||||
type RepoData struct {
|
||||
URL string `json:"url"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type AgentTaskResponse struct {
|
||||
ID string `json:"id"`
|
||||
AgentID string `json:"agent_id"`
|
||||
|
|
@ -94,6 +101,7 @@ type AgentTaskResponse struct {
|
|||
Result any `json:"result"`
|
||||
Error *string `json:"error"`
|
||||
Agent *TaskAgentData `json:"agent,omitempty"`
|
||||
Repos []RepoData `json:"repos,omitempty"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
PriorSessionID string `json:"prior_session_id,omitempty"` // session ID from a previous task on same issue
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,8 @@ func (h *Handler) DaemonRegister(w http.ResponseWriter, r *http.Request) {
|
|||
writeError(w, http.StatusBadRequest, "at least one runtime is required")
|
||||
return
|
||||
}
|
||||
if _, err := h.Queries.GetWorkspace(r.Context(), parseUUID(req.WorkspaceID)); err != nil {
|
||||
ws, err := h.Queries.GetWorkspace(r.Context(), parseUUID(req.WorkspaceID))
|
||||
if err != nil {
|
||||
writeError(w, http.StatusNotFound, "workspace not found")
|
||||
return
|
||||
}
|
||||
|
|
@ -106,7 +107,16 @@ func (h *Handler) DaemonRegister(w http.ResponseWriter, r *http.Request) {
|
|||
"runtimes": resp,
|
||||
})
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]any{"runtimes": resp})
|
||||
// Include workspace repos so the daemon can cache them locally.
|
||||
var repos []RepoData
|
||||
if ws.Repos != nil {
|
||||
json.Unmarshal(ws.Repos, &repos)
|
||||
}
|
||||
if repos == nil {
|
||||
repos = []RepoData{}
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]any{"runtimes": resp, "repos": repos})
|
||||
}
|
||||
|
||||
// DaemonDeregister marks runtimes as offline when the daemon shuts down.
|
||||
|
|
@ -217,6 +227,16 @@ func (h *Handler) ClaimTaskByRuntime(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
// Include workspace repos so the daemon can set up worktrees.
|
||||
if issue, err := h.Queries.GetIssue(r.Context(), task.IssueID); err == nil {
|
||||
if ws, err := h.Queries.GetWorkspace(r.Context(), issue.WorkspaceID); err == nil && ws.Repos != nil {
|
||||
var repos []RepoData
|
||||
if json.Unmarshal(ws.Repos, &repos) == nil && len(repos) > 0 {
|
||||
resp.Repos = repos
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Look up the prior session for this (agent, issue) pair so the daemon
|
||||
// can resume the Claude Code conversation context.
|
||||
if prior, err := h.Queries.GetLastTaskSession(r.Context(), db.GetLastTaskSessionParams{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue