Skip to content

🤖 feat: return workflow plan agent results#3627

Merged
ThomasK33 merged 1 commit into
mainfrom
plan-agent-t1e1
Jun 24, 2026
Merged

🤖 feat: return workflow plan agent results#3627
ThomasK33 merged 1 commit into
mainfrom
plan-agent-t1e1

Conversation

@ThomasK33

Copy link
Copy Markdown
Member

Summary

Make workflow-owned Plan agents return a { reportMarkdown, planFilePath } object instead of a raw markdown string, aligning the workflow-visible Plan result with existing durable task-output field names.

Background

Workflow Plan agents are new and complete through propose_plan, which already records both the plan content and canonical plan file path in durable task output metadata. Returning the same field names to workflow authors lets verifier fan-out steps read the plan file without inventing Plan-only aliases or repeating the full plan content in every prompt.

Implementation

  • Added a shared workflow-runtime mapper that returns structured output for schema-backed agents, markdown strings for ordinary no-schema agents, and { reportMarkdown, planFilePath } for Plan agents.
  • Wired that mapper through direct agent(), parallel(), and pipeline() paths.
  • Kept Plan agents incompatible with schema/outputSchema and updated the defensive TaskService rejection message.
  • Updated workflow-authoring docs and generated built-in skill content with the new Plan result object, final-result caveat, and verifier fan-out pattern.

Validation

  • bun test src/node/services/workflows/WorkflowRunner.test.ts
  • bun test src/node/services/workflows/WorkflowTaskServiceAdapter.test.ts
  • bun test src/common/orpc/schemas/workflow.test.ts
  • bun test src/node/services/subagentReportArtifacts.test.ts
  • bun test src/node/services/taskService.test.ts --test-name-pattern "workflow-owned plan"
  • bun test src/node/services/agentSkills/builtInWorkflowSkillDescriptions.test.ts
  • make typecheck
  • make lint
  • make static-check

Dogfooding: ran a real Plan → parallel verifier workflow through the CLI. The final report included a concrete Plan path: and verifier summaries with path=true, plan=true for Tests, UX, and Risks. UI dogfooding was attempted against the running local Mux server but blocked by the auth screen; blocker screenshot was captured in the workspace conversation.

Risks

The workflow-visible return type for agentId: "plan" changes from string to object. This is intentional while Plan workflow agents are new; no production workflow usage was found during planning. If an isolated runtime cannot read planFilePath, workflow authors can fall back to reportMarkdown as documented.


📋 Implementation Plan

Plan: Make workflow Plan agents return { reportMarkdown, planFilePath }

Goal

Make workflow-owned Plan agents return an object that uses the existing durable task-output field names:

const result = agent("Plan the change", { id: "plan", agentId: "plan" });
// result.reportMarkdown -> proposed plan markdown
// result.planFilePath   -> canonical plan file path in the Plan task/runtime

This still replaces the current workflow-visible string return value for agentId: "plan", but it avoids inventing Plan-only aliases. The workflow-visible object matches the fields already used by workflow task reports, UI/report plumbing, and durable StructuredTaskOutput records. Plan agents are newly added and currently have no production workflow usage in the repo, so this is the narrow window to make this API consistent before adoption.

Verified context and constraints

  • agent(prompt, options) is implemented in the QuickJS workflow runtime template in src/node/services/workflows/WorkflowRunner.ts.
  • Current workflow return mapping is:
    • schema / outputSchema present -> result.structuredOutput
    • otherwise -> result.reportMarkdown
  • Plan agents already complete through propose_plan, not agent_report.
  • WorkflowTaskServiceAdapter.waitForAgentTask() already returns the durable task output shape:
    {
      taskId: string;
      reportMarkdown: string;
      title?: string;
      planFilePath?: string;
      structuredOutput?: unknown;
    }
  • TaskService.handleSuccessfulWorkflowProposePlan() already finalizes workflow Plan tasks with:
    {
      reportMarkdown: planSummary.content,
      title: "Proposed plan",
      planFilePath: planSummary.path,
    }
  • StructuredTaskOutputSchema already includes reportMarkdown, optional planFilePath, optional structuredOutput, and optional taskId; no ORPC/schema migration is expected.
  • src/node/builtinSkills/workflow-authoring.md currently documents the old string-return behavior and must be updated.
  • Keep rejecting schema / outputSchema for Plan agents. The returned object is runtime-generated task metadata, not model structured output.
Important runtime-path caveat

planFilePath will initially map to the existing durable planFilePath captured when the Plan child proposes its plan. Treat it as the canonical plan file path in the Plan task/runtime, not as a universally portable filesystem path.

That path is expected to be directly readable by later child agents in local/worktree and SSH-style runtimes where workspaces share the same host filesystem. In Docker/container-isolated runtimes, the path may be scoped to the Plan child container and may not be readable by sibling verifier containers. The object still includes reportMarkdown as an immutable content snapshot, so workflows retain a fallback.

This plan does not add a cross-container immutable plan artifact copier in the first pass. If Docker dogfooding shows this is required for the intended verifier fan-out, handle it as a follow-up by copying the finalized plan snapshot into a workflow-owned artifact location that every later child runtime can read, then make planFilePath point at that portable snapshot.

Public API decision

Recommended API

Plan workflow agents return this workflow-visible JavaScript object:

interface WorkflowPlanAgentResult {
  reportMarkdown: string;
  planFilePath: string;
}

Mapping:

reportMarkdown = taskResult.reportMarkdown;
planFilePath = taskResult.planFilePath;

This is a minimal public object, not the raw internal task result. Do not expose taskId, title, or structuredOutput as part of this Plan-agent workflow API unless a later requirement explicitly needs them.

Rationale:

  • Uses the existing durable/report field names workflow authors and internal components already understand.
  • Keeps durable/internal storage and workflow-visible Plan output aligned (reportMarkdown / planFilePath).
  • Lets workflow authors pass result.planFilePath to verifier agents without repeating the full plan in every prompt.
  • Keeps result.reportMarkdown available as the immutable snapshot and fallback when path reads are not desirable or fail.

Final workflow result caveat:

  • normalizeWorkflowResultForEvent() treats any object with reportMarkdown as the workflow's final report shape.
  • Therefore, a workflow that returns planResult directly will preserve the plan text as final reportMarkdown, but should not rely on planFilePath being preserved in the final workflow result event.
  • If the workflow wants the path in its final output, include it inside the returned reportMarkdown text or in structuredOutput explicitly.

Net product-code LoC estimate: +25 to +40 LoC in WorkflowRunner.ts plus ~2 changed product-code lines for schema-rejection error messages. Tests/docs are excluded from this product-code estimate.

Implementation phases

Phase 1 — Add a central Plan-result mapper in the workflow runtime

File: src/node/services/workflows/WorkflowRunner.ts

Add a small helper inside the compiled runtime template near __muxAgent / __muxParallel / __muxPipeline:

function __muxPlanAgentResult(taskResult) {
  if (typeof taskResult.reportMarkdown !== "string") {
    throw new Error("Workflow plan agent result is missing reportMarkdown");
  }
  if (typeof taskResult.planFilePath !== "string" || taskResult.planFilePath.length === 0) {
    throw new Error("Workflow plan agent result is missing planFilePath");
  }
  return {
    reportMarkdown: taskResult.reportMarkdown,
    planFilePath: taskResult.planFilePath,
  };
}

function __muxAgentReturnValue(taskResult, hasSchema, isPlanAgent) {
  if (hasSchema) {
    return taskResult.structuredOutput;
  }
  if (isPlanAgent) {
    return __muxPlanAgentResult(taskResult);
  }
  return taskResult.reportMarkdown;
}

Notes:

  • The assertions are intentional: a successful workflow Plan task should always have both plan content and a canonical plan file path in the Plan task/runtime. If not, fail fast instead of silently giving verifier agents undefined.
  • Keep the helper local to the QuickJS template; no TypeScript type export is needed unless implementation ergonomics suggest otherwise.

Quality gate after Phase 1:

  • Run the focused workflow runner test file once the tests in Phase 2 are added:
    bun test src/node/services/workflows/WorkflowRunner.test.ts

Phase 2 — Update direct, parallel, and pipeline agent return mapping

