Files
memind/ops/src/api/admin.ts
T
john 666db0b939 feat(goal-run): add multi-checkpoint goal orchestration with H5 and admin surfaces.
Persist goal runs in MySQL, bind agent runs to checkpoints, expose awaiting-approval
UX in chat, and add admin inspection routes with local verify scripts.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-01 17:03:16 +08:00

1019 lines
28 KiB
TypeScript

async function adminFetch<T>(path: string, init?: RequestInit): Promise<T> {
const response = await fetch(path, {
...init,
credentials: 'include',
headers: {
Accept: 'application/json',
...(init?.body ? { 'Content-Type': 'application/json' } : {}),
...(init?.headers ?? {}),
},
});
const payload = (await response.json().catch(() => ({}))) as {
data?: T;
message?: string;
error?: { code: string; message: string };
};
if (!response.ok) {
throw new Error(payload?.error?.message ?? payload?.message ?? `请求失败 (${response.status})`);
}
return (payload.data !== undefined ? payload.data : payload) as T;
}
export type AdminUser = {
id: string;
username: string;
slug: string;
email: string;
displayName: string;
role: string;
status: string;
balanceCents: number;
createdAt: string;
};
export type AdminSummary = {
totalUsers: number;
activeUsers: number;
totalBalanceCents: number;
llm?: {
keyCount: number;
selectedKeyName: string | null;
globalModel: string | null;
};
};
export type LedgerEntry = {
id: string;
userId: string;
username?: string;
type: string;
amountCents: number;
note: string;
createdAt: string;
};
export type UsageRecord = {
id: string;
userId: string;
username?: string;
provider: string;
model: string;
inputTokens: number;
outputTokens: number;
costCents: number;
createdAt: string;
};
export type WechatAdminSummary = {
config: {
mpEnabled: boolean;
scheduleEnabled: boolean;
reminderWorkerEnabled: boolean;
appId: string | null;
publicBaseUrl: string | null;
bindPath: string | null;
tokenEndpointConfigured: boolean;
customerServiceEndpointConfigured: boolean;
};
counts: {
boundUsers: number;
routes: { total: number; active: number };
recentMessages: Record<string, number>;
digests: Record<string, number>;
recentDeliveries: Record<string, number>;
};
};
export type WechatBinding = {
userId: string;
username: string;
displayName: string;
status: string;
appId: string | null;
openidMasked: string;
nickname: string | null;
avatarUrl: string | null;
lastLoginAt: number;
boundAt: number;
routeId: string | null;
routeStatus: string | null;
agentSessionId: string | null;
routeUpdatedAt: number | null;
};
export type WechatMessage = {
appId: string | null;
openidMasked: string;
msgId: string;
status: string;
agentSessionId: string | null;
createdAt: number;
updatedAt: number;
userId: string | null;
username: string | null;
displayName: string | null;
};
export type WechatDigestSubscription = {
id: string;
userId: string;
username: string;
displayName: string;
digestType: string;
hour: number;
minute: number;
timezone: string;
channel: string;
status: string;
nextRunAt: number;
lastRunAt: number | null;
attempts: number;
lastError: string | null;
sourceText: string | null;
createdAt: number;
updatedAt: number;
};
export type WechatDeliveryLog = {
id: string;
reminderId: string | null;
subscriptionId: string | null;
userId: string;
username: string;
displayName: string;
channel: string;
status: string;
providerMessageId: string | null;
errorCode: string | null;
errorMessage: string | null;
createdAt: number;
digestType: string | null;
};
export type WechatWebNotification = {
id: string;
userId: string;
username: string;
displayName: string;
channel: string;
notificationType: string;
title: string;
body: string;
status: string;
readAt: number | null;
createdAt: number;
updatedAt: number;
};
export type CreateWechatNotificationPayload = {
userId?: string;
userIds?: string[];
audience?: 'all';
allUsers?: boolean;
title: string;
body: string;
notificationType?: string;
channel?: 'web' | 'wechat';
channels?: Array<'web' | 'wechat'>;
};
export type SystemDisclosureMode = 'off' | 'shadow' | 'enforce';
export type SystemDisclosurePolicyConfig = {
enabled: boolean;
mode: SystemDisclosureMode;
refusalText: string;
productNames: string[];
selfReferences: string[];
categories: Record<string, boolean>;
};
export type SystemDisclosurePolicyState = {
config: SystemDisclosurePolicyConfig;
policyVersion: number;
updatedBy: string | null;
updatedAt: number | null;
source: string;
refreshedAt?: number | null;
lastRefreshError?: string | null;
};
export type OrchestratorMode = 'off' | 'shadow' | 'canary' | 'active';
export type OrchestratorConfig = {
mode: OrchestratorMode;
primaryEngine: string;
fallbackEngine: string;
serviceUrl: string;
requestTimeoutMs: number;
rolloutPercent: number;
userAllowlist: string[];
workflowAllowlist: string[];
fallbackToNative: boolean;
requireHealthy: boolean;
executionEnabled: boolean;
};
export type OrchestratorRuntime = {
killSwitch: boolean;
configured: boolean;
effective: boolean;
reason: string | null;
executesLangGraph: boolean;
plansLangGraph: boolean;
shadowsLangGraph: boolean;
shadowObservation: {
requested: boolean;
enabled: boolean;
reason: string | null;
environmentGate: boolean;
};
pageDataValidationGate: {
requested: boolean;
enabled: boolean;
failClosed: boolean;
reason: string | null;
environmentGate: boolean;
};
executionHandoff: {
implemented: boolean;
requested: boolean;
enabled: boolean;
reason: string | null;
environmentGate: boolean;
};
};
export type OrchestratorEngineDescriptor = {
id: string;
label: string;
kind: string;
configured: boolean;
capabilities: string[];
};
export type OrchestratorExecutorDescriptor = {
id: string;
label: string;
kind: string;
enabled: boolean;
dispatchImplemented: boolean;
status: string;
capabilities: string[];
};
export type OrchestratorConfigState = {
config: OrchestratorConfig;
configVersion: number;
updatedBy: string | null;
updatedAt: number | null;
source: string;
runtime: OrchestratorRuntime;
engines: OrchestratorEngineDescriptor[];
executors: OrchestratorExecutorDescriptor[];
};
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;
executorGateway: {
dispatchImplemented: boolean;
executionEnabled: boolean;
store: { kind: string | null; durable: boolean } | null;
} | null;
} | null;
};
export type OrchestratorRuntimeState = OrchestratorConfigState & {
serviceHealth: OrchestratorServiceHealth;
};
export type OrchestratorShadowMetrics = {
observations: number;
successes: number;
failures: number;
skipped: number;
successRate: number | null;
failureRate: number | null;
skipRate: number | null;
latencyP50Ms: number | null;
latencyP95Ms: number | null;
nativeSucceeded: number;
nativeFailed: number;
lastObservedAt: number | null;
sampled: boolean;
};
export type OrchestratorShadowRun = {
eventId: string;
runId: string;
requestId: string;
userId: string;
sessionId: string | null;
nativeStatus: string;
nativeAttempts: number;
shadowStatus: 'succeeded' | 'failed' | 'skipped';
engine: string;
configVersion: number | null;
phase: string | null;
taskType: string | null;
synthetic: boolean;
executorAdapter: string | null;
latencyMs: number | null;
error: { code: string; message: string } | null;
skipReason: string | null;
observedAt: number;
nativeCompletedAt: number | null;
};
export type OrchestratorShadowRunList = {
generatedAt: number;
window: { hours: number; from: number };
metrics: OrchestratorShadowMetrics;
runs: OrchestratorShadowRun[];
};
export type OrchestratorExecutionPlan = {
eventId: string;
runId: string;
requestId: string;
userId: string;
sessionId: string | null;
nativeStatus: string;
nativeAttempts: number;
mode: string | null;
candidateEngine: string;
effectiveEngine: string;
fallbackEngine: string;
reason: string | null;
candidateReason: string | null;
configVersion: number | null;
bucket: number | null;
taskType: string | null;
dryRun: boolean;
handoffAllowed: boolean;
plannedAt: number;
nativeCompletedAt: number | null;
};
export type OrchestratorExecutionPlanMetrics = {
decisions: number;
candidateSelections: number;
candidateSelectionRate: number | null;
nativeSelections: number;
nativeSucceeded: number;
nativeFailed: number;
nativeSettledRate: number | null;
handoffAllowed: number;
distinctSessions: number;
lastPlannedAt: number | null;
candidateReasons: Array<{ value: string; count: number }>;
taskTypes: Array<{ value: string; count: number }>;
sampled: boolean;
};
export type OrchestratorExecutionPlanList = {
generatedAt: number;
window: { hours: number; from: number };
metrics: OrchestratorExecutionPlanMetrics;
plans: OrchestratorExecutionPlan[];
};
export type OrchestratorCanaryReadinessCheck = {
id: string;
passed: boolean;
actual: string | number | boolean | null;
target: string | number | boolean | null;
};
export type OrchestratorCanaryReadiness = {
generatedAt: number;
ready: boolean;
recommendation: 'keep_shadow' | 'manual_canary_review';
window: { hours: number; from: number };
thresholds: {
hours: number;
minObservations: number;
minSuccessRate: number;
maxSkipRate: number;
maxP95LatencyMs: number;
minLatencyCoverageRate: number;
minNativeSettledRate: number;
minDistinctSessions: number;
maxHoursSinceLastObservation: number;
};
samples: {
totalObservations: number;
eligibleObservations: number;
excludedSynthetic: number;
successes: number;
failures: number;
skipped: number;
successRate: number | null;
skipRate: number | null;
latencyCoverageRate: number | null;
latencyP95Ms: number | null;
nativeSettledRate: number | null;
distinctSessions: number;
lastObservedAt: number | null;
hoursSinceLastObservation: number | null;
sampled: boolean;
};
service: {
status: string | null;
latencyMs: number | null;
checkpointKind: string | null;
checkpointDurable: boolean | null;
executorJobStoreKind: string | null;
executorJobStoreDurable: boolean | null;
execution: string | null;
};
checks: OrchestratorCanaryReadinessCheck[];
blockers: string[];
failureCodes: Array<{ code: string; count: number }>;
};
export type OrchestratorShadowRunDetail = {
native: {
runId: string;
requestId: string;
userId: string;
sessionId: string | null;
status: string;
attempts: number;
error: string | null;
createdAt: number;
updatedAt: number;
startedAt: number | null;
completedAt: number | null;
};
localEvents: Array<{
eventId: string;
type: string;
data: Record<string, unknown> | null;
createdAt: number;
}>;
remote: {
available: boolean;
state: {
status?: string;
phase?: string;
plan?: Record<string, unknown>;
result?: Record<string, unknown>;
validation?: {
version?: string;
kind?: string;
taskType?: string | null;
required?: boolean;
verdict?: 'passed' | 'failed' | 'inconclusive';
checks?: Array<{
id?: string;
status?: 'passed' | 'failed' | 'skipped';
codes?: string[];
}>;
metrics?: {
pageCount?: number;
publicationCount?: number;
};
source?: string;
dataPolicy?: string;
observedAt?: number;
} | null;
} | null;
events: Array<{
sequence?: number;
type?: string;
timestamp?: number;
data?: Record<string, unknown> | null;
}>;
error: { code: string; message: string } | null;
executorJob: {
available: boolean;
state: {
version?: string;
jobId?: string;
executor?: string;
status?: string;
reason?: string;
attempts?: number;
createdAt?: number;
updatedAt?: number;
} | null;
events: Array<{
version?: string;
eventId?: string;
jobId?: string;
sequence?: number;
type?: string;
timestamp?: number;
data?: Record<string, unknown> | null;
}>;
error: { code: string; message: string } | null;
};
};
};
// ─── Summary ──────────────────────────────────────────────────────────────────
export async function fetchAdminSummary() {
return adminFetch<{ summary: AdminSummary }>('/admin-api/summary');
}
// ─── Trust & Policy ───────────────────────────────────────────────────────────
export async function fetchSystemDisclosurePolicy() {
return adminFetch<SystemDisclosurePolicyState>('/admin-api/system-disclosure-policy/config');
}
export async function updateSystemDisclosurePolicy(config: SystemDisclosurePolicyConfig) {
return adminFetch<SystemDisclosurePolicyState>('/admin-api/system-disclosure-policy/config', {
method: 'PUT',
body: JSON.stringify({ config }),
});
}
// ─── Workflow Orchestrator ───────────────────────────────────────────────────
export async function fetchOrchestratorConfig() {
return adminFetch<OrchestratorConfigState>('/admin-api/orchestrator/config');
}
export async function updateOrchestratorConfig(config: OrchestratorConfig) {
return adminFetch<OrchestratorConfigState>('/admin-api/orchestrator/config', {
method: 'PUT',
body: JSON.stringify({ config }),
});
}
export async function fetchOrchestratorRuntime() {
return adminFetch<OrchestratorRuntimeState>('/admin-api/orchestrator/runtime');
}
export async function fetchOrchestratorShadowRuns(params: {
hours?: number;
limit?: number;
status?: 'all' | 'succeeded' | 'failed' | 'skipped';
} = {}) {
const query = new URLSearchParams();
if (params.hours) query.set('hours', String(params.hours));
if (params.limit) query.set('limit', String(params.limit));
if (params.status) query.set('status', params.status);
return adminFetch<OrchestratorShadowRunList>(
`/admin-api/orchestrator/shadow-runs?${query}`,
);
}
export async function fetchOrchestratorExecutionPlans(params: {
hours?: number;
limit?: number;
selection?: 'all' | 'candidate' | 'native';
} = {}) {
const query = new URLSearchParams();
if (params.hours) query.set('hours', String(params.hours));
if (params.limit) query.set('limit', String(params.limit));
if (params.selection) query.set('selection', params.selection);
return adminFetch<OrchestratorExecutionPlanList>(
`/admin-api/orchestrator/execution-plans?${query}`,
);
}
export async function fetchOrchestratorCanaryReadiness() {
return adminFetch<OrchestratorCanaryReadiness>(
'/admin-api/orchestrator/canary-readiness',
);
}
export async function fetchOrchestratorShadowRun(runId: string) {
return adminFetch<OrchestratorShadowRunDetail>(
`/admin-api/orchestrator/shadow-runs/${encodeURIComponent(runId)}`,
);
}
// ─── Users ────────────────────────────────────────────────────────────────────
export async function fetchAdminUsers(params: {
page?: number;
pageSize?: number;
search?: string;
role?: string;
status?: string;
}) {
const q = new URLSearchParams();
if (params.page) q.set('page', String(params.page));
if (params.pageSize) q.set('pageSize', String(params.pageSize));
if (params.search) q.set('search', params.search);
if (params.role) q.set('role', params.role);
if (params.status) q.set('status', params.status);
return adminFetch<{ users: AdminUser[]; total: number; page: number; pageSize: number; totalPages: number }>(
`/admin-api/users?${q}`,
);
}
export async function createAdminUser(body: {
username: string;
password: string;
displayName?: string;
role?: string;
}) {
return adminFetch<{ user: AdminUser }>('/admin-api/users', {
method: 'POST',
body: JSON.stringify(body),
});
}
export async function patchAdminUser(
userId: string,
patch: { role?: string; status?: string; displayName?: string; password?: string },
) {
return adminFetch<{ user: AdminUser }>(`/admin-api/users/${userId}`, {
method: 'PATCH',
body: JSON.stringify(patch),
});
}
export async function rechargeUser(userId: string, amountCents: number, note = '') {
return adminFetch<{ user: AdminUser }>(`/admin-api/users/${userId}/recharge`, {
method: 'POST',
body: JSON.stringify({ amountCents, note }),
});
}
// ─── Billing ──────────────────────────────────────────────────────────────────
export async function fetchAdminLedger(params: { page?: number; pageSize?: number } = {}) {
const q = new URLSearchParams();
if (params.page) q.set('page', String(params.page));
if (params.pageSize) q.set('pageSize', String(params.pageSize));
return adminFetch<{ entries: LedgerEntry[]; total: number; page: number; totalPages: number }>(
`/admin-api/ledger?${q}`,
);
}
export async function fetchAdminUsage(params: { page?: number; pageSize?: number } = {}) {
const q = new URLSearchParams();
if (params.page) q.set('page', String(params.page));
if (params.pageSize) q.set('pageSize', String(params.pageSize));
return adminFetch<{ records: UsageRecord[]; total: number; page: number; totalPages: number }>(
`/admin-api/usage?${q}`,
);
}
// ─── WeChat MP ────────────────────────────────────────────────────────────────
export async function fetchWechatSummary() {
return adminFetch<WechatAdminSummary>('/admin-api/wechat/summary');
}
export async function fetchWechatBindings(params: { search?: string; limit?: number } = {}) {
const q = new URLSearchParams();
if (params.search) q.set('search', params.search);
if (params.limit) q.set('limit', String(params.limit));
return adminFetch<{ bindings: WechatBinding[] }>(`/admin-api/wechat/bindings?${q}`);
}
export async function fetchWechatMessages(params: { status?: string; limit?: number } = {}) {
const q = new URLSearchParams();
if (params.status) q.set('status', params.status);
if (params.limit) q.set('limit', String(params.limit));
return adminFetch<{ messages: WechatMessage[] }>(`/admin-api/wechat/messages?${q}`);
}
export async function fetchWechatDigests(params: { status?: string; limit?: number } = {}) {
const q = new URLSearchParams();
if (params.status) q.set('status', params.status);
if (params.limit) q.set('limit', String(params.limit));
return adminFetch<{ digests: WechatDigestSubscription[] }>(`/admin-api/wechat/digests?${q}`);
}
export async function fetchWechatDeliveries(params: { status?: string; limit?: number } = {}) {
const q = new URLSearchParams();
if (params.status) q.set('status', params.status);
if (params.limit) q.set('limit', String(params.limit));
return adminFetch<{ deliveries: WechatDeliveryLog[] }>(`/admin-api/wechat/deliveries?${q}`);
}
export async function fetchWechatWebNotifications(params: { status?: string; limit?: number } = {}) {
const q = new URLSearchParams();
if (params.status) q.set('status', params.status);
if (params.limit) q.set('limit', String(params.limit));
return adminFetch<{ notifications: WechatWebNotification[] }>(
`/admin-api/wechat/web-notifications?${q}`,
);
}
export async function createWechatWebNotification(body: CreateWechatNotificationPayload) {
return adminFetch<{
ok: boolean;
created: number;
wechatSent: number;
wechatFailures: Array<{ userId: string; message: string }>;
targets: number;
}>('/admin-api/wechat/web-notifications', {
method: 'POST',
body: JSON.stringify(body),
});
}
export async function clearWechatRoute(userId: string) {
return adminFetch<{ ok: boolean; deleted: number; openidMasked: string }>(
`/admin-api/wechat/users/${userId}/route/clear`,
{ method: 'POST' },
);
}
export async function cancelWechatDigest(id: string) {
return adminFetch<{ ok: boolean }>(`/admin-api/wechat/digests/${id}/cancel`, { method: 'POST' });
}
export async function resumeWechatDigest(id: string) {
return adminFetch<{ ok: boolean; nextRunAt: number }>(`/admin-api/wechat/digests/${id}/resume`, {
method: 'POST',
});
}
// ─── Agent Code Run ───────────────────────────────────────────────────────────
export type AgentCodeRunConfigShape = {
codeRun: {
enabled: boolean;
clientEnabled: boolean;
generalAutodetect: boolean;
requireValidation: boolean;
userAllowlist: string[];
taskTypeAllowlist: string[];
};
pageDataDev: {
autodetect: boolean;
};
meta: {
notes: string;
};
};
export type AgentCodeRunAdminConfig = {
config: AgentCodeRunConfigShape;
updatedAt: number | null;
updatedBy: string | null;
source: string;
envOverrideActive: boolean;
};
export type AgentCodeRunRuntimeState = {
source: string;
updatedAt: number | null;
updatedBy: string | null;
config: AgentCodeRunConfigShape;
policy: {
source: string;
enabled: boolean;
clientEnabled: boolean;
generalAutodetect: boolean;
requireValidation: boolean;
pageDataDevAutodetect: boolean;
userAllowlist: string[];
taskTypeAllowlist: string[];
};
envOverrideActive: boolean;
};
export async function fetchAgentCodeRunConfig() {
return adminFetch<AgentCodeRunAdminConfig>('/admin-api/agent-code-run/config');
}
export async function fetchAgentCodeRunRuntime() {
return adminFetch<AgentCodeRunRuntimeState>('/admin-api/agent-code-run/runtime');
}
export async function patchAgentCodeRunConfig(body: { config: AgentCodeRunConfigShape }) {
return adminFetch<AgentCodeRunAdminConfig>('/admin-api/agent-code-run/config', {
method: 'PATCH',
body: JSON.stringify(body),
});
}
export type AgentCodeRunHistoryRow = {
id: string;
userId: string;
requestId: string;
status: string;
taskType: string;
executor: string | null;
errorMessage: string | null;
createdAt: number | null;
updatedAt: number | null;
};
export async function fetchAgentCodeRunHistory(limit = 50) {
return adminFetch<{ runs: AgentCodeRunHistoryRow[]; limit: number }>(
`/admin-api/agent-code-run/runs?limit=${limit}`,
);
}
// ─── Unified LLM Providers ────────────────────────────────────────────────────
export type LlmProviderKeyRow = {
id: string;
providerId: string;
providerKind: 'builtin' | 'custom';
providerLabel: string;
name: string;
defaultModel: string;
models: string[];
apiUrl: string | null;
status: 'active' | 'disabled';
isSelected: boolean;
apiKeyMasked: string;
};
export type LlmGlobalSettings = {
keyId: string | null;
keyName: string | null;
providerLabel: string | null;
globalModel: string | null;
availableModels: string[];
};
export type ChatIntentRouterAdminConfig = {
enabled?: boolean;
shadowMode?: boolean;
canaryUserIds?: string;
modelProviderKeyId?: string;
model?: string;
modelApiType?: string;
minConfidence?: string;
memoryResolveEnabled?: boolean;
memoryResolveLimit?: string;
timeoutMs?: string;
fallbackRoute?: string;
};
export type MemoryV2AdminConfigState = {
config: {
chatIntentRouter?: ChatIntentRouterAdminConfig;
[key: string]: unknown;
};
updatedAt: number | null;
updatedBy: string | null;
};
export type MemoryV2RuntimeState = {
source: string;
updatedAt: number | null;
updatedBy: string | null;
fingerprint?: string;
overrides: Record<string, string>;
};
export async function fetchLlmProviderKeys() {
return adminFetch<{ keys: LlmProviderKeyRow[] }>('/admin-api/llm-providers/keys');
}
export async function fetchLlmGlobalSettings() {
return adminFetch<{ global: LlmGlobalSettings }>('/admin-api/llm-providers/global');
}
export async function fetchMemoryV2Config() {
return adminFetch<MemoryV2AdminConfigState>('/admin-api/memory-v2/config');
}
export async function patchMemoryV2Config(patch: Record<string, unknown>) {
return adminFetch<MemoryV2AdminConfigState>('/admin-api/memory-v2/config', {
method: 'PATCH',
body: JSON.stringify(patch),
});
}
export async function fetchMemoryV2Runtime() {
return adminFetch<MemoryV2RuntimeState>('/admin-api/memory-v2/runtime');
}
export type GoalRunRuntimeState = {
enabled: boolean;
canaryUserIds: string[];
canaryMode: boolean;
};
export type GoalRunSummaryResponse = {
counts: Record<string, number>;
runtime: GoalRunRuntimeState;
};
export type GoalRunListItem = {
id: string;
userId: string;
username: string | null;
title: string;
intentSummary: string;
status: string;
priority: number;
sourceChannel: string;
sourceSessionId: string | null;
currentCheckpointId: string | null;
checkpointCount: number;
activeAgentRunCount: number;
createdAt: number;
updatedAt: number;
completedAt: number | null;
};
export type GoalRunCheckpointRow = {
id: string;
goalRunId: string;
sequence: number;
title: string;
description: string | null;
status: string;
agentRunId: string | null;
outputSummary: string | null;
userFeedback: string | null;
approvedAt: number | null;
createdAt: number;
updatedAt: number;
startedAt: number | null;
completedAt: number | null;
};
export type GoalRunAgentRunRow = {
id: string;
status: string;
requestId: string;
goalCheckpointId: string | null;
createdAt: number;
updatedAt: number;
completedAt: number | null;
};
export type GoalRunDetail = {
id: string;
userId: string;
username: string | null;
title: string;
intentSummary: string;
status: string;
priority: number;
sourceChannel: string;
sourceSessionId: string | null;
sourceMessageId: string | null;
currentCheckpointId: string | null;
context: unknown;
memorySnapshot: unknown;
createdAt: number;
updatedAt: number;
completedAt: number | null;
checkpoints: GoalRunCheckpointRow[];
agentRuns: GoalRunAgentRunRow[];
canaryEnabled: boolean;
};
export async function fetchGoalRunSummary() {
return adminFetch<GoalRunSummaryResponse>('/admin-api/goal-runs/summary');
}
export async function fetchGoalRuns(params?: {
status?: string;
userId?: string;
limit?: number;
offset?: number;
}) {
const query = new URLSearchParams();
if (params?.status) query.set('status', params.status);
if (params?.userId) query.set('userId', params.userId);
if (params?.limit != null) query.set('limit', String(params.limit));
if (params?.offset != null) query.set('offset', String(params.offset));
const suffix = query.toString() ? `?${query.toString()}` : '';
return adminFetch<{ items: GoalRunListItem[]; status: string | null; limit: number; offset: number }>(
`/admin-api/goal-runs${suffix}`,
);
}
export async function fetchGoalRunDetail(goalRunId: string) {
return adminFetch<{ goal: GoalRunDetail }>(`/admin-api/goal-runs/${encodeURIComponent(goalRunId)}`);
}
export async function approveGoalRunCheckpoint(
goalRunId: string,
checkpointId: string,
feedback?: string | null,
) {
return adminFetch<{ ok: true; goal: GoalRunDetail }>(
`/admin-api/goal-runs/${encodeURIComponent(goalRunId)}/checkpoints/${encodeURIComponent(checkpointId)}/approve`,
{
method: 'POST',
body: JSON.stringify({ feedback: feedback ?? null }),
},
);
}
export async function cancelGoalRun(goalRunId: string) {
return adminFetch<{ ok: true; goal: GoalRunDetail }>(
`/admin-api/goal-runs/${encodeURIComponent(goalRunId)}/cancel`,
{ method: 'POST', body: JSON.stringify({}) },
);
}