feat(workbench): integrate operational workflows and map state
This commit is contained in:
@@ -33,7 +33,10 @@ describe("Agent API client sessions", () => {
|
||||
status: undefined,
|
||||
parentSessionId: undefined,
|
||||
isStreaming: undefined,
|
||||
runStatus: "running"
|
||||
runStatus: "running",
|
||||
purpose: "ordinary_chat",
|
||||
taskId: undefined,
|
||||
trigger: undefined
|
||||
},
|
||||
{
|
||||
id: "old",
|
||||
@@ -43,7 +46,10 @@ describe("Agent API client sessions", () => {
|
||||
status: undefined,
|
||||
parentSessionId: undefined,
|
||||
isStreaming: undefined,
|
||||
runStatus: undefined
|
||||
runStatus: undefined,
|
||||
purpose: "ordinary_chat",
|
||||
taskId: undefined,
|
||||
trigger: undefined
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import { env } from "@/shared/config/env";
|
||||
import type { AccessTokenProvider } from "@/shared/auth/keycloak-auth";
|
||||
|
||||
export type AgentRunStatus = "running" | "completed" | "error" | "aborted";
|
||||
export type AgentSessionPurpose = "ordinary_chat" | "task_execution" | "task_collaboration";
|
||||
|
||||
export type AgentEventTriggerContext = {
|
||||
schema_version: "agent-trigger@1";
|
||||
@@ -51,9 +52,12 @@ export type AgentChatSession = {
|
||||
created?: boolean;
|
||||
event_duplicate?: boolean;
|
||||
trigger_id?: string;
|
||||
purpose?: AgentSessionPurpose;
|
||||
task_id?: string;
|
||||
};
|
||||
|
||||
export type AgentChatSessionSummary = {
|
||||
trigger?: { subjectId: string; workflow: "water_quality_complaint"; occurredAt: string };
|
||||
id: string;
|
||||
title: string;
|
||||
createdAt: number;
|
||||
@@ -62,6 +66,8 @@ export type AgentChatSessionSummary = {
|
||||
parentSessionId?: string;
|
||||
isStreaming?: boolean;
|
||||
runStatus?: AgentRunStatus;
|
||||
purpose: AgentSessionPurpose;
|
||||
taskId?: string;
|
||||
};
|
||||
|
||||
export type AgentLoadedChatSession = AgentChatSessionSummary & {
|
||||
@@ -71,6 +77,7 @@ export type AgentLoadedChatSession = AgentChatSessionSummary & {
|
||||
};
|
||||
|
||||
export type AgentSessionStreamDataEventType =
|
||||
| "execution_plan"
|
||||
| "token"
|
||||
| "progress"
|
||||
| "todo_update"
|
||||
@@ -90,6 +97,7 @@ export type AgentSessionStreamDataEventType =
|
||||
export type AgentSessionStreamEvent =
|
||||
| {
|
||||
type: "state";
|
||||
executionPlans?: unknown[];
|
||||
sessionId: string;
|
||||
messages: unknown[];
|
||||
isStreaming: boolean;
|
||||
@@ -103,7 +111,8 @@ export type AgentSessionStreamEvent =
|
||||
|
||||
export type AgentApiClient = {
|
||||
createSession: (options?: { triggerContext?: AgentEventTriggerContext }) => Promise<AgentChatSession>;
|
||||
listSessions: () => Promise<AgentChatSessionSummary[]>;
|
||||
listSessions: (options?: { purpose?: AgentSessionPurpose; taskId?: string }) => Promise<AgentChatSessionSummary[]>;
|
||||
createTaskCollaborationSession: (taskId: string) => Promise<AgentChatSession>;
|
||||
loadSession: (sessionId: string) => Promise<AgentLoadedChatSession | null>;
|
||||
streamSession: (
|
||||
sessionId: string,
|
||||
@@ -184,11 +193,23 @@ export function createAgentApiClient(
|
||||
});
|
||||
},
|
||||
|
||||
async listSessions() {
|
||||
const payload = await requestJson<{ sessions?: unknown[] }>("/sessions");
|
||||
async listSessions(listOptions) {
|
||||
const params = new URLSearchParams();
|
||||
if (listOptions?.purpose) params.set("purpose", listOptions.purpose);
|
||||
if (listOptions?.taskId) params.set("task_id", listOptions.taskId);
|
||||
const payload = await requestJson<{ sessions?: unknown[] }>(
|
||||
`/sessions${params.size ? `?${params.toString()}` : ""}`
|
||||
);
|
||||
return (payload.sessions ?? []).map(toSessionSummary).filter(isPresent).sort(compareSessionSummaries);
|
||||
},
|
||||
|
||||
async createTaskCollaborationSession(taskId) {
|
||||
return requestJson<AgentChatSession>(
|
||||
`/task/${encodeURIComponent(taskId)}/collaboration/session`,
|
||||
{ method: "POST" }
|
||||
);
|
||||
},
|
||||
|
||||
async getFrontendActionRegistry() {
|
||||
return requestJson<unknown>("/frontend-action-registry");
|
||||
},
|
||||
@@ -432,6 +453,10 @@ function readRunStatus(value: unknown): AgentRunStatus | undefined {
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function readSessionPurpose(value: unknown): AgentSessionPurpose {
|
||||
return value === "task_execution" || value === "task_collaboration" ? value : "ordinary_chat";
|
||||
}
|
||||
|
||||
function toSessionSummary(value: unknown): AgentChatSessionSummary | null {
|
||||
if (!isRecord(value)) {
|
||||
return null;
|
||||
@@ -450,7 +475,14 @@ function toSessionSummary(value: unknown): AgentChatSessionSummary | null {
|
||||
status: typeof value.status === "string" ? value.status : undefined,
|
||||
parentSessionId: typeof value.parent_session_id === "string" ? value.parent_session_id : undefined,
|
||||
isStreaming: typeof value.is_streaming === "boolean" ? value.is_streaming : undefined,
|
||||
runStatus: readRunStatus(value.run_status)
|
||||
runStatus: readRunStatus(value.run_status),
|
||||
purpose: readSessionPurpose(value.purpose),
|
||||
taskId: typeof value.task_id === "string" ? value.task_id : undefined,
|
||||
trigger: isRecord(value.trigger) && value.trigger.workflow === "water_quality_complaint" &&
|
||||
typeof value.trigger.subject_id === "string" && typeof value.trigger.occurred_at === "string" &&
|
||||
Number.isFinite(Date.parse(value.trigger.occurred_at))
|
||||
? { subjectId: value.trigger.subject_id, workflow: "water_quality_complaint", occurredAt: value.trigger.occurred_at }
|
||||
: undefined
|
||||
};
|
||||
}
|
||||
|
||||
@@ -530,6 +562,7 @@ function emitSessionSseEvent(
|
||||
const sessionId = typeof data.session_id === "string" ? data.session_id : "";
|
||||
onEvent({
|
||||
type: "state",
|
||||
executionPlans: Array.isArray(data.execution_plans) ? data.execution_plans : undefined,
|
||||
sessionId,
|
||||
messages: Array.isArray(data.messages) ? data.messages : [],
|
||||
isStreaming: data.is_streaming === true,
|
||||
@@ -554,6 +587,7 @@ function isSessionStreamDataEventType(value: string): value is AgentSessionStrea
|
||||
value === "token" ||
|
||||
value === "progress" ||
|
||||
value === "todo_update" ||
|
||||
value === "execution_plan" ||
|
||||
value === "permission_request" ||
|
||||
value === "permission_response" ||
|
||||
value === "question_request" ||
|
||||
|
||||
@@ -35,13 +35,15 @@ describe("Agent API SWR helpers", () => {
|
||||
id: "session-1",
|
||||
title: "会话",
|
||||
createdAt: 1,
|
||||
updatedAt: 2
|
||||
updatedAt: 2,
|
||||
purpose: "ordinary_chat" as const
|
||||
}
|
||||
];
|
||||
const client = {
|
||||
listSessions: vi.fn(async () => sessions)
|
||||
} as Pick<AgentApiClient, "listSessions"> as AgentApiClient;
|
||||
|
||||
await expect(fetchAgentSessions(client)).resolves.toBe(sessions);
|
||||
await expect(fetchAgentSessions(client)).resolves.toEqual(sessions);
|
||||
expect(client.listSessions).toHaveBeenCalledWith({ purpose: "ordinary_chat" });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -32,5 +32,7 @@ export async function fetchAgentBootstrap(client: AgentApiClient): Promise<Agent
|
||||
}
|
||||
|
||||
export async function fetchAgentSessions(client: AgentApiClient): Promise<AgentChatSessionSummary[]> {
|
||||
return client.listSessions();
|
||||
const sessions = await client.listSessions({ purpose: "ordinary_chat" });
|
||||
// Keep the product boundary even while an older Agent service is rolling forward.
|
||||
return sessions.filter((session) => session.purpose === "ordinary_chat");
|
||||
}
|
||||
|
||||
@@ -108,6 +108,7 @@ type AgentCommandPanelProps = {
|
||||
sessionHistory?: AgentChatSessionSummary[];
|
||||
sessionHistoryLoading?: boolean;
|
||||
activeSessionId?: string | null;
|
||||
qaOnly?: boolean;
|
||||
statusLabel?: string;
|
||||
connectionStatus?: AgentConnectionStatus;
|
||||
streaming?: boolean;
|
||||
@@ -162,6 +163,7 @@ export function AgentCommandPanel({
|
||||
sessionHistory = [],
|
||||
sessionHistoryLoading = false,
|
||||
activeSessionId,
|
||||
qaOnly = false,
|
||||
statusLabel = "Agent 后端连接中",
|
||||
connectionStatus = "connecting",
|
||||
streaming = false,
|
||||
@@ -561,6 +563,7 @@ export function AgentCommandPanel({
|
||||
onPauseSpeech={pauseSpeech}
|
||||
onResumeSpeech={resumeSpeech}
|
||||
onStopSpeech={stopSpeech}
|
||||
qaOnly={qaOnly}
|
||||
/>
|
||||
) : (
|
||||
<motion.div
|
||||
@@ -581,7 +584,7 @@ export function AgentCommandPanel({
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<AgentUiEnvelopeRenderer results={uiResults} />
|
||||
{!qaOnly ? <AgentUiEnvelopeRenderer results={uiResults} /> : null}
|
||||
</ConversationContent>
|
||||
<AgentConversationScrollManager
|
||||
activeSessionId={activeSessionId}
|
||||
@@ -653,11 +656,7 @@ export function AgentCommandPanel({
|
||||
</PromptInputBody>
|
||||
<PromptInputFooter className="justify-between gap-2">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<ApprovalModeSelect
|
||||
value={approvalMode}
|
||||
disabled={streaming}
|
||||
onValueChange={onApprovalModeChange}
|
||||
/>
|
||||
{!qaOnly ? <ApprovalModeSelect value={approvalMode} disabled={streaming} onValueChange={onApprovalModeChange} /> : <span className="px-1 text-[11px] text-slate-500">任务协作问答</span>}
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
<AgentModelSelect
|
||||
@@ -1238,7 +1237,8 @@ function BackendMessageList({
|
||||
onSpeak,
|
||||
onPauseSpeech,
|
||||
onResumeSpeech,
|
||||
onStopSpeech
|
||||
onStopSpeech,
|
||||
qaOnly
|
||||
}: {
|
||||
messages: AgentChatMessage[];
|
||||
streaming: boolean;
|
||||
@@ -1256,6 +1256,7 @@ function BackendMessageList({
|
||||
onPauseSpeech: () => void;
|
||||
onResumeSpeech: () => void;
|
||||
onStopSpeech: () => void;
|
||||
qaOnly: boolean;
|
||||
}) {
|
||||
return (
|
||||
<motion.div
|
||||
@@ -1286,7 +1287,7 @@ function BackendMessageList({
|
||||
)}
|
||||
>
|
||||
<div className="space-y-3">
|
||||
{message.role === "assistant" ? (
|
||||
{message.role === "assistant" && !qaOnly ? (
|
||||
<AgentMessageProgress
|
||||
progress={message.progress}
|
||||
running={streaming && index === messages.length - 1}
|
||||
@@ -1319,7 +1320,7 @@ function BackendMessageList({
|
||||
) : streaming && index === messages.length - 1 ? (
|
||||
<span className="text-slate-500">正在生成...</span>
|
||||
) : null}
|
||||
{message.role === "assistant" ? (
|
||||
{message.role === "assistant" && !qaOnly ? (
|
||||
<AgentMessageDetails
|
||||
message={message}
|
||||
onReplyPermission={onReplyPermission}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { executionPlanStages, parseExecutionPlan, readExecutionPlanHistory } from "./execution-plan";
|
||||
|
||||
const snapshot = {
|
||||
schema_version: "execution-plan@1", session_id: "s1", revision: 1, updated_at: "2026-09-23T01:00:00.000Z",
|
||||
title: "调查", reason: "开始",
|
||||
nodes: [{ id: "a", title: '水质"] --> injected["<script>', status: "waiting", owner: "human", detail: "等待采样", evidence: [] }], edges: []
|
||||
};
|
||||
describe("execution plan projection", () => {
|
||||
it("groups independent checks before their shared downstream decision", () => {
|
||||
const plan = parseExecutionPlan({ ...snapshot,
|
||||
nodes: ["a", "b", "c", "d"].map((id) => ({ ...snapshot.nodes[0], id })),
|
||||
edges: [{ from: "a", to: "b", label: "" }, { from: "a", to: "c", label: "" }, { from: "b", to: "d", label: "" }, { from: "c", to: "d", label: "" }]
|
||||
}, "s1")!;
|
||||
expect(executionPlanStages(plan).map((stage) => stage.map((node) => node.id))).toEqual([["a"], ["b", "c"], ["d"]]);
|
||||
});
|
||||
it("rejects other sessions and dangling dependencies", () => {
|
||||
expect(parseExecutionPlan(snapshot, "s2")).toBeNull();
|
||||
expect(parseExecutionPlan({ ...snapshot, edges: [{ from: "a", to: "b", label: "" }] }, "s1")).toBeNull();
|
||||
});
|
||||
it("restores and deduplicates UI and active-run snapshots without completing waiting work", () => {
|
||||
const history = readExecutionPlanHistory([{ parts: [{ type: "data-execution_plan", data: snapshot }] }, { executionPlans: [snapshot] }], "s1");
|
||||
expect(history).toHaveLength(1);
|
||||
expect(history[0].nodes[0].status).toBe("waiting");
|
||||
});
|
||||
it("rejects cyclic dependencies rather than guessing their order", () => {
|
||||
expect(parseExecutionPlan({ ...snapshot, nodes: [snapshot.nodes[0], { ...snapshot.nodes[0], id: "b" }], edges: [{ from: "a", to: "b", label: "" }, { from: "b", to: "a", label: "" }] }, "s1")).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const id = z.string().regex(/^[a-zA-Z0-9_-]{1,64}$/);
|
||||
const schema = z.object({
|
||||
schema_version: z.literal("execution-plan@1"),
|
||||
session_id: z.string().min(1),
|
||||
revision: z.number().int().positive(),
|
||||
updated_at: z.string().datetime(),
|
||||
title: z.string().min(1).max(120),
|
||||
reason: z.string().min(1).max(1000),
|
||||
nodes: z.array(z.object({
|
||||
id, title: z.string().min(1).max(120),
|
||||
status: z.enum(["pending", "running", "waiting", "completed", "failed", "skipped", "cancelled"]),
|
||||
owner: z.enum(["agent", "human"]),
|
||||
detail: z.string().max(2000), evidence: z.array(z.string().min(1).max(1000)).max(12)
|
||||
})).min(1).max(40),
|
||||
edges: z.array(z.object({ from: id, to: id, label: z.string().max(120) })).max(80)
|
||||
}).refine((plan) => {
|
||||
const ids = new Set(plan.nodes.map((node) => node.id));
|
||||
return ids.size === plan.nodes.length && plan.edges.every((edge) => ids.has(edge.from) && ids.has(edge.to) && edge.from !== edge.to);
|
||||
});
|
||||
|
||||
export type AgentExecutionPlan = z.infer<typeof schema>;
|
||||
export function parseExecutionPlan(value: unknown, sessionId: string): AgentExecutionPlan | null {
|
||||
const parsed = schema.safeParse(value);
|
||||
return parsed.success && parsed.data.session_id === sessionId && executionPlanStages(parsed.data).length ? parsed.data : null;
|
||||
}
|
||||
|
||||
export function readExecutionPlanHistory(messages: unknown[], sessionId: string) {
|
||||
const plans = messages.flatMap((message) => {
|
||||
const value = (message as { executionPlans?: unknown } | null)?.executionPlans;
|
||||
const parts = (message as { parts?: unknown } | null)?.parts;
|
||||
const data = Array.isArray(parts) ? parts.filter((part) => part?.type === "data-execution_plan").map((part) => part.data) : [];
|
||||
return [...(Array.isArray(value) ? value : []), ...data];
|
||||
}).map((value) => parseExecutionPlan(value, sessionId)).filter((plan): plan is AgentExecutionPlan => plan !== null);
|
||||
return [...new Map(plans.map((plan) => [plan.revision, plan])).values()].sort((a, b) => a.revision - b.revision);
|
||||
}
|
||||
|
||||
export const executionStatusLabels = {
|
||||
pending: "待执行", running: "执行中", waiting: "等待资料/人工", completed: "已完成",
|
||||
failed: "失败", skipped: "不适用", cancelled: "已取消"
|
||||
};
|
||||
|
||||
|
||||
/** Same-stage nodes have no dependency on one another; this describes eligibility, not a scheduler. */
|
||||
export function executionPlanStages(plan: AgentExecutionPlan): AgentExecutionPlan["nodes"][] {
|
||||
const remaining = new Set(plan.nodes.map((node) => node.id));
|
||||
const stages: AgentExecutionPlan["nodes"][] = [];
|
||||
while (remaining.size) {
|
||||
const nodes = plan.nodes.filter((node) => remaining.has(node.id) &&
|
||||
plan.edges.filter((edge) => edge.to === node.id).every((edge) => !remaining.has(edge.from)));
|
||||
if (!nodes.length) return []; // Malformed cyclic history is never converted into a guessed sequence.
|
||||
stages.push(nodes);
|
||||
nodes.forEach((node) => remaining.delete(node.id));
|
||||
}
|
||||
return stages;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { appendExecutionReportText, readExecutionReport, updateExecutionReportProgress } from "./execution-report";
|
||||
|
||||
describe("execution report", () => {
|
||||
it("uses assistant output and public tool records without exposing reasoning or inventing completion", () => {
|
||||
const report = readExecutionReport([
|
||||
{ role: "user", content: "不应成为调查结论" },
|
||||
{ id: "a", role: "assistant", parts: [
|
||||
{ type: "text", text: "疑似二次供水问题,待现场核验。" },
|
||||
{ type: "data-progress", data: { id: "private", phase: "planning", title: "推理内容", status: "running" } },
|
||||
{ type: "data-progress", data: { id: "tool", phase: "tool", title: "核对记录", status: "running" } }
|
||||
] }
|
||||
]);
|
||||
expect(report.responses).toEqual([{ id: "a", text: "疑似二次供水问题,待现场核验。" }]);
|
||||
expect(report.activities).toHaveLength(1);
|
||||
expect(report.activities[0].status).toBe("running");
|
||||
expect(updateExecutionReportProgress(report, { id: "tool", phase: "tool", title: "查询失败", status: "error" }).activities[0].status).toBe("error");
|
||||
});
|
||||
it("joins resumed chunks to the active snapshot despite differing transport IDs", () => {
|
||||
const report = readExecutionReport([{ id: "legacy", role: "assistant", content: "上游指标正常。" }]);
|
||||
const next = appendExecutionReportText(report, { message_id: "ui-assistant", content: "等待现场采样。" });
|
||||
expect(next.responses).toEqual([{ id: "ui-assistant", text: "上游指标正常。等待现场采样。" }]);
|
||||
});
|
||||
it("keeps a new empty assistant turn separate from the previous report", () => {
|
||||
const report = readExecutionReport([{ id: "old", role: "assistant", content: "前序结论" }, { id: "current", role: "assistant", content: "" }]);
|
||||
const next = appendExecutionReportText(report, { message_id: "current-ui", content: "最新进展" });
|
||||
expect(next.responses.map((response) => response.text)).toEqual(["前序结论", "最新进展"]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
export type AgentExecutionReport = {
|
||||
responses: { id: string; text: string }[];
|
||||
activities: { id: string; title: string; detail?: string; status: "running" | "completed" | "error" }[];
|
||||
};
|
||||
|
||||
const record = (value: unknown): Record<string, unknown> | null =>
|
||||
value !== null && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : null;
|
||||
|
||||
export function updateExecutionReportProgress(report: AgentExecutionReport, value: unknown): AgentExecutionReport {
|
||||
const progress = record(value);
|
||||
// The report contains public execution facts, not the model's reasoning stream.
|
||||
if (!progress || !["tool", "start", "execution_plan"].includes(String(progress.phase)) ||
|
||||
typeof progress.id !== "string" || typeof progress.title !== "string" ||
|
||||
!["running", "completed", "error"].includes(String(progress.status))) return report;
|
||||
const activity = {
|
||||
id: progress.id, title: progress.title,
|
||||
detail: typeof progress.detail === "string" ? progress.detail : undefined,
|
||||
status: progress.status as "running" | "completed" | "error"
|
||||
};
|
||||
const existing = report.activities.findIndex((item) => item.id === activity.id);
|
||||
return { ...report, activities: existing < 0 ? [...report.activities, activity].slice(-8) :
|
||||
report.activities.map((item, index) => index === existing ? activity : item) };
|
||||
}
|
||||
|
||||
export function readExecutionReport(messages: unknown[]): AgentExecutionReport {
|
||||
let report: AgentExecutionReport = { responses: [], activities: [] };
|
||||
messages.forEach((value, index) => {
|
||||
const message = record(value);
|
||||
if (!message || message.role !== "assistant") return;
|
||||
const parts = Array.isArray(message.parts) ? message.parts.map(record).filter((part) => part !== null) : [];
|
||||
const text = parts.filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text).join("") ||
|
||||
(typeof message.content === "string" ? message.content : "");
|
||||
report.responses.push({ id: typeof message.id === "string" ? message.id : `response-${index}`, text });
|
||||
for (const progress of Array.isArray(message.progress) ? message.progress : []) report = updateExecutionReportProgress(report, progress);
|
||||
for (const part of parts) if (part.type === "data-progress") report = updateExecutionReportProgress(report, part.data);
|
||||
});
|
||||
return { ...report, responses: report.responses.slice(-3) };
|
||||
}
|
||||
|
||||
export function appendExecutionReportText(report: AgentExecutionReport, value: unknown): AgentExecutionReport {
|
||||
const data = record(value);
|
||||
if (!data || typeof data.content !== "string") return report;
|
||||
const id = typeof data.message_id === "string" ? data.message_id : "current-response";
|
||||
const matching = report.responses.findIndex((response) => response.id === id);
|
||||
// Active-run snapshots and UI stream chunks currently use different assistant IDs.
|
||||
const existing = matching < 0 ? report.responses.length - 1 : matching;
|
||||
return { ...report, responses: existing < 0 ? [...report.responses, { id, text: data.content }].slice(-3) :
|
||||
report.responses.map((response, index) => index === existing ? { ...response, id, text: response.text + data.content } : response) };
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
export { AgentCollapsedRail } from "./components/agent-collapsed-rail";
|
||||
export { readExecutionReport, updateExecutionReportProgress, appendExecutionReportText, type AgentExecutionReport } from "./execution-report";
|
||||
export { parseExecutionPlan, readExecutionPlanHistory, executionPlanStages, executionStatusLabels, type AgentExecutionPlan } from "./execution-plan";
|
||||
export { AgentCommandPanel } from "./components/agent-command-panel";
|
||||
export { countPendingAgentInputs, hasPendingAgentInput } from "./message-state";
|
||||
export type {
|
||||
@@ -11,6 +13,7 @@ export type {
|
||||
AgentApiClient,
|
||||
AgentApiClientOptions,
|
||||
AgentChatSessionSummary,
|
||||
AgentSessionPurpose,
|
||||
AgentEventTriggerContext,
|
||||
AgentLoadedChatSession,
|
||||
AgentSessionStreamEvent
|
||||
|
||||
Reference in New Issue
Block a user