ContextMenu.tsx reads parent-workspace children via
useCanvasStore.getState().nodes.filter(...) — a direct .getState()
call, not the selector-calling form. The existing vi.mock exposed
only the selector form, so rendering crashed with
"TypeError: useCanvasStore.getState is not a function".
Restructure the vi.mock factory to return Object.assign(fn, {
getState: () => mockStore }) so both call shapes resolve. Factory body
builds the function locally because vi.mock hoists above outer-scope
variable declarations and can't reference `mockStore` via closure.
Verified: all 15 tests in the file pass after the change.
Unblocks the Canvas (Next.js) CI check on PR #1743 (staging→main sync).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The E2E posts a bare "gpt-4o" as the workspace model. Hermes
template's derive-provider.sh parses the slug PREFIX (before the
slash) to set HERMES_INFERENCE_PROVIDER at install time. With no
prefix, provider falls back to hermes's auto-detect, which picks
the compiled-in Anthropic default. Hermes-agent then tries the
Anthropic API with the OpenAI key the E2E passed in SECRETS_JSON
and returns 401 "Invalid API key" at step 8/11 (A2A call).
Same trap PR #1714 fixed for the canvas Create flow. The E2E
was quietly broken on the same vector — it masked before today
because workspaces never reached "online" (pre-#231 install.sh
hook missing on staging; staging now deploys #231 via CP #236).
Fix: pin MODEL_SLUG="openai/gpt-4o" since the E2E's secret is
always the OpenAI key. Non-hermes runtimes ignore the prefix.
Now that both layers are fixed (install.sh runs AND the slug
steers hermes to OpenAI), the E2E should reach step 11/11.
Evidence from run 24822173171 attempt 2 (post-CP-#236 deploy):
07:55:25 ✅ CP reachable
07:57:28 ✅ Tenant provisioning complete (2:03, canary)
08:04:56 ✅ Workspace 52107c1a online (7:28, install.sh ran!)
08:05:06 ✅ Workspace 34a286df online
08:05:06 ❌ A2A 401 — hermes tried Anthropic with OpenAI key
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Existing external-agent-registration.md is 784 lines — great reference
but hostile to first-time devs evaluating Molecule. Add a tight
5-minute quickstart aimed at "make it work today":
- 40-line Python agent with A2A JSON-RPC skeleton
- Cloudflare quick-tunnel for instant public URL (no account)
- Single curl registration
- Common gotchas table (includes the canvas dedup + tunnel rotation
issues caught in the demo this afternoon)
- Production upgrade path
- Preview of polling mode (Phase N+1 transport)
- 4-step diagnostic checklist at the bottom
The reference doc (external-agent-registration.md) now has a prominent
"in a hurry?" callout pointing at the quickstart, so the discovery
path works either way.
Target audience: a developer who wants to see their code on canvas
inside 5 minutes, not a self-hoster hardening for prod.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Staging added hasChildren/children fields to workspace store shape.
Test assertion updated to use objectContaining to avoid false negatives.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs(devrel): add Phase 30 hero video — 3 aspect ratio cuts
Primary (16:9), social (9:16), and LinkedIn (1:1) cuts.
47.95s, 30fps H.264, dark zinc theme, burn-in captions, VO track.
Assembled from:
- marketing/assets/phase30-fleet-diagram.png
- marketing/audio/phase30-video-vo.mp3
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs(marketing): fill Discord adapter Day 2 blog URL — ready for Apr 22 push
Adds https://moleculesai.app/blog/discord-adapter to both Reddit
(r/LocalLLaMA) and Hacker News post bodies. Updates status line and
draft attribution. Reddit/HN copy is now complete and ready for
Social Media Brand coordination.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(marketing): correct Discord adapter blog URL — discord-adapter → 2026-04-21-discord-adapter
Fixes broken link in Reddit and HN Day 2 copy. Correct slug is
/blog/2026-04-21-discord-adapter.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Molecule AI Community Manager <community-manager@agents.moleculesai.app>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Molecule AI Technical Writer <technical-writer@agents.moleculesai.app>
* docs(social): EC2 Instance Connect SSH launch copy + terminal demo visual
PR #1533 (feat/terminal: remote path via aws ec2-instance-connect + pty)
Issue #1547 (social: launch thread for EC2 Instance Connect SSH)
Content:
- docs/marketing/social/2026-04-22-ec2-instance-connect-ssh/social-copy.md
5-post X thread + LinkedIn single post, dark theme brand voice
- docs/assets/blog/2026-04-22-ec2-instance-connect-ssh/ec2-terminal-demo.png (1200x800)
Canvas Terminal tab mockup showing EC2 bash prompt via EIC
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs(blog): AI agent credential model — one key, named, monitored
Companion post to the enterprise-key-management launch post.
Focuses on the agent-specific angle: dynamic tool interfaces,
emergent behavior containment, delegation chains, and the
security properties that survive agent compromise.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Molecule AI Social Media Brand <social-media-brand@agents.moleculesai.app>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Molecule AI DevRel Engineer <devrel-engineer@agents.moleculesai.app>
F1085 (Misconfiguration - Filesystems): the 2-arg exec form
[]string{"rm", "-rf", "/configs", filePath} passes /configs as
an rm target, so rm -rf /configs deletes the entire volume mount
regardless of what filePath resolves to.
Fix uses filepath.Join + filepath.Clean + HasPrefix assertion to
scope rm to the /configs/ prefix. validateRelPath (CWE-22) catches
leading/mid-path ".." before rm. HasPrefix guard is defence-in-depth.
Includes CP-BE's 12-case regression test suite (docker: nil,
validates all traversal forms rejected before Docker call).
Co-Authored-By: molecule-ai[bot] <276602405+molecule-ai[bot]@users.noreply.github.com>
Co-Authored-By: Molecule AI CP-BE <cp-be@agents.moleculesai.app>
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>
Three changes to stop ferrying sensitive content through our public
monorepo. All content already imported to Molecule-AI/internal (private)
— see linked PRs below.
Contained full security audit cycle records with CWE references,
file:line pointers to historical vulnerabilities, and severity
ratings. None of that belongs in a public repo.
→ Moved to Molecule-AI/internal/security/incident-log.md (PR #20).
Monorepo file becomes a 17-line stub pointing at the internal
location. Future incidents land in the internal file only.
Had AWS account ID `004947743811` and IAM role name
`MoleculeStagingProvisioner` embedded. Even though the fleet
described isn't actually running (see state note), these
identifiers are account-specific and don't belong in public git.
→ Removed both values, replaced with generic references + a pointer
to Molecule-AI/internal/runbooks/canary-fleet.md (PR #21) where
the actual identifiers live. Any future rotation touches the
internal file, no public-git-history rewrite needed.
Contained the full ops runbook: bootstrap script output, per-tenant
SG backfill loop with live SG IDs, customer slug names
(hongmingwang). Useful content but too specific for a public repo.
→ Moved to Molecule-AI/internal/runbooks/workspace-terminal.md
(PR #22). Monorepo file becomes a 30-line public summary of what
the feature does + pointers to code, so external readers /
self-hosters still get the design story.
Marketing briefs, SEO plans, campaign copy, research dossiers, and
internal product designs (hermes-adapter-plan, medo-integration,
cognee-*) are the next batches. See docs policy doc coming next to
set team expectations.
Net removal: ~820 lines from public git going forward.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Day 4: EC2 Console Output — approved by Marketing Lead + PM
Day 5: Org-Scoped API Keys — approved by Marketing Lead + PM
Both campaigns queued for Apr 24 and Apr 25.
Co-authored-by: Marketing Lead <marketing-lead@agents.moleculesai.app>
* docs(canary-release): flag as aspirational; link to current state
The canary-release.md doc describes the pipeline as if the fleet is
running — referring to AWS account 004947743811 and a configured
MoleculeStagingProvisioner role. Reality as of 2026-04-22: no canary
tenants are provisioned, the 3 GH Actions secrets are empty, and
canary-verify.yml has failed 7/7 times in a row.
Added a top-of-doc ⚠️ state note that:
1. Clarifies this is intended design, not deployed reality.
2. Notes the AWS account ID is historical / unverified.
3. Explains that merges currently rely on manual promote-latest.
4. Cross-links to molecule-controlplane/docs/canary-tenants.md for
the Phase 1 work that's shipped, the Phase 2 stand-up plan, and
the "should we even do this now?" decision framework.
5. Asks whoever lands Phase 2 to reconcile the two docs.
No behaviour change — doc-only.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(build): add missing fmt import in a2a_proxy.go, fix canvas Dockerfile GID
- a2a_proxy.go: missing "fmt" import caused build failure (8 undefined
references at lines 743-775). Likely dropped during a recent merge.
- canvas/Dockerfile: GID 1000 already in use in node base image.
Changed to dynamic group/user creation with fallback.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Hongming Wang <hongmingwang.rabbit@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Hongming Wang <hongmingwangrabbit@gmail.com>
- Add Version E: ephemeral key story (60-second RSA key lifecycle)
- Elevate Version D: zero key rot angle with explicit 60-second key window
- Add Version A/D as approved primary angles (ops simplicity / security)
- Update status to APPROVED, unblocked for Social Media Brand
- Add header: positioning angle confirmed per GH issue #1637
- Add image suggestion for ephemeral key timeline graphic
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three-channel brief covering partner platforms, marketplace resellers,
and enterprise CI/CD automation. Links to Phase 30 (mol_ws_* token model)
as cross-sell. Flags first-mover opportunity vs CrewAI/LangGraph Cloud.
Collocates collateral gap list and open PM questions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds competitive differentiation section explicitly calling out the
governance layer gap in LangGraph's current A2A PRs vs Molecule AI's
Phase 30 production implementation. Canonical URL verified correct.
Closes PMM A2A blog final-review item.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- PRs #6645, #7113, #7205 not found in langchain-ai/langgraph open PR list
- Added VERIFY flags to LangGraph tracker; requires manual re-check
- Updated market events log with verification result
- Battlecard v0.3 LangGraph status is now flagged as stale pending re-verify
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two latent bugs the self-hosted Mac mini had been hiding. Both caught
by the newer toolchain on ubuntu-latest runners after PR #1626.
1. workspace-server/internal/handlers/terminal.go:442
`fmt.Sprintf("%s:%d", host, port)` flagged by go vet as unsafe
for IPv6 (it omits the required [::] brackets). Replaced with
`net.JoinHostPort(host, strconv.Itoa(port))` which handles both
IPv4 and IPv6 correctly. No runtime behaviour change — the only
call site passes "127.0.0.1", so the bug would never trigger in
practice, but vet is right to flag it as a latent correctness
issue.
2. workspace/tests/test_a2a_executor.py::test_set_current_task_updates_heartbeat
`MagicMock()` auto-creates attributes on first access, so
`getattr(heartbeat, "active_tasks", 0)` in shared_runtime.py
returned a MagicMock rather than the default 0. Adding 1 to a
MagicMock returns another MagicMock, so the assertion
`heartbeat.active_tasks == 1` never held. Seeding
`heartbeat.active_tasks = 0` before the first call makes
getattr() return a real int, matching how the real HeartbeatLoop
class initialises itself.
Both pre-existed on main and were hidden by the older Python / Go
toolchains on the Mac mini runner. Verified locally (venv pytest
pass, `go vet ./...` + `go build ./...` clean on workspace-server).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
molecule-core is a public repo — GHA-hosted minutes are free. The
self-hosted Mac mini was only in play to dodge GHA rate limits
(memory feedback_selfhosted_runner), but for these specific
workflows it came with real costs:
- Docker-push workflows emulated linux/amd64 from arm64 via QEMU —
every canvas + platform image build ran ~2-3x slower than native.
- Six PRs worth of keychain-avoidance hacks in publish-* because
`docker login` on macOS writes to osxkeychain unconditionally,
and the Mac mini's launchd user-agent keychain is locked.
- Homebrew pin-down environment variables (HOMEBREW_NO_*) sprinkled
everywhere to work around the shared /opt/homebrew symlink mess
on the runner.
- Setup-python@v5 couldn't write to /Users/runner, so ci.yml
python-lint resorted to a hand-rolled Homebrew python3.11 dance.
- Single runner → fan-out contention; CodeQL's 45-min analysis
fought the canvas publish for the one slot.
Changes across the 7 workflows:
- runs-on: [self-hosted, macos, arm64] → ubuntu-latest (every job)
- publish-canvas-image + publish-workspace-server-image:
drop the hand-rolled auths-map step + QEMU setup + buildx v4
→ docker/login-action@v3 + setup-buildx@v3. Linux + amd64
target = native build.
- canary-verify + promote-latest: replace `brew install crane` +
HOMEBREW_NO_* incantations with imjasonh/setup-crane@v0.4.
- codeql.yml: drop `brew install jq` — jq is preinstalled on
ubuntu-latest.
- ci.yml shellcheck: drop the self-hosted existence check —
shellcheck is preinstalled via apt.
- ci.yml python-lint: replace the Homebrew python3.11 path dance
with actions/setup-python@v5 (which works fine on GHA-hosted),
add requirements.txt caching while we're there.
- Remove stale comments referencing "the self-hosted runner",
"Mac mini", keychain, osxkeychain etc.
The self-hosted Mac mini remains in service for private-repo
workflows only. Memory feedback_selfhosted_runner updated to
reflect the public-repo scope carve-out.
Net -96 lines across the 7 files.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Every standalone workspace-template repo now publishes to
ghcr.io/molecule-ai/workspace-template-<runtime>:latest via the
reusable publish-template-image workflow in molecule-ci (landed
today — one caller per template repo). This PR makes the
provisioner actually use those images:
- RuntimeImages map + DefaultImage switched from bare local tags
(workspace-template:<runtime>) to their GHCR equivalents.
- New ensureImageLocal step before ContainerCreate: if the image
isn't present locally, attempt `docker pull` and drain the
progress stream to completion. Best-effort — if the pull fails
(network, auth, rate limit) the subsequent ContainerCreate still
surfaces the actionable "No such image" error, now with a
GHCR-appropriate hint instead of the defunct
`bash workspace/build-all.sh <runtime>` advice.
- runtimeTagFromImage now handles both forms: legacy
`workspace-template:<runtime>` (local dev via build-all.sh /
rebuild-runtime-images.sh) and the current GHCR shape. Keeps
error hints sensible in both worlds.
- Tests cover the GHCR path for tag extraction and the new error
message shape. Legacy local tags still recognised.
Local dev path unchanged — scripts/build-images.sh and
workspace/rebuild-runtime-images.sh still produce locally-tagged
`workspace-template:<runtime>` images, and Docker's image
resolver matches them before any pull is attempted. So
contributors can keep iterating on a template repo without
round-tripping through GHCR.
Follow-on impact:
- hongmingwang.moleculesai.app (and any other tenant EC2) will
auto-pull `ghcr.io/molecule-ai/workspace-template-hermes:latest`
on the next hermes workspace provision — picking up the real
Nous hermes-agent behind the A2A bridge (template-hermes v2.1.0)
without any tenant-side rebuild step.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(F1085): scope rm to /configs volume in deleteViaEphemeral
Regressed by commit 49ab614 ("CWE-78/CWE-22 — block shell injection
in deleteViaEphemeral") which changed the rm form from the scoped
concat "/configs/" + filePath to the unscoped 2-arg "/configs", filePath.
With 2 args, rm receives /configs as the first target — rm -rf /configs
attempts to delete the entire volume mount before processing filePath,
which is the F1085 (Misconfiguration - Filesystems) defect. The concat
form passes a single scoped path so rm only touches files inside /configs.
validateRelPath call retained as CWE-22 defence-in-depth.
* docs: note F1085 defect in deleteViaEphemeral 2-arg rm form
Amends the CWE-22+CWE-78 incident entry to record that commit 49ab614
regressed the F1085 (volume deletion scope) fix, and that f1085-fix
commit a432df5 restores the correct concat form.
---------
Co-authored-by: Molecule AI CP-QA <cp-qa@agents.moleculesai.app>
publish-canvas-image has been failing on every main push since 2026-04-21
at `addgroup -g 1000 canvas` because node:20-alpine already ships a `node`
user/group at uid/gid 1000. Same collision workspace-server/Dockerfile.tenant
already fixes with `deluser --remove-home node` before `addgroup`.
Copying that pattern here so the workflow goes green again and canvas images
publish to ghcr. No runtime behaviour change — canvas still runs as non-root
uid 1000.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- dev-start.sh: $ROOT/platform → $ROOT/workspace-server (Go server
lives in workspace-server/, not platform/; any developer running
this script would get "no such directory" immediately)
- nuke-and-rebuild.sh: add ROOT variable and -f "$ROOT/docker-compose.yml"
so docker compose works from any CWD; fix post-rebuild-setup.sh path
- rollback-latest.sh: add 'local' to src_digest and new_digest vars
inside roll() function to prevent global-scope leakage
Co-authored-by: Molecule AI Core-DevOps <core-devops@agents.moleculesai.app>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(lint): unblock Platform Go CI — suppress 8 pre-existing errcheck warnings
golangci-lint errcheck has been flagging these since before this PR —
not regressions from the restart fix, just long-standing debt that
blocks Platform (Go) CI from ever going green. Prefix ignored returns
with `_ =` to make the signal explicit without changing behavior:
- channels/lark_test.go:97 (w.Write) + :118 (resp.Body.Close)
- channels/channels_test.go:620 + :760 (mockDB.Close in t.Cleanup)
- channels/manager.go:131 + :196 (defer rows.Close via closure wrapper)
- channels/manager.go:206–207 (json.Unmarshal into struct fields)
- artifacts/client_test.go:195, 237, 297 (json.Decode in test handlers)
The manager.go defer patch uses `defer func() { _ = rows.Close() }()`
since errcheck doesn't allow the `_ =` prefix directly on `defer`.
Build + `go test ./...` green locally for internal/channels and
internal/artifacts. The manager.go change touches production code so
I re-ran the channels test suite; passes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: trigger PR refresh
* test(handlers): add CWE-22 regression suite + KI-005 terminal access fix + tests
container_files_test.go (152 lines):
- 11 path-traversal test cases for copyFilesToContainer (F1501/CWE-22)
- Tests nil Docker client — validation logic runs before any Docker call
terminal.go KI-005 security fix (backport from ship/security-fix 6de7530c):
- Enforce CanCommunicate hierarchy check before granting terminal access
- Shell access is more dangerous than A2A message-passing; apply the
same hierarchy check used by A2A and discovery endpoints
- When X-Workspace-ID header is present and bearer token is valid
(ValidateAnyToken), reject unless CanCommunicate(callerID, targetID)
- Canvas/molecli callers without X-Workspace-ID header pass through to
WorkspaceAuth middleware for existing bearer check
- canCommunicateCheck exposed as package var for testability
terminal_test.go (5 test cases):
- TestTerminalConnect_KI005_RejectsUnauthorizedCrossWorkspace
- TestTerminalConnect_KI005_AllowsOwnTerminal
- TestTerminalConnect_KI005_SkipsCheckWithoutHeader
- TestTerminalConnect_KI005_RejectsInvalidToken
- TestTerminalConnect_KI005_AllowsSiblingWorkspace
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Hongming Wang <hongmingwang.rabbit@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Molecule AI Core-BE <core-be@agents.moleculesai.app>
* fix(canvas+templates): fetch runtime dropdown from /templates registry
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>
* feat(canvas+templates): model + required-env suggestions from template
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>
* fix(canvas): commit required_env on model change; add backend tests
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>
---------
Co-authored-by: Hongming Wang <hongmingwang.rabbit@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ContextMenu: useCanvasStore selector returned .filter() (new array on
every call), causing React 19's useSyncExternalStore to detect a
reference change and re-render infinitely. Fixed by using .some()
which returns a stable boolean.
Also deduplicates isSafeURL, isPrivateOrMetadataIP, validateRelPath
which existed in 3 files after PR merges collided. Canonical location
is ssrf.go. Removed unused imports (fmt, net, net/url, database/sql,
strings) from a2a_proxy.go, a2a_proxy_helpers.go, mcp_tools.go.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Molecule AI SDK-Dev <sdk-dev@agents.moleculesai.app>