Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
b1064ea
refactor: split 4 oversized handler files into focused sub-files
airenostars Apr 21, 2026
38cc501
Merge pull request #1363 from Molecule-AI/refactor/split-large-handlers
molecule-ai[bot] Apr 21, 2026
52e6bae
feat(runtime+scheduler): increment/decrement active_tasks counter (re…
molecule-ai[bot] Apr 21, 2026
1f41973
docs: workspace files API reference, skill catalog, and links
molecule-ai[bot] Apr 21, 2026
81afc88
fix(handlers): add saasMode() gating to isPrivateOrMetadataIP in a2a_…
Apr 21, 2026
b6850a3
docs(marketing): Discord adapter Day 2 Reddit + HN community copy
Apr 21, 2026
a7dc381
fix(tests): supply *events.Broadcaster pointer to captureBroadcaster
Apr 21, 2026
982d422
Merge pull request #1429 from fix/canvas-tooltip-clear-timer
molecule-ai[bot] Apr 21, 2026
c14e9e0
fix(canvas/test): add missing children:[] to setPendingDelete expecta…
molecule-ai[bot] Apr 21, 2026
870949b
fix(canvas/test): add children:[] to setPendingDelete + \' entit…
molecule-ai[bot] Apr 21, 2026
083dc3b
Merge pull request #1430 from fix/1421-saas-ssrf-helpers
molecule-ai[bot] Apr 21, 2026
302a90e
Merge pull request #1432 from Molecule-AI/docs/marketing/discord-adap…
molecule-ai[bot] Apr 21, 2026
19eada5
fix(P0): CWE-22 path traversal in copyFilesToContainer + ContextMenu …
Apr 21, 2026
5f10953
Merge pull request #1457 from Molecule-AI/fix-1442-resolved
molecule-ai[bot] Apr 21, 2026
545fda3
Merge pull request #1433 from Molecule-AI/fix/pr1368-broadcaster-vet
molecule-ai[bot] Apr 21, 2026
91fd951
fix(core): resolve staging build — remove duplicate SSRF function dec…
Apr 21, 2026
658e509
Merge pull request #1465 from Molecule-AI/fix/staging-build-dedupe-ssrf
molecule-ai[bot] Apr 21, 2026
80a4777
docs(blog): add org-scoped API keys explainer — Phase 30 GA content
molecule-ai[bot] Apr 21, 2026
898f88d
docs(blog): add Molecule AI Cloud waitlist — Phase 30 GA content
molecule-ai[bot] Apr 21, 2026
250c268
docs(blog): add active_tasks concurrency explainer — Phase 30 GA content
molecule-ai[bot] Apr 21, 2026
6073848
docs(blog): skills vs bundled tools explainer — HERMES v0.10.0 compet…
molecule-ai[bot] Apr 21, 2026
47f9fc8
docs(blog): add Discord adapter launch post — Phase 30 GA content
molecule-ai[bot] Apr 21, 2026
d548acd
marketing(briefs): Chrome DevTools MCP SEO audit — GH#1335 complete
molecule-ai[bot] Apr 21, 2026
a2ba185
marketing(social): Day 1 social queue — 2026-04-21 status update
molecule-ai[bot] Apr 21, 2026
42bc14e
marketing(pmm): Phase 30 launch calendar — blog posts live on staging…
molecule-ai[bot] Apr 21, 2026
ae196e0
brand(audio): skills intro TTS — GH#1415 HERMES counter-demo
molecule-ai[bot] Apr 21, 2026
158fc71
devrel(demo): skills showcase counter-demo README — GH#1415 HERMES v0…
molecule-ai[bot] Apr 21, 2026
67eed1e
marketing(pmm): Phase 30 launch calendar — Day 4 Cloudflare Artifacts…
molecule-ai[bot] Apr 21, 2026
7c64a40
social: add Cloudflare Artifacts launch copy to 2026-04-24 queue — GH…
molecule-ai[bot] Apr 21, 2026
b3d2cf8
marketing(social): Cloudflare Artifacts social copy draft — Day 4 cam…
molecule-ai[bot] Apr 21, 2026
12b130b
brand(img): cf-artifacts-api-endpoints.png for Cloudflare Artifacts l…
molecule-ai[bot] Apr 21, 2026
7897db0
brand(img): cf-artifacts-og-card.png for Cloudflare Artifacts launch …
molecule-ai[bot] Apr 21, 2026
da54348
marketing(social): social queue update — Discord Reddit/HN PR#1432 st…
molecule-ai[bot] Apr 21, 2026
99f5d3b
brand(img): cf-artifacts-api-endpoints.png for Cloudflare Artifacts l…
molecule-ai[bot] Apr 21, 2026
25c241d
brand(img): cf-artifacts-og-card.png for Cloudflare Artifacts launch …
molecule-ai[bot] Apr 21, 2026
d517b79
seo: Chrome DevTools MCP blog — meta description + keyword density fixes
molecule-ai[bot] Apr 21, 2026
5708933
social: update Cloudflare Artifacts asset inventory + visual refs — G…
molecule-ai[bot] Apr 21, 2026
f3ec07a
fix(F1085): deleteViaEphemeral rm argument scope — fixes volume-delet…
molecule-ai[bot] Apr 21, 2026
1ab6367
fix(P0): CWE-22 path traversal + ContextMenu test regression (#1476)
molecule-ai[bot] Apr 21, 2026
37ce2aa
Trigger CI for PR #1486
molecule-ai[bot] Apr 21, 2026
79a8103
fix(mcp): surface commit_memory MCP calls in activity_logs (GH#1490) …
molecule-ai[bot] Apr 21, 2026
5a6f164
docs(marketing): soften unverified latency claim in Cloudflare Artifa…
Apr 21, 2026
80fedd6
fix(canvas/a11y): MissingKeysModal dialog semantics + focus management
Apr 21, 2026
eb87737
fix(platform): session cookie auth for /registry/:id/peers (SaaS canv…
Apr 21, 2026
f675500
fix(canvas/a11y): add aria-hidden to decorative SVG icons in dialog a…
Apr 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .ci-trigger/RERUN
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
PR_1486_retrigger
153 changes: 153 additions & 0 deletions .github/workflows/canary-staging.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
name: Canary — staging SaaS smoke (every 30 min)

# Minimum viable health check: provisions one Hermes workspace on a fresh
# staging org, sends one A2A message, verifies PONG, tears down. ~8 min
# wall clock. Pages on failure by opening a GitHub issue; auto-closes the
# issue on the next green run.
#
# The full-SaaS workflow (e2e-staging-saas.yml) covers the broader surface
# but runs only on provisioning-critical pushes + nightly — this one
# catches drift in the 30-min window between those runs (AMI health, CF
# cert rotation, WorkOS session stability, etc.).
#
# Lean mode: E2E_MODE=canary skips the child workspace + HMA memory +
# peers/activity checks. One parent workspace + one A2A turn is enough
# to signal "SaaS stack end-to-end is alive."

on:
schedule:
# Every 30 min. Cron on GitHub-hosted runners has a known drift of
# a few minutes under load — that's fine for a canary.
- cron: '*/30 * * * *'
workflow_dispatch:

# Serialise with the full-SaaS workflow so they don't contend for the
# same org-create quota on staging. Different group key from
# e2e-staging-saas since we don't mind queueing canaries behind one
# full run, but two canaries SHOULD queue against each other.
concurrency:
group: canary-staging
cancel-in-progress: false

permissions:
# Needed to open / close the alerting issue.
issues: write
contents: read

jobs:
canary:
name: Canary smoke
runs-on: ubuntu-latest
timeout-minutes: 15

env:
MOLECULE_CP_URL: https://staging-api.moleculesai.app
MOLECULE_ADMIN_TOKEN: ${{ secrets.MOLECULE_STAGING_ADMIN_TOKEN }}
E2E_MODE: canary
E2E_RUNTIME: hermes
E2E_RUN_ID: "canary-${{ github.run_id }}"

steps:
- uses: actions/checkout@v4

- name: Verify admin token present
run: |
if [ -z "$MOLECULE_ADMIN_TOKEN" ]; then
echo "::error::MOLECULE_STAGING_ADMIN_TOKEN not set"
exit 2
fi

- name: Canary run
id: canary
run: bash tests/e2e/test_staging_full_saas.sh

# Alerting: open an issue on first failure, auto-close on recovery.
# Title includes a stable marker so multiple consecutive failures
# don't spam — they just add comments to the existing issue.
- name: Open issue on failure
if: failure()
uses: actions/github-script@v7
with:
script: |
const title = '🔴 Canary failing: staging SaaS smoke';
const runURL = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
const body =
`Canary run failed at ${new Date().toISOString()}.\n\n` +
`Run: ${runURL}\n\n` +
`This issue auto-closes on the next green canary run. ` +
`Consecutive failures add a comment here rather than a new issue.`;

// Find an existing open canary issue (stable title match).
const { data: existing } = await github.rest.issues.listForRepo({
owner: context.repo.owner, repo: context.repo.repo,
state: 'open', labels: 'canary-staging',
per_page: 10,
});
const match = existing.find(i => i.title === title);

if (match) {
await github.rest.issues.createComment({
owner: context.repo.owner, repo: context.repo.repo,
issue_number: match.number,
body: `Canary still failing. ${runURL}`,
});
core.info(`Commented on existing issue #${match.number}`);
} else {
await github.rest.issues.create({
owner: context.repo.owner, repo: context.repo.repo,
title, body,
labels: ['canary-staging', 'bug'],
});
core.info('Opened new canary failure issue');
}

- name: Auto-close canary issue on success
if: success()
uses: actions/github-script@v7
with:
script: |
const title = '🔴 Canary failing: staging SaaS smoke';
const { data: open } = await github.rest.issues.listForRepo({
owner: context.repo.owner, repo: context.repo.repo,
state: 'open', labels: 'canary-staging',
per_page: 10,
});
const match = open.find(i => i.title === title);
if (match) {
await github.rest.issues.createComment({
owner: context.repo.owner, repo: context.repo.repo,
issue_number: match.number,
body: `Canary recovered at ${new Date().toISOString()}. Closing.`,
});
await github.rest.issues.update({
owner: context.repo.owner, repo: context.repo.repo,
issue_number: match.number,
state: 'closed',
});
core.info(`Closed recovered canary issue #${match.number}`);
}

- name: Teardown safety net
if: always()
env:
ADMIN_TOKEN: ${{ secrets.MOLECULE_STAGING_ADMIN_TOKEN }}
run: |
set +e
orgs=$(curl -sS "$MOLECULE_CP_URL/cp/admin/orgs" \
-H "Authorization: Bearer $ADMIN_TOKEN" 2>/dev/null \
| python3 -c "
import json, sys
d = json.load(sys.stdin)
today = __import__('datetime').date.today().strftime('%Y%m%d')
candidates = [o['slug'] for o in d.get('orgs', [])
if o.get('slug','').startswith(f'e2e-{today}-canary-')
and o.get('status') not in ('purged',)]
print('\n'.join(candidates))
" 2>/dev/null)
for slug in $orgs; do
curl -sS -X DELETE "$MOLECULE_CP_URL/cp/admin/tenants/$slug" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"confirm\":\"$slug\"}" >/dev/null || true
done
exit 0
5 changes: 2 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,9 @@ jobs:
run: |
# For push events: diff against previous commit (handles merge commits)
# For PR events: diff against the base branch
if [ "${{ github.event_name }}" = "pull_request" ]; then
BASE="${GITHUB_BASE_REF:-${{ github.event.before }}}"
if [ "${{ github.event_name }}" = "pull_request" ] && [ -n "${{ github.event.pull_request.base.sha }}" ]; then
BASE="${{ github.event.pull_request.base.sha }}"
else
BASE="${{ github.event.before }}"
fi
# Fallback: if BASE is empty or all zeros (new branch), run everything
if [ -z "$BASE" ] || echo "$BASE" | grep -qE '^0+$'; then
Expand Down
116 changes: 116 additions & 0 deletions .github/workflows/e2e-staging-canvas.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
name: E2E Staging Canvas (Playwright)

# Playwright test suite that provisions a fresh staging org per run and
# verifies every workspace-panel tab renders without crashing. Complements
# e2e-staging-saas.yml (which tests the API shape) by exercising the
# actual browser + canvas bundle against live staging.
#
# Triggers: push to main or PR touching canvas sources + this workflow,
# manual dispatch, and weekly cron to catch browser/runtime drift even
# when canvas is quiet.

on:
push:
branches: [main]
paths:
- 'canvas/**'
- '.github/workflows/e2e-staging-canvas.yml'
pull_request:
branches: [main]
paths:
- 'canvas/**'
- '.github/workflows/e2e-staging-canvas.yml'
workflow_dispatch:
schedule:
# Weekly on Sunday 08:00 UTC — catches Chrome / Playwright / Next.js
# release-note-shaped regressions that don't ride in with a PR.
- cron: '0 8 * * 0'

concurrency:
group: e2e-staging-canvas
cancel-in-progress: false

jobs:
playwright:
name: Canvas tabs E2E
runs-on: ubuntu-latest
timeout-minutes: 40

env:
CANVAS_E2E_STAGING: '1'
MOLECULE_CP_URL: https://staging-api.moleculesai.app
MOLECULE_ADMIN_TOKEN: ${{ secrets.MOLECULE_STAGING_ADMIN_TOKEN }}

defaults:
run:
working-directory: canvas

steps:
- uses: actions/checkout@v4

- name: Verify admin token present
run: |
if [ -z "$MOLECULE_ADMIN_TOKEN" ]; then
echo "::error::Missing MOLECULE_STAGING_ADMIN_TOKEN"
exit 2
fi

- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: canvas/package-lock.json

- name: Install canvas deps
run: npm ci

- name: Install Playwright browsers
run: npx playwright install --with-deps chromium

- name: Run staging canvas E2E
run: npx playwright test --config=playwright.staging.config.ts

- name: Upload Playwright report on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-report-staging
path: canvas/playwright-report-staging/
retention-days: 14

- name: Upload screenshots on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-screenshots
path: canvas/test-results/
retention-days: 14

# Safety-net teardown mirrors the bash-harness workflow — if
# globalTeardown didn't run (worker crash, runner cancel), this
# step sweeps any e2e-canvas-* org tagged with today's date.
- name: Teardown safety net
if: always()
env:
ADMIN_TOKEN: ${{ secrets.MOLECULE_STAGING_ADMIN_TOKEN }}
run: |
set +e
orgs=$(curl -sS "$MOLECULE_CP_URL/cp/admin/orgs" \
-H "Authorization: Bearer $ADMIN_TOKEN" 2>/dev/null \
| python3 -c "
import json, sys
d = json.load(sys.stdin)
today = __import__('datetime').date.today().strftime('%Y%m%d')
candidates = [o['slug'] for o in d.get('orgs', [])
if o.get('slug','').startswith(f'e2e-canvas-{today}-')
and o.get('status') not in ('purged',)]
print('\n'.join(candidates))
" 2>/dev/null)
for slug in $orgs; do
curl -sS -X DELETE "$MOLECULE_CP_URL/cp/admin/tenants/$slug" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"confirm\":\"$slug\"}" >/dev/null || true
done
exit 0
Loading
Loading