fix(canvas+templates): fetch runtime dropdown from /templates registry - #1526
Conversation
Canvas hardcoded 6 runtime options, drifting from manifest.json which already registers hermes + gemini-cli as first-class workspace templates. A Hermes workspace had runtime=hermes in its DB row but Config showed "LangGraph (default)" — the HTML select fell back to its first option because "hermes" wasn't listed, and saving would clobber the runtime back to empty. Now: - GET /templates returns the runtime field from each cloned template's config.yaml (previously dropped on the floor) - ConfigTab fetches /templates on mount, dedupes non-empty runtimes, and renders them as <option>s. Falls back to the static list if the fetch fails (offline, older backend), so the control never renders empty. Adding a template to manifest.json now flows through automatically — no canvas PR required. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| from datetime import datetime, timezone | ||
| from typing import TYPE_CHECKING, Any | ||
|
|
||
| from .snapshot_scrub import scrub_snapshot |
| import os | ||
| import tempfile | ||
|
|
||
| import pytest |
| try: | ||
| os.remove(target) | ||
| logger.debug("Snapshot deleted: %s", target) | ||
| except FileNotFoundError: |
Extends the dropdown fix so Model and Required Env also flow from
the template registry instead of being free-form fields the user
has to remember.
Template config.yaml now declares:
runtime_config:
model: <default>
models:
- id: nous-hermes-3-70b
name: Nous Hermes 3 70B (Nous Portal)
required_env: [HERMES_API_KEY]
- id: nousresearch/hermes-3-llama-3.1-70b
name: Hermes 3 70B (via OpenRouter)
required_env: [OPENROUTER_API_KEY]
Platform: GET /templates now returns runtime + model + models[] per
template (was previously dropping runtime + ignoring runtime_config).
Canvas:
- Runtime dropdown built from /templates (was hardcoded 6 options)
- Model input becomes a datalist combobox; free-form input still
allowed since model names rotate faster than templates
- Required Env Vars default to the selected model's required_env,
labelled "(suggested)" so the user knows it's template-driven
- Everything falls back to a static list when /templates is
unreachable, so offline editing still works
Follow-up: add models[] to the other 7 template repos (claude-code,
crewai, autogen, deepagents, openclaw, gemini-cli, langgraph). This
PR updates the platform + canvas; the Hermes template config update
goes in a separate PR against its own repo.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Expanded scope (commit 82d7bf0)Extended this PR beyond runtime dropdown to cover Model + Required Env too — per user ask "the model should fetch dynamically what this runtime supports" + "the Required Env Vars should be dynamic as well, based on selected model". Schema now surfaced through `/templates````yaml Canvas ConfigTab
Paired
|
Self-review (five-axis)Re-read the diff cold. Findings below. TL;DR: one Important UX bug in how the "(suggested)" Required Env Vars propagate on save, plus missing tests. No Critical issues. Important: suggested required_env is cosmetic-onlyThe "Required Env Vars (suggested)" display is derived but never committed to state, which means saving doesn't write the suggested values to config.yaml. Trace: ```tsx
Concrete scenario: User selects "MiniMax M2.7 (via OpenRouter)", UI shows `OPENROUTER_API_KEY` as suggested, saves, workspace fails because config still has `HERMES_API_KEY` as the required env. Fix: when the model input's value becomes an exact match for a known modelSpec id, commit that model's required_env to state — IF the current required_env is empty OR matches the previously-selected modelSpec's required_env (i.e. was itself template-driven, not user-edited). Rough shape: ```tsx Important: no tests
Per the user's own rule "E2E tests must verify data flow", the minimal adequate coverage here is a backend unit test on List(). Canvas test is nice-to-have. Optional: duplicate-id defence`` in the datalist — if a template author accidentally ships two model entries with the same id, React emits a dup-key warning and `availableModels.find((m) => m.id === currentModelId)` silently returns only the first, hiding the second entry's env. Use `key={`${m.id}-${i}`}` and consider a dev-time warning if backend returns duplicate ids per template. Optional: extract RuntimeAndModelSectionConfigTab is ~370 lines and growing. The new runtime/model/env block is ~60 self-contained lines — reasonable extraction when someone next touches this file. Nit: string-match special case`if (!v || v === "langgraph") continue;` — skip logic keyed on a literal. A const `const DEFAULT_RUNTIME = "langgraph"` would make it more grep-friendly. Not blocking. FYI: not browser-verifiedCanvas is deployed from Vercel, so this branch can't be exercised in browser until merge + Vercel redeploy. Went as far as typecheck + Go build + cold diff re-read. Acknowledged risk: visual/rendering regressions in the datalist combobox that only show up in Safari/Firefox. VerdictRequest changes — primarily to fix the required_env propagation bug. Everything else is optional/nit. |
Review turned up that the \"Required Env Vars (suggested)\" display was cosmetic-only — users picking a different model saw the new env suggestion in the TagList, but the values never made it into state, so Save serialized an empty (or stale) required_env and the workspace ran with the wrong auth check. Canvas fixes: - Model input onChange now commits the matched modelSpec's required_env to state — but only when the prior required_env was empty or matched the previous modelSpec's list (i.e. user hadn't manually edited). User-typed envs always win. - Dropped the display-only fallback in TagList values; shows only what's actually in state. - New \"Template suggests X, Apply\" hint button covers the edge case where state and template differ (existing workspace whose required_env lags the template's current recommendation). - datalist option key now includes index so template authors shipping duplicate model ids don't trigger a silent React key collision. - Small arraysEqual helper. Backend tests: - TestTemplatesList_RuntimeAndModelsRegistry — asserts /templates response carries runtime + models[] with per-model required_env. - TestTemplatesList_LegacyTopLevelModel — asserts older templates with top-level model: still surface correctly, with empty Models[]. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Review fixes applied (commit 6f06ebf)Important #1: required_env propagation
Important #2: backend tests
Optional: duplicate-id defence
Deferred (optional, not blocking)
Verification
Re-review: verdict flipped from Request changes → ready for merge modulo normal CI. |
There was a problem hiding this comment.
PR #1526 — fix(canvas+templates): fetch runtime dropdown from /templates registry
Approve. Solid multi-part fix. A few observations to document for future work:
Main fix (runtime field surfacing): Correct. templateSummary now includes runtime, and canvas/ConfigTab dynamically builds the dropdown from GET /templates. The silent fallback to first-option (the original bug) is fully resolved.
SSRF hardening (ssrf.go): Good defence-in-depth. Blocking RFC-1918 + metadata + link-local at the URL-resolution layer protects the A2A proxy path. The net.LookupHost approach correctly handles DNS rebinding over time. Test coverage is comprehensive.
E2E pipeline additions (canary, full SaaS, canvas Playwright): Worth calling out: the 30-min canary catches regressions between nightly runs, the canvas Playwright suite exercises the actual runtime dropdown in-browser, and the e2e-canvas teardown safety-net is a good pattern. These complement the existing API-level tests.
Org import (org_import.go): The workspace tree creation logic is clean. Env var expansion in workspace_dir (the ${WORKSPACE_DIR} fix) is handled correctly before validation. Plugin pre-install, channel adapters, and schedule cron expressions all have appropriate error handling with graceful skips.
One minor note for follow-up: ConfigTab.tsx adds ~150 lines with a new data-fetching pattern (/templates → build dropdown). If the /templates endpoint ever returns a template with a runtime value not in the static fallback list, the dropdown will still render the full runtime value from the registry. That's the right behavior, just worth verifying once against a Hermes-registered template on staging.
Clean fix, approved.
…-agent) Claude Code supports two auth paths that use different env vars: - OAuth (via `claude login`) → CLAUDE_CODE_OAUTH_TOKEN, tied to a Claude Code subscription - Direct API key → ANTHROPIC_API_KEY, pay-as-you-go via the Anthropic Console Previously the template only listed CLAUDE_CODE_OAUTH_TOKEN, hiding the API-key path and forcing API-key users to override manually. Now models[] exposes both as distinct dropdown entries — users pick the one matching the credential they have; canvas auto-suggests the right env var. Model IDs differ intentionally: - OAuth entries use CLI aliases (sonnet/opus/haiku — resolve to latest) - API-key entries use explicit versioned ids (claude-sonnet-4-6, etc.) claude CLI accepts either auth style transparently — OAuth wins when both are set, which preserves existing workspace behaviour. Paired with EnterOS-AI/enter-os-core#1526 (platform + canvas). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…in-dev-agent) * feat(config): add models[] registry with per-model required_env Canvas now reads this list to populate the Model dropdown and suggest the correct API-key env vars when a model is selected, so users don't have to remember which key each provider wants. Paired with EnterOS-AI/enter-os-core#1526 on the platform side — older platforms just ignore the new `models:` key, so this change is backward-compatible. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(config): expose MiniMax models in canvas dropdown MiniMax was already registered as a provider in providers.py (line 169, MINIMAX_API_KEY → api.minimax.io/v1) but users had no way to discover it from canvas. Adding 4 entries so the Hermes template's Model dropdown surfaces both direct (MiniMax-M2.7, MiniMax-M1) and OpenRouter-proxied (minimax/minimax-m2.7, minimax/minimax-m1) paths. M2.7 is MiniMax's flagship coding-tuned model (~197K ctx). M1 is the open-weight 1M-context reasoning model, useful for long-context tasks without leaving the OSS ecosystem. Description updated to reflect the full provider matrix. No providers.py changes — MiniMax was already wired. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs+feat: clarify MiniMax Token Plan compat, add M2.7-highspeed MiniMax Token Plan keys (sk-cp-*) and pay-as-you-go keys (sk-api-*) both go through the same MINIMAX_API_KEY env var and same api.minimax.io/v1 endpoint — MiniMax's backend routes by key prefix. No code changes needed; comment added so operators know either key works. MiniMax-M2.7-highspeed added as a 9th model entry. It's a Token-Plan-exclusive variant (same model, same price, higher rate limits — the one you pick if you pay for the subscription tier). Labelled so users without a Token Plan know to pick regular M2.7. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fad4065 to
0506e0c
Compare
…-agent) Claude Code supports two auth paths that use different env vars: - OAuth (via `claude login`) → CLAUDE_CODE_OAUTH_TOKEN, tied to a Claude Code subscription - Direct API key → ANTHROPIC_API_KEY, pay-as-you-go via the Anthropic Console Previously the template only listed CLAUDE_CODE_OAUTH_TOKEN, hiding the API-key path and forcing API-key users to override manually. Now models[] exposes both as distinct dropdown entries — users pick the one matching the credential they have; canvas auto-suggests the right env var. Model IDs differ intentionally: - OAuth entries use CLI aliases (sonnet/opus/haiku — resolve to latest) - API-key entries use explicit versioned ids (claude-sonnet-4-6, etc.) claude CLI accepts either auth style transparently — OAuth wins when both are set, which preserves existing workspace behaviour. Paired with EnterOS-AI/enter-os-core#1526 (platform + canvas). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Claude Code supports two auth paths that use different env vars: - OAuth (via `claude login`) → CLAUDE_CODE_OAUTH_TOKEN, tied to a Claude Code subscription - Direct API key → ANTHROPIC_API_KEY, pay-as-you-go via the Anthropic Console Previously the template only listed CLAUDE_CODE_OAUTH_TOKEN, hiding the API-key path and forcing API-key users to override manually. Now models[] exposes both as distinct dropdown entries — users pick the one matching the credential they have; canvas auto-suggests the right env var. Model IDs differ intentionally: - OAuth entries use CLI aliases (sonnet/opus/haiku — resolve to latest) - API-key entries use explicit versioned ids (claude-sonnet-4-6, etc.) claude CLI accepts either auth style transparently — OAuth wins when both are set, which preserves existing workspace behaviour. Paired with EnterOS-AI/enter-os-core#1526 (platform + canvas). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…forward-port fix(canvas): forward-port dynamic runtime dropdown (#1526) to main
Two related workflow hygiene changes: ## (1) canary-verify: graceful-skip when canary secrets absent Before: canary-verify hit `scripts/canary-smoke.sh` which exited non-zero when CANARY_TENANT_URLS was empty. Every main publish ran → canary-verify failed → red check on main CI signal (7/7 in past 24h). Noise, no value. After: smoke step detects the missing-secrets case, writes a warning to the step summary, sets an output `smoke_ran=false`, and exits 0. The workflow completes green without pretending to have tested anything. Gated downstream: `promote-to-latest` now requires BOTH `needs.canary-smoke.result == success` AND `needs.canary-smoke.outputs.smoke_ran == true`. A skip does NOT auto-promote — manual `promote-latest.yml` remains the release gate while Phase 2 canary is absent (see molecule-controlplane/docs/canary-tenants.md for the fleet stand-up plan + decision framework). When the canary fleet is stood up and secrets populated: delete the early-exit branch + the smoke_ran gate. The workflow goes back to its original "smoke gates promotion" semantics. ## (2) auto-promote-staging.yml — draft New workflow that fires after CI / E2E Staging Canvas / E2E API / CodeQL complete on the staging branch, checks that ALL four are green on the same SHA, and fast-forwards `main` to that SHA. Shipped disabled: the promote step is gated behind repo variable `AUTO_PROMOTE_ENABLED=true`. Until that's set, the workflow dry-runs and logs what it would have done. Toggle via Settings → Variables when staging CI has been reliably green for a few days. Safety: - workflow_run events only fire on push to staging (PRs into staging don't promote). - Every required gate must be `completed/success` on the same head_sha. Pending / failed / skipped / cancelled → abort. - `--ff-only` push. Refuses to advance main if it has diverged from staging history (someone landed a direct-to-main commit that's not on staging). Human resolves the fork. - `workflow_dispatch` with `force=true` lets us test the flow end-to-end before flipping the variable on. Motivation: molecule-core#1496 has been open with 1172 commits divergence between staging and main. Today that trapped PR #1526 (dynamic canvas runtime dropdown) on staging while prod users hit the hardcoded-dropdown bug. Auto-promote retires the bulk staging→main PR pattern once the staging CI it depends on is reliable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…1526) PR #1526 shipped the /templates registry + canvas dynamic Runtime / Model / Required-Env fields on 2026-04-22 — but merged into the staging branch, not main. The staging→main promotion PR #1496 has been open unmerged for a while with 1172 commits divergence, so prod (which builds from main) still carries the old hardcoded dropdown. Symptom seen on hongmingwang.moleculesai.app today: - New Hermes Agent workspace (template declares runtime: hermes) loads Config tab → Runtime dropdown shows "LangGraph (default)" because there's no <option value="hermes"> in the hardcoded list; it falls back to empty-value silently. - Model field is a plain TextInput with static placeholder "e.g. anthropic:claude-sonnet-4-6" — should be a combobox populated from the selected runtime's models[]. - Required Env Vars is a TagList with static placeholder "e.g. CLAUDE_CODE_OAUTH_TOKEN" — should auto-populate from the selected model's required_env. - Net effect: "Save & Deploy" sends empty model + empty env to the provisioner → workspace instant-fails. This PR cherry-picks the exact three files from PR #1526 (#359dc61 on staging) forward to main, without pulling the other 1171 commits: - canvas/src/components/tabs/ConfigTab.tsx - RuntimeOption interface + FALLBACK_RUNTIME_OPTIONS (hermes, gemini-cli included) - useEffect fetches /templates and populates runtimeOptions dynamically - dropdown renders from runtimeOptions (no hardcoded list) - Model becomes a combobox with datalist of available models per selected runtime - Required Env Vars auto-populates from the selected model's required_env on model change - workspace-server/internal/handlers/templates.go - /templates endpoint returns [{id, name, runtime, models}] with per-template models registry (id, name, required_env) - workspace-server/internal/handlers/templates_test.go - Tests for runtime+models parsing and legacy top-level model fallback The canvas Runtime dropdown now resolves "hermes" correctly; Model dropdown shows the models[] from the hermes template; Env auto-populates with HERMES_API_KEY (or whichever model selected). Verified locally: - workspace-server builds clean - Template handler tests pass: TestTemplatesList_RuntimeAndModelsRegistry, TestTemplatesList_LegacyTopLevelModel, TestTemplatesList_NonexistentDir Follow-up: the staging→main promotion gap (#1496) is the underlying process issue. Either merge that PR or adopt a policy of landing fixes directly on main (as several PRs have today). Files here were chosen minimally to avoid pulling unrelated staging changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two related workflow hygiene changes: ## (1) canary-verify: graceful-skip when canary secrets absent Before: canary-verify hit `scripts/canary-smoke.sh` which exited non-zero when CANARY_TENANT_URLS was empty. Every main publish ran → canary-verify failed → red check on main CI signal (7/7 in past 24h). Noise, no value. After: smoke step detects the missing-secrets case, writes a warning to the step summary, sets an output `smoke_ran=false`, and exits 0. The workflow completes green without pretending to have tested anything. Gated downstream: `promote-to-latest` now requires BOTH `needs.canary-smoke.result == success` AND `needs.canary-smoke.outputs.smoke_ran == true`. A skip does NOT auto-promote — manual `promote-latest.yml` remains the release gate while Phase 2 canary is absent (see molecule-controlplane/docs/canary-tenants.md for the fleet stand-up plan + decision framework). When the canary fleet is stood up and secrets populated: delete the early-exit branch + the smoke_ran gate. The workflow goes back to its original "smoke gates promotion" semantics. ## (2) auto-promote-staging.yml — draft New workflow that fires after CI / E2E Staging Canvas / E2E API / CodeQL complete on the staging branch, checks that ALL four are green on the same SHA, and fast-forwards `main` to that SHA. Shipped disabled: the promote step is gated behind repo variable `AUTO_PROMOTE_ENABLED=true`. Until that's set, the workflow dry-runs and logs what it would have done. Toggle via Settings → Variables when staging CI has been reliably green for a few days. Safety: - workflow_run events only fire on push to staging (PRs into staging don't promote). - Every required gate must be `completed/success` on the same head_sha. Pending / failed / skipped / cancelled → abort. - `--ff-only` push. Refuses to advance main if it has diverged from staging history (someone landed a direct-to-main commit that's not on staging). Human resolves the fork. - `workflow_dispatch` with `force=true` lets us test the flow end-to-end before flipping the variable on. Motivation: molecule-core#1496 has been open with 1172 commits divergence between staging and main. Today that trapped PR #1526 (dynamic canvas runtime dropdown) on staging while prod users hit the hardcoded-dropdown bug. Auto-promote retires the bulk staging→main PR pattern once the staging CI it depends on is reliable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…1526) PR #1526 shipped the /templates registry + canvas dynamic Runtime / Model / Required-Env fields on 2026-04-22 — but merged into the staging branch, not main. The staging→main promotion PR #1496 has been open unmerged for a while with 1172 commits divergence, so prod (which builds from main) still carries the old hardcoded dropdown. Symptom seen on hongmingwang.moleculesai.app today: - New Hermes Agent workspace (template declares runtime: hermes) loads Config tab → Runtime dropdown shows "LangGraph (default)" because there's no <option value="hermes"> in the hardcoded list; it falls back to empty-value silently. - Model field is a plain TextInput with static placeholder "e.g. anthropic:claude-sonnet-4-6" — should be a combobox populated from the selected runtime's models[]. - Required Env Vars is a TagList with static placeholder "e.g. CLAUDE_CODE_OAUTH_TOKEN" — should auto-populate from the selected model's required_env. - Net effect: "Save & Deploy" sends empty model + empty env to the provisioner → workspace instant-fails. This PR cherry-picks the exact three files from PR #1526 (#359dc61 on staging) forward to main, without pulling the other 1171 commits: - canvas/src/components/tabs/ConfigTab.tsx - RuntimeOption interface + FALLBACK_RUNTIME_OPTIONS (hermes, gemini-cli included) - useEffect fetches /templates and populates runtimeOptions dynamically - dropdown renders from runtimeOptions (no hardcoded list) - Model becomes a combobox with datalist of available models per selected runtime - Required Env Vars auto-populates from the selected model's required_env on model change - workspace-server/internal/handlers/templates.go - /templates endpoint returns [{id, name, runtime, models}] with per-template models registry (id, name, required_env) - workspace-server/internal/handlers/templates_test.go - Tests for runtime+models parsing and legacy top-level model fallback The canvas Runtime dropdown now resolves "hermes" correctly; Model dropdown shows the models[] from the hermes template; Env auto-populates with HERMES_API_KEY (or whichever model selected). Verified locally: - workspace-server builds clean - Template handler tests pass: TestTemplatesList_RuntimeAndModelsRegistry, TestTemplatesList_LegacyTopLevelModel, TestTemplatesList_NonexistentDir Follow-up: the staging→main promotion gap (#1496) is the underlying process issue. Either merge that PR or adopt a policy of landing fixes directly on main (as several PRs have today). Files here were chosen minimally to avoid pulling unrelated staging changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two related workflow hygiene changes: ## (1) canary-verify: graceful-skip when canary secrets absent Before: canary-verify hit `scripts/canary-smoke.sh` which exited non-zero when CANARY_TENANT_URLS was empty. Every main publish ran → canary-verify failed → red check on main CI signal (7/7 in past 24h). Noise, no value. After: smoke step detects the missing-secrets case, writes a warning to the step summary, sets an output `smoke_ran=false`, and exits 0. The workflow completes green without pretending to have tested anything. Gated downstream: `promote-to-latest` now requires BOTH `needs.canary-smoke.result == success` AND `needs.canary-smoke.outputs.smoke_ran == true`. A skip does NOT auto-promote — manual `promote-latest.yml` remains the release gate while Phase 2 canary is absent (see molecule-controlplane/docs/canary-tenants.md for the fleet stand-up plan + decision framework). When the canary fleet is stood up and secrets populated: delete the early-exit branch + the smoke_ran gate. The workflow goes back to its original "smoke gates promotion" semantics. ## (2) auto-promote-staging.yml — draft New workflow that fires after CI / E2E Staging Canvas / E2E API / CodeQL complete on the staging branch, checks that ALL four are green on the same SHA, and fast-forwards `main` to that SHA. Shipped disabled: the promote step is gated behind repo variable `AUTO_PROMOTE_ENABLED=true`. Until that's set, the workflow dry-runs and logs what it would have done. Toggle via Settings → Variables when staging CI has been reliably green for a few days. Safety: - workflow_run events only fire on push to staging (PRs into staging don't promote). - Every required gate must be `completed/success` on the same head_sha. Pending / failed / skipped / cancelled → abort. - `--ff-only` push. Refuses to advance main if it has diverged from staging history (someone landed a direct-to-main commit that's not on staging). Human resolves the fork. - `workflow_dispatch` with `force=true` lets us test the flow end-to-end before flipping the variable on. Motivation: molecule-core#1496 has been open with 1172 commits divergence between staging and main. Today that trapped PR #1526 (dynamic canvas runtime dropdown) on staging while prod users hit the hardcoded-dropdown bug. Auto-promote retires the bulk staging→main PR pattern once the staging CI it depends on is reliable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…1526) PR #1526 shipped the /templates registry + canvas dynamic Runtime / Model / Required-Env fields on 2026-04-22 — but merged into the staging branch, not main. The staging→main promotion PR #1496 has been open unmerged for a while with 1172 commits divergence, so prod (which builds from main) still carries the old hardcoded dropdown. Symptom seen on hongmingwang.moleculesai.app today: - New Hermes Agent workspace (template declares runtime: hermes) loads Config tab → Runtime dropdown shows "LangGraph (default)" because there's no <option value="hermes"> in the hardcoded list; it falls back to empty-value silently. - Model field is a plain TextInput with static placeholder "e.g. anthropic:claude-sonnet-4-6" — should be a combobox populated from the selected runtime's models[]. - Required Env Vars is a TagList with static placeholder "e.g. CLAUDE_CODE_OAUTH_TOKEN" — should auto-populate from the selected model's required_env. - Net effect: "Save & Deploy" sends empty model + empty env to the provisioner → workspace instant-fails. This PR cherry-picks the exact three files from PR #1526 (#359dc61 on staging) forward to main, without pulling the other 1171 commits: - canvas/src/components/tabs/ConfigTab.tsx - RuntimeOption interface + FALLBACK_RUNTIME_OPTIONS (hermes, gemini-cli included) - useEffect fetches /templates and populates runtimeOptions dynamically - dropdown renders from runtimeOptions (no hardcoded list) - Model becomes a combobox with datalist of available models per selected runtime - Required Env Vars auto-populates from the selected model's required_env on model change - workspace-server/internal/handlers/templates.go - /templates endpoint returns [{id, name, runtime, models}] with per-template models registry (id, name, required_env) - workspace-server/internal/handlers/templates_test.go - Tests for runtime+models parsing and legacy top-level model fallback The canvas Runtime dropdown now resolves "hermes" correctly; Model dropdown shows the models[] from the hermes template; Env auto-populates with HERMES_API_KEY (or whichever model selected). Verified locally: - workspace-server builds clean - Template handler tests pass: TestTemplatesList_RuntimeAndModelsRegistry, TestTemplatesList_LegacyTopLevelModel, TestTemplatesList_NonexistentDir Follow-up: the staging→main promotion gap (#1496) is the underlying process issue. Either merge that PR or adopt a policy of landing fixes directly on main (as several PRs have today). Files here were chosen minimally to avoid pulling unrelated staging changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two related workflow hygiene changes: ## (1) canary-verify: graceful-skip when canary secrets absent Before: canary-verify hit `scripts/canary-smoke.sh` which exited non-zero when CANARY_TENANT_URLS was empty. Every main publish ran → canary-verify failed → red check on main CI signal (7/7 in past 24h). Noise, no value. After: smoke step detects the missing-secrets case, writes a warning to the step summary, sets an output `smoke_ran=false`, and exits 0. The workflow completes green without pretending to have tested anything. Gated downstream: `promote-to-latest` now requires BOTH `needs.canary-smoke.result == success` AND `needs.canary-smoke.outputs.smoke_ran == true`. A skip does NOT auto-promote — manual `promote-latest.yml` remains the release gate while Phase 2 canary is absent (see molecule-controlplane/docs/canary-tenants.md for the fleet stand-up plan + decision framework). When the canary fleet is stood up and secrets populated: delete the early-exit branch + the smoke_ran gate. The workflow goes back to its original "smoke gates promotion" semantics. ## (2) auto-promote-staging.yml — draft New workflow that fires after CI / E2E Staging Canvas / E2E API / CodeQL complete on the staging branch, checks that ALL four are green on the same SHA, and fast-forwards `main` to that SHA. Shipped disabled: the promote step is gated behind repo variable `AUTO_PROMOTE_ENABLED=true`. Until that's set, the workflow dry-runs and logs what it would have done. Toggle via Settings → Variables when staging CI has been reliably green for a few days. Safety: - workflow_run events only fire on push to staging (PRs into staging don't promote). - Every required gate must be `completed/success` on the same head_sha. Pending / failed / skipped / cancelled → abort. - `--ff-only` push. Refuses to advance main if it has diverged from staging history (someone landed a direct-to-main commit that's not on staging). Human resolves the fork. - `workflow_dispatch` with `force=true` lets us test the flow end-to-end before flipping the variable on. Motivation: molecule-core#1496 has been open with 1172 commits divergence between staging and main. Today that trapped PR #1526 (dynamic canvas runtime dropdown) on staging while prod users hit the hardcoded-dropdown bug. Auto-promote retires the bulk staging→main PR pattern once the staging CI it depends on is reliable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
The bug
A Hermes workspace had `runtime=hermes` in the DB row (top badge rendered "hermes") but the Config dropdown showed "LangGraph (default)" because "hermes" wasn't among the hardcoded options. HTML `` silently fell back to the first option, and saving would have clobbered the runtime back to empty. Test plan [ ] Rebuild workspace-server image, hit `GET /templates` → response includes `runtime` field per entry [ ] Load canvas ConfigTab on a Hermes workspace → dropdown shows "Hermes Agent" as selected [ ] Load canvas offline (e.g. tenant unreachable) → dropdown falls back to the static 8-option list [ ] Add a new template to manifest.json, redeploy → appears in dropdown without code change Fixes the user-visible issue in `hongmingwang.moleculesai.app` where the Hermes workspace's Config tab showed the wrong runtime.