# 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)
- `platform/internal/handlers/org.go` — Added `OrgDefaults.Model` field + model fallback propagation so org templates correctly pass model to workspaces
- `platform/internal/handlers/workspace_provision.go` — Model always at top level in generated `config.yaml` (config.py reads `raw["model"]` for all runtimes); deepagents excluded from `runtime_config` block
- `workspace-template/agent.py` — Added Cerebras provider support (`cerebras:model` format)
- `workspace-template/adapters/deepagents/adapter.py` — Full SDK utilization: FilesystemBackend, MemorySaver checkpointer, FilesystemPermission, memory files, InMemoryCache, native skills, plus cerebras/google_genai/ollama providers
- `workspace-template/adapters/deepagents/requirements.txt` — Added `langchain-google-genai` + `langchain-anthropic` deps
- `workspace-template/adapters/langgraph/requirements.txt` — Added `langchain-google-genai` dep for Gemini support

### Code Review Fixes
- `adapter.py` — Removed unused `Path` import
- `adapter.py` — Changed default provider from `"openai"` to `"anthropic"` (aligned with `agent.py`)
- `adapter.py` — Replaced silent OpenAI fallback with `ValueError` for unknown providers (fail fast)
- `adapter.py` — Added guard on `self.agent` in `create_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 empty
- `workspace_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-delegation` onto main, resolved edit-history conflict
- `tools/delegation.py` — non-blocking `delegate_to_workspace` + `check_delegation_status` polling
- `adapters/base.py` — registered `check_delegation_status` as 6th core tool
- `coordinator.py` — `route_task_to_team` uses async delegation
- 13 delegation tests rewritten for async model

### Delegation Lint Fixes (PR #52)
- `test_delegation.py` — moved `import os` after module docstring
- `base.py` — fixed stale comment "5 core" → "6 core" tools
- `delegation.py` — log notify failures at debug level instead of silent `pass`

### Social Channel System (PR #54)
- `platform/internal/channels/adapter.go` — `ChannelAdapter` interface + `InboundMessage` + `MessageHandler`
- `platform/internal/channels/registry.go` — adapter registry (Telegram registered)
- `platform/internal/channels/telegram.go` — Telegram adapter (webhook + long-polling)
- `platform/internal/channels/manager.go` — orchestrator with hot reload, conversation history (Redis), allowlist, A2A proxy, typing indicator
- `platform/internal/handlers/channels.go` — REST API (CRUD, send, test, webhook, discover)
- `platform/migrations/016_workspace_channels.sql` — workspace_channels table
- `platform/internal/handlers/a2a_proxy.go` — added `"channel:"` to system caller prefixes
- `canvas/src/components/tabs/ChannelsTab.tsx` — Canvas UI for connecting/managing social channels
- `mcp-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_id` field accepts comma-separated list. One Telegram bot can serve multiple groups from a single channel entry.
- **Auto-detect chats** — `POST /channels/discover` calls Telegram getUpdates, returns groups/DMs the bot has seen. Canvas "Detect Chats" button auto-populates the chat_id field.
- **`/start` welcome 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_messages` field).

**Reliability:**
- Bot instance cache (sync.RWMutex) — eliminates `getMe` API 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`, `/cancel` autocomplete.
- `/help`, `/reset` (clears Redis history), `/cancel` handled inline.
- `my_chat_member` event handling: bot auto-greets when added to a group.
- `channel_post` support (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-template/config.py` — added `required_env: list[str]` to `RuntimeConfig`. Deprecated `auth_token_file` / `auth_token_env` (backward compat retained).
- `workspace-template/preflight.py` — checks `required_env` vars exist; legacy `auth_token_file` still works.
- `workspace-template/cli_executor.py` — `_resolve_auth_token()` checks `required_env` first.
- `workspace-template/adapters/claude_code/adapter.py` — schema declares `required_env: ["CLAUDE_CODE_OAUTH_TOKEN"]`.
- `platform/internal/handlers/workspace_provision.go` — generates `required_env` per runtime, removed `.auth-token` file copying.
- `claude-code-default/config.yaml`, `molecule-dev/org.yaml`, `reno-stars/org.yaml` — `required_env` replaces `auth_token_file`.
- `canvas/src/components/tabs/ConfigTab.tsx` — `TagList` for `required_env` replaces `TextInput` for `auth_token_file`.
- New `reno-stars` org 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()` used `c.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 `bgCtx` set by `Start()` via `sync.Once`. All pollers spawn from `bgCtx`, 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 flagged `needsRestart`; auto-clears flag and disappears after successful restart. Toast feedback for partial failures.
- Global secret CRUD (both legacy `secrets-section.tsx` + new `secrets-store.ts`) marks all workspaces as `needsRestart`. 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 `.env` files (workspace `.env` > org root `.env` > platform process env).
- New `OrgChannel` struct, `expandWithEnv()` helper using `os.Expand`, regex-based `hasUnresolvedVarRef()` (literal `$5` no 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_skipped` field with reason).
- Extracted `loadWorkspaceEnv()` helper used by both secret injection and channel config expansion.
- `org-templates/molecule-dev/pm/.env.example` documents required vars (real `.env` gitignored).
- `org-templates/molecule-dev/org.yaml` PM block references vars in `channels: 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-template/a2a_executor.py` — added `recursion_limit` to LangGraph run_config (default 100, configurable via `LANGGRAPH_RECURSION_LIMIT`). Library default of 25 wasn't enough for DeepAgents planning + delegation cycles.
- `canvas/src/components/tabs/ChatTab.tsx` — three fixes:
  1. **Hardcoded "Processing with Claude..."** → uses `runtimeDisplayName(data.runtime)` so DeepAgents/LangGraph/CrewAI workspaces show their actual runtime.
  2. **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.
  3. **Race condition** between WS event and HTTP response → both paths now check `sendingFromAPIRef` and the first-to-fire wins (no duplicate agent messages).
- `canvas/src/lib/runtime-names.ts` — extracted shared `runtimeDisplayName()` for reuse.
- `A2AResponse` type alias + `extractReplyText()` helper extracted in ChatTab (mirrors Go-side `extractReplyText` in `manager.go`).
- `.env.example` — documented `LANGGRAPH_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
