feat: add shadow LangGraph orchestrator runtime

This commit is contained in:
john
2026-07-24 21:25:40 +08:00
parent 46ea22b342
commit 24336d0178
29 changed files with 3247 additions and 25 deletions
+21
View File
@@ -240,6 +240,23 @@ export type OrchestratorConfigState = {
engines: OrchestratorEngineDescriptor[];
};
export type OrchestratorServiceHealth = {
checkedAt: number;
ok: boolean;
status: 'healthy' | 'unhealthy' | 'unconfigured' | 'timeout' | 'unreachable' | 'fetch_unavailable';
latencyMs: number;
httpStatus: number | null;
details: {
service: string | null;
checkpoint: { kind?: string; durable?: boolean } | null;
execution: string | null;
} | null;
};
export type OrchestratorRuntimeState = OrchestratorConfigState & {
serviceHealth: OrchestratorServiceHealth;
};
// ─── Summary ──────────────────────────────────────────────────────────────────
export async function fetchAdminSummary() {
@@ -272,6 +289,10 @@ export async function updateOrchestratorConfig(config: OrchestratorConfig) {
});
}
export async function fetchOrchestratorRuntime() {
return adminFetch<OrchestratorRuntimeState>('/admin-api/orchestrator/runtime');
}
// ─── Users ────────────────────────────────────────────────────────────────────
export async function fetchAdminUsers(params: {
+41 -5
View File
@@ -1,9 +1,11 @@
import { useEffect, useMemo, useState } from 'react';
import {
fetchOrchestratorConfig,
fetchOrchestratorRuntime,
updateOrchestratorConfig,
type OrchestratorConfig,
type OrchestratorConfigState,
type OrchestratorServiceHealth,
} from '../../api/admin';
function listToText(values: string[]) {
@@ -31,6 +33,8 @@ export function OrchestratorPage() {
const [workflowAllowlist, setWorkflowAllowlist] = useState('');
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [checking, setChecking] = useState(false);
const [serviceHealth, setServiceHealth] = useState<OrchestratorServiceHealth | null>(null);
const [error, setError] = useState<string | null>(null);
const [notice, setNotice] = useState<string | null>(null);
@@ -54,6 +58,19 @@ export function OrchestratorPage() {
void load();
}, []);
const checkService = async () => {
setChecking(true);
setError(null);
try {
const result = await fetchOrchestratorRuntime();
setServiceHealth(result.serviceHealth);
} catch (err) {
setError(err instanceof Error ? err.message : '服务检查失败');
} finally {
setChecking(false);
}
};
const normalizedDraft = useMemo(() => draft ? {
...draft,
userAllowlist: textToList(userAllowlist),
@@ -75,7 +92,7 @@ export function OrchestratorPage() {
}
if (
normalizedDraft.mode === 'active'
&& !window.confirm('确认启用 Active?工作流白名单内的任务将由 LangGraph Orchestrator 接管。')
&& !window.confirm('确认保存 Active 预配置?Phase 2 不会交出执行权,Native 仍是唯一执行者。')
) {
return;
}
@@ -104,7 +121,7 @@ export function OrchestratorPage() {
: state.runtime.shadowsLangGraph
? 'Shadow 已生效:Native 继续执行,LangGraph 只观察和生成决策。'
: state.runtime.executesLangGraph
? 'LangGraph 路由已生效。'
? 'Canary / Active 路由决策已预配置;Phase 2 尚未交出执行权,Native 仍是唯一执行者。'
: '配置有效。';
return (
@@ -158,8 +175,8 @@ export function OrchestratorPage() {
>
<option value="off">Off Native only</option>
<option value="shadow">Shadow Native LangGraph </option>
<option value="canary">Canary /</option>
<option value="active">Active </option>
<option value="canary">Canary Phase 2 </option>
<option value="active">Active Phase 2 </option>
</select>
</label>
<label>
@@ -195,6 +212,25 @@ export function OrchestratorPage() {
<div className="card grid">
<h3 style={{ margin: 0 }}></h3>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12 }}>
<span>
<strong></strong>
{serviceHealth
? `${serviceHealth.ok ? '健康' : '不可用'}${serviceHealth.status}${serviceHealth.latencyMs} ms`
: '尚未检查'}
{serviceHealth?.details?.checkpoint
? ` · checkpoint ${serviceHealth.details.checkpoint.kind ?? 'unknown'}`
: ''}
</span>
<button
type="button"
className="btn secondary"
onClick={() => void checkService()}
disabled={checking}
>
{checking ? '检查中…' : '测试连接'}
</button>
</div>
<label>
<strong>Orchestrator URL</strong>
<input
@@ -244,7 +280,7 @@ export function OrchestratorPage() {
<label>
<strong></strong>
<span style={{ display: 'block', color: '#68716c', fontSize: 12, margin: '4px 0 8px' }}>
Active
Active
</span>
<textarea
rows={8}