Steering console: send prompts to sessions from the UI + scroll-stable follow output #25

Merged
ClaudiuSchuster merged 1 commit from oss-oo/observatory-steer-console-teer20260924 into main 2026-09-24 17:10:55 +00:00
Owner

The observatory's first WRITE-capable action: a prompt dialog in the response modal AND the full transcript drawer that runs a follow-up turn in the session through the installed zcode-dispatch — the observatory still never writes the ZCode DB. Plus the owner-reported scroll fix: the response modal drifted downwards on every refresh cycle; both surfaces are now strictly position-stable with a proper follow-output toggle.

Backend

  • POST /api/session/<id>/steer with {text}: auto-detects cwd and model exactly like the dispatcher's notify_parent (session.directory; last json_extract(data,'$.modelSelection.modelId') by sequence — never guessed), writes the prompt to ~/.config/zcode-dispatch/observatory-steers/<ts>-<sid>.prompt (override: ZDO_STEER_DIR), and spawns the INSTALLED dispatcher detached (start_new_session, stdout/stderr → <ts>-<sid>.log, stdin devnull, no --timeout — long turns legal), returning immediately (202) with steer id/paths.
  • Validation: empty text → 400, unknown session → 404, dispatcher missing from PATH → 503, a steer already in flight for the session → 409 (one-driver rule, lock-guarded spawn).
  • GET /api/session/<id>/steer/latest: the newest steer's incremental result JSON (status/turnStatus/error) + process liveness + exit code + response tail (last 500 chars) + the auto-detected target. After an observatory restart the in-memory record is gone — the newest result file on disk still reports.

Frontend

  • Prompt dialog in both surfaces: multi-line input (Enter sends, Shift+Enter newline), Send to session button, target line showing title · model · cwd basename so it is always visible WHERE the message goes. The send button is disabled ONLY while a steer for that session is in flight (visible ⏳ steer in flight — turn is running… state); on completion the dialog shows the exit-code line and the response tail. Exit-code semantics shown inline: 0 ok · 2 bad args/wrong model · 3 busy/lock/mismatch — session has a running turn · 4 failed · 5 timeout, session intact · 6 bridge/config. A steer sent while a modal sits on a terminal session restarts its 5 s poll so the new output streams in.
  • Scroll stability (the addendum): the render path is now content-keyed — a refresh cycle that returns no new content does not touch the DOM at all, so scrollTop is preserved exactly (the old code re-rendered + re-assigned scrollTop every cycle). Verified programmatically: scrollTop before/after multiple cycles identical in both surfaces.
  • Follow-output toggle (top-right of the body, both surfaces): ON auto-scrolls smoothly on new content; OFF leaves the position entirely to the user. Default ON for running sessions (live tail), OFF for terminal ones; choice persists per surface for the page session. Classic terminal behavior: scrolling up pauses follow and shows a ↓ jump to latest pill; scrolling back, clicking the pill, or re-toggling resumes. Both surfaces open at the conversation tail.

Evidence

  • Fixture smoke (fake dispatcher for deterministic states): 400/404/202/409 paths, lifecycle turn-running → completed exit 0 with tail, exit-3 busy semantics, disk fallback after a server restart.
  • CDP harness (headless Chrome, trusted events): 22/22 checks — scrollTop untouched across refresh cycles without new content in drawer AND modal; follow-ON follows injected new content; follow-OFF does not; jump pill appears on scroll-up and returns to bottom; steer target lines; send disabled/enabled lifecycle; exit-0 completion with tail in the dialog; exit-3 busy state; Shift+Enter keeps the draft without sending.
  • REAL end-to-end steer: scratch session created via zcode-dispatch (GLM-5.3-Flash, worktree scratch dir), steered FROM the drawer UI — API walked turn-running (alive) → completed (exit 0), dialog showed ✅ steer ended — exit 0 with the tail E2E-STEERED-OK 43, transcript grew to 4 messages. Screenshots in the PR conversation.
  • Gates: py_compile + --help, make check-social-preview, Super-Linter slim v8.7.0 (workflow env) — EXIT 0.

Docs

README: "Steering from the observatory" section (what it does, busy semantics, Steer API, exit-code table, scratch-file layout) + "Scroll stability & follow output" section (defaults documented) + security section updated (the observatory now triggers dispatcher runs — still never writes the ZCode DB; steer scratch files contain owner content, no credentials).

