Skip to content

feat: render plan approval as markdown and capture plans as Todos under Session Workflow - #13445

Draft
4ekuct25 wants to merge 2 commits into
QwenLM:mainfrom
4ekuct25:feat/plan-approval-markdown-todo
Draft

4ekuct25 wants to merge 2 commits into
QwenLM:mainfrom
4ekuct25:feat/plan-approval-markdown-todo

Conversation

@4ekuct25

@4ekuct25 4ekuct25 commented Oct 5, 2026

Copy link
Copy Markdown

What

Both parts of #13340 in one draft PR, per the plan discussed with @doudouOUC in that thread.

1. Web Shell: render the ExitPlanMode approval as markdown

packages/web-shell/client/components/messages/ToolApproval.tsx rendered the plan as a raw <pre>{contentText}</pre>, so the markdown the model writes (the exit_plan_mode schema itself declares "Supports markdown") showed up as literal ## / # lines in a fixed-height box.

The isExitPlanApproval branch now renders the plan with the existing Markdown primitive (react-markdown + remark-gfm, with the isSafeHref/isSafeImageSrc sanitisation that matters for model-generated content). The .planContent height clamp is kept, and the raw plan text remains available as the element's title plus the rendered plain text, so the alertdialog's aria-describedby keeps a plain-text accessible equivalent. Non-plan content paths still use <pre>.

2. Core: make the plan-mode reminder ask for a Todo list when Session Workflow is on

getPlanModeSystemReminder() never mentioned todo_write, so with experimental.sessionWorkflow enabled the model almost always wrote a text plan and the structured DAG approval path (planTodos.length > 0) was effectively unreachable (documented in #8393).

  • getPlanModeSystemReminder now takes a sessionWorkflow parameter; when it is true (and the interactive exit-plan flow applies, i.e. not plan-only mode) the "When to Converge" section instructs the model to capture the plan as a structured Todo list via todo_write (stable ids, blockedBy for real dependencies) before calling exit_plan_mode.
  • All call sites pass the gate through from config: client.ts (per-turn reminder), enterPlanMode.ts (tool llmContent), the ACP session (Session.ts) and nonInteractiveHelpers.ts — all via config.isSessionWorkflowTodoContextActive?.() === true, the same source todo_write already uses for its sessionWorkflow stamp.
  • plan-mode-entry-policy.ts now also precomputes the session-workflow variant so the tool-response budget protection keeps matching enter_plan_mode output.

Plan-only mode (SDK/subagent contexts, which never run the interactive exit-plan approval) is unchanged.

Tests

  • ToolApproval.test.tsx: new test — the plan renders as markdown (real <h2>/<li>, no raw <pre>, plain-text equivalent preserved for a11y).
  • prompts.test.ts: new tests — the session-workflow reminder contains the Todo instruction in the right order; the gate-off and plan-only variants stay text-only; the new variant is exactly the base reminder plus one inserted sentence.
  • client.test.ts: updated the call assertion for the new argument.

Local run (vitest, node v26.5.0):

  • packages/web-shell: ToolApproval.test.tsx — 81 passed
  • packages/core: prompts.test.ts — 205 passed; client.test.ts + coreToolScheduler.test.ts — 956 passed; plan-mode-entry-policy.test.ts + enterPlanMode.test.ts + tool-response-finalizer.test.ts — 59 passed
  • eslint + prettier clean on all changed files; tsc --noEmit clean in web-shell and core

Notes

This branch has not been deployed

No deployments
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