Renames: - platform/ → workspace-server/ (Go module path stays as "platform" for external dep compat — will update after plugin module republish) - workspace-template/ → workspace/ Removed (moved to separate repos or deleted): - PLAN.md — internal roadmap (move to private project board) - HANDOFF.md, AGENTS.md — one-time internal session docs - .claude/ — gitignored entirely (local agent config) - infra/cloudflare-worker/ → Molecule-AI/molecule-tenant-proxy - org-templates/molecule-dev/ → standalone template repo - .mcp-eval/ → molecule-mcp-server repo - test-results/ — ephemeral, gitignored Security scrubbing: - Cloudflare account/zone/KV IDs → placeholders - Real EC2 IPs → <EC2_IP> in all docs - CF token prefix, Neon project ID, Fly app names → redacted - Langfuse dev credentials → parameterized - Personal runner username/machine name → generic Community files: - CONTRIBUTING.md — build, test, branch conventions - CODE_OF_CONDUCT.md — Contributor Covenant 2.1 All Dockerfiles, CI workflows, docker-compose, railway.toml, render.yaml, README, CLAUDE.md updated for new directory names. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
12 KiB
12 KiB
2026-04-11 Session
Summary
Restored 6 changes lost during PR squash merge, then ran comprehensive code review and fixed all findings. Added 100% test coverage for DeepAgents adapter and model fallback logic across Go and Python. Deleted stale feat/cron-scheduler branch.
Changes
Squash Merge Restoration (PR #50)
workspace-server/internal/handlers/org.go— AddedOrgDefaults.Modelfield + model fallback propagation so org templates correctly pass model to workspacesworkspace-server/internal/handlers/workspace_provision.go— Model always at top level in generatedconfig.yaml(config.py readsraw["model"]for all runtimes); deepagents excluded fromruntime_configblockworkspace/agent.py— Added Cerebras provider support (cerebras:modelformat)workspace/adapters/deepagents/adapter.py— Full SDK utilization: FilesystemBackend, MemorySaver checkpointer, FilesystemPermission, memory files, InMemoryCache, native skills, plus cerebras/google_genai/ollama providersworkspace/adapters/deepagents/requirements.txt— Addedlangchain-google-genai+langchain-anthropicdepsworkspace/adapters/langgraph/requirements.txt— Addedlangchain-google-genaidep for Gemini support
Code Review Fixes
adapter.py— Removed unusedPathimportadapter.py— Changed default provider from"openai"to"anthropic"(aligned withagent.py)adapter.py— Replaced silent OpenAI fallback withValueErrorfor unknown providers (fail fast)adapter.py— Added guard onself.agentincreate_executor()(RuntimeError if setup not called)org.go— Added third-level model fallback: ws.Model → defaults.Model → runtime-specific default (matches runtime/tier pattern)
Test Coverage (100% on changed files)
org_test.go— 8 new tests: Model YAML parsing, empty model, workspace overrides default, fallback for claude-code/deepagents/langgraph, defaults.Model used when ws emptyworkspace_provision_test.go— 6 new tests: deepagents runtime, openclaw/crewai get runtime_config, empty runtime defaults to langgraph, empty name/role, model-always-top-level (3 sub-tests)test_adapters.py— 18 new/updated tests: cerebras/google_genai/ollama providers, unknown provider raises ValueError, default provider is anthropic, create_executor guard, multiple colons in model string, openrouter fallback chain, empty API keys, base URL presence/absence, MAX_TOKENS env var
Async Delegation Merge (PR #41)
- Rebased
feat/async-delegationonto main, resolved edit-history conflict tools/delegation.py— non-blockingdelegate_to_workspace+check_delegation_statuspollingadapters/base.py— registeredcheck_delegation_statusas 6th core toolcoordinator.py—route_task_to_teamuses async delegation- 13 delegation tests rewritten for async model
Delegation Lint Fixes (PR #52)
test_delegation.py— movedimport osafter module docstringbase.py— fixed stale comment "5 core" → "6 core" toolsdelegation.py— log notify failures at debug level instead of silentpass
Social Channel System (PR #54)
workspace-server/internal/channels/adapter.go—ChannelAdapterinterface +InboundMessage+MessageHandlerworkspace-server/internal/channels/registry.go— adapter registry (Telegram registered)workspace-server/internal/channels/telegram.go— Telegram adapter (webhook + long-polling)workspace-server/internal/channels/manager.go— orchestrator with hot reload, conversation history (Redis), allowlist, A2A proxy, typing indicatorworkspace-server/internal/handlers/channels.go— REST API (CRUD, send, test, webhook, discover)workspace-server/migrations/016_workspace_channels.sql— workspace_channels tableworkspace-server/internal/handlers/a2a_proxy.go— added"channel:"to system caller prefixescanvas/src/components/tabs/ChannelsTab.tsx— Canvas UI for connecting/managing social channelsmcp-server/src/index.ts— 7 new MCP tools (list_channel_adapters, list_channels, add_channel, update_channel, remove_channel, send_channel_message, test_channel)- 41 unit tests (channels package) + 13 handler tests (sqlmock) + 23 E2E API checks
- Go test count: 406 → 448, MCP tools: 54 → 61
UX iterations (during PR #54)
- Multi-chat IDs per channel —
chat_idfield accepts comma-separated list. One Telegram bot can serve multiple groups from a single channel entry. - Auto-detect chats —
POST /channels/discovercalls Telegram getUpdates, returns groups/DMs the bot has seen. Canvas "Detect Chats" button auto-populates the chat_id field. /startwelcome reply — bot replies immediately with chat ID so users get instant feedback that it works.PausePollersForToken— discovery pauses any active poller for the same bot token to avoid Telegram's 409 "only one getUpdates" conflict.- Hidden manual input — after Detect Chats, the redundant text input is hidden behind an "edit manually" toggle.
Telegram Bot API audit fixes (PR #54 follow-up)
Critical bugs:
- SQL
LIKE '%id%'substring match → exact match in code (chat_id "123" was matching "1234"). - Webhook secret_token verification (X-Telegram-Bot-Api-Secret-Token).
- 4096-char message splitting at paragraph/line/word boundaries.
- Group privacy mode warning surfaced in Discover (
can_read_all_group_messagesfield).
Reliability:
- Bot instance cache (sync.RWMutex) — eliminates
getMeAPI call on every send. - Typed Telegram error handling: 401→invalidate token, 403→forbidden, 429→honor RetryAfter and retry once.
- DisableWebPagePreview by default.
UX:
sendChatAction("typing")goroutine during agent calls — re-sends every 4s.- Bot commands registered via
setMyCommands→/start,/help,/reset,/cancelautocomplete. /help,/reset(clears Redis history),/cancelhandled inline.my_chat_memberevent handling: bot auto-greets when added to a group.channel_postsupport (Telegram channels in addition to groups/DMs).- Token format regex validation rejects malformed tokens before API call.
auth_token_file → required_env (PR #55)
workspace/config.py— addedrequired_env: list[str]toRuntimeConfig. Deprecatedauth_token_file/auth_token_env(backward compat retained).workspace/preflight.py— checksrequired_envvars exist; legacyauth_token_filestill works.workspace/cli_executor.py—_resolve_auth_token()checksrequired_envfirst.workspace/adapters/claude_code/adapter.py— schema declaresrequired_env: ["CLAUDE_CODE_OAUTH_TOKEN"].workspace-server/internal/handlers/workspace_provision.go— generatesrequired_envper runtime, removed.auth-tokenfile copying.claude-code-default/config.yaml,molecule-dev/org.yaml,reno-stars/org.yaml—required_envreplacesauth_token_file.canvas/src/components/tabs/ConfigTab.tsx—TagListforrequired_envreplacesTextInputforauth_token_file.- New
reno-starsorg template added (15-agent team with full system prompts, knowledge bases, skills). - 17 Python preflight tests, 10 Go provisioner tests updated.
E2E Flaky Test Fixes
tests/e2e/test_comprehensive_e2e.sh— Runtime image checks now poll up to 30s for container readiness instead of fixed 10s sleep. Eliminates intermittent FAILs on cold-start container provisioning.
Restart Pending UX + Poller Lifetime Fix (PR #56)
Critical bug fix:
- Channel pollers were dying ~50ms after channel creation because
Reload()usedc.Request.Context()from the HTTP handler — when the handler returned, the request ctx was cancelled, killing the polling goroutine. - Fix: Manager now stores a long-lived
bgCtxset byStart()viasync.Once. All pollers spawn frombgCtx, not request ctx. - 2 regression tests:
TestManager_PollerSurvivesRequestContext,TestManager_BgCtxFallback.
UX improvements:
canvas/src/components/Toolbar.tsx— "Restart Pending (N)" button replaces always-visible "Restart All". Only shows workspaces flaggedneedsRestart; auto-clears flag and disappears after successful restart. Toast feedback for partial failures.- Global secret CRUD (both legacy
secrets-section.tsx+ newsecrets-store.ts) marks all workspaces asneedsRestart. Workspace-scoped secrets only mark the affected workspace. ConfirmDialog.tsx— uses React Portal (escapes parent transform/filter containing blocks); added"warning"amber variant; callbacks via refs to avoid keydown handler churn on parent re-renders.- New shared helper
canvas/src/lib/canvas-actions.ts—markAllWorkspacesNeedRestart/markWorkspaceNeedsRestart(was duplicated across 2 files).
Org template channels: field
- New
channels:section in org.yaml auto-creates social channel rows on import. Config values support${VAR}expansion from.envfiles (workspace.env> org root.env> platform process env). - New
OrgChannelstruct,expandWithEnv()helper usingos.Expand, regex-basedhasUnresolvedVarRef()(literal$5no longer false-flagged). - Adapter validation upfront via
channels.GetAdapter()+ValidateConfig()— fails fast for unknown types or invalid config. - Idempotent insert:
ON CONFLICT (workspace_id, channel_type) DO UPDATE— re-importing the same org doesn't fail. channelMgr.Reload()called once at end of Import (not per-workspace).- Skipped channels surfaced in import response (
channels_skippedfield with reason). - Extracted
loadWorkspaceEnv()helper used by both secret injection and channel config expansion. org-templates/molecule-dev/pm/.env.exampledocuments required vars (real.envgitignored).org-templates/molecule-dev/org.yamlPM block references vars inchannels: telegram— talk to PM directly from Telegram immediately after deploy.- 10 new tests: OrgChannel YAML parsing, expandWithEnv (4 paths), hasUnresolvedVarRef (5 cases).
Verified live: Org import created PM workspace, telegram channel auto-linked, poller started polling @molecule_team_bot for the configured chat — no manual setup needed.
Gemini Org + Chat UX Fixes (post-merge)
org-templates/molecule-worker-gemini/org.yaml—gemini-2.0-flash→gemini-2.5-flash(the older model was decommissioned).workspace/a2a_executor.py— addedrecursion_limitto LangGraph run_config (default 100, configurable viaLANGGRAPH_RECURSION_LIMIT). Library default of 25 wasn't enough for DeepAgents planning + delegation cycles.canvas/src/components/tabs/ChatTab.tsx— three fixes:- Hardcoded "Processing with Claude..." → uses
runtimeDisplayName(data.runtime)so DeepAgents/LangGraph/CrewAI workspaces show their actual runtime. - Stuck "Processing..." indicator after agent finishes → HTTP
.then()handler now extracts the reply from the synchronous response and clears the spinner, in addition to the existing WebSocket path. - Race condition between WS event and HTTP response → both paths now check
sendingFromAPIRefand the first-to-fire wins (no duplicate agent messages).
- Hardcoded "Processing with Claude..." → uses
canvas/src/lib/runtime-names.ts— extracted sharedruntimeDisplayName()for reuse.A2AResponsetype alias +extractReplyText()helper extracted in ChatTab (mirrors Go-sideextractReplyTextinmanager.go)..env.example— documentedLANGGRAPH_RECURSION_LIMIT.
Documentation
- Created
docs/edit-history/2026-04-11.md(this file) - Updated
CLAUDE.md— test counts, API routes, MCP tool count, migration count - Updated
PLAN.md— Phase 25 (Social Channels), test coverage table - Updated
.env.example— added GROQ_API_KEY, CEREBRAS_API_KEY, GOOGLE_API_KEY, MAX_TOKENS, TELEGRAM_BOT_TOKEN