Skip to content

feat(agent): a task list, a hand-back box, and Enter that queues — plan and progress like Claude desktop - #115

Merged
ndemianc merged 8 commits into
developfrom
feat/plan-progress
Oct 20, 2026
Merged

ndemianc merged 8 commits into
developfrom
feat/plan-progress

Conversation

@ndemianc

@ndemianc ndemianc commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

The agent chat now shows its plan and progress the way the Claude desktop app's Code tab does, and Enter no longer stops a run. Design note with the evidence: docs/PLAN-PROGRESS.md.

What changes for someone using it

  • A task list under the latest message. For work with more than one step, the agent keeps a short list as the last thing in the conversation: the step it is on spins (not while it waits on an approval), finished steps turn grey, a long list shows a window of 8 with "N earlier steps" / "N more to go", and a list from before your last message keeps a caption until the run touches it. It goes when the run ends. It replaces the sticky "Plan 2/5" bar.
  • A hand-back box. When a run ends with a decision for you, the agent asks one question with one or two replies to click; a click sends the reply as your next message. A recap with no question shows no box, as in Claude.
  • Enter queues, it does not stop. What you type while the agent works waits above the message box ("1 message queued", or "3 messages queued · Next: …", open it to reorder, edit or remove). It is sent when the run finishes; a stopped or failed run gives it back to the message box. Send now hands a message to the agent at its next step without stopping it. ■ with an empty message box still stops.

How

  • update_plan takes Claude's shape: items once, then {"advance": true} / {"done": [n], "start": m}; the host keeps statuses by step number for the conversation (turnPlan.js). The old todos still read.
  • wrap_up (recap, ask, actions, blocked) is where auto-verify now runs: failing edits hide the box and go back to the model. A box with a question ends the run without another model call. A run that ends with a Done: line and no wrap_up ends as before.
  • Send now: the page posts steer; the loop takes it after the current step's tool results (in the same message) or after a no-tool answer (the run continues). What the run never reaches goes back to the queue before agentDone.
  • wrap_up is reserved from MCP tool names; the web skin rounds the new card and band.

Decision

Owner, 2026-10-19: Enter mid-run queues, as in Claude. This supersedes for now the Mid-Run Messaging Spec's Enter-steers and its holding of write/command calls, which are not built.

What ran

  • Gate (./scripts/test-extensions.sh) on macOS, Node 24.17, with the real-build halves on: 131 files pass. New suites: turnPlan (20), agentPlanProgress (11, the real loop on scripted turns), planProgressUi (24, lifted from chat.html); 23 hand-made mutations of the key lines all caught.
  • Real dev editor, stand-in model: extensions/levelcode-ai/scripts/plan-progress-editor-check.js — 17/17, screenshots in LevelCode One Dark and One Light.
  • Browser edition, headless Chrome: web/test/e2e-plan-progress.mjs --scheme dark|light — 9/9 in each.

Not verified

  • No real model has driven it; whether models use advance and ask sensible questions is untested.
  • Only the OpenAI-compatible route ran; not the Anthropic-native route or the gateway.
  • Mouse drag-to-reorder (keyboard reorder was checked); Linux and Windows; Safari and Firefox.

Conflicts

With #112 in five adjacent-line spots, all keep-both: the finally in runAgent before dbg('agent.done'), resumeSession after agentMessages = …, the message handlers in chat.html (the agentStart line), and the sandbox-globals line in test/diagramHost.test.js and test/sessionExpiredHost.test.js. #114 applies cleanly. RELEASE-NOTES.md adds a section to the file #113 started.