File: src/node/services/workflows/WorkflowRunner.ts

  1. In __muxAgent, compute Plan status after option normalization:

    const hasSchema = Object.prototype.hasOwnProperty.call(spec, "outputSchema");
    const isPlanAgent = spec.agentId === "plan";
  2. Keep the existing schema rejection, but update the message:

    if (isPlanAgent && hasSchema) {
      throw new Error("Workflow plan agents return { reportMarkdown, planFilePath }; do not provide schema/outputSchema.");
    }
  3. Direct agent call path:

    const result = __workflowAgent(spec);
    return __muxAgentReturnValue(result, hasSchema, isPlanAgent);
  4. Parallel collection path:

    • Include hasSchema and isPlanAgent on the marker object returned from each thunk.
    • Map completed task results with __muxAgentReturnValue(taskResult, branchResult.hasSchema, branchResult.isPlanAgent).
  5. Pipeline collection path:

    • Include hasSchema and isPlanAgent in the collected agent metadata.
    • When a pipeline agent completes, assign:
      pendingEntry.state.value = __muxAgentReturnValue(
        completion.result,
        pendingEntry.collectedAgent.hasSchema,
        pendingEntry.collectedAgent.isPlanAgent
      );

Quality gate after Phase 2:

  • Verify existing schema-backed and markdown-only non-Plan agent behavior still works:
    bun test src/node/services/workflows/WorkflowRunner.test.ts --test-name-pattern "new agent API returns structured output"
  • Then run the full file:
    bun test src/node/services/workflows/WorkflowRunner.test.ts

Phase 3 — Update Plan schema-rejection handling outside the JS runtime

File: src/node/services/taskService.ts

Update the existing workflow Plan completion rejection message in handleSuccessfulWorkflowProposePlan() from the old text to:

Workflow plan agents return { reportMarkdown, planFilePath }; do not provide schema/outputSchema.

Why this exists:

  • The JS runtime rejects schema / outputSchema before spawning Plan agents.
  • TaskService still needs the defensive backend check because a workflow-owned Plan task could already be running or be finalized through another path.

Quality gate after Phase 3:

  • Run relevant TaskService tests around workflow Plan completion:
    bun test src/node/services/taskService.test.ts --test-name-pattern "workflow-owned plan"

Phase 4 — Add/adjust tests

4.1 Workflow runtime tests

File: src/node/services/workflows/WorkflowRunner.test.ts

Add focused tests for the public workflow JS API:

  1. Direct Plan agent returns object

    • Workflow source:

      export default function workflow({ agent }) {
        const result = agent("Plan the change", { id: "plan", agentId: "plan" });
        return { reportMarkdown: result.reportMarkdown + "\n" + result.planFilePath };
      }
    • Mock runAgent() returns:

      {
        taskId: "task_plan",
        reportMarkdown: "Plan contents",
        planFilePath: "/tmp/mux/plans/example.md",
      }
    • Assert final reportMarkdown contains both values.

    • Do not test this by returning planResult directly as the workflow result; field-access assertions avoid conflating the Plan-agent API with final workflow result normalization.

  2. Parallel Plan agents return objects in input order

    • Workflow uses parallel([() => agent(...plan-a...), () => agent(...plan-b...)]).
    • Assert each returned branch value has .reportMarkdown and .planFilePath, and the final report proves ordering.
  3. Pipeline Plan agents pass objects to the next stage

    • Workflow uses pipeline() with a Plan stage followed by a stage that reads result.reportMarkdown and result.planFilePath.
    • Assert the pipeline receives objects, not markdown strings.
  4. Existing schema rejection test stays, with updated message

    • Keep rejects schema on built-in plan agent steps but update expected text to { reportMarkdown, planFilePath }.
  5. Non-Plan no-schema agents still return strings

    • The existing new agent API returns structured output for schema-backed steps and markdown otherwise test already covers this. Leave it in place and ensure it still passes.
  6. Plan agent missing planFilePath fails fast

    • Mock runAgent() returns reportMarkdown but omits planFilePath.
    • Assert the workflow fails with:
      Workflow plan agent result is missing planFilePath
      
    • This documents the invariant that successful workflow Plan completion must preserve a readable path alongside the plan snapshot.

4.2 Adapter/schema tests

Files:

  • src/node/services/workflows/WorkflowTaskServiceAdapter.test.ts
  • src/common/orpc/schemas/workflow.test.ts
  • src/node/services/subagentReportArtifacts.test.ts

Expected action:

  • No required schema changes because these tests already cover preservation of planFilePath metadata.
  • If failures occur due to renamed public wording, update only exact error/help text assertions.

4.3 TaskService tests

File: src/node/services/taskService.test.ts

Update expectations that assert the old schema-rejection message. Existing search evidence points to workflow-owned Plan schema tests around the workflow-owned plan region.

Quality gate after Phase 4:

