fix(chat): keep Author and Plan and Execute runs alive when leaving the page - #39
Merged
Merged
Conversation
…he page
Starting a run locked the whole workflow page. Back, the History toggle
and New chat were disabled until it finished, so a run could not be sent
to the background even though the backend already runs it detached and
serves /status from an in-memory cache.
The guard was load bearing rather than gratuitous: nothing on the client
remembered a run once the component unmounted, so removing the
:disabled alone would have orphaned the run in the UI while the server
kept working.
Three pieces make the guards removable:
- persist the run_id on its assistant message, written as soon as
/execute answers rather than on the first poll, so clicking Back
within the first second still keeps the handle
- re-attach a poller on mount to the newest message still marked
RUNNING, and read its status once up front so the view paints the
real state instead of stale content
- stop the poller in onBeforeUnmount, which also fixes the 1s interval
leaking whenever the component unmounted by any other route
hydrate() now only fails a RUNNING message it cannot resume: an older
one, or one that never captured a run_id. Those still point at the
History tab, as does a re-attach that 404s because the server restarted
or the run aged out of the live cache.
The composer stays gated while a run is in flight. This view polls one
run at a time, and a second prompt would silently orphan the first, so
that guard is about concurrency rather than about trapping the user.
Its placeholder now says the page can be left.
Two supporting fixes in the poller, both reachable now that New chat
works mid-run: drop status snapshots for a run the composable has moved
on from, which would otherwise strand the view on a RUNNING status
nothing polls any more, and ride out transient status failures instead
of failing a healthy run on a single blip.
Refs mitre#30
An adversarial review of b1f5f4e found four client-side races, three of them reachable only because that commit unlocked navigation. start() checked neither supersession nor detachment after its POST. Back mid-POST leaked a poller into an unmounted view, and New chat mid-POST adopted the abandoned run's ids into the transcript that replaced it. The interval callback cleared whichever timer was currently installed rather than its own, so a superseded run whose status GET settled late killed the next run's poller, stranding it on RUNNING with the composer disabled and no in-view recovery. The run_id reached localStorage only through watchers Vue stops on unmount, so clicking Back during the POST stored a live run with runId null and the next mount labelled a working run as failed. It is now written imperatively at the point /execute answers. An expired session arrives as a 200 carrying the login page rather than a snapshot, which was parsed into status "unknown" and persisted. That value is recognised by neither the resume path nor the hydrate sweep, so the bubble could be neither re-attached nor failed. gui/composables already documents this trap; reuse its SESSION_EXPIRED message. Also stop reporting a lost poll as a failed run: three consecutive status errors now say contact was lost and point at the History tab. Refs mitre#30
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Starting a run in Author or Plan and Execute disabled Back, the History toggle and
New chat until it finished, so a run could not be sent to the background.
The backend already ran detached and served
/plugin/mcp/statusfrom an in-memorycache, but the client forgot the run once
ChatWorkflowunmounted, so removing the:disabledalone would have orphaned it. This makes the run recoverable first:run_idon its assistant message, written where/executeanswersrather than through a watcher that unmount stops
onBeforeUnmount, fixing the leaked 1s intervalhydrate()now fails only a RUNNING message it cannot resume, pointing it at theHistory tab. A second commit closes three races that unlocking navigation made
reachable: a submit superseded mid-POST adopting its ids into the transcript that
replaced it, a stale poll clearing the next run's timer, and the login page CALDERA
returns as a 200 on an expired session being parsed as a run snapshot.
Not in scope: the composer stays disabled during a run, since this view polls one
run at a time and a second prompt would orphan the first. Also out of scope are the
synchronous MLflow calls noted as secondary in #30.
Refs #30
Type of change
How Has This Been Tested?
Manually against a running CALDERA on both workflows: submit, click Back while the
header shows Working, confirm the run keeps progressing in the server log, re-enter
and confirm the bubble picks up mid-stage. Also hard-refreshed mid-run, restarted
CALDERA mid-run, and used New chat mid-run.
All nine chat SFCs compile cleanly under the same
@vue/compiler-sfcversion Magmabundles them with. The two composables were exercised by 80 behavioural checks
against the real Vue runtime with a stubbed
$api, stubbed timers and a fakelocalStorage, including one regression check per race above; removing any guardfails them. Not committed, as the plugin has no JS test tooling.
Checklist: