-
Notifications
You must be signed in to change notification settings - Fork 1k
fix(cubeops): default AgentHub creates to a registered template #1334
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 3 commits
48df165
9b83692
7c40da0
85466e6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -42,6 +42,19 @@ type CubeMasterClient interface { | |
| // SDKInstanceType is the CubeMaster instance_type used for AgentHub sandboxes. | ||
| const SDKInstanceType = "cubebox" | ||
|
|
||
| // defaultAgentTemplateID is the last-resort template identifier CreateInstance | ||
| // uses when the caller supplies neither a snapshot nor a templateId and no | ||
| // AgentHub template is registered to default to. | ||
| // | ||
| // Nothing in this repository provisions a template under this identifier: it is | ||
| // not a tpl-/snap- id, so CubeMaster resolves it through GetTemplateByAlias, | ||
| // and it only resolves on installs where an operator happened to claim it as a | ||
| // template alias. Reaching it therefore means "no template is registered", | ||
| // which CreateInstance reports as such instead of surfacing CubeMaster's | ||
| // not-found. Kept as a fallback rather than dropped so that installs which do | ||
| // carry the alias keep working. | ||
| const defaultAgentTemplateID = "wecom-ds-openclaw" | ||
|
|
||
| // ── Error ─────────────────────────────────────────────────────────────────── | ||
|
|
||
| // Error is the service-layer error type. It carries an HTTP status code so | ||
|
|
@@ -105,6 +118,13 @@ func wrapCMError(err error) *Error { | |
| } | ||
| } | ||
|
|
||
| // isCMNotFound reports whether err is a CubeMaster not-found (130404). Callers | ||
| // that need the full status mapping should use wrapCMError instead. | ||
| func isCMNotFound(err error) bool { | ||
| var cmErr *cubemaster.CMError | ||
| return errors.As(err, &cmErr) && cmErr.IsNotFound() | ||
| } | ||
|
|
||
| // ── AgentStore interface ──────────────────────────────────────────────────── | ||
|
|
||
| // AgentStore is the subset of *store.Store that AgentHubService depends on. | ||
|
|
@@ -123,6 +143,8 @@ type AgentStore interface { | |
| GetAgentSnapshot(ctx context.Context, agentID, snapshotID string) (*store.AgentSnapshot, error) | ||
| DeleteAgentSnapshot(ctx context.Context, agentID, snapshotID string) error | ||
| GetAgentTemplate(ctx context.Context, templateID string) (*store.AgentTemplate, error) | ||
| GetRecommendedAgentTemplate(ctx context.Context) (*store.AgentTemplate, error) | ||
| ListAgentTemplates(ctx context.Context, limit, offset int) ([]store.AgentTemplate, error) | ||
| RecordOperation(ctx context.Context, agentID, sandboxID, operationType, status, errMsg string) error | ||
| LatestHealthySnapshot(ctx context.Context, agentID string) (string, error) | ||
| SetBaseSnapshotID(ctx context.Context, agentID, snapshotID string) error | ||
|
|
@@ -391,6 +413,41 @@ type CreateInstanceResult struct { | |
| Instance *store.AgentInstance | ||
| } | ||
|
|
||
| // defaultTemplateID picks the template CreateInstance uses when the caller | ||
| // omits templateId: the template an operator marked recommended if there is | ||
|
ENCHIGO marked this conversation as resolved.
|
||
| // one, otherwise the most recently registered one. | ||
| // | ||
| // Both are single bounded queries — the recommended preference is exact | ||
| // without reading the registry, which matters because nothing sets the flag | ||
| // automatically (see store.GetRecommendedAgentTemplate), so an install can | ||
| // accumulate any number of newer non-recommended templates after the marked | ||
| // one. | ||
| // | ||
| // none is true only when the registry was read successfully and holds nothing. | ||
| // A failed read returns none=false — the caller must not report "nothing is | ||
| // registered" on the strength of a query that never completed. Either way the | ||
| // returned id is then defaultAgentTemplateID, which the caller still passes to | ||
| // CubeMaster so installs carrying that alias keep working. | ||
| func (s *AgentHubService) defaultTemplateID(ctx context.Context) (id string, none bool) { | ||
| recommended, err := s.Store.GetRecommendedAgentTemplate(ctx) | ||
| if err != nil { | ||
| logging.G(ctx).Warnf("failed to read the recommended agent template: %v", err) | ||
| return defaultAgentTemplateID, false | ||
|
ENCHIGO marked this conversation as resolved.
|
||
| } | ||
| if recommended != nil { | ||
| return recommended.TemplateID, false | ||
| } | ||
| newest, err := s.Store.ListAgentTemplates(ctx, 1, 0) | ||
| if err != nil { | ||
| logging.G(ctx).Warnf("failed to list agent templates while choosing a default: %v", err) | ||
| return defaultAgentTemplateID, false | ||
| } | ||
| if len(newest) > 0 { | ||
| return newest[0].TemplateID, false | ||
| } | ||
| return defaultAgentTemplateID, true | ||
| } | ||
|
|
||
| // CreateInstance orchestrates the full agent creation flow: | ||
| // | ||
| // 1. Resolve LLM config + domain + egress network config from settings. | ||
|
|
@@ -431,14 +488,19 @@ func (s *AgentHubService) CreateInstance(ctx context.Context, req CreateInstance | |
| snapshotID := strings.TrimSpace(req.SnapshotID) | ||
| rootfsSourceType := "template" | ||
| rootfsSourceID := "" | ||
| // Set when the request named no template and the registry was read and | ||
| // found empty, so the unprovisioned defaultAgentTemplateID was used; lets | ||
| // the CreateSandbox error below name the real problem. Deliberately not | ||
| // set when the registry could not be read — see defaultTemplateID. | ||
| noTemplateRegistered := false | ||
| if snapshotID != "" { | ||
| rootfsSourceType = "snapshot" | ||
| rootfsSourceID = snapshotID | ||
| } else { | ||
| if req.TemplateID != "" { | ||
| rootfsSourceID = req.TemplateID | ||
| } else { | ||
| rootfsSourceID = "wecom-ds-openclaw" | ||
| rootfsSourceID, noTemplateRegistered = s.defaultTemplateID(ctx) | ||
| } | ||
| } | ||
| templateID := rootfsSourceID | ||
|
|
@@ -535,6 +597,14 @@ func (s *AgentHubService) CreateInstance(ctx context.Context, req CreateInstance | |
| // --- Create sandbox --- | ||
| sandboxResp, err := s.CM.CreateSandbox(ctx, cmReq) | ||
| if err != nil { | ||
| // The caller named no template, none is registered, and CubeMaster | ||
| // could not resolve the fallback either. Report the missing | ||
| // registration rather than a not-found for an identifier the caller | ||
| // never supplied. | ||
| if noTemplateRegistered && isCMNotFound(err) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The rewrite fires on any CubeMaster not-found once the registry was read empty — not specifically a template-resolution failure. In the empty-registry request the only template-ish input is the fallback id, but |
||
| return nil, NewBadRequest("no agent template is registered: register one from the template market " + | ||
| "(POST /agenthub/templates/market), or pass templateId explicitly") | ||
| } | ||
| return nil, NewBadGateway("failed to create sandbox: " + err.Error()) | ||
| } | ||
| var sbResult struct { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.