fix(canvas): propagate runtime through WORKSPACE_PROVISIONING event
The side-panel runtime pill read "unknown" for newly-deployed workspaces
because canvas-events.ts created the node from WORKSPACE_PROVISIONING
payload — and the payload only carried name + tier. No refetch filled
the gap during provisioning, so the user saw "RUNTIME unknown" on the
card even though the DB row had the real runtime set.
Includes runtime in every WORKSPACE_PROVISIONING emitter:
* handlers/workspace.go — initial create
* handlers/workspace_restart.go — explicit restart, auto-restart, and
crash-recovery resume loop
* handlers/org_import.go — multi-workspace org imports
Canvas-side: canvas-events.ts reads payload.runtime when creating the
node; the provisioning test asserts the pill value is populated before
any refetch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
dc50a1c775
commit
e337efe974
@ -269,7 +269,7 @@ describe("applyEvent", () => {
|
||||
makeMsg({
|
||||
event: "WORKSPACE_PROVISIONING",
|
||||
workspace_id: "ws-new",
|
||||
payload: { name: "Fresh", tier: 2 },
|
||||
payload: { name: "Fresh", tier: 2, runtime: "hermes" },
|
||||
})
|
||||
);
|
||||
|
||||
@ -281,6 +281,9 @@ describe("applyEvent", () => {
|
||||
expect(newNode.data.name).toBe("Fresh");
|
||||
expect(newNode.data.tier).toBe(2);
|
||||
expect(newNode.data.status).toBe("provisioning");
|
||||
// Runtime must flow through the provisioning event so the side-panel
|
||||
// pill renders the real runtime instead of "unknown" until a refetch.
|
||||
expect(newNode.data.runtime).toBe("hermes");
|
||||
// Position is offset by existing node count * 40
|
||||
expect(newNode.position.x).toBeGreaterThanOrEqual(0);
|
||||
expect(newNode.position.y).toBeGreaterThanOrEqual(0);
|
||||
|
||||
@ -145,6 +145,7 @@ export function handleCanvasEvent(
|
||||
url: "",
|
||||
parentId: null,
|
||||
currentTask: "",
|
||||
runtime: (msg.payload.runtime as string) ?? "",
|
||||
needsRestart: false,
|
||||
},
|
||||
},
|
||||
|
||||
@ -103,9 +103,10 @@ func (h *OrgHandler) createWorkspaceTree(ws OrgWorkspace, parentID *string, defa
|
||||
log.Printf("Org import: canvas layout insert failed for %s: %v", ws.Name, err)
|
||||
}
|
||||
|
||||
// Broadcast
|
||||
// Broadcast — include runtime so the canvas pill renders the right
|
||||
// badge immediately instead of "unknown".
|
||||
h.broadcaster.RecordAndBroadcast(ctx, "WORKSPACE_PROVISIONING", id, map[string]interface{}{
|
||||
"name": ws.Name, "tier": tier,
|
||||
"name": ws.Name, "tier": tier, "runtime": runtime,
|
||||
})
|
||||
|
||||
// Seed initial memories from workspace config or defaults (issue #1050).
|
||||
|
||||
@ -254,10 +254,14 @@ func (h *WorkspaceHandler) Create(c *gin.Context) {
|
||||
// Non-fatal: failures are logged but don't block workspace creation.
|
||||
seedInitialMemories(ctx, id, payload.InitialMemories, awarenessNamespace)
|
||||
|
||||
// Broadcast provisioning event
|
||||
// Broadcast provisioning event. Include `runtime` so the canvas can
|
||||
// populate the Runtime pill on the side panel immediately — without it
|
||||
// the node lives as "runtime: unknown" until something refetches the
|
||||
// workspace row (which nothing does during provisioning).
|
||||
h.broadcaster.RecordAndBroadcast(ctx, "WORKSPACE_PROVISIONING", id, map[string]interface{}{
|
||||
"name": payload.Name,
|
||||
"tier": payload.Tier,
|
||||
"name": payload.Name,
|
||||
"tier": payload.Tier,
|
||||
"runtime": payload.Runtime,
|
||||
})
|
||||
|
||||
// External workspaces: no container provisioning — just set the URL and mark online
|
||||
|
||||
@ -112,8 +112,9 @@ func (h *WorkspaceHandler) Restart(c *gin.Context) {
|
||||
db.DB.ExecContext(ctx,
|
||||
`UPDATE workspaces SET status = 'provisioning', url = '', updated_at = now() WHERE id = $1`, id)
|
||||
h.broadcaster.RecordAndBroadcast(ctx, "WORKSPACE_PROVISIONING", id, map[string]interface{}{
|
||||
"name": wsName,
|
||||
"tier": tier,
|
||||
"name": wsName,
|
||||
"tier": tier,
|
||||
"runtime": containerRuntime,
|
||||
})
|
||||
|
||||
// Read template from request body or try to find matching config
|
||||
@ -331,7 +332,7 @@ func (h *WorkspaceHandler) RestartByID(workspaceID string) {
|
||||
db.DB.ExecContext(ctx,
|
||||
`UPDATE workspaces SET status = 'provisioning', url = '', updated_at = now() WHERE id = $1`, workspaceID)
|
||||
h.broadcaster.RecordAndBroadcast(ctx, "WORKSPACE_PROVISIONING", workspaceID, map[string]interface{}{
|
||||
"name": wsName, "tier": tier,
|
||||
"name": wsName, "tier": tier, "runtime": dbRuntime,
|
||||
})
|
||||
|
||||
// Runtime from DB — no more config file parsing
|
||||
@ -463,7 +464,7 @@ func (h *WorkspaceHandler) Resume(c *gin.Context) {
|
||||
db.DB.ExecContext(ctx,
|
||||
`UPDATE workspaces SET status = 'provisioning', updated_at = now() WHERE id = $1`, ws.id)
|
||||
h.broadcaster.RecordAndBroadcast(ctx, "WORKSPACE_PROVISIONING", ws.id, map[string]interface{}{
|
||||
"name": ws.name, "tier": ws.tier,
|
||||
"name": ws.name, "tier": ws.tier, "runtime": ws.runtime,
|
||||
})
|
||||
payload := models.CreateWorkspacePayload{Name: ws.name, Tier: ws.tier, Runtime: ws.runtime}
|
||||
// Dispatch to the matching provisioner (mirrors the Create +
|
||||
|
||||
Loading…
Reference in New Issue
Block a user