feat(m1-08): stream safe execution summaries
This commit is contained in:
+35
-4
@@ -116,6 +116,10 @@ export type TaskProgress = {
|
||||
cancellable: boolean;
|
||||
};
|
||||
|
||||
/** Display-only, adapter-authoritative execution trace. It grants no ability. */
|
||||
export type AgentExecutionStep = { id: string; title: string; status: "running" | "completed" | "failed" };
|
||||
export type AgentExecutionTrace = { execution_id: string; status: "running" | "completed" | "failed"; steps: readonly AgentExecutionStep[] };
|
||||
|
||||
/**
|
||||
* A user-originated action awaiting delivery through the Transport Adapter.
|
||||
* It is data only: the Store owns retries and the Kernel owns state changes.
|
||||
@@ -180,8 +184,9 @@ type ConversationItemBase = {
|
||||
|
||||
export type ConversationItem =
|
||||
| (ConversationItemBase & { kind: "user-message"; text: string; delivery: DeliveryState })
|
||||
| (ConversationItemBase & { kind: "markdown"; markdown: string })
|
||||
| (ConversationItemBase & { kind: "agent-status"; status: string; detail: string })
|
||||
| (ConversationItemBase & { kind: "markdown"; markdown: string; execution_id?: string })
|
||||
| (ConversationItemBase & { kind: "agent-status"; status: string; detail: string; execution_id?: string })
|
||||
| (ConversationItemBase & { kind: "execution-summary"; trace: AgentExecutionTrace })
|
||||
| (ConversationItemBase & { kind: "progress"; title: string; percent: number; status: string; task?: TaskProgress })
|
||||
| (ConversationItemBase & { kind: "error"; message: string; code?: string })
|
||||
| (ConversationItemBase & { kind: "tool-call"; call: ToolCallRequest })
|
||||
@@ -199,6 +204,7 @@ const ACTOR_KINDS = new Set<ActorKind>(["human", "agent", "app", "system"]);
|
||||
const SUPPORTED_TYPES = new Set([
|
||||
"lineup.v1.text",
|
||||
"lineup.v1.agent.status",
|
||||
"lineup.v1.agent.execution",
|
||||
"lineup.v1.agent.progress",
|
||||
"lineup.v1.error",
|
||||
"lineup.v1.tool.call",
|
||||
@@ -323,6 +329,7 @@ const TOOL_FINAL_STATUSES = new Set<ToolCallFinalStatus>(["completed", "failed",
|
||||
const ACTION_GROUP_MODES = new Set<ActionGroupMode>(["single-choice", "multi-choice", "button"]);
|
||||
const INPUT_FIELD_TYPES = new Set<InputFieldType>(["text", "textarea", "number"]);
|
||||
const TASK_STATUSES = new Set<TaskStatus>(["running", "waiting", "completed", "failed", "cancelled"]);
|
||||
const EXECUTION_STATUSES = new Set(["running", "completed", "failed"]);
|
||||
|
||||
function optionalTimestamp(value: unknown): string | undefined {
|
||||
return typeof value === "string" && !Number.isNaN(Date.parse(value)) ? value : undefined;
|
||||
@@ -436,6 +443,24 @@ function parseTaskProgress(payload: JsonObject, percent: number): TaskProgress |
|
||||
return { operation_id: operationID, title, percent, status, cancellable: payload.cancellable === true };
|
||||
}
|
||||
|
||||
function parseExecutionTrace(payload: JsonObject): AgentExecutionTrace | undefined {
|
||||
const executionID = identifier(payload.execution_id);
|
||||
const status = text(payload.status);
|
||||
if (!executionID || !EXECUTION_STATUSES.has(status) || !Array.isArray(payload.steps) || payload.steps.length > 12) return undefined;
|
||||
const steps: AgentExecutionStep[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (const raw of payload.steps) {
|
||||
const step = object(raw);
|
||||
const id = step && identifier(step.id);
|
||||
const title = step && text(step.title).trim();
|
||||
const stepStatus = step && text(step.status);
|
||||
if (!id || !title || title.length > 160 || !stepStatus || !EXECUTION_STATUSES.has(stepStatus) || seen.has(id)) return undefined;
|
||||
seen.add(id);
|
||||
steps.push({ id, title, status: stepStatus as AgentExecutionStep["status"] });
|
||||
}
|
||||
return { execution_id: executionID, status: status as AgentExecutionTrace["status"], steps };
|
||||
}
|
||||
|
||||
function parseToolResult(payload: JsonObject): ToolCallResult | undefined {
|
||||
const callID = identifier(payload.call_id);
|
||||
const status = text(payload.status) as ToolCallFinalStatus;
|
||||
@@ -484,14 +509,20 @@ export function decodeConversationItem(raw: string): ConversationItem {
|
||||
switch (envelope.type) {
|
||||
case "lineup.v1.text": {
|
||||
const markdown = text(payload.markdown) || text(payload.text);
|
||||
return markdown ? { kind: "markdown", markdown, ...base } : fallbackItem({ code: "invalid_payload", detail: "Text payload requires text or markdown.", envelope });
|
||||
return markdown
|
||||
? { kind: "markdown", markdown, ...(identifier(payload.execution_id) ? { execution_id: identifier(payload.execution_id)! } : {}), ...base }
|
||||
: fallbackItem({ code: "invalid_payload", detail: "Text payload requires text or markdown.", envelope });
|
||||
}
|
||||
case "lineup.v1.agent.status": {
|
||||
const status = text(payload.status);
|
||||
return status
|
||||
? { kind: "agent-status", status, detail: text(payload.detail), ...base }
|
||||
? { kind: "agent-status", status, detail: text(payload.detail), ...(identifier(payload.execution_id) ? { execution_id: identifier(payload.execution_id)! } : {}), ...base }
|
||||
: fallbackItem({ code: "invalid_payload", detail: "Agent status payload requires status.", envelope });
|
||||
}
|
||||
case "lineup.v1.agent.execution": {
|
||||
const trace = parseExecutionTrace(payload);
|
||||
return trace ? { kind: "execution-summary", trace, ...base } : fallbackItem({ code: "invalid_payload", detail: "Agent execution requires bounded public steps.", envelope });
|
||||
}
|
||||
case "lineup.v1.agent.progress": {
|
||||
const percent = payload.percent;
|
||||
if (typeof percent !== "number" || !Number.isFinite(percent) || percent < 0 || percent > 100) {
|
||||
|
||||
Reference in New Issue
Block a user