Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 6 additions & 3 deletions .github/workflows/publish-plugins.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ on:
catalog_base_url:
description: "Catalog base URL for immutable artifact links"
required: false
default: "https://corvus.profiletailors.com"
default: "https://plugins.corvus.profiletailors.com"
oci_repository:
description: "OCI repository (example: ghcr.io/org/corvus-plugins/memory-surreal-graphs)"
required: false
Expand Down Expand Up @@ -80,7 +80,10 @@ jobs:
input_plugin_id = os.environ.get("INPUT_PLUGIN_ID", "").strip()
input_plugin_version = os.environ.get("INPUT_PLUGIN_VERSION", "").strip()
input_catalog_base_url = (
os.environ.get("INPUT_CATALOG_BASE_URL", "https://corvus.profiletailors.com")
os.environ.get(
"INPUT_CATALOG_BASE_URL",
"https://plugins.corvus.profiletailors.com",
)
.strip()
.rstrip("/")
)
Expand All @@ -91,7 +94,7 @@ jobs:
effective_cf_project_name = input_cf_project_name or default_cf_project_name

if not input_catalog_base_url:
input_catalog_base_url = "https://corvus.profiletailors.com"
input_catalog_base_url = "https://plugins.corvus.profiletailors.com"

if not input_deploy_cf:
input_deploy_cf = "true"
Expand Down
71 changes: 51 additions & 20 deletions clients/agent-runtime/src/config/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1023,7 +1023,7 @@ fn default_plugin_revocation_refresh_minutes() -> u64 {
}

fn default_plugin_revocation_sources() -> Vec<String> {
vec!["https://corvus.profiletailors.com/revocations.json".to_string()]
vec!["https://plugins.corvus.profiletailors.com/revocations.json".to_string()]
}

