Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
10 changes: 5 additions & 5 deletions docs/hooks/tools.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -641,17 +641,17 @@ If a value is too large for the environment, it may be omitted (not set). Mux al
| `MUX_TOOL_INPUT_MIN_COMPLETED` | `min_completed` | number | Number of awaited tasks that must complete before this call returns. Defaults to 1, so by default task_await returns as soon as the FIRST awaited task completes, letting you act on it while the rest keep running. The result still includes every task complete at that moment plus current status (running/queued) for the rest. Tasks that have not yet completed keep running and remain re-awaitable on a later task_await call. Raise this (e.g. set it to the total number of awaited tasks) when you genuinely need more before proceeding — for example best-of-N synthesis that must compare every candidate. Clamped to the number of awaited tasks; values above that behave like 'wait for all'. |
| `MUX_TOOL_INPUT_TASK_IDS_<INDEX>` | `task_ids[<INDEX>]` | string | List of task IDs or workflow run IDs to await — use only real IDs returned by prior task, bash, or workflow_run results; never fabricate an ID. task_list can rediscover sub-agent/background bash IDs, but workflow run rediscovery is done by omitting task_ids. When omitted, waits for active descendant tasks and workflow runs of the current workspace, excluding workflow-owned sub-agents and their background bash tasks because those results are consumed through workflow runs. |
| `MUX_TOOL_INPUT_TASK_IDS_COUNT` | `task_ids.length` | number | Number of elements in task_ids (List of task IDs or workflow run IDs to await — use only real IDs returned by prior task, bash, or workflow_run results; never fabricate an ID. task_list can rediscover sub-agent/background bash IDs, but workflow run rediscovery is done by omitting task_ids. When omitted, waits for active descendant tasks and workflow runs of the current workspace, excluding workflow-owned sub-agents and their background bash tasks because those results are consumed through workflow runs.) |
| `MUX_TOOL_INPUT_TIMEOUT_SECS` | `timeout_secs` | number | Maximum time to wait in seconds for each task. For bash tasks, this waits for NEW output (or process exit). If exceeded, the result returns status=queued\|running\|awaiting_report (task is still active). Defaults to 600 seconds (10 minutes) if not specified. Set to 0 for a non-blocking status check. |
| `MUX_TOOL_INPUT_TIMEOUT_SECS` | `timeout_secs` | number | Maximum time to wait in seconds for each task. For bash tasks, this waits for NEW output (or process exit). If exceeded, the result returns status=queued\|starting\|running\|awaiting_report (task is still active). Defaults to 600 seconds (10 minutes) if not specified. Set to 0 for a non-blocking status check. |

</details>

<details>
<summary>task_list (2)</summary>

| Env var | JSON path | Type | Description |
| --------------------------------- | ------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------- |
| `MUX_TOOL_INPUT_STATUSES_<INDEX>` | `statuses[<INDEX>]` | enum | Task statuses to include. Defaults to active tasks: queued, running, awaiting_report. |
| `MUX_TOOL_INPUT_STATUSES_COUNT` | `statuses.length` | number | Number of elements in statuses (Task statuses to include. Defaults to active tasks: queued, running, awaiting_report.) |
| Env var | JSON path | Type | Description |
| --------------------------------- | ------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------- |
| `MUX_TOOL_INPUT_STATUSES_<INDEX>` | `statuses[<INDEX>]` | enum | Task statuses to include. Defaults to active tasks: queued, starting, running, awaiting_report. |
| `MUX_TOOL_INPUT_STATUSES_COUNT` | `statuses.length` | number | Number of elements in statuses (Task statuses to include. Defaults to active tasks: queued, starting, running, awaiting_report.) |

</details>