bun test src/node/services/workflows/WorkflowRunner.test.ts
bun test src/node/services/taskService.test.ts --test-name-pattern "workflow-owned plan"
bun test src/node/services/workflows/WorkflowTaskServiceAdapter.test.ts
bun test src/common/orpc/schemas/workflow.test.ts
bun test src/node/services/subagentReportArtifacts.test.ts

Phase 5 — Update workflow-authoring docs/skill text

File: src/node/builtinSkills/workflow-authoring.md

Update the agent(prompt, options) section:

  • Replace old wording that says Plan agent() returns a markdown string.
  • Document:
    const planResult = agent("Plan the requested change", {
      id: "plan",
      agentId: "plan",
    });
  • Explain fields:
    • reportMarkdown: proposed plan markdown snapshot.
    • planFilePath: canonical path to the plan file captured at proposal time in the Plan task/runtime; it may not be readable from every isolated sibling runtime.
  • Keep saying schema / outputSchema is not allowed for Plan agents.
  • Update the plan-to-exec example:
    const planResult = agent("Plan the requested change", { id: "plan", agentId: "plan" });
    const implementation = agent(`Implement this accepted plan:\n\n${planResult.reportMarkdown}`, {
      id: "implement",
      agentId: "exec",
    });
    return { reportMarkdown: implementation };
  • Add a final-result caveat: if a workflow returns the Plan object directly, final report normalization uses reportMarkdown; include planFilePath in final reportMarkdown or structuredOutput when the final workflow output must show it.
  • Add a verifier fan-out example:
    const planResult = agent("Plan the requested change", { id: "plan", agentId: "plan" });
    const reviews = parallel(
      ["tests", "architecture", "UX"].map((section) => () =>
        agent(
          `Read the proposed plan at ${planResult.planFilePath}. Focus on ${section}, but inspect the whole plan if needed.`,
          { id: `verify-${section}`, agentId: "explore", schema: reviewSchema }
        )
      )
    );
  • Add a short caveat: if a runtime cannot read planFilePath, pass or fall back to planResult.reportMarkdown.

Quality gate after Phase 5:

bun test src/node/services/agentSkills/builtInWorkflowSkillDescriptions.test.ts

Phase 5.5 — Search for stale wording and usage

Run these searches after code/docs updates and fix any stale references:

rg "Workflow plan agents return plan markdown|proposed plan markdown|agent\(\) returns the proposed plan" src tests
rg "planFilePath|planPath|agentId: \"plan\"" src/node/builtinSkills src/node/services src/common tests

Expected outcomes:

  • No remaining public docs claim Plan workflow agents return a raw markdown string.
  • Internal durable/report references should continue using reportMarkdown / planFilePath.
  • No public workflow examples should use the abandoned plan / planPath aliases.
  • No tests still expect the old schema-rejection wording.

Phase 6 — Full validation

Run focused checks first, then broader static validation. This repo sources .mux/tool_env before bash tool calls, so use run_and_report when batching validation; if it is unavailable, run the raw commands after each step name.

run_and_report workflow-runner-tests bun test src/node/services/workflows/WorkflowRunner.test.ts
run_and_report workflow-task-adapter-tests bun test src/node/services/workflows/WorkflowTaskServiceAdapter.test.ts
run_and_report workflow-schema-tests bun test src/common/orpc/schemas/workflow.test.ts
run_and_report plan-artifact-tests bun test src/node/services/subagentReportArtifacts.test.ts
run_and_report taskservice-workflow-plan-tests bun test src/node/services/taskService.test.ts --test-name-pattern "workflow-owned plan"
run_and_report typecheck make typecheck
run_and_report lint make lint

Raw-command equivalents:

bun test src/node/services/workflows/WorkflowRunner.test.ts
bun test src/node/services/workflows/WorkflowTaskServiceAdapter.test.ts
bun test src/common/orpc/schemas/workflow.test.ts
bun test src/node/services/subagentReportArtifacts.test.ts
bun test src/node/services/taskService.test.ts --test-name-pattern "workflow-owned plan"
make typecheck
make lint

If lint is memory-constrained in this workspace, use the known repo fallback:

MUX_ESLINT_CONCURRENCY=1 make lint

Before claiming completion, also run either:

make static-check

or explicitly report why full static-check was not available and provide the completed subset above.

Dogfooding plan

Dogfooding must prove the new API in a real workflow, not only unit tests.