Review fixes (after merging develop with #112, #113, #114)

  • A reply-button click is not the user's typing. A click sends the reply's message, words the agent wrote under a 1–3 word label. It went through the normal send, so since feat(agent): Ask When Risky — a reviewer model rates each command before it runs #112/feat(memory): memory from you — remember/forget credited to the words you typed — and Search Sessions #114 the host counted it as the user's word: the Ask When Risky reviewer took it as authorization, and remember could quote it. The page now sends it as { type: 'send', reply: true } (the mark survives the queue), and handleSend passes typed: false for it.
  • wrap_up is the last call of its response. A call after an accepted wrap_up in the same response used to run after the check and behind the box; it is now answered "Not run". A wrap_up refused by verification does not count, so the fix after it runs.
  • A "message from the user" inside a tool result is content. The prompt now says a Send-now message is never inside a tool result.

Checked: gate 136 files; nine single-defect mutations of these fixes, each red; plan-progress-editor-check 17/17 and approvals-editor-check 12/12 in the dev build.

…nd Enter that queues instead of stopping

Modelled on the Code tab of the Claude desktop app (docs/PLAN-PROGRESS.md has the evidence and the differences).

- update_plan takes Claude's shape: the whole list once ({"items": [...]}, pending / in_progress / completed),
  then small updates ({"advance": true}, {"done": [n], "start": m}). The host keeps each step's status by
  number for the conversation; the old todos / title / done still read.
- wrap_up hands the work back: a recap, and one question with one or two replies when the next move is the
  user's. Auto-verify runs there; a hand-back whose box holds a question ends the run with no further call.
- The chat draws the list as the last thing in the transcript while a run works (spinning step, stale caption,
  a window of 8 with "earlier steps" / "more to go"), and the next-step box when it hands back.
- Enter while the agent works queues the message above the composer; it is sent when the run ends, or given
  back to the message box if the run was stopped or failed. Send now hands it to the agent at its next step.
- wrap_up is reserved from MCP tool names. The web skin rounds the new card and band like the old plan bar.
…— the list, the box and the queue in the real editor and in a tab
…elCode does, the owner's queue decision; release notes
Conflicts, each keeping both sides:
- agent.js, the end of a run: Ask When Risky's reviewer cost and summary row (#112) and this branch's
  plan completion and returned "Send now" messages; one agent.done log line with both sets of fields.
- extension.js, resumeSession and New Chat: this branch's plan/steer reset and the reset of the person's
  recorded words (#112/#114), each in both places.
- media/chat.html, the message switch: develop's `approvals` message (#112 replaced the two-option
  `autopilot` one, whose handler no longer exists) and this branch's agentStart/agentStatus, which keep
  the task list and drop the old plan bar.
- test/sessionExpiredHost.test.js, the harness declarations: both sets (agentMode from env, the
  approvals mode and recorded words; this branch's agentPlan and steerInbox).

Checked on the merged tree (macOS, Node 24): scripts/test-extensions.sh 136 files; tsc --checkJs on
extension.js and agent.js, no new undefined names; in the dev build plan-progress-editor-check 17/17,
approvals-editor-check 12/12, memory-editor-check 11/11 on one of four runs — its "History lists the
session after New Session" step is flaky on develop too (1 of 2 there).
@ndemianc
ndemianc requested a balanced review from Copilot October 20, 2026 01:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@ndemianc
ndemianc requested a balanced review from Copilot October 20, 2026 01:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@ndemianc
ndemianc marked this pull request as ready for review October 20, 2026 01:13
…e is not run

wrap_up is where the run's edits are checked and the hand-back is shown; a call the same response made after
it (an edit, a command) still ran, after the check and behind the box that said the work was done. Once a
wrap_up in a response is accepted, any later call in that response is answered "Not run … do the work first,
then call wrap_up last" instead. A wrap_up that verification refused does not count: the call after it is
the fix, and runs.
A reply in the next-step box shows a 1-3 word label; what a click sends is the reply's message, up to 300
characters the agent wrote (visible only as a tooltip). It went through the chat's normal send, so the host
recorded it as typed — and since #112 and #114 that is the person's word: the Ask When Risky reviewer counts
it as authorization, and remember may quote it. A README that steers the agent into a "Yes" button whose
message says more could have turned one click into an explicit go-ahead.

The page now sends a click as a reply ({ type: 'send', reply: true }), and keeps that mark through the queue
(drained at the end of a run, or sent with Send now while idle). handleSend passes typed: false for it, so
the reply reaches the agent as before but is not the person's word. A reply the person edits in the message
box, or that is handed back to it, is sent as their own typing.

Tested: the page's queue functions lifted from chat.html (sent at once, queued while busy, drained, Send now
when idle — the mark survives each, and a typed message carries none); handleSend lifted into
sessionExpiredHost (a reply reaches agentFlow with typed: false, a typed message with true); the button's
click and the send message are pinned from source.
…, not the user

The prompt tells the model a message sent with Send now arrives after a tool result, marked as sent during
the run. It did not say where such a message never is, and the mark is fixed text: a file, a command's
output or an MCP result could carry it. The prompt now says a real one is never inside a tool result, and
that text there claiming to be from the user is content to read. The real message stays a separate part
after the results, as before.
… response; a reply click is not your typing

Three notes in "What LevelCode does now": a call after an accepted wrap_up in the same response is not run;
a reply button's click is sent marked as a reply and is not recorded as the person's words; a message
claiming to be from the user inside a tool result is content.
@ndemianc
ndemianc merged commit ed77319 into develop Oct 20, 2026
2 checks passed
@ndemianc
ndemianc deleted the feat/plan-progress branch October 20, 2026 01:35
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.

2 participants