Commit Graph

829 Commits

Author SHA1 Message Date
e41a75606d docs(security): SAFE-MCP internal advisory 2026-04-17 (distilled from PR #808 audit)
Adds a concise action advisory for engineering leads summarising the 9 open
findings from the full SAFE-MCP audit, with immediate remediation steps for
NEW-003 (unpinned npm packages in .mcp.json — HIGH), a Phase 35 scoping
recommendation for plugin supply-chain hardening (VULN-003, VULN-004), and
medium-term GLOBAL memory scope controls (VULN-002, VULN-005).

Pairs with: monorepo PR #808, docs PR #18

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 23:39:00 +00:00
rabbitblood
7675f6c837 chore: extract molecule-medo plugin to standalone repo
molecule-medo now lives at Molecule-AI/molecule-ai-plugin-molecule-medo
(same pattern as all other plugins). Removed the gitignore exception
that kept it in the monorepo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 16:11:50 -07:00
rabbitblood
6485c34c61 chore: move spike/ → docs/spikes/ — keep explorations out of repo root
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 16:09:12 -07: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
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
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
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
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
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
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
molecule-ai[bot]
89d89e1459 docs: tenant image upgrade strategies
docs: tenant image upgrade strategies
2026-04-17 19:16:30 +00:00
molecule-ai[bot]
a8fcff947d docs(security): SAFE-MCP audit report 2026-04-17 (closes #747)
docs(security): SAFE-MCP audit report 2026-04-17 (closes #747)
2026-04-17 19:06:42 +00:00
molecule-ai[bot]
1d7d1eed9f docs(env): audit .env.example completeness (closes #782)
docs(env): audit .env.example completeness — issue #782
2026-04-17 19:06:39 +00:00
molecule-ai[bot]
6efb30eb54 fix(scheduler): detect phantom-producing crons — consecutive-empty tracking (closes #795)
fix(scheduler): detect phantom-producing crons — consecutive-empty tracking (#795)
2026-04-17 19:06:35 +00:00
molecule-ai[bot]
fa2d4cde3e feat(platform): Temporal checkpoint DB persistence layer (closes #788)
feat(platform): Temporal checkpoint DB persistence layer (#788)
2026-04-17 19:05:48 +00:00
molecule-ai[bot]
32b579921e test(supply-chain): TDD spec for plugin supply-chain hardening (closes #768)
test(supply-chain): TDD spec for plugin supply-chain hardening (#768)
2026-04-17 19:05:14 +00:00
molecule-ai[bot]
bfc6e56aa5 fix(security): plugin supply chain hardening — SAFE-T1102 (closes #768)
fix(security): plugin supply chain hardening — SAFE-T1102 (issue #768)
2026-04-17 19:04:04 +00:00
Hongming Wang
20750cf128 docs: tenant image upgrade strategies (Options A/B/C)
Documents three upgrade strategies for keeping tenant EC2 instances
current with platform-tenant:latest:
- Option A: Rolling restart via CP admin endpoint (coordinated)
- Option B: Sidecar auto-updater cron (implemented, 5 min interval)
- Option C: Blue-green via Worker (zero downtime, future)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 11:59:15 -07:00
Molecule AI Security Auditor
857dd941d5 docs(security): SAFE-MCP audit report 2026-04-17 (issue #747)
Adds docs/security/safe-mcp-audit-2026-04-17.md — full SAFE-MCP ATT&CK
audit of @molecule-ai/mcp-server against 4 high-priority techniques:

SAFE-T1102 (Supply chain):
  - NEW-003 HIGH: Unpinned npm MCP packages in .mcp.json (npx -y)
  - VULN-003 HIGH: No manifest signing on GitHub plugin install
  - VULN-004 HIGH: Floating plugin refs, no version pinning enforced

SAFE-T1201 (Prompt injection):
  - VULN-002 HIGH: GLOBAL memory poisoning — delimiter spoofing gap
    (partial mitigation via #767 globalMemoryDelimiter confirmed)
  - VULN-006 MEDIUM: No tool output sanitization in MCP server

SAFE-T1301 (Excessive permissions):
  - NEW-002 MEDIUM: Default subprocess sandbox allows language=shell/bash

SAFE-T1401 (Secret exfiltration):
  - NEW-001 MEDIUM: builtin_tools missing auth_headers() on A2A calls
  - VULN-005 MEDIUM: GLOBAL memories readable by all workspaces

Confirmed fix: VULN-001 (X-Workspace-ID system-caller forge, #761) CLOSED.

Closes #747.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 18:54:08 +00:00
Molecule AI Frontend Engineer
cdc51d4d30 fix(canvas): color-code similarity badge by score tier (issue #783)
Badge was always text-zinc-500; apply blue-500 (>=0.8), zinc-400 (0.5–0.8),
zinc-600 (<0.5) per spec. Add 3 vitest tests for each color tier (725 total).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 18:51:22 +00:00
Hongming Wang
fc702273a7 Merge pull request #787 from Molecule-AI/feat/issue-783-memory-search-ui
feat(canvas): semantic search UI for memory inspector (issue #783)
2026-04-17 11:48:47 -07:00
Molecule AI Backend Engineer
b6d9af5fc2 feat(platform): Temporal checkpoint DB persistence layer (#788)
Adds step-level checkpoint storage so workflows can resume from the
last completed step after a crash or restart without replaying prior work.

- Migration: `workflow_checkpoints` table — workspace_id (FK + CASCADE),
  workflow_id, step_name, step_index, completed_at, payload JSONB.
  UNIQUE(workspace_id, workflow_id, step_name) + covering index on
  (workspace_id, workflow_id, completed_at DESC).

- Handlers (platform/internal/handlers/checkpoints.go):
  POST   /workspaces/:id/checkpoints        — upsert via ON CONFLICT DO UPDATE
  GET    /workspaces/:id/checkpoints/:wfid  — list steps ordered step_index DESC
  DELETE /workspaces/:id/checkpoints/:wfid  — clear on clean shutdown (404 if none)

- Router: all three routes on the wsAuth group (WorkspaceAuth middleware);
  workspace A's token cannot reach workspace B's checkpoints.

- Tests (11 cases, sqlmock + race-safe): upsert-insert, upsert-update,
  payload forwarding, list-ordered, list-not-found, rows.Err() → 500,
  delete-success, delete-not-found, callerMismatch 403 on all 3 endpoints.

Closes #788. Parent: #583-1.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 18:36:12 +00:00
rabbitblood
3249d3ffdb fix(scheduler): detect phantom-producing crons via consecutive-empty tracking (#795)
Post-mortem fix: UIUX Designer ran 22 cron fires over 23 hours with
every single response being empty or '(no response generated)'. The
scheduler reported status=ok because the HTTP call succeeded — nobody
caught it until the CEO asked.

Changes:
- Migration 032: adds consecutive_empty_runs INT to workspace_schedules
- scheduler.go: captures response body from ProxyA2ARequest (was _),
  checks for empty/sentinel markers via isEmptyResponse(), increments
  consecutive_empty_runs on empty ok responses, resets on non-empty.
  When consecutive_empty_runs >= 3, sets last_status='stale' with a
  descriptive error message.

The 'stale' status is surfaced via:
- GET /admin/schedules/health (merged in #671)
- PM's silence detector (companion fix in org-template PR)
- Maintenance loop response-body sampling (operator-side fix)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 11:11:05 -07:00
molecule-ai[bot]
a41a2ba663 Merge pull request #786 from Molecule-AI/docs/wildcard-dns-proxy
docs: wildcard DNS + Cloudflare Worker proxy architecture (Phase 33)
2026-04-17 17:21:13 +00:00
molecule-ai[bot]
f5a8caefe1 Merge pull request #791 from Molecule-AI/fix/ci-skip-docs-only
fix(ci): skip CI jobs for docs-only PRs
2026-04-17 17:21:09 +00:00