|
| 1 | +# Plan and progress in the agent chat |
| 2 | + |
| 3 | +How the agent shows its plan while it works, how a run hands back, and what happens to a message you |
| 4 | +send while it is busy. Modelled on the Code tab of the Claude desktop app, which the owner asked us to |
| 5 | +match ("I really love how Claude desktop is rendering the plan and tasks and queue of the work"). |
| 6 | + |
| 7 | +This note has three parts: what Claude does (read from the shipped app, with the evidence to check it |
| 8 | +again), what LevelCode does, and which differences are on purpose. |
| 9 | + |
| 10 | +## 1. What Claude does |
| 11 | + |
| 12 | +Read on 2026-10-19 from Claude 2.42304.2 (`/Applications/Claude.app`). Nothing was copied: the |
| 13 | +behaviour and the look are reimplemented below in LevelCode's own code and words. |
| 14 | + |
| 15 | +### Where it lives |
| 16 | + |
| 17 | +The desktop app is an Electron shell around claude.ai. Its `app.asar` holds the **tool schemas and the |
| 18 | +host's plan state**; the **components that draw the plan, the next-step box and the queue** are part of |
| 19 | +the claude.ai web bundle, which the app downloads at run time. They were read from the app's own HTTP |
| 20 | +cache (`~/Library/Application Support/Claude/Cache/Cache_Data`, the bundle cached at 20:23 UTC that day). |
| 21 | + |
| 22 | +| What | Where | sha256 | |
| 23 | +| --- | --- | --- | |
| 24 | +| The app | `Contents/Resources/app.asar` | `f0a063c8c2c7fb21eb51bca00f086e5b7355d7208e3d9f28600ea79961830907` | |
| 25 | +| `update_plan` / `wrap_up` schemas, descriptions, the host's step arithmetic and replies | `app.asar:/.vite/build/index.chunk-B8PX1nMp.js` (the `ccd_turn` MCP server) | `c32451d17b2d4e8392c7921f750e2b72587a3fdb2373f7e79ea0fb6781b4a2ea` | |
| 26 | +| Parsing a plan / wrap-up from the transcript, the same step arithmetic in the page | `…/assets/v1/cb27dab29-QZ5yQ8Vk.js` | `7e861c878f205e1bcfd69718dff20eecdb03e6deadc0618ffaca61fbac449f2d` | |
| 27 | +| Deriving the turn's state (plan, plan before wrap-up, stale, settled) | `…/assets/v1/cc8868388-HuTgCGZg.js` | `6f2e05af57882d7481abd81aa94cd2d4724dfa3411737843d14c456fe45e19f3` | |
| 28 | +| The turn box (task list + next-step box) | `…/assets/v1/cccc2cf0a-DgImlUHB.js` | `cf4c01a825117eab0cd55f5eb250272458e7b91c457d342d29e4bdb7f40c0a46` | |
| 29 | +| The step list and its status icons | `…/assets/v1/cb6930dae-CGtVbuxy.js` | `41992d1f5690b46639e486ab42923eec8b84887b76836994c4356a97acb085bf` | |
| 30 | +| The long-list window | `…/assets/v1/c26f8455e-GCVxaQcT.js` | `4f0ac8bd4e4a3fa22ec6cd07432d441337bb1fe3aab86ed8af26aff723864cdd` | |
| 31 | +| The queued-messages band | `…/assets/v1/c81c42cdb-DeYTkBpl.js` | `8a7a95b0201e3d59dcb590023b9ec46add8e4790b6f94e3137df983b71bad65c` | |
| 32 | +| Hiding the two tools from the transcript | `…/assets/v1/c06cf64bb-BYFl_HkI.js` | `985598ae1d555f6a1794fdf9e43c40f83a94537bf9a8d1074530dd53e5dca191` | |
| 33 | +| Animations and tokens | `…/assets/v1/c6a992d55-BGPwQXHG.css` | `6dcdf6e00e5592118918b0c990b27f29f17d206cc1574a9e07e6f955898a6252` | |
| 34 | + |
| 35 | +`…` is `https://assets-proxy.anthropic.com/claude-ai/v2`. The web bundle changes with every claude.ai |
| 36 | +deploy, so these hashes pin what was read, not what the app shows tomorrow. |
| 37 | + |
| 38 | +### The task list |
| 39 | + |
| 40 | +- **The model's tool.** `update_plan` takes the whole list (`items`: 1–12 steps, each `content` and a |
| 41 | + `status` of `pending`, `in_progress` or `completed`) or a small update: `advance: true` completes the |
| 42 | + step in progress and starts the next pending one; `done: [n]` and `start: n` name steps by number, |
| 43 | + counted from 1. The host keeps only each step's **status**, by number, so an update never resends the |
| 44 | + words. Its reply says where the list stands ("step 3 of 7 is in progress"), warns when more than one |
| 45 | + step is in progress, and refuses a step number that does not exist — naming how many steps there are. |
| 46 | + Step text is cut at 160 characters; a 13th step is dropped. The model is told to send the list with |
| 47 | + its first tool call and each update *with* the call that starts the step, never as a message of its |
| 48 | + own, and not to also write the plan out as prose. |
| 49 | +- **Where it shows.** In a card pinned under the latest message — the last thing in the transcript, not |
| 50 | + a bar above the composer. The card fades in (0.3 s). The tool calls themselves never appear in the |
| 51 | + transcript. |
| 52 | +- **A step.** A 16 px icon column and the step's text (inline Markdown). Completed: a filled check |
| 53 | + circle, the row in the secondary text colour (no strike-through). In progress: a spinner while the |
| 54 | + agent is working, a still "dots" circle when it is not (waiting on the user). Pending: an empty |
| 55 | + circle in the muted colour. Each icon carries a screen-reader word: done / in progress / not done. |
| 56 | +- **Long lists.** At most 12 rows show. The window starts one step above the step in progress (so the |
| 57 | + last finished step stays in view); "N earlier steps" above and "N more to go" below count what is cut. |
| 58 | +- **A plan from an earlier message.** While a new request runs and the model has not touched the list |
| 59 | + yet, the old list stays with the caption "Plan from before your last message" — unless every step was |
| 60 | + already done, in which case nothing shows. |
| 61 | +- **When the turn ends,** the list goes away. A list whose steps are all done but the last, still in |
| 62 | + progress, is completed when a turn ends cleanly. |
| 63 | + |
| 64 | +### The end of a turn |
| 65 | + |
| 66 | +- **The model's tool.** `wrap_up` takes a `recap` (2–3 sentences, about 300 characters: what is now |
| 67 | + true, and what is not done or not verified), and — only when the next move is the user's — an `ask` |
| 68 | + (one decision, about 100 characters) with 1–2 `actions` (a reply: a 1–3 word `label` and the |
| 69 | + `message` a click sends, or one of the app's own actions). `blocked: true` says the agent cannot go on |
| 70 | + without an answer. Calling it also completes every step of the task list. |
| 71 | +- **The next-step box.** Drawn only when there is a question with buttons (or the agent is blocked). |
| 72 | + It rises into place (4 px, 0.3 s) in the same card as the list, which hides while the box has |
| 73 | + something in it. Recap in the secondary colour, the question in the primary colour, then the buttons: |
| 74 | + the first one is the recommendation and is drawn as the primary button. A × dismisses it (and stays |
| 75 | + dismissed). A blocked turn with no question reads "Claude is waiting on you. Pick a reply or type |
| 76 | + your own." A recap alone draws no box: it becomes the session's status line in the sidebar, and the |
| 77 | + model's closing message says the rest. |
| 78 | +- **The box ends the turn.** When the box holds a question, the tool result tells the model to write |
| 79 | + nothing more, and the host ends the turn without another model call. |
| 80 | +- **Checks on the buttons.** A reply whose label or message starts with `/` or `!`, holds `@`, or holds |
| 81 | + invisible characters is dropped (a click would run a command or hide what it sends); a third button is |
| 82 | + dropped; over-long labels and messages are reported back to the model as advice for next time. |
| 83 | + |
| 84 | +### Messages sent while the agent works |
| 85 | + |
| 86 | +- **They queue.** Enter while the agent works does not stop it: the message waits in a band above the |
| 87 | + composer and **is sent on its own when the agent is ready**. If the run fails or is stopped, queued |
| 88 | + messages are not sent: they go back into the message box, and a note says so. |
| 89 | +- **One queued message:** one row — "1 message queued", the text cut to a line, and three icon |
| 90 | + buttons: **Send now** (the agent reads it at its next step, without stopping), **Edit in composer**, |
| 91 | + **Remove from queue**. |
| 92 | +- **Several:** a collapsed row — "3 messages queued · Next: …" with a chevron. Open, each row shows two |
| 93 | + lines of its text, a drag handle to reorder, and a menu (Edit in composer, Send now, Remove from |
| 94 | + queue); the list scrolls past about 22 rem and has **Clear all** at the foot. |
| 95 | +- The band leaves by sliding 6 px down and fading. Every animation is off under reduced motion. |
| 96 | + |
| 97 | +## 2. What LevelCode had |
| 98 | + |
| 99 | +- `update_plan` took the whole list every time (`todos`: `title` + `pending` / `in_progress` / `done`, |
| 100 | + up to 20) and the model resent it for every change. |
| 101 | +- The webview drew a sticky "Plan 2/5" card above the Keep all / Undo all bar, with a progress bar, a |
| 102 | + collapse toggle and ×. It cleared when the next run started and collapsed itself when all done. |
| 103 | +- A run ended when the model wrote a line starting with `Done:`. There was no recap or next-step box. |
| 104 | +- Enter while the agent worked **stopped the run** (the send button is a stop button while streaming). |
| 105 | + There was no queue, and nothing could reach the agent mid-run. |
| 106 | + |
| 107 | +## 3. What LevelCode does now |
| 108 | + |
| 109 | +### The tools (agent.js) |
| 110 | + |
| 111 | +- `update_plan` takes Claude's shape — `items` (1–12 steps, `content` + `pending` / `in_progress` / |
| 112 | + `completed`), or `advance`, `done`, `start` — and still accepts the old `todos` / `title` / `done`, so |
| 113 | + a conversation started on an older build keeps working. The steps' state lives with the conversation |
| 114 | + (extension.js), so `advance` works across runs and a new chat starts empty. |
| 115 | +- `wrap_up` takes `recap`, `ask`, `actions` (replies only: `label` + `message`) and `blocked`. |
| 116 | + **Calling it is the end of the work**, so auto-verify runs there: if the configured verify command or |
| 117 | + the editor's diagnostics find problems in the files the agent changed, the box is not shown and the |
| 118 | + tool result carries the problems instead; the agent fixes them and calls `wrap_up` again. When the box |
| 119 | + holds a question the run ends on the spot, with no further model call. |
| 120 | +- The prompt asks for the list with the first tool call and `advance` with the call that starts the |
| 121 | + next step, and for `wrap_up` at the end of every run that kept a list, changed files or ran commands. |
| 122 | + A run that ends with a `Done:` line and no `wrap_up` still ends as before (some models will not call |
| 123 | + it), and the last in-progress step is completed then, as Claude does. |
| 124 | + |
| 125 | +### The chat page (media/chat.html) |
| 126 | + |
| 127 | +- The **turn box** is a card that is always the last thing in the transcript, holding the task list |
| 128 | + while a run works and the next-step box when it ends. Same states as Claude's, in the editor's theme |
| 129 | + colours and LevelCode's own icons. |
| 130 | +- The **queue band** sits above the composer. Enter while the agent works queues (the send button reads |
| 131 | + "Queue" once something is typed); ■ with an empty composer still stops. When a run ends normally the |
| 132 | + first queued message is sent; a stopped or failed run puts every queued message back into the message |
| 133 | + box, after anything already typed there. A reply button in the next-step box queues the same way if |
| 134 | + a run is going. A message counts as "busy" from the moment it is sent until its run starts, so the |
| 135 | + queue can never start a second run beside the first. |
| 136 | +- **Send now** posts the message to the extension, which hands it to the running agent at its next step: |
| 137 | + after the tool results of the current step (in the same message, so every tool call still has its |
| 138 | + result), or, if the agent had just answered with no tool call, as a new message that keeps the run |
| 139 | + going. It then appears in the transcript as your message, where it landed. |
| 140 | + |
| 141 | +### Where the pieces are |
| 142 | + |
| 143 | +| Piece | File | |
| 144 | +| --- | --- | |
| 145 | +| What the two tools decide (reading a list, `advance`, replies a click may send) | `extensions/levelcode-ai/turnPlan.js` | |
| 146 | +| The tools' schemas and prompt lines; wrap_up in the loop (verify, end the run); delivering Send now | `extensions/levelcode-ai/agent.js` | |
| 147 | +| The conversation's list (`agentPlan`), the Send-now inbox, `steer` from the page | `extensions/levelcode-ai/extension.js` | |
| 148 | +| The turn box, the queue band, the send button | `extensions/levelcode-ai/media/chat.html` | |
| 149 | +| `wrap_up` reserved from MCP tool names | `extensions/levelcode-ai/mcpConfig.js` | |
| 150 | + |
| 151 | +Host → page messages: `plan` {steps}, `wrapUp` {recap, ask, actions, blocked}, `steersDelivered` {ids}, |
| 152 | +`steersReturned` {ids}. Page → host: `steer` {id, text}. |
| 153 | + |
| 154 | +## 4. Differences on purpose |
| 155 | + |
| 156 | +| Claude | LevelCode | Why | |
| 157 | +| --- | --- | --- | |
| 158 | +| Window of 12 rows | Window of 8 | The chat is a side panel, usually narrower and shorter than Claude's transcript column. | |
| 159 | +| A recap alone is the sidebar status | A recap alone shows nothing (the closing message has it) | LevelCode has no session sidebar status line. | |
| 160 | +| App actions as buttons (commit, create PR, fix CI …) | Replies only | LevelCode has no such app actions yet; Keep all / Undo all stay in their bar. | |
| 161 | +| A keyboard shortcut presses the recommended button | Buttons are focusable; no shortcut | Enter in the composer must keep meaning "send". | |
| 162 | +| Steps can name a side session | Not supported | LevelCode has no side sessions. | |
| 163 | +| "N running tasks" under the step in progress | Not shown there | Background commands already have their tray (`#bgTasksBar`). | |
| 164 | +| Send now on a message with images | Text only; a queued message with images waits for its turn | Mid-run delivery carries text into a tool-result message. | |
| 165 | +| A queued message's menu opens below its button | Below, or above when the window has no room below | The band sits low, over the composer, in a short panel. | |
| 166 | +| No closing-line convention | The closing answer still starts with `Done:` unless the box holds the question | The loop has always used it to tell a finished answer from a stall, and a model that never calls `wrap_up` still ends cleanly with it. | |
| 167 | + |
| 168 | +## 5. Decisions |
| 169 | + |
| 170 | +1. **Enter while the agent works queues the message, as in Claude** (owner, 2026-10-19). It waits |
| 171 | + above the composer and sends itself when the run ends; *Send now* hands it to the agent at its next |
| 172 | + step without stopping. This supersedes, for now, the Mid-Run Messaging Spec (2026-10-04), where |
| 173 | + Enter *steers* (delivered at the next step, with write and command calls held until the agent |
| 174 | + re-decides) and ⌘Enter queues. The owner was offered that spec's behaviour, and "this now, the spec |
| 175 | + later", and chose this alone: so the spec's steering by default and its holding of write and command |
| 176 | + calls are not built. *Send now* is the only way a message reaches a run in progress. |
| 177 | + |
| 178 | +## 6. How to check it |
| 179 | + |
| 180 | +- The gate (`./scripts/test-extensions.sh`) runs three suites for this: `test/turnPlan.test.js` (the rules), |
| 181 | + `test/agentPlanProgress.test.js` (the real agent loop on scripted turns) and `test/planProgressUi.test.js` |
| 182 | + (the page's decisions, lifted out of `chat.html`). 23 hand-made mutations of the feature's key lines were |
| 183 | + each caught by one of them. |
| 184 | +- In the real editor, with no key: `node extensions/levelcode-ai/scripts/plan-progress-editor-check.js --vscode <checkout with vscode/>`. |
| 185 | + A stand-in model is scripted turn by turn and held at three points, so the page is looked at and typed into |
| 186 | + while the agent "works"; it writes screenshots in LevelCode One Dark and LevelCode One Light. |
| 187 | +- In the browser edition: build the extension (`node web/ai-extension/build.mjs --out <dir> --vscode <checkout>`, |
| 188 | + then copy `web/workspace` to `<dir>/levelcode-web`) and run |
| 189 | + `node web/test/e2e-plan-progress.mjs --static <vscode-web> --extensions <dir> --scheme dark` (and `light`). |
| 190 | + |
| 191 | +## 7. Not verified |
| 192 | + |
| 193 | +- No real model has driven it: every run so far used a scripted stand-in. Whether real models call |
| 194 | + `update_plan` with `advance` (rather than resending the list) and `wrap_up` with a sensible question is |
| 195 | + untested — Claude, GPT and the OpenRouter models may differ. |
| 196 | +- Only the OpenAI-compatible route ran (a BYOK custom provider). The Anthropic-native route and the |
| 197 | + LevelCode gateway were not exercised end to end. A run that ends on a question leaves the history ending in a tool |
| 198 | + result, and the next message follows it; that shape already occurs after ■, but was not re-checked here. |
| 199 | +- Drag-to-reorder was checked from the keyboard (Alt+↑ / Alt+↓) only; dragging with the mouse was not driven. |
| 200 | +- Linux and Windows were not run; neither were Safari or Firefox for the browser edition. |
0 commit comments