The observatory's first WRITE-capable action: a prompt dialog in the response modal AND the full transcript drawer that runs a follow-up turn in the session through the installed `zcode-dispatch` — the observatory still never writes the ZCode DB. Plus the owner-reported scroll fix: the response modal drifted downwards on every refresh cycle; both surfaces are now strictly position-stable with a proper follow-output toggle. ## Backend - `POST /api/session/<id>/steer` with `{text}`: auto-detects cwd and model exactly like the dispatcher's `notify_parent` (`session.directory`; last `json_extract(data,'$.modelSelection.modelId')` by sequence — never guessed), writes the prompt to `~/.config/zcode-dispatch/observatory-steers/<ts>-<sid>.prompt` (override: `ZDO_STEER_DIR`), and spawns the INSTALLED dispatcher detached (`start_new_session`, stdout/stderr → `<ts>-<sid>.log`, stdin devnull, **no `--timeout`** — long turns legal), returning immediately (202) with steer id/paths. - Validation: empty text → 400, unknown session → 404, dispatcher missing from PATH → 503, a steer already in flight for the session → 409 (one-driver rule, lock-guarded spawn). - `GET /api/session/<id>/steer/latest`: the newest steer's incremental result JSON (status/turnStatus/error) + process liveness + exit code + response tail (last 500 chars) + the auto-detected target. After an observatory restart the in-memory record is gone — the newest result file on disk still reports. ## Frontend - Prompt dialog in both surfaces: multi-line input (Enter sends, Shift+Enter newline), **Send to session** button, target line showing title · model · cwd basename so it is always visible WHERE the message goes. The send button is disabled ONLY while a steer for that session is in flight (visible `⏳ steer in flight — turn is running…` state); on completion the dialog shows the exit-code line and the response tail. Exit-code semantics shown inline: `0 ok · 2 bad args/wrong model · 3 busy/lock/mismatch — session has a running turn · 4 failed · 5 timeout, session intact · 6 bridge/config`. A steer sent while a modal sits on a terminal session restarts its 5 s poll so the new output streams in. - **Scroll stability (the addendum):** the render path is now content-keyed — a refresh cycle that returns no new content does not touch the DOM at all, so `scrollTop` is preserved exactly (the old code re-rendered + re-assigned `scrollTop` every cycle). Verified programmatically: scrollTop before/after multiple cycles identical in both surfaces. - **Follow-output toggle** (top-right of the body, both surfaces): ON auto-scrolls smoothly on new content; OFF leaves the position entirely to the user. Default ON for running sessions (live tail), OFF for terminal ones; choice persists per surface for the page session. Classic terminal behavior: scrolling up pauses follow and shows a `↓ jump to latest` pill; scrolling back, clicking the pill, or re-toggling resumes. Both surfaces open at the conversation tail. ## Evidence - Fixture smoke (fake dispatcher for deterministic states): 400/404/202/409 paths, lifecycle `turn-running → completed` exit 0 with tail, exit-3 busy semantics, disk fallback after a server restart. - CDP harness (headless Chrome, trusted events): **22/22 checks** — scrollTop untouched across refresh cycles without new content in drawer AND modal; follow-ON follows injected new content; follow-OFF does not; jump pill appears on scroll-up and returns to bottom; steer target lines; send disabled/enabled lifecycle; exit-0 completion with tail in the dialog; exit-3 busy state; Shift+Enter keeps the draft without sending. - REAL end-to-end steer: scratch session created via `zcode-dispatch` (GLM-5.3-Flash, worktree scratch dir), steered FROM the drawer UI — API walked `turn-running (alive) → completed (exit 0)`, dialog showed `✅ steer ended — exit 0` with the tail `E2E-STEERED-OK 43`, transcript grew to 4 messages. Screenshots in the PR conversation. - Gates: `py_compile` + `--help`, `make check-social-preview`, Super-Linter slim v8.7.0 (workflow env) — EXIT 0. ## Docs README: "Steering from the observatory" section (what it does, busy semantics, Steer API, exit-code table, scratch-file layout) + "Scroll stability & follow output" section (defaults documented) + security section updated (the observatory now triggers dispatcher runs — still never writes the ZCode DB; steer scratch files contain owner content, no credentials).
Steering console + scroll-stable follow output
All checks were successful
Checks / Functional checks (pull_request) Successful in 14s
a4ff67884d
- POST /api/session/<id>/steer spawns the installed zcode-dispatch
  (detached, --resume, no --timeout) after auto-detecting cwd/model
  like notify_parent; one steer per session (409 while in flight),
  400 empty text, 404 unknown session, 503 dispatcher missing.
- GET /api/session/<id>/steer/latest: incremental result JSON plus
  process liveness and response tail; newest-result disk fallback
  keeps status across an observatory restart.
- Prompt dialog in the response modal AND the transcript drawer:
  target line (title, model, cwd basename), Enter sends / Shift+Enter
  newline, send disabled only while steering, status walks
  sending -> turn-running -> terminal with the exit-code legend,
  response tail (~500 chars) surfaced on completion.
- Scroll stability: refresh cycles without new content never touch
  scrollTop (content-key render skip); follow-output toggle
  (top-right) in both surfaces, ON by default for running sessions
  and OFF for terminal ones, persisted per page session; scrolling
  up pauses follow and shows a jump-to-latest pill.
- README: steering section with Steer API + exit-code table, security
  section updated (observatory triggers dispatcher runs, still never
  writes the ZCode DB).
Author
Owner

Evidence

Real end-to-end steer (real DB, real zcode-dispatch, driven from the drawer UI)

real e2e dashboard

Drawer open on the scratch session (# Observatory steer e2e scratch session, GLM-5.3-Flash, .e2e-scratch) — target line + prompt dialog + follow toggle:

drawer before

After clicking Send to session (API status at that moment: turn-running, processAlive: true) — button disabled, in-flight state:

in flight

Final: API walked turn-running → completed (exit 0), dialog shows ✅ steer ended — exit 0 · turn completed with the response tail E2E-STEERED-OK 43, transcript grew to 4 messages, send re-enabled:

completed

CDP fixture harness (22/22 checks)

Follow toggle ON (default for running) + steer box in the drawer:

follow on

Stability probe: scrollTop held at 500 across multiple refresh cycles with no new content (drawer and modal — logged before/after, identical):

stable

Scroll-up pauses follow and shows the ↓ jump-to-latest pill (click returns to bottom and resumes):

jump pill

Steer in flight in the drawer (fake dispatcher, deterministic states):

drawer inflight

Completed with exit-0 line + response tail + legend:

drawer completed

Modal: steer box + stable scroll (scrollTop 40 → 40 across cycles), follow OFF default for terminal sessions:

modal

Exit-3 busy semantics in the modal:

busy

Full checklist (22/22): drawer opens at bottom (follow ON default) · scrollTop untouched over cycles (drawer, modal) · follow-OFF ignores injected new content · follow-ON follows it · re-toggle jumps to bottom · scroll-up shows pill · pill click returns+resumes · steer target lines (both surfaces) · send disabled while in flight · in-flight status visible · exit-0 completion + tail · send re-enabled · one-driver state (disabled + visible status) · exit-3 busy semantics · Shift+Enter keeps draft without sending.

## Evidence ### Real end-to-end steer (real DB, real zcode-dispatch, driven from the drawer UI) ![real e2e dashboard](attachments/39149ef1-c261-4177-a2e9-407657794be0) Drawer open on the scratch session (`# Observatory steer e2e scratch session`, GLM-5.3-Flash, `.e2e-scratch`) — target line + prompt dialog + follow toggle: ![drawer before](attachments/80e31236-56c5-473f-91ad-70912c4b3ca5) After clicking **Send to session** (API status at that moment: `turn-running`, `processAlive: true`) — button disabled, in-flight state: ![in flight](attachments/cd877a03-d990-4c28-8d4a-dadb84e81ad4) Final: API walked `turn-running → completed (exit 0)`, dialog shows `✅ steer ended — exit 0 · turn completed` with the response tail `E2E-STEERED-OK 43`, transcript grew to 4 messages, send re-enabled: ![completed](attachments/2b4761da-2546-417c-b1b3-91446c62c806) ### CDP fixture harness (22/22 checks) Follow toggle ON (default for running) + steer box in the drawer: ![follow on](attachments/e2a6f570-6ccb-4dac-aef5-228a9f5ce8d8) Stability probe: scrollTop held at 500 across multiple refresh cycles with no new content (drawer **and** modal — logged before/after, identical): ![stable](attachments/124e89ca-1bce-4f7a-8e17-98c360dbca59) Scroll-up pauses follow and shows the ↓ jump-to-latest pill (click returns to bottom and resumes): ![jump pill](attachments/00f5219f-d23a-4711-b6ad-ffbc9b3d7a52) Steer in flight in the drawer (fake dispatcher, deterministic states): ![drawer inflight](attachments/f7c8d90d-d9f4-495f-94d7-78495dc24318) Completed with exit-0 line + response tail + legend: ![drawer completed](attachments/9904fc9e-cb9b-43df-b627-d1d30259037b) Modal: steer box + stable scroll (scrollTop 40 → 40 across cycles), follow OFF default for terminal sessions: ![modal](attachments/9927b6c4-879b-4656-a69f-3060fb927694) Exit-3 busy semantics in the modal: ![busy](attachments/150cc568-da9e-4821-8f21-03c0065e3afd) Full checklist (22/22): drawer opens at bottom (follow ON default) · scrollTop untouched over cycles (drawer, modal) · follow-OFF ignores injected new content · follow-ON follows it · re-toggle jumps to bottom · scroll-up shows pill · pill click returns+resumes · steer target lines (both surfaces) · send disabled while in flight · in-flight status visible · exit-0 completion + tail · send re-enabled · one-driver state (disabled + visible status) · exit-3 busy semantics · Shift+Enter keeps draft without sending.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
OSS-Singularity/zcode-dispatch-observatory!25
No description provided.