feat(capabilities): detect Hermes kanban plugin (foundation for /swarm <-> dashboard sync)

Adds capability detection for the upstream Hermes Agent kanban plugin
mounted at /api/plugins/kanban/. Lays the groundwork for the v2.3.0
work where the workspace's /swarm kanban surface syncs with the
dashboard's SQLite-backed kanban DB.

Changes:

* gateway-capabilities.ts: new probeKanban() probes
  /api/plugins/kanban/board on the dashboard URL with a short timeout.
  GatewayCapabilities now carries a 'kanban' boolean. Probed once per
  PROBE_TTL_MS alongside conductor.

* connection-status.ts: surfaces caps.kanban so client-side feature
  gates can react.

* use-feature-capability.ts + feature-gates.ts: 'kanban' is now a
  recognized FeatureKey / EnhancedFeature so useFeatureCapability('kanban')
  works.

* .env.example: documents HERMES_DASHBOARD_URL (default 127.0.0.1:9119
  on current Hermes Agent v0.13+; the legacy 9120 is gone).

Tested locally with hermes-agent main pulled into
/Users/aurora/hermes-dashboard-fresh/repo. Workspace gateway-status
now reports kanban: true when the plugin is mounted, false otherwise.
This commit is contained in:
Aurora release bot
2026-05-04 10:57:46 -04:00
parent bba4c08f57
commit 2526984fae
5 changed files with 49 additions and 1 deletions

View File

@@ -103,6 +103,13 @@
# STREAM_ACCEPTED_TIMEOUT_MS=120000
# STREAM_HANDOFF_TIMEOUT_MS=300000
# Dashboard URL
#
# Where Hermes Agent's dashboard is reachable (default: 127.0.0.1:9119).
# /api/sessions, the conductor mission API, and the upstream kanban plugin
# all live on the dashboard, not the gateway.
# HERMES_DASHBOARD_URL=http://127.0.0.1:9119
# Dashboard API bearer token (optional)
#
# Preferred over the legacy HTML-scrape token flow. Set this to a dashboard

View File

@@ -19,6 +19,7 @@ export type FeatureKey =
| 'jobs'
| 'dashboard'
| 'enhancedChat'
| 'kanban'
export type CapabilityState = {
/** True when the capability is available right now. */

View File

@@ -10,6 +10,7 @@ export type EnhancedFeature =
| 'jobs'
| 'mcp'
| 'mcpFallback'
| 'kanban'
const FEATURE_LABELS: Record<EnhancedFeature, string> = {
sessions: 'Sessions',
@@ -19,6 +20,7 @@ const FEATURE_LABELS: Record<EnhancedFeature, string> = {
jobs: 'Jobs',
mcp: 'MCP Servers',
mcpFallback: 'MCP Servers (config fallback)',
kanban: 'Kanban (Hermes plugin)',
}
function normalizeFeature(
@@ -32,7 +34,8 @@ function normalizeFeature(
normalized === 'config' ||
normalized === 'jobs' ||
normalized === 'mcp' ||
normalized === 'mcpfallback'
normalized === 'mcpfallback' ||
normalized === 'kanban'
) {
return normalized === 'mcpfallback' ? 'mcpFallback' : normalized
}

View File

@@ -128,6 +128,7 @@ export const Route = createFileRoute('/api/connection-status')({
jobs: caps.jobs,
mcp: caps.mcp,
conductor: caps.conductor,
kanban: caps.kanban,
enhancedChat: caps.enhancedChat,
dashboard: caps.dashboard.available,
},

View File

@@ -179,6 +179,15 @@ export type EnhancedCapabilities = {
* placeholder instead of failing mid-action. See #262.
*/
conductor: boolean
/**
* True when the dashboard exposes `/api/plugins/kanban/board` (the native
* Hermes kanban plugin shipped upstream). When available, the workspace's
* /swarm kanban surface can sync with the dashboard's kanban DB so both
* UIs read/write the same SQLite source of truth instead of running
* separate stores. When false, the workspace falls back to its local
* file-backed swarm-kanban store. See v2.3.0 plan.
*/
kanban: boolean
}
export type DashboardCapabilities = {
@@ -224,6 +233,7 @@ let capabilities: GatewayCapabilities = {
mcp: false,
mcpFallback: false,
conductor: false,
kanban: false,
dashboard: {
available: false,
url: CLAUDE_DASHBOARD_URL,
@@ -606,6 +616,30 @@ async function probeConductor(dashboardAvailable: boolean): Promise<boolean> {
}
}
/**
* Lightweight probe for the upstream Hermes kanban plugin. When the dashboard
* exposes `/api/plugins/kanban/board` we assume the kanban plugin is loaded
* and the workspace can sync its /swarm kanban surface with the dashboard's
* SQLite-backed kanban DB. Mounted by hermes_cli.web_server
* `_mount_plugin_api_routes()`. See v2.3.0 plan.
*/
async function probeKanban(dashboardAvailable: boolean): Promise<boolean> {
if (!dashboardAvailable) return false
try {
const res = await dashboardFetch('/api/plugins/kanban/board', {
method: 'GET',
signal: AbortSignal.timeout(PROBE_TIMEOUT_MS),
})
if (res.status === 404 || res.status === 405) return false
// The plugin route is unauthenticated by design (loopback-only), so
// 200 is the normal success. Some auth setups may return 401 — still
// means the route exists.
return true
} catch {
return false
}
}
// Vanilla hermes-agent 0.10.0 satisfies: health, chatCompletions, models, streaming,
// sessions, skills, config, jobs. Dashboard-only endpoints (themes/plugins) and the
@@ -761,6 +795,7 @@ export async function probeGateway(options?: {
// Conductor probe runs after dashboard probe.
const conductor = await probeConductor(dashboard.available)
const kanban = await probeKanban(dashboard.available)
// Phase 1.5 fallback: when native /api/mcp is missing but the dashboard
// exposes `config.mcp_servers` AND we are loopback-only, allow a config
@@ -791,6 +826,7 @@ export async function probeGateway(options?: {
mcp,
mcpFallback,
conductor,
kanban,
dashboard,
}
lastProbeAt = Date.now()