Expand Down
9 changes: 5 additions & 4 deletions src/browser/components/AgentListItem/AgentListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ import { useWorkspaceUnread } from "@/browser/hooks/useWorkspaceUnread";
import { useRuntimeStatus } from "@/browser/stores/RuntimeStatusStore";
import { useWorkspaceSidebarState } from "@/browser/stores/WorkspaceStore";
import { stopKeyboardPropagation } from "@/browser/utils/events";
import type {
AgentRowRenderMeta,
WorkspaceDelegatedActivity,
import {
isRunningOrStartingTaskStatus,
type AgentRowRenderMeta,
type WorkspaceDelegatedActivity,
} from "@/browser/utils/ui/workspaceFiltering";
import { cn } from "@/common/lib/utils";
import {
Expand Down Expand Up @@ -1192,7 +1193,7 @@ function AgentListItemInner(props: UnifiedAgentListItemProps) {
if (rowMeta?.rowKind === "subagent") {
// Connector geometry is driven by render metadata so visible siblings keep
// consistent single/middle/last shapes as parents expand/collapse children.
const isElbowActive = props.metadata.taskStatus === "running";
const isElbowActive = isRunningOrStartingTaskStatus(props.metadata.taskStatus);
const connectorLayout = props.subAgentConnectorLayout ?? "default";
const connectorDepth = props.depth ?? rowMeta.depth;
const connectorRailX = getSubAgentParentRailX(connectorDepth, connectorLayout);
Expand Down
35 changes: 24 additions & 11 deletions src/browser/components/ChatPane/ChatPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ import {
computeOperationalBundleInfos,
computeWorkBundleInfos,
} from "@/browser/utils/messages/transcriptRenderProjection";
import { isBlockedPreStreamTaskStatus } from "@/browser/utils/ui/workspaceFiltering";
import { recordSyntheticReactRenderSample } from "@/browser/utils/perf/reactProfileCollector";

// Perf e2e runs load the production bundle where React's onRender profiler callbacks may not
Expand Down Expand Up @@ -321,9 +322,13 @@ const ChatPaneContent: React.FC<ChatPaneContentProps> = (props) => {
// so the transcript stays readable while new sends remain disabled.
const meta = workspaceMetadata.get(workspaceId);
const transcriptOnly = meta?.transcriptOnly ?? false;
const isQueuedAgentTask = Boolean(meta?.parentWorkspaceId) && meta?.taskStatus === "queued";
const isPreStreamAgentTask =
Boolean(meta?.parentWorkspaceId) && isBlockedPreStreamTaskStatus(meta?.taskStatus);
const preStreamAgentTaskLabel = meta?.taskStatus === "starting" ? "Starting" : "Queued";
const queuedAgentTaskPrompt =
isQueuedAgentTask && typeof meta?.taskPrompt === "string" && meta.taskPrompt.trim().length > 0
isPreStreamAgentTask &&
typeof meta?.taskPrompt === "string" &&
meta.taskPrompt.trim().length > 0
? meta.taskPrompt
: null;
const shouldShowQueuedAgentTaskPrompt =
Expand Down Expand Up @@ -1048,7 +1053,9 @@ const ChatPaneContent: React.FC<ChatPaneContentProps> = (props) => {
node: (
<div className="mt-4 mb-1 ml-auto w-fit max-w-full">
<div className="rounded-lg border border-[var(--color-user-border)] bg-[var(--color-user-surface)] px-3 py-2 text-sm">
<div className="text-muted mb-1 text-[11px] font-medium">Queued</div>
<div className="text-muted mb-1 text-[11px] font-medium">
{preStreamAgentTaskLabel}
</div>
<MarkdownRenderer
content={queuedAgentTaskPrompt ?? ""}
className="user-message-markdown text-foreground"
Expand Down Expand Up @@ -1528,7 +1535,8 @@ const ChatPaneContent: React.FC<ChatPaneContentProps> = (props) => {
isTranscriptCaughtUp={isTranscriptCaughtUp}
isHydratingTranscript={isHydratingTranscript}
runtimeConfig={runtimeConfig}
isQueuedAgentTask={isQueuedAgentTask}
isPreStreamAgentTask={isPreStreamAgentTask}
preStreamAgentTaskStatus={meta?.taskStatus === "starting" ? "starting" : "queued"}
isCompacting={isCompacting}
shouldShowPinnedTodoList={shouldShowPinnedTodoList}
shouldShowReviewsBanner={shouldShowReviewsBanner}
Expand Down Expand Up @@ -1580,7 +1588,8 @@ interface ChatInputPaneProps {
projectName: string;
workspaceName: string;
runtimeConfig?: RuntimeConfig;
isQueuedAgentTask: boolean;
isPreStreamAgentTask: boolean;
preStreamAgentTaskStatus: "queued" | "starting";
isCompacting: boolean;
isStreamStarting: boolean;
isTranscriptCaughtUp: boolean;
Expand Down Expand Up @@ -1695,12 +1704,14 @@ const ChatInputPane: React.FC<ChatInputPaneProps> = (props) => {
),
});
}
if (props.isQueuedAgentTask) {
if (props.isPreStreamAgentTask) {
addDecorationEntry({
key: "queued-agent-task",
key: "pre-stream-agent-task",
node: (
<div className="border-border-medium bg-background-secondary text-muted rounded-md border px-3 py-2 text-xs">
This agent task is queued and will start automatically when a parallel slot is available.
{props.preStreamAgentTaskStatus === "starting"
? "This agent task is starting and will become editable after launch accepts the initial prompt."
: "This agent task is queued and will start automatically when a parallel slot is available."}
</div>
),
});
Expand All @@ -1726,10 +1737,12 @@ const ChatInputPane: React.FC<ChatInputPaneProps> = (props) => {
onResetContext={props.onResetContext}
onTruncateHistory={props.onTruncateHistory}
onModelChange={props.onModelChange}
disabled={!props.projectName || !props.workspaceName || props.isQueuedAgentTask}
disabled={!props.projectName || !props.workspaceName || props.isPreStreamAgentTask}
disabledReason={
props.isQueuedAgentTask
? "Queued - waiting for an available parallel task slot. This will start automatically."
props.isPreStreamAgentTask
? props.preStreamAgentTaskStatus === "starting"
? "Starting - waiting for launch to accept the initial prompt."
: "Queued - waiting for an available parallel task slot. This will start automatically."
: undefined
}
isTranscriptCaughtUp={props.isTranscriptCaughtUp}
Expand Down
5 changes: 4 additions & 1 deletion src/browser/components/ProjectSidebar/ProjectSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import {
getSectionExpandedKey,
getSectionTierKey,
resolveEffectiveSectionId,
isRunningOrStartingTaskStatus,
type AgentRowRenderMeta,
} from "@/browser/utils/ui/workspaceFiltering";
import { Tooltip, TooltipTrigger, TooltipContent } from "../Tooltip/Tooltip";
Expand Down Expand Up @@ -2587,7 +2588,9 @@ const ProjectSidebarInner: React.FC<ProjectSidebarProps> = ({

let lastRunningSiblingIndex = -1;
for (let index = siblings.length - 1; index >= 0; index -= 1) {
if (siblings[index]?.taskStatus === "running") {
if (
isRunningOrStartingTaskStatus(siblings[index]?.taskStatus)
) {
lastRunningSiblingIndex = index;
break;
}
Expand Down
7 changes: 6 additions & 1 deletion src/browser/utils/messages/modelMessageTransform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,12 @@ function coalesceConsecutiveNoProgressTaskAwaitPairs(messages: ModelMessage[]):
}

const status = (entry as { status?: unknown }).status;
if (status !== "queued" && status !== "running" && status !== "awaiting_report") {
if (
status !== "queued" &&
status !== "starting" &&
status !== "running" &&
status !== "awaiting_report"
) {
return false;
}

Expand Down
22 changes: 18 additions & 4 deletions src/browser/utils/ui/workspaceFiltering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,22 @@ function hasDelegatedActivity(activity: WorkspaceDelegatedActivity): boolean {
return activity.activeCount > 0 || activity.queuedCount > 0;
}

function isActiveDelegatedStatus(status: FrontendWorkspaceMetadata["taskStatus"]): boolean {
return status === "running" || status === "awaiting_report";
export function isActiveOrStartingTaskStatus(
status: FrontendWorkspaceMetadata["taskStatus"]
): boolean {
return status === "starting" || status === "running" || status === "awaiting_report";
}

export function isRunningOrStartingTaskStatus(
status: FrontendWorkspaceMetadata["taskStatus"]
): boolean {
return status === "starting" || status === "running";
}

export function isBlockedPreStreamTaskStatus(
status: FrontendWorkspaceMetadata["taskStatus"]
): boolean {
return status === "queued" || status === "starting";
}

function getIsWorkspaceLiveActive(workspaceId: string, options: DelegatedActivityOptions): boolean {
Expand All @@ -166,7 +180,7 @@ export function isWorkspaceDelegatedActivityActive(
workspace: FrontendWorkspaceMetadata,
options: DelegatedActivityOptions = {}
): boolean {
if (isActiveDelegatedStatus(workspace.taskStatus)) {
if (isActiveOrStartingTaskStatus(workspace.taskStatus)) {
return true;
}
if (hasCompletedAgentReport(workspace)) {
Expand Down Expand Up @@ -410,7 +424,7 @@ export function computeAgentRowRenderMeta(

let lastRunningSiblingIndex = -1;
for (let index = siblings.length - 1; index >= 0; index -= 1) {
if (siblings[index]?.taskStatus === "running") {
if (isRunningOrStartingTaskStatus(siblings[index]?.taskStatus)) {
lastRunningSiblingIndex = index;
break;
}
Expand Down
2 changes: 1 addition & 1 deletion src/common/orpc/schemas/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1657,7 +1657,7 @@ export const tasks = {
z.object({
taskId: z.string(),
kind: z.literal("agent"),
status: z.enum(["queued", "running"]),
status: z.enum(["queued", "starting", "running"]),
}),
z.string()
),
Expand Down
7 changes: 5 additions & 2 deletions src/common/orpc/schemas/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,12 +159,15 @@ export const WorkspaceMetadataSchema = z.object({
description: "Grouping metadata for child tasks spawned from the same parent tool call.",
}),
taskStatus: z
.enum(["queued", "running", "awaiting_report", "interrupted", "reported"])
.enum(["queued", "starting", "running", "awaiting_report", "interrupted", "reported"])
.optional()
.meta({
description:
"Agent task lifecycle status for child workspaces (queued|running|awaiting_report|interrupted|reported).",
"Agent task lifecycle status for child workspaces (queued|starting|running|awaiting_report|interrupted|reported).",
}),
taskLaunchError: z.string().optional().meta({
description: "Startup failure recorded before an agent task could begin streaming.",
}),
reportedAt: z.string().optional().meta({
description: "ISO 8601 timestamp for when an agent task reported completion (optional).",
}),
Expand Down
7 changes: 5 additions & 2 deletions src/common/schemas/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,15 @@ export const WorkspaceConfigSchema = z.object({
description: "Grouping metadata for child tasks spawned from the same parent tool call.",
}),
taskStatus: z
.enum(["queued", "running", "awaiting_report", "interrupted", "reported"])
.enum(["queued", "starting", "running", "awaiting_report", "interrupted", "reported"])
.optional()
.meta({
description:
"Agent task lifecycle status for child workspaces (queued|running|awaiting_report|interrupted|reported).",
"Agent task lifecycle status for child workspaces (queued|starting|running|awaiting_report|interrupted|reported).",
}),
taskLaunchError: z.string().optional().meta({
description: "Startup failure recorded before an agent task could begin streaming.",
}),
reportedAt: z.string().optional().meta({
description: "ISO 8601 timestamp for when an agent task reported completion (optional).",
}),
Expand Down
9 changes: 8 additions & 1 deletion src/common/utils/agentTaskCompletion.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
export interface AgentTaskCompletionCandidate {
taskStatus?: "queued" | "running" | "awaiting_report" | "interrupted" | "reported" | null;
taskStatus?:
| "queued"
| "starting"
| "running"
| "awaiting_report"
| "interrupted"
| "reported"
| null;
reportedAt?: string | null;
}

Expand Down
Loading
Loading