Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions app/networks/providers/network-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -294,15 +294,15 @@ export function NetworkProvider({
const allProgressEvents: ProgressEvent[] = []
for (const message of messages) {
if (message.role === "assistant" && message.parts !== null) {
for (const part of message.parts) {
for (const [partIndex, part] of message.parts.entries()) {
// Handle agent and workflow progress events
if (part.type === "data-tool-agent" || part.type === "data-tool-workflow") {
const agentPart = part as { data: AgentDataPart }
const eventData = agentPart.data

if (eventData?.data?.text?.trim()) {
allProgressEvents.push({
id: `${message.id}-${part.type}-${Date.now()}`,
id: `${message.id}-${part.type}-${partIndex}`,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question (bug_risk): Using partIndex instead of Date.now() might create duplicate IDs if messages are reprocessed or parts change order.

This makes IDs deterministic, but also means reprocessed messages or reordered/updated parts can emit progress events with the same ID. If these IDs are used as keys or for de-duplication, that can introduce subtle bugs. Consider adding another stable discriminator (e.g., a per-event sequence, eventData.status, or some unique field from the part) to keep IDs both deterministic and distinct across logically separate events.

stage: part.type.replace("data-tool-", ""),
status: "in-progress",
message: eventData.data.text,
Expand All @@ -320,7 +320,7 @@ export function NetworkProvider({

if (eventData?.status && (eventData.status === "in-progress" || eventData.status === "done" || eventData.status === "error")) {
allProgressEvents.push({
id: `${message.id}-${part.type}-${Date.now()}`,
id: `${message.id}-${part.type}-${partIndex}`,
stage: eventData.stage ?? "progress",
status: eventData.status,
message: eventData.message ?? `${part.type} ${eventData.status}`,
Expand Down
23 changes: 23 additions & 0 deletions app/workflows/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,29 @@ app/workflows/
└── workflow-output.tsx # Streaming output panel (bottom-right)
```

## Progress Event Handling

```mermaid
sequenceDiagram
participant Assistant as Assistant<br/>(Message)
participant Context as Workflow/Network<br/>Context
participant Extractor as Progress Event<br/>Extractor
participant State as Context State
participant Panel as Progress<br/>Panel/Dialog

Assistant->>Context: Receive message with<br/>data-tool-* parts
Context->>Extractor: Extract progress events<br/>from message parts
Extractor->>Extractor: Parse stage, status,<br/>message, agentId
Extractor->>State: Update progressEvents<br/>and suspendPayload
State->>Panel: Trigger re-render with<br/>new events
Panel->>Panel: Group by stage &<br/>render event items

Note over Panel: If suspendPayload exists<br/>& status="paused"
Panel->>Panel: Show SuspendDialog<br/>with approve/reject
Panel->>Context: User clicks Approve<br/>(calls approveWorkflow)
Context->>Context: Clear suspendPayload<br/>Resume workflow
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (typo): Consider adding punctuation or a conjunction between "Clear suspendPayload" and "Resume workflow" for grammatical clarity.

Right now it reads like a run-on. Consider phrasing it as Clear suspendPayload, then resume workflow or Clear suspendPayload and resume workflow so the sequence is clearer.

Suggested change
Context->>Context: Clear suspendPayload<br/>Resume workflow
Context->>Context: Clear suspendPayload,<br/>then resume workflow

```

## AI SDK Integration

Uses `useChat` from `@ai-sdk/react` with `DefaultChatTransport` to connect to Mastra's `/workflow/:workflowId` routes:
Expand Down
2 changes: 1 addition & 1 deletion app/workflows/components/workflow-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export function WorkflowHeader() {
onValueChange={(value) => selectWorkflow(value)}
disabled={isWorkflowActive}
>
<SelectTrigger className="w-70">
<SelectTrigger className="w-72">
<SelectValue />
</SelectTrigger>
<SelectContent>
Expand Down
6 changes: 3 additions & 3 deletions app/workflows/providers/workflow-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -260,15 +260,15 @@ export function WorkflowProvider({

for (const message of messages) {
if (message.role === "assistant" && message.parts !== null) {
for (const part of message.parts) {
for (const [partIndex, part] of message.parts.entries()) {
// Handle workflow progress events (data-workflow, data-tool-workflow)
if (part.type === "data-workflow" || part.type === "data-tool-workflow") {
const workflowPart = part as { data?: { text?: string; status?: string; stepId?: string } }
const eventData = workflowPart.data

if (eventData && eventData.text !== null && typeof eventData.text === "string" && eventData.text.trim()) {
allProgressEvents.push({
id: `${message.id}-${part.type}-${Date.now()}`,
id: `${message.id}-${part.type}-${partIndex}`,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question (bug_risk): Deterministic IDs based on partIndex may collide across workflow progress updates.

Using partIndex instead of Date.now() means multiple progress updates for the same message/part/type can share an ID if this logic runs more than once or the data changes. If these IDs are used to track or update individual progress entries, collisions will make distinct updates indistinguishable. Consider including additional fields (e.g. eventData.stepId, eventData.status, or a sequence number) in the ID to keep it deterministic but unique per update.

stage: part.type.replace("data-", "").replace("-tool-", " "),
status: "in-progress",
message: eventData.text,
Expand Down Expand Up @@ -303,7 +303,7 @@ export function WorkflowProvider({
if (eventData && eventData.status !== null && typeof eventData.status === "string" &&
(eventData.status === "in-progress" || eventData.status === "done" || eventData.status === "error")) {
allProgressEvents.push({
id: `${message.id}-${part.type}-${Date.now()}`,
id: `${message.id}-${part.type}-${partIndex}`,
stage: eventData.stage ?? "workflow",
status: eventData.status,
message: eventData.message ?? `${part.type} ${eventData.status}`,
Expand Down
Loading