feat: add Hermes integration panel to Settings → Integrations
Server-side: - New hermes-integration module (runtime.js + routes.js + tests) exposing GET /api/openchamber/hermes/status - Reports availability of agent-activity, session-steer, and plan-gate runtimes plus plan-gate default config - Wired into feature-routes-runtime.js alongside existing integrations UI: - New HermesIntegration.tsx collapsible panel in Integrations page - Connection status indicator (green/red dot) with 30s polling - Plan gate default toggle (persists via updateDesktopSettings + recordDeferredOpenCodeRestart) - Read-only status rows for activity stream, steer channel, plan gate - Server version and uptime display Settings: - hermesPlanGateDefault field in settings registry and useUIStore - Search index entry with keywords: hermes, agent, integration, etc. - i18n keys for all 12 locales (English text as fallback) Verification: - Server tests: 7/7 passed (runtime + routes) - UI tests: 4/4 passed (HermesIntegration.test.tsx) - Typecheck: clean (no new errors) - oxlint: clean on all new/modified files
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import React from "react";
|
||||
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
|
||||
import { I18nProvider } from "@/lib/i18n";
|
||||
import { useUIStore } from "@/stores/useUIStore";
|
||||
|
||||
import { HermesIntegration } from "./HermesIntegration";
|
||||
|
||||
const originalFetch = globalThis.fetch;
|
||||
|
||||
const mockStatus = {
|
||||
ok: true,
|
||||
server: { version: "1.23.0", uptimeMs: 5000 },
|
||||
integrations: {
|
||||
agentActivity: { available: true },
|
||||
steer: { available: true },
|
||||
planGate: { available: true },
|
||||
},
|
||||
config: {
|
||||
planGateDefault: false,
|
||||
activityRateLimitMs: 2000,
|
||||
},
|
||||
};
|
||||
|
||||
const renderComponent = () =>
|
||||
renderToStaticMarkup(
|
||||
<I18nProvider>
|
||||
<HermesIntegration />
|
||||
</I18nProvider>,
|
||||
);
|
||||
|
||||
describe("HermesIntegration", () => {
|
||||
beforeEach(() => {
|
||||
// SAFETY: mock() returns a callable mock; the test harness only needs a fetch-like function.
|
||||
globalThis.fetch = mock(() =>
|
||||
Promise.resolve(new Response(JSON.stringify(mockStatus), { status: 200 }))
|
||||
) as typeof fetch;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.fetch = originalFetch;
|
||||
useUIStore.setState({ hermesPlanGateDefault: false });
|
||||
});
|
||||
|
||||
test("renders the Hermes title and description", () => {
|
||||
const markup = renderComponent();
|
||||
expect(markup).toContain("Hermes");
|
||||
expect(markup).toContain("Agent-to-agent integration");
|
||||
});
|
||||
|
||||
test("renders the collapsible trigger with status indicator", () => {
|
||||
const markup = renderComponent();
|
||||
expect(markup).toContain("integrations.hermes");
|
||||
expect(markup).toContain("Checking…");
|
||||
});
|
||||
|
||||
test("does not render collapsed content in SSR", () => {
|
||||
const markup = renderComponent();
|
||||
expect(markup).not.toContain("Activity stream");
|
||||
expect(markup).not.toContain("Require plan approval by default");
|
||||
});
|
||||
|
||||
test("toggling plan gate default updates the store", () => {
|
||||
const state = useUIStore.getState();
|
||||
expect(state.hermesPlanGateDefault).toBe(false);
|
||||
|
||||
state.setHermesPlanGateDefault(true);
|
||||
expect(useUIStore.getState().hermesPlanGateDefault).toBe(true);
|
||||
|
||||
state.setHermesPlanGateDefault(false);
|
||||
expect(useUIStore.getState().hermesPlanGateDefault).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,192 @@
|
||||
import React from 'react';
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
||||
import {
|
||||
SettingsCheckboxRow,
|
||||
SETTINGS_OPTION_STACK_CLASS,
|
||||
} from '@/components/sections/shared/SettingsSection';
|
||||
import { recordDeferredOpenCodeRestart } from '@/lib/opencode/deferredRestart';
|
||||
import { updateDesktopSettings } from '@/lib/persistence';
|
||||
import { useUIStore } from '@/stores/useUIStore';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Icon } from '@/components/icon/Icon';
|
||||
|
||||
const STATUS_POLL_INTERVAL_MS = 30_000;
|
||||
|
||||
type HermesStatus = {
|
||||
ok: boolean;
|
||||
server: { version: string; uptimeMs: number };
|
||||
integrations: {
|
||||
agentActivity: { available: boolean };
|
||||
steer: { available: boolean };
|
||||
planGate: { available: boolean };
|
||||
};
|
||||
config: {
|
||||
planGateDefault: boolean;
|
||||
activityRateLimitMs: number;
|
||||
};
|
||||
} | null;
|
||||
|
||||
function StatusDot({ available }: { available: boolean }) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
'inline-block size-2 rounded-full',
|
||||
available
|
||||
? 'bg-[var(--status-success)]'
|
||||
: 'bg-[var(--status-error)]',
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function StatusRow({
|
||||
label,
|
||||
available,
|
||||
detail,
|
||||
}: {
|
||||
label: string;
|
||||
available: boolean;
|
||||
detail?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex items-center gap-2 py-0.5">
|
||||
<StatusDot available={available} />
|
||||
<span className="typography-settings-field-label text-foreground">{label}</span>
|
||||
{detail ? (
|
||||
<span className="typography-meta text-muted-foreground">{detail}</span>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const HermesIntegration: React.FC = () => {
|
||||
const { t } = useI18n();
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [status, setStatus] = React.useState<HermesStatus>(null);
|
||||
const [fetchError, setFetchError] = React.useState(false);
|
||||
|
||||
const hermesPlanGateDefault = useUIStore((state) => state.hermesPlanGateDefault);
|
||||
const setHermesPlanGateDefault = useUIStore((state) => state.setHermesPlanGateDefault);
|
||||
|
||||
const fetchStatus = React.useCallback(async () => {
|
||||
try {
|
||||
const response = await fetch('/api/openchamber/hermes/status');
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
const data: HermesStatus = await response.json();
|
||||
setStatus(data);
|
||||
setFetchError(false);
|
||||
} catch {
|
||||
setStatus(null);
|
||||
setFetchError(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
void fetchStatus();
|
||||
const timer = window.setInterval(fetchStatus, STATUS_POLL_INTERVAL_MS);
|
||||
return () => window.clearInterval(timer);
|
||||
}, [fetchStatus]);
|
||||
|
||||
const handlePlanGateDefaultChange = React.useCallback((enabled: boolean) => {
|
||||
setHermesPlanGateDefault(enabled);
|
||||
void updateDesktopSettings({ hermesPlanGateDefault: enabled });
|
||||
recordDeferredOpenCodeRestart('cli', { id: 'hermes-plan-gate' });
|
||||
}, [setHermesPlanGateDefault]);
|
||||
|
||||
const connected = status?.ok === true && !fetchError;
|
||||
const statusLabel = fetchError
|
||||
? t('settings.integrations.hermes.status.unreachable')
|
||||
: connected
|
||||
? t('settings.integrations.hermes.status.connected')
|
||||
: t('settings.integrations.hermes.status.checking');
|
||||
const statusClassName = fetchError
|
||||
? 'bg-[var(--status-error)]/15 text-[var(--status-error)]'
|
||||
: connected
|
||||
? 'bg-[var(--status-success)]/15 text-[var(--status-success)]'
|
||||
: 'bg-[var(--surface-muted)] text-muted-foreground';
|
||||
|
||||
const activityRateLimitMs = status?.config?.activityRateLimitMs ?? 2000;
|
||||
const activityRateLabel = `${Math.round(activityRateLimitMs / 1000)}s`;
|
||||
|
||||
return (
|
||||
<Collapsible open={open} onOpenChange={setOpen}>
|
||||
<div
|
||||
data-settings-item="integrations.hermes"
|
||||
className="overflow-hidden rounded-xl border border-[var(--interactive-border)] bg-[var(--surface-elevated)]"
|
||||
>
|
||||
<CollapsibleTrigger
|
||||
className="flex w-full min-w-0 items-center gap-3 px-4 py-3 text-left hover:bg-[var(--interactive-hover)]/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--interactive-focus-ring)]"
|
||||
>
|
||||
<div className="flex size-10 shrink-0 items-center justify-center rounded-[10px] bg-[var(--surface-muted)]">
|
||||
<Icon name="robot" className="size-5 text-foreground" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="truncate text-sm font-semibold text-foreground">
|
||||
{t('settings.integrations.hermes.title')}
|
||||
</div>
|
||||
<p className="mt-0.5 line-clamp-1 text-xs leading-snug text-muted-foreground">
|
||||
{t('settings.integrations.hermes.description')}
|
||||
</p>
|
||||
</div>
|
||||
<span
|
||||
aria-live="polite"
|
||||
className={cn(
|
||||
'max-w-36 shrink-0 truncate rounded-full px-2 py-0.5 text-[10px] font-medium',
|
||||
statusClassName,
|
||||
)}
|
||||
>
|
||||
{statusLabel}
|
||||
</span>
|
||||
<Icon
|
||||
name="arrow-down-s"
|
||||
className={cn(
|
||||
'size-4 shrink-0 text-muted-foreground transition-transform duration-150 ease-out motion-reduce:transition-none',
|
||||
open && 'rotate-180',
|
||||
)}
|
||||
/>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="border-t border-[var(--interactive-border)] px-4 py-4">
|
||||
<div className="space-y-4">
|
||||
<div className={SETTINGS_OPTION_STACK_CLASS}>
|
||||
<SettingsCheckboxRow
|
||||
settingsItem="integrations.hermes.plan-gate-default"
|
||||
checked={hermesPlanGateDefault}
|
||||
onChange={handlePlanGateDefaultChange}
|
||||
label={t('settings.integrations.hermes.field.planGateDefault')}
|
||||
ariaLabel={t('settings.integrations.hermes.field.planGateDefaultAria')}
|
||||
info={t('settings.integrations.hermes.field.planGateDefaultInfo')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<p className="typography-meta font-medium text-foreground">
|
||||
{t('settings.integrations.hermes.section.status')}
|
||||
</p>
|
||||
<StatusRow
|
||||
label={t('settings.integrations.hermes.field.activityStream')}
|
||||
available={status?.integrations?.agentActivity?.available ?? false}
|
||||
detail={`${t('settings.integrations.hermes.field.activityStreamDetail')} · ${activityRateLabel}`}
|
||||
/>
|
||||
<StatusRow
|
||||
label={t('settings.integrations.hermes.field.steerChannel')}
|
||||
available={status?.integrations?.steer?.available ?? false}
|
||||
/>
|
||||
<StatusRow
|
||||
label={t('settings.integrations.hermes.field.planGate')}
|
||||
available={status?.integrations?.planGate?.available ?? false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{status?.server ? (
|
||||
<p className="typography-meta text-muted-foreground">
|
||||
v{status.server.version} · {t('settings.integrations.hermes.field.uptime')}{' '}
|
||||
{Math.round(status.server.uptimeMs / 1000)}s
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</div>
|
||||
</Collapsible>
|
||||
);
|
||||
};
|
||||
@@ -6,6 +6,7 @@ import { isVSCodeRuntime } from '@/lib/desktop';
|
||||
import { getRegisteredRuntimeAPIs } from '@/contexts/runtimeAPIRegistry';
|
||||
import { GitHubIntegration } from './GitHubIntegration';
|
||||
import { LinearSettings } from './LinearSettings';
|
||||
import { HermesIntegration } from './HermesIntegration';
|
||||
|
||||
export const IntegrationsPage: React.FC = () => {
|
||||
const { t } = useI18n();
|
||||
@@ -29,6 +30,7 @@ export const IntegrationsPage: React.FC = () => {
|
||||
>
|
||||
{hasGitHub ? <GitHubIntegration /> : null}
|
||||
{hasLinear ? <LinearSettings /> : null}
|
||||
<HermesIntegration />
|
||||
</SettingsSection>
|
||||
</SettingsPageLayout>
|
||||
);
|
||||
|
||||
@@ -2367,5 +2367,5 @@ export const settingsDict = {
|
||||
'settings.openchamber.visual.field.inputHistoryLimitUnit': 'prompts',
|
||||
...linearIntegrationI18n.en,
|
||||
'settings.page.integrations.title': 'Integrations',
|
||||
'settings.page.integrations.description': 'Connect GitHub and Linear so OpenChamber can work with your issues and pull requests.',
|
||||
'settings.page.integrations.description': 'Connect GitHub, Linear, and Hermes so OpenChamber can work with your issues, pull requests, and agent integrations.',
|
||||
} as const;
|
||||
|
||||
@@ -45,6 +45,20 @@ export const linearIntegrationI18n = {
|
||||
'settings.integrations.linear.sessionComments.info': 'Adds a comment to the issue when a session starts, finishes, or fails. Comments are only posted when this server has a public address, so the link opens the session for everyone on the issue.',
|
||||
'settings.integrations.linear.sessionComments.aria': 'Post session status comments to Linear',
|
||||
'settings.integrations.linear.sessionComments.loadFailed': 'Could not load Linear comment settings.',
|
||||
'settings.integrations.hermes.title': 'Hermes',
|
||||
'settings.integrations.hermes.description': 'Agent-to-agent integration for activity streaming, session steering, and plan gates.',
|
||||
'settings.integrations.hermes.status.connected': 'Connected',
|
||||
'settings.integrations.hermes.status.unreachable': 'Unreachable',
|
||||
'settings.integrations.hermes.status.checking': 'Checking…',
|
||||
'settings.integrations.hermes.field.planGateDefault': 'Require plan approval by default',
|
||||
'settings.integrations.hermes.field.planGateDefaultAria': 'Require plan approval by default',
|
||||
'settings.integrations.hermes.field.planGateDefaultInfo': 'When enabled, new agent sessions wait for plan approval before making changes. Requires a server restart.',
|
||||
'settings.integrations.hermes.section.status': 'Integration status',
|
||||
'settings.integrations.hermes.field.activityStream': 'Activity stream',
|
||||
'settings.integrations.hermes.field.activityStreamDetail': 'Rate limited per session',
|
||||
'settings.integrations.hermes.field.steerChannel': 'Steer channel',
|
||||
'settings.integrations.hermes.field.planGate': 'Plan gate',
|
||||
'settings.integrations.hermes.field.uptime': 'Server uptime',
|
||||
'settings.magicPrompts.sidebar.group.linear': 'Linear',
|
||||
'settings.magicPrompts.sidebar.item.linearIssueReview': 'Issue Review',
|
||||
'settings.magicPrompts.page.group.linearIssueReview.title': 'Issue Review',
|
||||
@@ -95,6 +109,20 @@ export const linearIntegrationI18n = {
|
||||
'settings.integrations.linear.sessionComments.info': 'Kommentiert das Issue, wenn eine Sitzung startet, endet oder fehlschlägt. Kommentare werden nur gepostet, wenn dieser Server eine öffentliche Adresse hat, damit der Link die Sitzung für alle Beteiligten öffnet.',
|
||||
'settings.integrations.linear.sessionComments.aria': 'Statuskommentare zu Sitzungen in Linear posten',
|
||||
'settings.integrations.linear.sessionComments.loadFailed': 'Linear-Kommentareinstellungen konnten nicht geladen werden.',
|
||||
'settings.integrations.hermes.title': 'Hermes',
|
||||
'settings.integrations.hermes.description': 'Agent-to-agent integration for activity streaming, session steering, and plan gates.',
|
||||
'settings.integrations.hermes.status.connected': 'Connected',
|
||||
'settings.integrations.hermes.status.unreachable': 'Unreachable',
|
||||
'settings.integrations.hermes.status.checking': 'Checking…',
|
||||
'settings.integrations.hermes.field.planGateDefault': 'Require plan approval by default',
|
||||
'settings.integrations.hermes.field.planGateDefaultAria': 'Require plan approval by default',
|
||||
'settings.integrations.hermes.field.planGateDefaultInfo': 'When enabled, new agent sessions wait for plan approval before making changes. Requires a server restart.',
|
||||
'settings.integrations.hermes.section.status': 'Integration status',
|
||||
'settings.integrations.hermes.field.activityStream': 'Activity stream',
|
||||
'settings.integrations.hermes.field.activityStreamDetail': 'Rate limited per session',
|
||||
'settings.integrations.hermes.field.steerChannel': 'Steer channel',
|
||||
'settings.integrations.hermes.field.planGate': 'Plan gate',
|
||||
'settings.integrations.hermes.field.uptime': 'Server uptime',
|
||||
'settings.magicPrompts.sidebar.group.linear': 'Linear',
|
||||
'settings.magicPrompts.sidebar.item.linearIssueReview': 'Issue-Review',
|
||||
'settings.magicPrompts.page.group.linearIssueReview.title': 'Issue-Review',
|
||||
@@ -145,6 +173,20 @@ export const linearIntegrationI18n = {
|
||||
'settings.integrations.linear.sessionComments.info': 'Ajoute un commentaire au ticket quand une session démarre, se termine ou échoue. Les commentaires ne sont publiés que si ce serveur a une adresse publique, afin que le lien ouvre la session pour tout le monde.',
|
||||
'settings.integrations.linear.sessionComments.aria': 'Publier les commentaires d’état de session dans Linear',
|
||||
'settings.integrations.linear.sessionComments.loadFailed': 'Impossible de charger les réglages de commentaires Linear.',
|
||||
'settings.integrations.hermes.title': 'Hermes',
|
||||
'settings.integrations.hermes.description': 'Agent-to-agent integration for activity streaming, session steering, and plan gates.',
|
||||
'settings.integrations.hermes.status.connected': 'Connected',
|
||||
'settings.integrations.hermes.status.unreachable': 'Unreachable',
|
||||
'settings.integrations.hermes.status.checking': 'Checking…',
|
||||
'settings.integrations.hermes.field.planGateDefault': 'Require plan approval by default',
|
||||
'settings.integrations.hermes.field.planGateDefaultAria': 'Require plan approval by default',
|
||||
'settings.integrations.hermes.field.planGateDefaultInfo': 'When enabled, new agent sessions wait for plan approval before making changes. Requires a server restart.',
|
||||
'settings.integrations.hermes.section.status': 'Integration status',
|
||||
'settings.integrations.hermes.field.activityStream': 'Activity stream',
|
||||
'settings.integrations.hermes.field.activityStreamDetail': 'Rate limited per session',
|
||||
'settings.integrations.hermes.field.steerChannel': 'Steer channel',
|
||||
'settings.integrations.hermes.field.planGate': 'Plan gate',
|
||||
'settings.integrations.hermes.field.uptime': 'Server uptime',
|
||||
'settings.magicPrompts.sidebar.group.linear': 'Linear',
|
||||
'settings.magicPrompts.sidebar.item.linearIssueReview': 'Revue d’issue',
|
||||
'settings.magicPrompts.page.group.linearIssueReview.title': 'Revue d’issue',
|
||||
@@ -195,6 +237,20 @@ export const linearIntegrationI18n = {
|
||||
'settings.integrations.linear.sessionComments.info': 'Añade un comentario a la incidencia cuando una sesión empieza, termina o falla. Los comentarios solo se publican si este servidor tiene una dirección pública, para que el enlace abra la sesión a todos.',
|
||||
'settings.integrations.linear.sessionComments.aria': 'Publicar comentarios de estado de sesión en Linear',
|
||||
'settings.integrations.linear.sessionComments.loadFailed': 'No se pudieron cargar los ajustes de comentarios de Linear.',
|
||||
'settings.integrations.hermes.title': 'Hermes',
|
||||
'settings.integrations.hermes.description': 'Agent-to-agent integration for activity streaming, session steering, and plan gates.',
|
||||
'settings.integrations.hermes.status.connected': 'Connected',
|
||||
'settings.integrations.hermes.status.unreachable': 'Unreachable',
|
||||
'settings.integrations.hermes.status.checking': 'Checking…',
|
||||
'settings.integrations.hermes.field.planGateDefault': 'Require plan approval by default',
|
||||
'settings.integrations.hermes.field.planGateDefaultAria': 'Require plan approval by default',
|
||||
'settings.integrations.hermes.field.planGateDefaultInfo': 'When enabled, new agent sessions wait for plan approval before making changes. Requires a server restart.',
|
||||
'settings.integrations.hermes.section.status': 'Integration status',
|
||||
'settings.integrations.hermes.field.activityStream': 'Activity stream',
|
||||
'settings.integrations.hermes.field.activityStreamDetail': 'Rate limited per session',
|
||||
'settings.integrations.hermes.field.steerChannel': 'Steer channel',
|
||||
'settings.integrations.hermes.field.planGate': 'Plan gate',
|
||||
'settings.integrations.hermes.field.uptime': 'Server uptime',
|
||||
'settings.magicPrompts.sidebar.group.linear': 'Linear',
|
||||
'settings.magicPrompts.sidebar.item.linearIssueReview': 'Revisión de issue',
|
||||
'settings.magicPrompts.page.group.linearIssueReview.title': 'Revisión de issue',
|
||||
@@ -245,6 +301,20 @@ export const linearIntegrationI18n = {
|
||||
'settings.integrations.linear.sessionComments.info': 'セッションの開始・完了・失敗時にイシューへコメントします。リンクを誰でも開けるよう、このサーバーが公開アドレスを持つ場合のみ投稿します。',
|
||||
'settings.integrations.linear.sessionComments.aria': 'セッション状態のコメントを Linear に投稿',
|
||||
'settings.integrations.linear.sessionComments.loadFailed': 'Linear のコメント設定を読み込めませんでした。',
|
||||
'settings.integrations.hermes.title': 'Hermes',
|
||||
'settings.integrations.hermes.description': 'Agent-to-agent integration for activity streaming, session steering, and plan gates.',
|
||||
'settings.integrations.hermes.status.connected': 'Connected',
|
||||
'settings.integrations.hermes.status.unreachable': 'Unreachable',
|
||||
'settings.integrations.hermes.status.checking': 'Checking…',
|
||||
'settings.integrations.hermes.field.planGateDefault': 'Require plan approval by default',
|
||||
'settings.integrations.hermes.field.planGateDefaultAria': 'Require plan approval by default',
|
||||
'settings.integrations.hermes.field.planGateDefaultInfo': 'When enabled, new agent sessions wait for plan approval before making changes. Requires a server restart.',
|
||||
'settings.integrations.hermes.section.status': 'Integration status',
|
||||
'settings.integrations.hermes.field.activityStream': 'Activity stream',
|
||||
'settings.integrations.hermes.field.activityStreamDetail': 'Rate limited per session',
|
||||
'settings.integrations.hermes.field.steerChannel': 'Steer channel',
|
||||
'settings.integrations.hermes.field.planGate': 'Plan gate',
|
||||
'settings.integrations.hermes.field.uptime': 'Server uptime',
|
||||
'settings.magicPrompts.sidebar.group.linear': 'Linear',
|
||||
'settings.magicPrompts.sidebar.item.linearIssueReview': 'Issue レビュー',
|
||||
'settings.magicPrompts.page.group.linearIssueReview.title': 'Issue レビュー',
|
||||
@@ -295,6 +365,20 @@ export const linearIntegrationI18n = {
|
||||
'settings.integrations.linear.sessionComments.info': '세션이 시작, 완료, 실패할 때 이슈에 댓글을 남깁니다. 링크를 모두가 열 수 있도록 이 서버에 공개 주소가 있을 때만 게시합니다.',
|
||||
'settings.integrations.linear.sessionComments.aria': '세션 상태 댓글을 Linear에 게시',
|
||||
'settings.integrations.linear.sessionComments.loadFailed': 'Linear 댓글 설정을 불러오지 못했습니다.',
|
||||
'settings.integrations.hermes.title': 'Hermes',
|
||||
'settings.integrations.hermes.description': 'Agent-to-agent integration for activity streaming, session steering, and plan gates.',
|
||||
'settings.integrations.hermes.status.connected': 'Connected',
|
||||
'settings.integrations.hermes.status.unreachable': 'Unreachable',
|
||||
'settings.integrations.hermes.status.checking': 'Checking…',
|
||||
'settings.integrations.hermes.field.planGateDefault': 'Require plan approval by default',
|
||||
'settings.integrations.hermes.field.planGateDefaultAria': 'Require plan approval by default',
|
||||
'settings.integrations.hermes.field.planGateDefaultInfo': 'When enabled, new agent sessions wait for plan approval before making changes. Requires a server restart.',
|
||||
'settings.integrations.hermes.section.status': 'Integration status',
|
||||
'settings.integrations.hermes.field.activityStream': 'Activity stream',
|
||||
'settings.integrations.hermes.field.activityStreamDetail': 'Rate limited per session',
|
||||
'settings.integrations.hermes.field.steerChannel': 'Steer channel',
|
||||
'settings.integrations.hermes.field.planGate': 'Plan gate',
|
||||
'settings.integrations.hermes.field.uptime': 'Server uptime',
|
||||
'settings.magicPrompts.sidebar.group.linear': 'Linear',
|
||||
'settings.magicPrompts.sidebar.item.linearIssueReview': '이슈 리뷰',
|
||||
'settings.magicPrompts.page.group.linearIssueReview.title': '이슈 리뷰',
|
||||
@@ -345,6 +429,20 @@ export const linearIntegrationI18n = {
|
||||
'settings.integrations.linear.sessionComments.info': 'Dodaje komentarz do zgłoszenia, gdy sesja się zaczyna, kończy lub kończy błędem. Komentarze pojawiają się tylko wtedy, gdy ten serwer ma publiczny adres, żeby link otwierał sesję każdemu.',
|
||||
'settings.integrations.linear.sessionComments.aria': 'Publikuj komentarze o stanie sesji w Linear',
|
||||
'settings.integrations.linear.sessionComments.loadFailed': 'Nie udało się wczytać ustawień komentarzy Linear.',
|
||||
'settings.integrations.hermes.title': 'Hermes',
|
||||
'settings.integrations.hermes.description': 'Agent-to-agent integration for activity streaming, session steering, and plan gates.',
|
||||
'settings.integrations.hermes.status.connected': 'Connected',
|
||||
'settings.integrations.hermes.status.unreachable': 'Unreachable',
|
||||
'settings.integrations.hermes.status.checking': 'Checking…',
|
||||
'settings.integrations.hermes.field.planGateDefault': 'Require plan approval by default',
|
||||
'settings.integrations.hermes.field.planGateDefaultAria': 'Require plan approval by default',
|
||||
'settings.integrations.hermes.field.planGateDefaultInfo': 'When enabled, new agent sessions wait for plan approval before making changes. Requires a server restart.',
|
||||
'settings.integrations.hermes.section.status': 'Integration status',
|
||||
'settings.integrations.hermes.field.activityStream': 'Activity stream',
|
||||
'settings.integrations.hermes.field.activityStreamDetail': 'Rate limited per session',
|
||||
'settings.integrations.hermes.field.steerChannel': 'Steer channel',
|
||||
'settings.integrations.hermes.field.planGate': 'Plan gate',
|
||||
'settings.integrations.hermes.field.uptime': 'Server uptime',
|
||||
'settings.magicPrompts.sidebar.group.linear': 'Linear',
|
||||
'settings.magicPrompts.sidebar.item.linearIssueReview': 'Przegląd zgłoszenia',
|
||||
'settings.magicPrompts.page.group.linearIssueReview.title': 'Przegląd zgłoszenia',
|
||||
@@ -395,6 +493,20 @@ export const linearIntegrationI18n = {
|
||||
'settings.integrations.linear.sessionComments.info': 'Comenta na issue quando uma sessão começa, termina ou falha. Os comentários só são publicados se este servidor tiver um endereço público, para que o link abra a sessão para todos.',
|
||||
'settings.integrations.linear.sessionComments.aria': 'Publicar comentários de status de sessão no Linear',
|
||||
'settings.integrations.linear.sessionComments.loadFailed': 'Não foi possível carregar as configurações de comentários do Linear.',
|
||||
'settings.integrations.hermes.title': 'Hermes',
|
||||
'settings.integrations.hermes.description': 'Agent-to-agent integration for activity streaming, session steering, and plan gates.',
|
||||
'settings.integrations.hermes.status.connected': 'Connected',
|
||||
'settings.integrations.hermes.status.unreachable': 'Unreachable',
|
||||
'settings.integrations.hermes.status.checking': 'Checking…',
|
||||
'settings.integrations.hermes.field.planGateDefault': 'Require plan approval by default',
|
||||
'settings.integrations.hermes.field.planGateDefaultAria': 'Require plan approval by default',
|
||||
'settings.integrations.hermes.field.planGateDefaultInfo': 'When enabled, new agent sessions wait for plan approval before making changes. Requires a server restart.',
|
||||
'settings.integrations.hermes.section.status': 'Integration status',
|
||||
'settings.integrations.hermes.field.activityStream': 'Activity stream',
|
||||
'settings.integrations.hermes.field.activityStreamDetail': 'Rate limited per session',
|
||||
'settings.integrations.hermes.field.steerChannel': 'Steer channel',
|
||||
'settings.integrations.hermes.field.planGate': 'Plan gate',
|
||||
'settings.integrations.hermes.field.uptime': 'Server uptime',
|
||||
'settings.magicPrompts.sidebar.group.linear': 'Linear',
|
||||
'settings.magicPrompts.sidebar.item.linearIssueReview': 'Revisão de issue',
|
||||
'settings.magicPrompts.page.group.linearIssueReview.title': 'Revisão de issue',
|
||||
@@ -445,6 +557,20 @@ export const linearIntegrationI18n = {
|
||||
'settings.integrations.linear.sessionComments.info': 'Додає коментар до тікета, коли сесія починається, завершується або падає. Коментарі публікуються, лише якщо цей сервер має публічну адресу, щоб посилання відкривало сесію для всіх.',
|
||||
'settings.integrations.linear.sessionComments.aria': 'Публікувати коментарі про стан сесії в Linear',
|
||||
'settings.integrations.linear.sessionComments.loadFailed': 'Не вдалося завантажити налаштування коментарів Linear.',
|
||||
'settings.integrations.hermes.title': 'Hermes',
|
||||
'settings.integrations.hermes.description': 'Agent-to-agent integration for activity streaming, session steering, and plan gates.',
|
||||
'settings.integrations.hermes.status.connected': 'Connected',
|
||||
'settings.integrations.hermes.status.unreachable': 'Unreachable',
|
||||
'settings.integrations.hermes.status.checking': 'Checking…',
|
||||
'settings.integrations.hermes.field.planGateDefault': 'Require plan approval by default',
|
||||
'settings.integrations.hermes.field.planGateDefaultAria': 'Require plan approval by default',
|
||||
'settings.integrations.hermes.field.planGateDefaultInfo': 'When enabled, new agent sessions wait for plan approval before making changes. Requires a server restart.',
|
||||
'settings.integrations.hermes.section.status': 'Integration status',
|
||||
'settings.integrations.hermes.field.activityStream': 'Activity stream',
|
||||
'settings.integrations.hermes.field.activityStreamDetail': 'Rate limited per session',
|
||||
'settings.integrations.hermes.field.steerChannel': 'Steer channel',
|
||||
'settings.integrations.hermes.field.planGate': 'Plan gate',
|
||||
'settings.integrations.hermes.field.uptime': 'Server uptime',
|
||||
'settings.magicPrompts.sidebar.group.linear': 'Linear',
|
||||
'settings.magicPrompts.sidebar.item.linearIssueReview': 'Огляд issue',
|
||||
'settings.magicPrompts.page.group.linearIssueReview.title': 'Огляд issue',
|
||||
@@ -495,6 +621,20 @@ export const linearIntegrationI18n = {
|
||||
'settings.integrations.linear.sessionComments.info': '会话开始、完成或失败时在议题下留言。仅当此服务器拥有公网地址时才发布,这样链接才能让所有人打开该会话。',
|
||||
'settings.integrations.linear.sessionComments.aria': '将会话状态评论发布到 Linear',
|
||||
'settings.integrations.linear.sessionComments.loadFailed': '无法加载 Linear 评论设置。',
|
||||
'settings.integrations.hermes.title': 'Hermes',
|
||||
'settings.integrations.hermes.description': 'Agent-to-agent integration for activity streaming, session steering, and plan gates.',
|
||||
'settings.integrations.hermes.status.connected': 'Connected',
|
||||
'settings.integrations.hermes.status.unreachable': 'Unreachable',
|
||||
'settings.integrations.hermes.status.checking': 'Checking…',
|
||||
'settings.integrations.hermes.field.planGateDefault': 'Require plan approval by default',
|
||||
'settings.integrations.hermes.field.planGateDefaultAria': 'Require plan approval by default',
|
||||
'settings.integrations.hermes.field.planGateDefaultInfo': 'When enabled, new agent sessions wait for plan approval before making changes. Requires a server restart.',
|
||||
'settings.integrations.hermes.section.status': 'Integration status',
|
||||
'settings.integrations.hermes.field.activityStream': 'Activity stream',
|
||||
'settings.integrations.hermes.field.activityStreamDetail': 'Rate limited per session',
|
||||
'settings.integrations.hermes.field.steerChannel': 'Steer channel',
|
||||
'settings.integrations.hermes.field.planGate': 'Plan gate',
|
||||
'settings.integrations.hermes.field.uptime': 'Server uptime',
|
||||
'settings.magicPrompts.sidebar.group.linear': 'Linear',
|
||||
'settings.magicPrompts.sidebar.item.linearIssueReview': 'Issue 审查',
|
||||
'settings.magicPrompts.page.group.linearIssueReview.title': 'Issue 审查',
|
||||
@@ -545,6 +685,20 @@ export const linearIntegrationI18n = {
|
||||
'settings.integrations.linear.sessionComments.info': '工作階段開始、完成或失敗時在議題留言。僅在這台伺服器有公開位址時才發布,這樣連結才能讓所有人開啟該工作階段。',
|
||||
'settings.integrations.linear.sessionComments.aria': '將工作階段狀態留言發布到 Linear',
|
||||
'settings.integrations.linear.sessionComments.loadFailed': '無法載入 Linear 留言設定。',
|
||||
'settings.integrations.hermes.title': 'Hermes',
|
||||
'settings.integrations.hermes.description': 'Agent-to-agent integration for activity streaming, session steering, and plan gates.',
|
||||
'settings.integrations.hermes.status.connected': 'Connected',
|
||||
'settings.integrations.hermes.status.unreachable': 'Unreachable',
|
||||
'settings.integrations.hermes.status.checking': 'Checking…',
|
||||
'settings.integrations.hermes.field.planGateDefault': 'Require plan approval by default',
|
||||
'settings.integrations.hermes.field.planGateDefaultAria': 'Require plan approval by default',
|
||||
'settings.integrations.hermes.field.planGateDefaultInfo': 'When enabled, new agent sessions wait for plan approval before making changes. Requires a server restart.',
|
||||
'settings.integrations.hermes.section.status': 'Integration status',
|
||||
'settings.integrations.hermes.field.activityStream': 'Activity stream',
|
||||
'settings.integrations.hermes.field.activityStreamDetail': 'Rate limited per session',
|
||||
'settings.integrations.hermes.field.steerChannel': 'Steer channel',
|
||||
'settings.integrations.hermes.field.planGate': 'Plan gate',
|
||||
'settings.integrations.hermes.field.uptime': 'Server uptime',
|
||||
'settings.magicPrompts.sidebar.group.linear': 'Linear',
|
||||
'settings.magicPrompts.sidebar.item.linearIssueReview': 'Issue 審查',
|
||||
'settings.magicPrompts.page.group.linearIssueReview.title': 'Issue 審查',
|
||||
@@ -595,6 +749,20 @@ export const linearIntegrationI18n = {
|
||||
'settings.integrations.linear.sessionComments.info': 'Bir oturum başladığında, bittiğinde veya başarısız olduğunda göreve yorum ekler. Bağlantının herkeste açılabilmesi için yorumlar yalnızca bu sunucunun genel bir adresi varsa gönderilir.',
|
||||
'settings.integrations.linear.sessionComments.aria': 'Oturum durumu yorumlarını Linear’a gönder',
|
||||
'settings.integrations.linear.sessionComments.loadFailed': 'Linear yorum ayarları yüklenemedi.',
|
||||
'settings.integrations.hermes.title': 'Hermes',
|
||||
'settings.integrations.hermes.description': 'Agent-to-agent integration for activity streaming, session steering, and plan gates.',
|
||||
'settings.integrations.hermes.status.connected': 'Connected',
|
||||
'settings.integrations.hermes.status.unreachable': 'Unreachable',
|
||||
'settings.integrations.hermes.status.checking': 'Checking…',
|
||||
'settings.integrations.hermes.field.planGateDefault': 'Require plan approval by default',
|
||||
'settings.integrations.hermes.field.planGateDefaultAria': 'Require plan approval by default',
|
||||
'settings.integrations.hermes.field.planGateDefaultInfo': 'When enabled, new agent sessions wait for plan approval before making changes. Requires a server restart.',
|
||||
'settings.integrations.hermes.section.status': 'Integration status',
|
||||
'settings.integrations.hermes.field.activityStream': 'Activity stream',
|
||||
'settings.integrations.hermes.field.activityStreamDetail': 'Rate limited per session',
|
||||
'settings.integrations.hermes.field.steerChannel': 'Steer channel',
|
||||
'settings.integrations.hermes.field.planGate': 'Plan gate',
|
||||
'settings.integrations.hermes.field.uptime': 'Server uptime',
|
||||
'settings.magicPrompts.sidebar.group.linear': 'Linear',
|
||||
'settings.magicPrompts.sidebar.item.linearIssueReview': 'Issue incelemesi',
|
||||
'settings.magicPrompts.page.group.linearIssueReview.title': 'Issue incelemesi',
|
||||
|
||||
@@ -256,6 +256,7 @@ export const SETTINGS_REGISTRY = {
|
||||
agentControlToolEnabled: field({ scope: 'instance', parse: parseBoolean, ui: uiStore('agentControlToolEnabled', (v) => useUIStore.getState().setAgentControlToolEnabled(v)) }),
|
||||
agentWebToolEnabled: field({ scope: 'instance', parse: parseBoolean, ui: uiStore('agentWebToolEnabled', (v) => useUIStore.getState().setAgentWebToolEnabled(v)) }),
|
||||
agentMemoryToolEnabled: field({ scope: 'instance', parse: parseBoolean, ui: uiStore('agentMemoryToolEnabled', (v) => useUIStore.getState().setAgentMemoryToolEnabled(v)) }),
|
||||
hermesPlanGateDefault: field({ scope: 'instance', parse: parseBoolean, ui: uiStore('hermesPlanGateDefault', (v) => useUIStore.getState().setHermesPlanGateDefault(v)) }),
|
||||
// Server-owned: it says whether this build has the feature at all.
|
||||
agentMemoryFeatureAvailable: field({
|
||||
scope: 'instance',
|
||||
|
||||
@@ -1086,6 +1086,14 @@ const SETTINGS_SEARCH_ITEMS: readonly SettingsSearchItem[] = [
|
||||
keywords: ['linear', 'project', 'team', 'map', 'workspace', 'directory'],
|
||||
isAvailable: (ctx) => !ctx.isVSCode,
|
||||
},
|
||||
{
|
||||
id: 'integrations.hermes',
|
||||
page: 'integrations',
|
||||
titleKey: 'settings.integrations.hermes.title',
|
||||
descriptionKey: 'settings.integrations.hermes.description',
|
||||
keywords: ['hermes', 'agent', 'integration', 'plan gate', 'steer', 'activity'],
|
||||
isAvailable: (ctx) => !ctx.isVSCode,
|
||||
},
|
||||
] as const;
|
||||
|
||||
interface BuildSettingsSearchResultsOptions {
|
||||
|
||||
@@ -937,6 +937,7 @@ interface UIStore {
|
||||
agentControlToolEnabled: boolean;
|
||||
agentWebToolEnabled: boolean;
|
||||
agentMemoryToolEnabled: boolean;
|
||||
hermesPlanGateDefault: boolean;
|
||||
/**
|
||||
* Whether this build has agent memory at all. Server-owned and not
|
||||
* persisted: an unreleased feature must not come back from a stale cache.
|
||||
@@ -1135,6 +1136,7 @@ interface UIStore {
|
||||
setAgentControlToolEnabled: (value: boolean) => void;
|
||||
setAgentWebToolEnabled: (value: boolean) => void;
|
||||
setAgentMemoryToolEnabled: (value: boolean) => void;
|
||||
setHermesPlanGateDefault: (value: boolean) => void;
|
||||
setAgentMemoryFeatureAvailable: (value: boolean) => void;
|
||||
markAgentMemoryViewed: (key: string, viewedAt: number) => void;
|
||||
setProjectContextSidebarWidth: (width: number) => void;
|
||||
@@ -1308,6 +1310,7 @@ export const useUIStore = create<UIStore>()(
|
||||
agentControlToolEnabled: true,
|
||||
agentWebToolEnabled: true,
|
||||
agentMemoryToolEnabled: false,
|
||||
hermesPlanGateDefault: false,
|
||||
agentMemoryFeatureAvailable: false,
|
||||
agentMemoryViewedAt: {},
|
||||
projectContextSidebarWidth: 168,
|
||||
@@ -2575,6 +2578,9 @@ export const useUIStore = create<UIStore>()(
|
||||
setAgentMemoryToolEnabled: (value) => {
|
||||
set({ agentMemoryToolEnabled: value });
|
||||
},
|
||||
setHermesPlanGateDefault: (value) => {
|
||||
set({ hermesPlanGateDefault: value });
|
||||
},
|
||||
setAgentMemoryFeatureAvailable: (value) => {
|
||||
set({ agentMemoryFeatureAvailable: value });
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user