-
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 2 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,7 @@ 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) | ||
| 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 +412,55 @@ type CreateInstanceResult struct { | |
| Instance *store.AgentInstance | ||
| } | ||
|
|
||
| // defaultTemplateID picks the template CreateInstance uses when the caller | ||
| // omits templateId: the recommended registered template if there is one, | ||
| // otherwise the most recently registered one (ListAgentTemplates orders by | ||
| // created_at DESC). The recommended flag is what the Dashboard sets on every | ||
|
ENCHIGO marked this conversation as resolved.
Outdated
|
||
| // template it registers from the market. | ||
| // | ||
| // The preference is exact rather than window-limited: pages are walked until | ||
| // one comes back short, so a recommended template still wins when newer | ||
| // non-recommended ones were registered after it. Any realistic registry fits | ||
| // in the first page, so this is one query in practice. | ||
| // | ||
| // 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 listing that never happened. 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) { | ||
| mostRecent := "" | ||
| for offset := 0; ; offset += store.MaxListLimit { | ||
|
ENCHIGO marked this conversation as resolved.
Outdated
|
||
| page, err := s.Store.ListAgentTemplates(ctx, store.MaxListLimit, offset) | ||
| if err != nil { | ||
| logging.G(ctx).Warnf("failed to list agent templates while choosing a default: %v", err) | ||
| break | ||
| } | ||
| for _, tmpl := range page { | ||
| if tmpl.Recommended { | ||
| return tmpl.TemplateID, false | ||
| } | ||
| } | ||
| if mostRecent == "" && len(page) > 0 { | ||
| mostRecent = page[0].TemplateID | ||
| } | ||
| if len(page) < store.MaxListLimit { | ||
| // Short page: the registry is exhausted and held no recommended | ||
| // template, so mostRecent (if any) is the answer. | ||
| if mostRecent != "" { | ||
| return mostRecent, false | ||
| } | ||
| return defaultAgentTemplateID, true | ||
| } | ||
| } | ||
| // Listing failed part-way. Use anything already seen, and do not claim the | ||
| // registry is empty. | ||
| if mostRecent != "" { | ||
| return mostRecent, false | ||
| } | ||
| return defaultAgentTemplateID, false | ||
| } | ||
|
|
||
| // CreateInstance orchestrates the full agent creation flow: | ||
| // | ||
| // 1. Resolve LLM config + domain + egress network config from settings. | ||
|
|
@@ -431,14 +501,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 +610,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.