Dogfood setup

  1. Build or run Mux from a clean dev sandbox.
  2. Create a workspace-contained throwaway workflow script, then delete it before finalizing the branch. Prefer ./workflows/dogfood-plan-result.js because workflow execution expects explicit trusted workspace paths; verify git status is clean of dogfood artifacts afterward. Script contents:
    export const meta = { description: "Dogfood Plan result object" };
    
    const reviewSchema = {
      type: "object",
      required: ["section", "foundPlan", "foundPath"],
      properties: {
        section: { type: "string" },
        foundPlan: { type: "boolean" },
        foundPath: { type: "boolean" },
        notes: { type: "string" },
      },
    };
    
    export default function workflow({ agent, parallel }) {
      const planResult = agent("Write a small plan with sections: Tests, UX, Risks", {
        id: "plan",
        agentId: "plan",
      });
    
      const reviews = parallel(
        ["Tests", "UX", "Risks"].map((section) => () =>
          agent(
            `Read the plan file at ${planResult.planFilePath}. Focus on ${section}. If the path is unreadable, fall back to this plan snapshot:\n\n${planResult.reportMarkdown}`,
            { id: `verify-${section.toLowerCase()}`, agentId: "explore", schema: reviewSchema }
          )
        )
      );
    
      return {
        reportMarkdown:
          `Plan path: ${planResult.planFilePath}\n\n` +
          reviews.map((review) => `- ${review.section}: path=${review.foundPath}, plan=${review.foundPlan}`).join("\n"),
      };
    }
  3. Run the workflow through the same user-facing mechanism expected for workflow users (workflow_run tool if available in-agent; otherwise the app/CLI workflow runner).

Dogfood quality gates

  • Confirm the Plan step proposed a plan and the workflow did not treat the return value as a string.
  • Confirm verifier steps can access planResult.planFilePath in the default local/worktree runtime.
  • Confirm the final workflow report includes the plan path and verifier summaries.
  • Inspect the workflow run UI/card to confirm Plan task metadata still shows normal report content and no UI regressions.

Dogfood evidence

Always capture terminal evidence from the workflow run, including the final report proving planResult.planFilePath and verifier summaries were produced.

If a running UI/dev server is available, also use agent-browser per the browser automation instructions. Before running browser commands, load current CLI guidance with:

agent-browser skills get core
agent-browser skills get dogfood

When UI dogfooding is available, capture and attach:

  1. A screenshot of the workflow run card showing the completed Plan step and verifier steps.
  2. A screenshot of the final workflow report showing Plan path: and verifier summaries.
  3. A short video recording of the workflow run or review interaction so reviewers can verify the dogfood flow.

If visual dogfooding is not possible in the implementation environment, explicitly report the blocker and provide the terminal workflow-run evidence instead; do not claim screenshots/video were captured.

Acceptance criteria

  • agent(prompt, { id, agentId: "plan" }) returns an object with:
    • reportMarkdown: string
    • planFilePath: string
  • Direct workflow agent calls, parallel(), and pipeline() all receive the same Plan object shape.
  • Non-Plan no-schema agents continue returning markdown strings.
  • Schema-backed agents continue returning structuredOutput.
  • Plan agents still reject schema / outputSchema with updated wording.
  • Durable storage and ORPC schemas remain unchanged unless implementation discovers an actual type gap.
  • workflow-authoring docs show the new reportMarkdown / planFilePath object API and verifier fan-out pattern.
  • Focused workflow/task/schema tests pass.
  • Typecheck and lint pass, or any inability to run full validation is explicitly reported with blockers.
  • Dogfooding demonstrates a real Plan -> parallel verifier workflow with terminal evidence; screenshots/video are included when a UI/dev server is available, otherwise the visual-dogfood blocker is reported explicitly.

Risks and mitigations

Risk Mitigation
Existing workflow code expects a Plan string Accepted workflow-visible change; repo search found no active production workflows using Plan agents. Using reportMarkdown / planFilePath aligns with existing task-output conventions and avoids Plan-only aliases.
planFilePath missing in a Plan result Fail fast in __muxPlanAgentResult; successful Plan finalization should always include it.
planFilePath unreadable in isolated runtimes Keep reportMarkdown snapshot in the object; document fallback. Consider follow-up portable snapshot artifact if Docker dogfood requires it.
Parallel/pipeline return mapping diverges from direct agent() Centralize mapping in one helper and use it from all three paths.
Error message drift between runtime and TaskService Update both places and test assertions.
Tests become tautological Test behavior by executing workflow JS and observing returned values, not by asserting docs strings except existing rejection messages.

