Commit Graph

1048 Commits

Author SHA1 Message Date
Molecule AI Frontend Engineer
eba6e3a3de fix(canvas): expand a11y htmlFor/aria-label to SkillsTab, FilesTab, ChannelsTab, ScheduleTab (issue #856)
WCAG 1.3.1 fixes for 4 remaining tabs identified in UIUX Cycle 4 audit:

- SkillsTab: aria-label="Install plugin from source URL" on bare source input
- FilesTab: aria-label="New file path" on bare new-file input
- ChannelsTab: useId() + htmlFor/id pairs for Platform, Bot Token,
  Chat IDs, and Allowed Users label↔input associations (4 pairs)
- ScheduleTab: aria-label="Schedule name" on bare name input;
  useId() + htmlFor/id pairs for Cron Expression, Timezone,
  and Prompt/Task label↔control associations (3 pairs)
- DetailsTab: fix ReactElement<{ id?: string }> cast in Field
  component to resolve React 19 TypeScript overload error

Adds 14 new WCAG tests in tabs.a11y.test.tsx covering all above fixes.
No visual change. All 736 tests pass. Build clean.

Closes #856

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 23:01:43 +00:00
Molecule AI Backend Engineer
c1b4dca5b7 fix(security): redact secrets from commit_memory payloads (#834)
Add _redact_secrets() in builtin_tools/security.py and apply it at every
commit_memory call site before content reaches the memories table.

Patterns scrubbed (replaced with [REDACTED]):
- sk-[A-Za-z0-9_-]{20,}          OpenAI/Anthropic keys (sk-, sk-ant-, sk-proj-)
- ghp_[A-Za-z0-9]{36}            GitHub classic PAT
- ghs_[A-Za-z0-9]{36}            GitHub server-to-server token
- github_pat_[A-Za-z0-9_]{82}    GitHub fine-grained PAT
- AKIA[0-9A-Z]{16}               AWS access key ID
- key/token/secret/password/api_key=<40+ chars>  Generic contextual (value replaced,
  keyword preserved: "api_key=[REDACTED]" not "[REDACTED]")

Call sites wired:
- builtin_tools/memory.py::commit_memory()     — LangChain tool (LangGraph path)
- a2a_tools.py::tool_commit_memory()           — MCP server path
- executor_helpers.py::commit_memory()         — CLI/SDK executor path

Implementation guarantees:
- Pure function (no side effects, no I/O)
- Idempotent: [REDACTED] does not match any pattern
- No false positives on normal prose (all patterns require ≥20-char prefix
  or ≥40-char value after known keyword)

Tests (36 passing):
- Per-pattern unit tests for all 6 secret types
- Idempotency tests
- Normal prose non-regression tests
- Integration: a2a_tools.tool_commit_memory scrubs ghp_ tokens before HTTP POST
- Integration: executor_helpers.commit_memory scrubs AWS keys and OpenAI keys
- Source inspection: memory.py imports and applies _redact_secrets before
  build_awareness_client() (i.e. before any storage operation)

conftest.py updated to load the real builtin_tools/security.py so that
executor_helpers and a2a_tools can import _redact_secrets during test collection.

Closes #834
Sub-issue of #725

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 22:43:50 +00:00
Molecule AI Frontend Engineer
8611d38638 fix(canvas): resolve TypeScript errors exposed by incremental cache invalidation
- WorkspaceNode.eject.test.tsx: add draggable/selectable/deletable to
  NodeProps render call (TS2739); add `as WorkspaceNodeData` cast on
  makeNodeData return to silence Partial<> spread widening (TS2322)

The cherry-picked fix/canvas-test-fixture-budgetlimit commit (9e0aa61)
also lands here — it resolves latent test-fixture drift in 7 test files
that the incremental tsc cache had masked on main but that became visible
once the new WorkspaceNode.eject.test.tsx file invalidated the cache.

tsc --noEmit: 0 errors | npm test: 726 passed | npm run build: clean

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 22:41:16 +00:00
Molecule AI Frontend Engineer
9e0aa61837 fix(canvas): add missing budgetLimit/budget_limit to test fixtures, fix AuthGate mock types
The budget PR (#541) added budgetLimit: number | null as a required field
on WorkspaceNodeData and budget_limit: number | null on WorkspaceData.
Seven test fixture factories were not updated, causing tsc --noEmit to
produce 34 TS2322/TS2345 errors (runtime tests still passed because
Vitest transpiles via esbuild which strips types).

Fixes:
- canvas-events.test.ts: makeNode factory +budgetLimit: null
- canvas-events-pan.test.ts: makeNode factory +budgetLimit: null
- canvas-capabilities.test.ts: makeNodeData factory +budgetLimit: null
- canvas-topology.test.ts: makeWS factory +budget_limit: null
- canvas.test.ts: makeWS factory +budget_limit: null; two inline
  summarizeWorkspaceCapabilities args +budgetLimit: null; context-menu
  fixture +budgetLimit: null
- ProvisioningTimeout.test.tsx: makeWS factory +budget_limit: null

Also fixes 3 TS2348 errors in AuthGate.test.tsx: newer Vitest type defs
resolve ReturnType<typeof vi.fn> to Mock<Procedure|Constructable> which
TypeScript no longer considers directly callable in a vi.mock factory.
Fix: intersect the mock variables with a plain function type so both the
call expression and the mock API (mockReturnValue etc.) type-check.

tsc --noEmit: 0 errors. npm test: 722/722.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 22:39:54 +00:00
molecule-ai[bot]
5d7e58ff5b Merge pull request #840 from Molecule-AI/feat/issue-800-opencode-mcp-bridge
feat(platform): opencode MCP bridge — remote A2A tools over HTTP (#800)
2026-04-17 22:15:38 +00:00
molecule-ai[bot]
55f719f737 chore: sync opencode.md with main (conflict resolution post PR#842 merge)
PR#842 merged the docs/opencode.json to main with the correct MCP URL path.
PR#840 branch had an older version — sync to main's content to resolve conflict.
2026-04-17 22:14:59 +00:00
molecule-ai[bot]
1649808c09 chore: sync opencode.json with main (conflict resolution post PR#842 merge)
PR#842 merged the docs/opencode.json to main with the correct MCP URL path.
PR#840 branch had an older version — sync to main's content to resolve conflict.
2026-04-17 22:14:57 +00:00
molecule-ai[bot]
bbb2f1b847 fix(mcp): add TODO(#838) in toolCommitMemory + document X-Workspace-ID trust in toolDelegateTask
Security Auditor pre-merge conditions for PR#840:

C5: toolCommitMemory passes content directly to DB insert without secret
redaction. Gap is tracked to #838 (platform-wide _redactSecrets pass).
Adds inline TODO(#838) comment at the insert site so the gap is visible
in-code, not only in the issue tracker.

C6: toolDelegateTask sets X-Workspace-ID but no bearer token on the
outbound A2A call. The /workspaces/:id/a2a route is intentionally outside
WorkspaceAuth (by design in router.go). CanCommunicate is enforced before
the request is constructed, and callerID was authenticated by WorkspaceAuth
on the MCP bridge entry point. Documents this trust assumption at the call
site.
2026-04-17 22:13:55 +00:00
molecule-ai[bot]
d1855863e9 Merge pull request #842 from Molecule-AI/feat/issue-813-814-opencode-template
feat(opencode): org-template + integration guide for remote MCP auth (closes #813, closes #814)
2026-04-17 22:12:10 +00:00
molecule-ai[bot]
a0425903c7 fix(opencode): update URL example in opencode.md + add WORKSPACE_ID env var
The inline JSON example still showed the bare ${MOLECULE_MCP_URL} without
the /workspaces/${WORKSPACE_ID}/mcp path. Updated to match opencode.json fix
in previous commit (bf80f15). Added WORKSPACE_ID to the env section.
2026-04-17 22:06:37 +00:00
molecule-ai[bot]
bf80f15619 fix(opencode): add full MCP path to opencode.json URL
Security Auditor FINDING-1: bare ${MOLECULE_MCP_URL} missing the router path.
Fix adds /workspaces/${WORKSPACE_ID}/mcp so opencode reaches MCPHandler.
Unblocks PR#842 merge.
2026-04-17 22:06:05 +00:00
rabbitblood
9ef314fef5 fix(slack): tables as monospace blocks + ASCII dividers + strikethrough
Tables: Slack has no table syntax. Converter now detects markdown tables
and renders them as monospace code blocks with aligned columns.

Dividers: replaced unicode em-dash (caused encoding artifacts) with
plain ASCII dashes.

Strikethrough: ~~text~~ converts to ~text~ (Slack native).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 15:01:46 -07:00
rabbitblood
9b1139ee90 fix(slack): restore FetchChannelHistory — was lost during branch juggling
The function was defined on a feature branch, referenced by manager.go
and slack_test.go, but never made it to main after the rebase. This
caused go build to fail with 'undefined: FetchChannelHistory', which
Docker masked by using a cached binary from the last successful build.

That cached binary had neither the mrkdwn blocks nor the Level 3
context injection — explaining why Slack messages showed raw markdown
despite the source having the converter.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:55:53 -07:00
Molecule AI Frontend Engineer
ee07380ae0 fix(canvas): dynamic aria-label + title on TeamMemberChip eject button (issue #854)
- EjectIcon now accepts React.SVGProps<SVGSVGElement> so aria-hidden can be passed
- Eject button: aria-label and title both use `Extract ${data.name} from team`
  (previously title was static 'Extract from team'; aria-label was absent)
- <EjectIcon aria-hidden="true"> prevents assistive tech from double-announcing
  the icon content inside the already-labelled button
- Added WorkspaceNode.eject.test.tsx (4 tests) covering aria-label, title,
  label==title invariant, and aria-hidden on the SVG

Closes #854

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 21:54:51 +00:00
Molecule AI Backend Engineer
210c6b5b2c fix(security): allowlist-based env sanitization for LocalPythonExecutor (#826)
Replace denylist approach with strict allowlist: only PATH, HOME, LANG,
PYTHONPATH, WORKSPACE_ID, WORKSPACE_NAME, PLATFORM_URL (and a small set
of locale/Python runtime vars) pass through to agent-executed code.  Every
other env var — including ANTHROPIC_API_KEY, GH_TOKEN, DATABASE_URL,
REDIS_URL, *_SECRET, *_PASSWORD — is stripped from os.environ for the
duration of SafeLocalPythonExecutor.__call__ and restored on exit.

- make_safe_env() is a pure read (never mutates os.environ)
- _ENV_PATCH_LOCK serialises concurrent calls for thread safety
- os.environ fully restored even on exception (try/finally)
- 38 unit tests covering all secret categories, thread safety, import
  restrictions, and env-restore guarantees

Closes #826
Sub-issue of #804

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 21:54:11 +00:00
rabbitblood
230cf693c6 fix(slack): use blocks API for mrkdwn rendering + restore Level 3
Slack's chat.postMessage renders the text field as plain text when
username override is used. Switching to blocks with type=mrkdwn
forces rich formatting (bold, links, code, dividers).

Also restores FetchWorkspaceChannelContext that was lost in rebase.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:47:07 -07:00
molecule-ai[bot]
37d0b3005f fix(canvas): a11y — keyboard access, role=alert, close label, ProvisioningTimeout (#830 #831 #832 #833)
Closes #830, Closes #831, Closes #832, Closes #833

QA-approved (verified via A2A relay — QA token-blocked). All 4 fixes confirmed against local source:
- #830: role=alert + aria-live=assertive on error elements (MemoryInspectorPanel)
- #831: TeamMemberChip role=button + tabIndex + aria-label + onKeyDown Enter/Space (WorkspaceNode)
- #832: aria-label='Close workspace panel' + aria-hidden on SVG (SidePanel)
- #833: ProvisioningTimeout uncommented and mounted in Canvas tree

731/731 tests pass, build clean, use client check clean.
2026-04-17 21:44:17 +00:00
rabbitblood
e415dfb60e fix(slack): restore mrkdwn converter + FetchWorkspaceChannelContext after rebase
Both were lost during the PR #844 rebase — the converter was in the
source but the binary couldn't compile because FetchWorkspaceChannelContext
was missing from manager.go (interface mismatch). Previous deploys
silently used the cached old binary without the converter.

Also removed unused 'log' import that blocked compilation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:38:53 -07:00
Molecule AI Frontend Engineer
20bc92a2a9 fix(canvas): add missing budgetLimit/budget_limit to test fixtures, fix AuthGate mock types
The budget PR (#541) added budgetLimit: number | null as a required field
on WorkspaceNodeData and budget_limit: number | null on WorkspaceData.
Seven test fixture factories were not updated, causing tsc --noEmit to
produce 34 TS2322/TS2345 errors (runtime tests still passed because
Vitest transpiles via esbuild which strips types).

Fixes:
- canvas-events.test.ts: makeNode factory +budgetLimit: null
- canvas-events-pan.test.ts: makeNode factory +budgetLimit: null
- canvas-capabilities.test.ts: makeNodeData factory +budgetLimit: null
- canvas-topology.test.ts: makeWS factory +budget_limit: null
- canvas.test.ts: makeWS factory +budget_limit: null; two inline
  summarizeWorkspaceCapabilities args +budgetLimit: null; context-menu
  fixture +budgetLimit: null
- ProvisioningTimeout.test.tsx: makeWS factory +budget_limit: null

Also fixes 3 TS2348 errors in AuthGate.test.tsx: newer Vitest type defs
resolve ReturnType<typeof vi.fn> to Mock<Procedure|Constructable> which
TypeScript no longer considers directly callable in a vi.mock factory.
Fix: intersect the mock variables with a plain function type so both the
call expression and the mock API (mockReturnValue etc.) type-check.

tsc --noEmit: 0 errors. npm test: 722/722.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 21:37:50 +00:00
Hongming Wang
2d9083b155 Merge pull request #851 from Molecule-AI/fix/slack-mrkdwn-formatting
fix(slack): convert Markdown → mrkdwn before posting
2026-04-17 14:27:17 -07:00
rabbitblood
72cb636692 fix(slack): convert Markdown to mrkdwn before posting
Agents output standard Markdown (Claude Code default) but Slack uses
its own mrkdwn format. Without conversion:
  **bold** shows as literal **bold**
  ### heading shows as literal ###
  [text](url) shows as raw markdown link

Converter handles:
  **bold** → *bold* (Slack bold is single asterisk)
  ### heading → *heading* (bold text, no headings in Slack)
  [text](url) → <url|text> (Slack link format)
  --- → ——— (visual separator)
  `code` and ```blocks``` pass through unchanged

6 new tests: bold, heading, link, hr, code block, mixed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:26:41 -07:00
Molecule AI Frontend Engineer
c49616292d fix(canvas): add role=alert and focus-return to delete confirm in DetailsTab
Two WCAG violations in the Danger Zone delete flow:

1. WCAG 4.1.3 (Status Messages): the confirmation UI that appears when
   the user clicks "Delete Workspace" had no ARIA live region, so screen
   readers never announced the confirmation prompt. Adding role="alert"
   to the confirmation container makes it an implicit assertive live
   region that is announced immediately.

2. WCAG 2.4.3 (Focus Order): pressing Cancel left focus wherever the
   browser placed it (often body). Keyboard users had to re-navigate to
   find the Delete Workspace button. The Cancel handler now calls
   deleteButtonRef.current?.focus() to return focus to the trigger
   button, matching the expected modal/disclosure focus-management pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 21:18:05 +00:00
Molecule AI Frontend Engineer
c32334c8db fix(canvas): add ARIA landmark and live region to OnboardingWizard
WCAG 1.3.1 / 4.1.3: the onboarding card had no landmark role and no
live region, so screen readers had no way to know the card exists or
that the step changed.

- Add role="complementary" aria-label="Onboarding guide" to the card
  container so it appears as a named landmark in assistive technology.
- Add a role="status" aria-live="polite" aria-atomic="true" sr-only div
  that holds the current step label. When the step state changes React
  updates the div content, which the live region broadcasts to the AT
  without pulling focus away from the user's current position.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 21:17:32 +00:00
Molecule AI Frontend Engineer
3cee4e1859 fix(canvas): add aria-label to Toolbar buttons and status pills
NVDA and other screen readers ignore the title attribute on interactive
elements and non-interactive divs. Add aria-label alongside title on:
- Stop All button (dynamic label reflects active task count)
- Restart All button (dynamic label reflects pending workspace count)
- StatusPill component (online/offline/failed/provisioning counts)
- WsStatusPill component (connected/connecting/disconnected variants)

Inner dot and text spans get aria-hidden="true" so the screen reader
reads the single aria-label rather than individual child nodes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 21:17:05 +00:00
Hongming Wang
66fce40d44 Merge pull request #844 from Molecule-AI/feat/slack-bot-api-channels
feat(slack): Bot API adapter with per-agent identity + fix pgvector migration guard
2026-04-17 14:16:44 -07:00
Molecule AI Frontend Engineer
dc5f74231d fix(canvas): add role=alert to deploy error in EmptyState
WCAG 1.3.1 / 4.1.3: the error div that appears after a failed workspace
deploy or blank-workspace create had no ARIA live region, so screen
readers never announced it. Adding role="alert" makes the message an
implicit aria-live="assertive" region so assistive technology surfaces
the error immediately without requiring the user to navigate to it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 21:16:14 +00:00
rabbitblood
49a32260c3 test(slack): add 12 unit tests for Slack adapter
Covers: message splitting (short/long/newline boundary), config
validation (bot_token/webhook/missing), FetchChannelHistory edge
cases (empty token/channel), adapter type/name.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:16:13 -07:00
Molecule AI Frontend Engineer
fb17f430b7 fix(canvas): add htmlFor/id pairs to all bare labels in ConfigTab and DetailsTab
Wire WCAG 1.3.1 label associations: 6 bare <label>+control pairs in
ConfigTab (Description, Tier, Runtime, Effort, Task Budget, Backend) now
use stable useId() IDs with matching htmlFor/id. Field helper in
DetailsTab updated to generate its own fieldId via useId() and inject it
into the child element via cloneElement, so every Name/Role/Tier field in
edit mode is correctly associated without requiring call-site changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 21:15:52 +00:00
rabbitblood
8f89ba0b0a feat(slack): Level 3 — ambient cross-agent context from Slack channels
When a cron fires, the scheduler now fetches the last 10 messages from
the workspace's Slack channel via conversations.history and prepends them
to the cron prompt as '[Slack channel context — recent team messages]'.

This gives each agent ambient awareness of what peers are doing:
- Backend sees Frontend posted 'PR #840 ready for review' → can check
- Security Auditor sees Backend posted 'new endpoint added' → plans review
- PM sees all engineering activity → better synthesis in rollup

Implementation:
- slack.go: FetchChannelHistory() calls conversations.history, filters
  bot's own messages, returns last N as SlackHistoryMessage structs
- manager.go: FetchWorkspaceChannelContext() looks up the workspace's
  Slack config, fetches history, formats as readable context block
- scheduler.go: ChannelBroadcaster interface extended with
  FetchWorkspaceChannelContext; fireSchedule injects context before
  the cron prompt (prepended, not appended, so the agent sees team
  context BEFORE its task instructions)

Best-effort: if Slack API fails or workspace has no channels, the
prompt is unchanged. Truncated to 200 chars per message, 10 messages
max to keep prompt overhead bounded.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:15:51 -07:00
rabbitblood
65a3496522 fix(slack): address code review — 6 critical + improvement fixes
Code review findings addressed:

Critical:
1. Bot echo loop: add bot_id + subtype='bot_message' check in ParseWebhook
   to prevent outbound auto-posts from triggering inbound → infinite loop
2. Connection leak: close resp.Body immediately after reading instead of
   defer inside loop (was holding N connections open for N chunks)
3. Cancelled context: auto-post goroutine now uses context.Background()
   with 30s timeout instead of inheriting fireCtx (which gets cancelled
   by deferred cancel() when fireSchedule returns)
4. Slug validation: regex ^[a-zA-Z0-9 _-]+$ rejects path traversal and
   special chars in [slug] routing

Improvements:
5. Shared HTTP client (slackHTTPClient) for connection pooling instead of
   per-request &http.Client{}
6. Rune-safe truncation in BroadcastToWorkspaceChannels for CJK/emoji
7. Log async HandleInbound errors instead of silently discarding
8. url_verification challenge properly returned (c.JSON with challenge)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:15:51 -07:00
rabbitblood
8213fcd7b0 feat(channels): [slug] routing for inbound Slack messages
Humans type [backend] what's #800? in a shared #mol-engineering channel
and the message routes specifically to Backend Engineer's workspace.

Matching logic (case-insensitive):
  [pm]         → PM
  [backend]    → Backend Engineer
  [dev-lead]   → Dev Lead
  [security]   → Security Auditor (prefix match on 'security-auditor')

Unknown slugs return the available agent list for that channel so the
user knows what slugs are valid.

Messages without a [slug] prefix route to the first matching workspace
(backward compat with Level 2).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:15:51 -07:00
rabbitblood
19ab9667ee feat(slack): Level 1 auto-post + Level 2 inbound routing
Level 1 — Auto-post cron output to Slack:
- scheduler.go: captures A2A response body, extracts agent text via
  extractResponseSummary(), broadcasts to workspace's configured Slack
  channels on successful non-empty cron completions
- manager.go: adds BroadcastToWorkspaceChannels() — fans out to all
  enabled channels for a workspace (engineering+firehose for eng agents,
  research+firehose for research agents, etc.)
- main.go: wires scheduler → channel manager via SetChannels()
- Truncates output to 500 chars for Slack readability

Level 2 — Inbound Slack messages route to workspaces:
Already implemented by the existing webhook handler (POST /webhooks/slack)
+ the ParseWebhook method in slack.go which handles both Events API JSON
payloads and slash command form-encoded payloads. Needs Slack App Events
API URL configured to: https://<platform-host>/webhooks/slack

Also in this commit:
- slack.go: dual-mode adapter (bot_token + webhook fallback)
- 031 migration: pgvector guard wraps entire DO block

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:15:51 -07:00
rabbitblood
0fddfbc863 feat(slack): upgrade adapter to Bot API with per-agent identity + fix pgvector migration
Slack adapter: adds chat.postMessage mode alongside legacy webhooks.
When bot_token is configured, uses chat:write.customize for per-agent
display name + emoji on every message. Each of the 15 active agents
posts with a distinct identity (PM 💼, Backend ⚙️, etc.).

5 channels configured:
  #mol-engineering — PM, Dev Lead, Frontend, Backend, QA, Security, UIUX, Docs
  #mol-research    — Research Lead, Market Analyst, Tech Researcher, Competitive Intel
  #mol-ops         — DevOps, Triage, Offensive Security
  #mol-ceo-feed    — PM synthesized rollup (CEO-facing)
  #mol-firehose    — all agents (raw feed)

Tested live: 5 test messages across 4 channels, all ok=true.

pgvector migration: moved ALTER TABLE + CREATE INDEX inside the DO
block so the entire migration is skipped when pgvector extension is
unavailable (was crashing platform on restart — the guard caught
CREATE EXTENSION but execution continued to ALTER TABLE which used
the non-existent vector type).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:15:51 -07:00
Hongming Wang
7094290850 docs: Partner API Keys architecture + Phase 34 plan
Adds programmatic org management for partner platforms, CI/CD, and
automation. Partners authenticate with mol_pk_* API keys (SHA-256
hashed, scoped, rate-limited, revocable) alongside existing WorkOS
browser auth.

- Full architecture doc with schema, scopes, middleware integration,
  security considerations, and use cases
- Phase 34 in PLAN.md (4 sub-phases)
- CLAUDE.md cross-reference

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:07:50 -07:00
Molecule AI Backend Engineer
3c260d65a9 fix(platform): atomic hibernate via UPDATE WHERE active_tasks=0 (#819)
Replaces the racy SELECT-then-Stop two-step in HibernateWorkspace with a
three-step atomic pattern that eliminates the TOCTOU window (SAFE-819):

  1. Atomic claim: single UPDATE WHERE id=$1
                   AND status IN ('online','degraded')
                   AND active_tasks = 0
     — rowsAffected=0 means another caller already claimed it or tasks
       arrived; we abort immediately without calling Stop.

  2. provisioner.Stop: safe because status='hibernating' blocks new task
     routing between step 1 and step 2 (no new task can be dispatched).

  3. Final UPDATE to 'hibernated': records the completed hibernation.

Also adds stopFnOverride func(ctx, id) to WorkspaceHandler (always nil in
production) so tests can count Stop calls without a running Docker daemon.

Tests added/updated (13 total across 2 files):
  - TestHibernateWorkspace_ActiveTasksNotHibernated
  - TestHibernateWorkspace_AlreadyHibernatingNotHibernated
  - TestHibernateWorkspace_SuccessPath
  - TestHibernateWorkspace_ConcurrentOnlyOneStop
  - TestHibernateWorkspace_DBErrorOnClaim
  - Updated 3 existing HibernateWorkspace tests + 1 HTTP handler test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 20:52:20 +00:00
Molecule AI Frontend Engineer
8697a42447 fix(canvas): add keyboard resize + ARIA to SidePanel resize handle
Add role="separator" + aria-valuenow/min/max/orientation + tabIndex={0}
to make the resize handle focusable and discoverable by screen readers
(WAI-ARIA slider pattern). Add onKeyDown handler: ArrowLeft/Right moves
by 16px, Home/End snaps to min/max. Persist width to localStorage on
keyboard resize, matching the existing mouse behaviour.
Focus ring uses focus-visible:ring-2 to avoid showing on mouse click.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 20:35:15 +00:00
Molecule AI Frontend Engineer
56f085bae4 fix(canvas): expose loadMessagesFromDB failures with error banner + Retry
Previously loadMessagesFromDB swallowed all errors and returned [] — a
network failure was indistinguishable from an empty history, so the user
had no way to know loading failed. Now the function returns
{ messages, error } and the MyChatPanel renders a role="alert" banner
with the error message and a Retry button when messages are empty and
a load error occurred.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 20:34:48 +00:00
Molecule AI Frontend Engineer
d07909f46b fix(canvas): fix degraded error text contrast and accessibility
Replace title attribute (not read by screen readers for truncated text)
with aria-label, add role="status" so live regions announce the error,
and raise text color from text-amber-300/60 (~2.1:1) to text-amber-400
(~10.6:1) to meet WCAG AA contrast (4.5:1 minimum).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 20:34:04 +00:00
Molecule AI Frontend Engineer
cbc523a2d9 fix(canvas): wire aria-controls on MemoryEntryRow expand toggle
Add bodyId derived from entry.key, attach aria-controls={bodyId} to the
toggle button, and add id={bodyId} role="region" aria-label to the
collapsible body div. Screen readers can now announce the expand/collapse
relationship between the button and the region it controls (WCAG 4.1.2).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 20:33:52 +00:00
Hongming Wang
aad5b0334d Merge pull request #843 from Molecule-AI/fix/pgvector-migration-guard
fix(migrations): wrap entire pgvector migration in DO block — unblocks E2E
2026-04-17 13:31:49 -07:00
Hongming Wang
36d80b2024 fix: correct RAISE NOTICE parameter — %% → % for Postgres syntax
The migration SQL is read as raw SQL (not through Go fmt.Sprintf),
so %% is two parameters, not an escaped percent. Postgres RAISE
uses single % for parameter substitution.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 13:20:58 -07:00
Hongming Wang
a8e4d194e8 fix(migrations): wrap entire pgvector migration in DO block guard
The ALTER TABLE and CREATE INDEX referenced vector(1536) outside the
exception-handling DO block, so when pgvector wasn't installed they
crashed the migration runner — blocking ALL E2E runs on main.

Fix: move all DDL inside the single DO block so the EXCEPTION handler
catches any pgvector-related failure and skips the entire migration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 12:36:42 -07:00
Hongming Wang
5436b0e04e Merge pull request #771 from Molecule-AI/feat/issue-765-mcp-eval-ci
feat(ci): add mcp-eval quality gate for @molecule-ai/mcp-server (#765)
2026-04-17 12:35:30 -07:00
molecule-ai[bot]
9342c1c88c chore(env): add MOLECULE_MCP_URL + MOLECULE_MCP_TOKEN for opencode integration (#813) 2026-04-17 19:26:50 +00:00
molecule-ai[bot]
f485cc3296 docs(opencode): integration guide — token scoping, tools, SAFE-T1401 note (closes #814) 2026-04-17 19:26:36 +00:00
molecule-ai[bot]
745a256b53 feat(opencode): add org-template opencode.json with header-based MCP auth (closes #813) 2026-04-17 19:26:10 +00:00
Molecule AI Backend Engineer
18c00726b8 feat(platform): opencode MCP bridge — remote A2A tools over HTTP (#800)
Implements sub-issues #809 (MCPHandler), #810 (tool filtering), #811
(per-token rate limiting), #813 (opencode.json), #814 (docs).

Routes (registered under wsAuth — bearer token binds to :id):
  GET  /workspaces/:id/mcp/stream  — SSE transport (backwards compat)
  POST /workspaces/:id/mcp         — Streamable HTTP transport (primary)

Security conditions from review (all mandatory):
  C1: WorkspaceAuth middleware rejects requests without valid bearer token
  C2: MCPRateLimiter (120 req/min/token, SHA-256 keyed) applied on both routes
  C3: commit_memory/recall_memory with scope=GLOBAL → permission error;
      send_message_to_user excluded unless MOLECULE_MCP_ALLOW_SEND_MESSAGE=true

Tools: list_peers, get_workspace_info, delegate_task, delegate_task_async,
check_task_status, send_message_to_user (opt-in), commit_memory, recall_memory.
All mirror workspace-template/a2a_mcp_server.py TOOLS list.

Also adds: org-templates/molecule-dev/opencode.json, docs/integrations/opencode.md,
.env.example entries for MOLECULE_MCP_ALLOW_SEND_MESSAGE and MOLECULE_MCP_URL.

Tests: 29 new tests (20 handler + 9 middleware). All passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 19:25:22 +00:00
molecule-ai[bot]
cd87cd01bb fix(canvas): color-code similarity badge by score tier (closes #783)
fix(canvas): color-code similarity badge by score tier (issue #783)
2026-04-17 19:24:44 +00:00
molecule-ai[bot]
55200e95d8 fix(gate-5): update test — zinc-400 italic + tilde assertion for low-score badge 2026-04-17 19:24:02 +00:00
molecule-ai[bot]
1e9fd37460 fix(gate-5): WCAG AA — zinc-400 italic for low-score badge per [uiux-agent] review 2026-04-17 19:23:51 +00:00