impl Default for PluginRevocationConfig {
Expand Down Expand Up @@ -1067,7 +1067,7 @@ fn default_plugin_allow_publishers() -> Vec<String> {
fn default_plugin_sources() -> Vec<PluginSourceConfig> {
vec![PluginSourceConfig {
name: "official".to_string(),
url: "https://corvus.profiletailors.com/catalog.json".to_string(),
url: "https://plugins.corvus.profiletailors.com/catalog.json".to_string(),
plugin_identity_regex: None,
}]
}
Expand Down Expand Up @@ -2220,12 +2220,20 @@ fn env_override_optional(var_name: &str, target: &mut Option<String>) {

fn migrate_deprecated_plugin_registry_urls(config: &mut Config) -> bool {
const OLD_PLUGIN_HOST: &str = "plugins.corvus.ai";
const NEW_PLUGIN_HOST: &str = "corvus.profiletailors.com";
const LEGACY_PLUGIN_HOST: &str = "corvus.profiletailors.com";
const NEW_PLUGIN_HOST: &str = "plugins.corvus.profiletailors.com";

fn migrate_url_host(raw_url: &str, old_host: &str, new_host: &str) -> Option<String> {
fn host_matches(candidate: &str, host: &str) -> bool {
candidate == host || candidate.ends_with(&format!(".{host}"))
}

fn migrate_url_host(raw_url: &str, old_hosts: &[&str], new_host: &str) -> Option<String> {
let mut parsed = Url::parse(raw_url).ok()?;
let host = parsed.host_str()?;
if host == old_host || host.ends_with(&format!(".{old_host}")) {
if old_hosts
.iter()
.any(|old_host| host_matches(host, old_host))
{
parsed.set_host(Some(new_host)).ok()?;
return Some(parsed.to_string());
}
Expand All @@ -2234,18 +2242,17 @@ fn migrate_deprecated_plugin_registry_urls(config: &mut Config) -> bool {

let mut changed = false;
let mut revocation_changed = false;
let old_hosts = [OLD_PLUGIN_HOST, LEGACY_PLUGIN_HOST];

for source in &mut config.plugins.sources {
if let Some(migrated) = migrate_url_host(&source.url, OLD_PLUGIN_HOST, NEW_PLUGIN_HOST) {
if let Some(migrated) = migrate_url_host(&source.url, &old_hosts, NEW_PLUGIN_HOST) {
source.url = migrated;
changed = true;
}
}

for source_url in &mut config.plugins.revocation.source_urls {
if let Some(migrated) =
migrate_url_host(source_url.as_str(), OLD_PLUGIN_HOST, NEW_PLUGIN_HOST)
{
if let Some(migrated) = migrate_url_host(source_url.as_str(), &old_hosts, NEW_PLUGIN_HOST) {
*source_url = migrated;
changed = true;
revocation_changed = true;
Expand Down Expand Up @@ -2336,8 +2343,9 @@ impl Config {

if migrate_deprecated_plugin_registry_urls(&mut config) {
tracing::warn!(
"Migrated deprecated plugin registry host entries from 'plugins.corvus.ai' \
to 'corvus.profiletailors.com' in plugins.sources and \
"Migrated deprecated plugin registry host entries from \
'plugins.corvus.ai' and 'corvus.profiletailors.com' to \
'plugins.corvus.profiletailors.com' in plugins.sources and \
plugins.revocation.source_urls"
);
if let Err(error) = config.save() {
Expand Down Expand Up @@ -2903,19 +2911,19 @@ default_temperature = 0.7
assert!(!plugins.sources.is_empty());
assert_eq!(
plugins.sources[0].url,
"https://corvus.profiletailors.com/catalog.json"
"https://plugins.corvus.profiletailors.com/catalog.json"
);
assert_eq!(
plugins.revocation.source_urls,
vec!["https://corvus.profiletailors.com/revocations.json".to_string()]
vec!["https://plugins.corvus.profiletailors.com/revocations.json".to_string()]
);
assert_eq!(
default_plugin_sources()[0].url,
"https://corvus.profiletailors.com/catalog.json"
"https://plugins.corvus.profiletailors.com/catalog.json"
);
assert_eq!(
default_plugin_revocation_sources(),
vec!["https://corvus.profiletailors.com/revocations.json".to_string()]
vec!["https://plugins.corvus.profiletailors.com/revocations.json".to_string()]
);
assert!(plugins
.allow_publishers
Expand All @@ -2939,22 +2947,22 @@ default_temperature = 0.7
];
config.plugins.revocation.source_urls = vec![
"https://plugins.corvus.ai/revocations.json".to_string(),
"https://corvus.profiletailors.com/revocations.json".to_string(),
"https://plugins.corvus.profiletailors.com/revocations.json".to_string(),
];

let changed = migrate_deprecated_plugin_registry_urls(&mut config);
assert!(changed);
assert_eq!(
config.plugins.sources[0].url,
"https://corvus.profiletailors.com/catalog.json"
"https://plugins.corvus.profiletailors.com/catalog.json"
);
assert_eq!(
config.plugins.sources[1].url,
"https://mirror.example/catalog.json"
);
assert_eq!(
config.plugins.revocation.source_urls,
vec!["https://corvus.profiletailors.com/revocations.json".to_string()]
vec!["https://plugins.corvus.profiletailors.com/revocations.json".to_string()]
);
}

Expand All @@ -2965,11 +2973,34 @@ default_temperature = 0.7
assert!(!changed);
assert_eq!(
config.plugins.sources[0].url,
"https://corvus.profiletailors.com/catalog.json"
"https://plugins.corvus.profiletailors.com/catalog.json"
);
assert_eq!(
config.plugins.revocation.source_urls,
vec!["https://plugins.corvus.profiletailors.com/revocations.json".to_string()]
);
}

#[test]
fn migrate_deprecated_plugin_registry_urls_rewrites_legacy_profiletailors_host() {
let mut config = Config::default();
config.plugins.sources = vec![PluginSourceConfig {
name: "official".to_string(),
url: "https://corvus.profiletailors.com/catalog.json".to_string(),
plugin_identity_regex: None,
}];
config.plugins.revocation.source_urls =
vec!["https://corvus.profiletailors.com/revocations.json".to_string()];

let changed = migrate_deprecated_plugin_registry_urls(&mut config);
assert!(changed);
assert_eq!(
config.plugins.sources[0].url,
"https://plugins.corvus.profiletailors.com/catalog.json"
);
assert_eq!(
config.plugins.revocation.source_urls,
vec!["https://corvus.profiletailors.com/revocations.json".to_string()]
vec!["https://plugins.corvus.profiletailors.com/revocations.json".to_string()]
);
}

Expand Down
4 changes: 2 additions & 2 deletions clients/agent-runtime/src/plugins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const MAX_ARTIFACT_BYTES: usize = 50 * 1024 * 1024;
const MAX_SIGNATURE_BYTES: usize = 64 * 1024;
const MAX_CERTIFICATE_BYTES: usize = 512 * 1024;
const COSIGN_VERIFY_TIMEOUT: Duration = Duration::from_secs(30);
const OFFICIAL_PLUGIN_CATALOG_HOST: &str = "corvus.profiletailors.com";
const OFFICIAL_PLUGIN_CATALOG_HOST: &str = "plugins.corvus.profiletailors.com";
const SIGSTORE_GITHUB_OIDC_ISSUER: &str = "https://token.actions.githubusercontent.com";
const OFFICIAL_PLUGIN_IDENTITY_REGEX: &str =
r"^https://github\.com/dallay/corvus/\.github/workflows/publish-plugins\.yml@.*$";
Expand Down Expand Up @@ -2065,7 +2065,7 @@ mod tests {
fn signature_policy_requires_keyless_for_official_remote_source() {
let source = PluginSourceConfig {
name: "official".to_string(),
url: "https://corvus.profiletailors.com/catalog.json".to_string(),
url: "https://plugins.corvus.profiletailors.com/catalog.json".to_string(),
plugin_identity_regex: None,
};

Expand Down
12 changes: 12 additions & 0 deletions clients/web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ clients/web/
│ ├── docs/ # Documentation (Astro + Starlight)
│ ├── marketing/ # Marketing landing and campaign pages (Astro)
│ ├── plugins/ # Plugin catalog and revocations (Astro)
│ ├── plugins-edge/ # Plugin distribution API (Cloudflare Worker + R2)
│ └── chat/ # ChatGPT-style conversational chat (Vue 3 + Vite)
├── packages/
│ └── shared/ # Shared utilities
Expand Down Expand Up @@ -37,6 +38,12 @@ clients/web/
- URL configurable with `PLUGINS_URL` (dev default: `http://localhost:9990`)
- Publishes official plugin metadata at `/catalog.json` and `/revocations.json`

### plugins-edge

- Runtime metadata/artifact API served from Cloudflare Worker + R2
- Intended production source for `/catalog.json`, `/revocations.json`, and `/artifacts/*`
- Default local port: 9797

### chat

- Framework: Vue 3 + Vite + Tailwind + shadcn-vue style components
Expand All @@ -61,6 +68,7 @@ pnpm build
pnpm build:docs
pnpm build:marketing
pnpm build:plugins
pnpm build:plugins-edge
pnpm build:chat

# Compatibility (legacy alias)
Expand All @@ -70,6 +78,7 @@ pnpm build:landing
pnpm dev
pnpm dev:marketing
pnpm dev:plugins
pnpm dev:plugins-edge
pnpm dev:chat

# Compatibility (legacy alias)
Expand All @@ -80,6 +89,9 @@ pnpm format
pnpm check
pnpm test
pnpm test:chat

# Deploy plugins edge worker
pnpm deploy:plugins-edge
```

## Biome (Linter & Formatter)
Expand Down
6 changes: 3 additions & 3 deletions clients/web/apps/docs/src/content/docs/en/guides/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ All new official plugins must satisfy:

Relevant configuration defaults:

- Catalog: `https://corvus.profiletailors.com/catalog.json`
- Revocations: `https://corvus.profiletailors.com/revocations.json`
- Catalog: `https://plugins.corvus.profiletailors.com/catalog.json`
- Revocations: `https://plugins.corvus.profiletailors.com/revocations.json`

See:

Expand Down Expand Up @@ -212,4 +212,4 @@ Recommended production rollout:
### Migration from old plugin host

On config load, Corvus migrates old `plugins.corvus.ai` host references to
`corvus.profiletailors.com` for both catalog and revocation source URLs.
`plugins.corvus.profiletailors.com` for both catalog and revocation source URLs.
6 changes: 3 additions & 3 deletions clients/web/apps/docs/src/content/docs/es/guides/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ Todos los plugins oficiales nuevos deben satisfacer:

Configuración por defecto relevante:

- Catálogo: `https://corvus.profiletailors.com/catalog.json`
- Revocaciones: `https://corvus.profiletailors.com/revocations.json`
- Catálogo: `https://plugins.corvus.profiletailors.com/catalog.json`
- Revocaciones: `https://plugins.corvus.profiletailors.com/revocations.json`

Ver:

Expand Down Expand Up @@ -215,4 +215,4 @@ Rollout recomendado para producción:
### Migración del host de plugins antiguo

Al cargar config, Corvus migra referencias antiguas del host `plugins.corvus.ai` a
`corvus.profiletailors.com` para ambas URLs de fuente de catálogo y revocación.
`plugins.corvus.profiletailors.com` para ambas URLs de fuente de catálogo y revocación.
3 changes: 3 additions & 0 deletions clients/web/apps/plugins-edge/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.wrangler/
dist/
node_modules/
53 changes: 53 additions & 0 deletions clients/web/apps/plugins-edge/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Plugins Edge API (Cloudflare Worker + R2)

This worker serves runtime plugin distribution assets directly from Cloudflare R2.

It is designed to keep plugin installation metadata (`catalog.json`, `revocations.json`)
and immutable artifacts (`.wasm`, `.sig`, `.pem`) consistent and fast to fetch.

## Endpoints

- `GET /catalog.json`
- `GET /revocations.json`
- `GET /artifacts/<plugin-id>/<version>/<file>`

## Security Defaults

- Rejects unsupported methods (`405`)
- Rejects malformed artifact paths (`400`)
- Rejects path traversal attempts (`..`, backslashes)
- Applies `X-Content-Type-Options: nosniff`
- Does not expose directory listing routes

## Cache Strategy

- `catalog.json`: `public, max-age=300, stale-while-revalidate=60`
- `revocations.json`: `no-store, max-age=0`
- `artifacts/*`: `public, max-age=31536000, immutable`

## Local Development

```bash
pnpm --filter @corvus/plugins-edge run dev
```

## Deploy

```bash
pnpm --filter @corvus/plugins-edge run deploy
```

## Cloudflare Configuration

1. Create R2 bucket (example: `corvus-plugins-catalog-prod`).
2. Bind R2 bucket to worker as `PLUGINS_BUCKET`.
3. Configure custom domain route for this worker (`plugins.corvus.profiletailors.com`).
4. Upload objects into R2 using these keys:
- `catalog/catalog.json`
- `catalog/revocations.json`
- `artifacts/<plugin-id>/<version>/<plugin-id>.wasm`
- `artifacts/<plugin-id>/<version>/<plugin-id>.wasm.sig`
- `artifacts/<plugin-id>/<version>/<plugin-id>.wasm.pem`

`CATALOG_OBJECT_KEY` and `REVOCATIONS_OBJECT_KEY` can override defaults via
`wrangler.toml` vars if needed.
19 changes: 19 additions & 0 deletions clients/web/apps/plugins-edge/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@corvus/plugins-edge",
"version": "0.1.8",
"private": true,
"description": "Corvus plugins catalog edge API (Cloudflare Worker + R2)",
"type": "module",
"scripts": {
"build": "tsc --noEmit && mkdir -p dist",
"dev": "wrangler dev --local --port 9797",
"deploy": "wrangler deploy",
"format": "biome format --write src package.json wrangler.toml tsconfig.json README.md",
"check": "biome check src package.json wrangler.toml tsconfig.json README.md",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"typescript": "5.9.3",
"wrangler": "4.67.0"
}
}
Loading
Loading