Out of scope for this change

  • Adding an opt-in returnMode; the user explicitly chose a default Plan-object return while Plan agents are new.
  • Allowing Plan agents to use workflow schema / outputSchema.
  • Renaming durable StructuredTaskOutput / ORPC fields or adding Plan-only aliases such as plan / planPath.
  • Building a new immutable, cross-runtime plan artifact path. This remains a follow-up only if isolated-runtime dogfooding proves the current planFilePath is insufficient.

Generated with mux • Model: openai:gpt-5.5 • Thinking: xhigh • Cost: $44.09

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep them coming!

Reviewed commit: 508a98bf77

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33 ThomasK33 added this pull request to the merge queue Jun 24, 2026
Merged via the queue into main with commit 620c2a6 Jun 24, 2026
40 of 42 checks passed
@ThomasK33 ThomasK33 deleted the plan-agent-t1e1 branch June 24, 2026 15:54
@mux-bot mux-bot Bot mentioned this pull request Jun 24, 2026
LeonidasZhak pushed a commit to LeonidasZhak/mux that referenced this pull request Jun 30, 2026
## Summary

Long-lived auto-cleanup PR maintained by the **Auto-Cleanup Agent**.
Each pass lands at most one extremely low-risk, behavior-preserving
cleanup drawn from recently merged `main` activity, then advances the
checkpoint below.

Auto-cleanup checkpoint: 620c2a6

This PR currently contains six accumulated cleanups against `main`:

1. `src/node/services/taskService.ts` — dedupe queued-message foreground
backgrounding into a private helper.
2. `src/node/services/workflows/workflowScriptResolver.ts` — reuse the
`SKILL_SCRIPT_PATH_PREFIX` constant for canonical-path construction.
3. `src/node/services/workflows/WorkflowTaskServiceAdapter.ts` — extract
the duplicated agent-task creation arg shape into a shared
`WorkflowTaskCreateArgs` type.
4. `src/constants/workspaceTags.ts` (new) — centralize the
workspace-turn task tag keys, removing the cross-layer
`"mux.taskHandleId"` string duplication.
5. `src/node/services/tools/workflowProgress.ts` — extract the
duplicated "latest phase" lookup into a shared `getLatestPhaseEvent`
helper.
6. `src/browser/features/RightSidebar/Workflows/WorkflowBadges.tsx` —
map workflow run-status glyphs through an exhaustive
`Record<WorkflowRunStatus, LucideIcon>` instead of a repeated
`if`-chain.

## This pass

Rebased the branch onto the latest `origin/main` (clean) and considered
the three new commits since the prior checkpoint:

- coder#3624 (`feat: add Workflows right-sidebar tab with live run
streaming`)
- coder#3626 (`tests: add UI primitive stories and fix design-sync previews`)
- coder#3627 (`feat: return workflow plan agent results`)

coder#3624 added `WorkflowBadges.tsx`, whose `WorkflowStatusIcon` selected a
glyph through a four-branch `if`-chain that re-spelled the identical
`className="h-3 w-3 shrink-0"` + `style={{ color }}` props in every
branch and fell back to a default `<Circle>` for unmatched statuses.
Replaced that with a module-level `WORKFLOW_STATUS_ICON:
Record<WorkflowRunStatus, LucideIcon>` map (mirroring the existing
`WORKFLOW_STATUS_META` / `WORKFLOW_TONE_VAR` records in the same
feature) and render the looked-up icon once. Behavior-preserving: the
map reproduces the prior mapping exactly (`pending`/`running` →
`Circle`, `backgrounded`/`interrupted` → `Pause`, `completed` → `Check`,
`failed` → `X`), and using a `Record` makes the mapping exhaustive so a
newly added run status is a compile error here rather than a silent
fall-through. coder#3626 (Storybook/design-sync, test-only) and coder#3627
(already extracts its own `__muxAgentReturnValue` runtime helper)
offered no additional low-risk dedup.

<details>
<summary>Prior pass — workflowProgress getLatestPhaseEvent
helper</summary>

coder#3623 added `src/node/services/tools/workflowProgress.ts`, in which both
`buildWorkflowProgressSummary` and `formatWorkflowProgressNote`
independently re-spelled the same `run.events.findLast((event) =>
event.type === "phase")` expression to find the latest phase event.
Extracted that into a single private `getLatestPhaseEvent(run)` helper
and referenced it from both functions, giving the "latest phase"
definition one source of truth so the summary builder and the note
formatter can't drift. Behavior-preserving: the helper's body and
inferred return type are identical to the inline expression, so both
call sites compute exactly the same value.

</details>

<details>
<summary>Prior pass — checkpoint-only advance for coder#3621 (Claude Design
integration)</summary>

coder#3621 is purely additive and lives entirely under `.design-sync/` (plus
one `.gitignore` entry); it contains zero production (`src/`) code.
`.design-sync/**` is outside both the ESLint scope (`src/**`) and the
TypeScript `include`, so `make static-check` cannot validate the
behavior-preservation of any refactor there. That pass advanced the
checkpoint without a code change.

</details>

<details>
<summary>Prior pass — workspace-turn task tag keys
centralization</summary>

The workspace-turn task tag key `"mux.taskHandleId"` is **written** by
`TaskService.createWorkspaceTurn` (node, coder#3617/coder#3619) and **read** by
`findWorkspaceForTaskTarget` in `TaskToolCall.tsx` (browser, coder#3613) —
the same magic string duplicated across the node/browser boundary.
Extracted the three workspace-turn task tag keys (`handle`,
`ownerWorkspaceId`, `turn`) into a new `@/constants/workspaceTags`
module and referenced `WORKSPACE_TURN_TASK_TAGS` from both the node
write site and the browser read site. Behavior-preserving: the extracted
values are byte-identical to the previous literals, so the persisted tag
keys and all lookups are unchanged.

</details>

<details>
<summary>Prior pass — WorkflowTaskServiceAdapter WorkflowTaskCreateArgs
type</summary>

`WorkflowTaskServiceLike` (in `WorkflowTaskServiceAdapter.ts`) declared
the agent-task creation argument object **twice** — once inline in
`create(args: { ... })` and again inside `createMany?(args: Array<{ ...
}>)` — as byte-identical 10-field shapes. Extracted a named
`WorkflowTaskCreateArgs` interface and referenced it from both `create`
and `createMany`. Behavior-preserving: structurally identical interface,
signatures unchanged.

</details>

<details>
<summary>Prior pass — workflowScriptResolver SKILL_SCRIPT_PATH_PREFIX
reuse</summary>

In `workflowScriptResolver.ts`, the `skill://` prefix is named by the
module-level constant `SKILL_SCRIPT_PATH_PREFIX`, but the two
canonical-path builders re-spelled the literal `` `skill://${...}` ``
instead of reusing the constant. Both builders now interpolate
`${SKILL_SCRIPT_PATH_PREFIX}`. Behavior-preserving:
`SKILL_SCRIPT_PATH_PREFIX` is exactly `"skill://"`, so the interpolation
is byte-identical.

</details>

<details>
<summary>Prior pass — taskService queued-message dedup</summary>

Considered coder#3605 (`fix: background foreground waits for prequeued
messages`), which open-coded the same queued-message guard at two
`TaskService` wait-registration sites. Both sites now delegate to a
single private helper
`backgroundForegroundWaitIfQueued(shouldBackgroundOnQueuedMessage,
requestingWorkspaceId)`. Behavior-preserving: the helper folds in the
`requestingWorkspaceId` truthiness guard and the pushed call is
unchanged.

</details>

## Validation

- `make static-check` is green for the touched code (ESLint, `tsconfig`
+ `tsconfig.main.json`, Prettier). (`fmt-shell-check` is the only
non-passing step, solely because the `shfmt` binary is absent in this
environment; no shell files are touched.)
- Targeted tests pass: `bun test
src/browser/features/RightSidebar/Workflows/workflowDisplay.test.ts
src/browser/features/RightSidebar/Workflows/projectWorkflowRun.test.ts`
(32 tests) cover the same Workflows feature module the change lives in.

## Risks

Minimal. All accumulated changes are pure local extractions / constant
reuse; no logic, types, or call semantics change.

---

_Generated with `mux` • Model: `anthropic:claude-opus-4-8` • Thinking:
`xhigh`_

<!-- mux-attribution: model=anthropic:claude-opus-4-8 thinking=xhigh -->

---------

Co-authored-by: mux-bot[bot] <264182336+mux-bot[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant