Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## 0.6.24 - 2026-07-25

### Fixed
- **A plain-string `interrupt("Approve deleting X?")` — the canonical LangGraph HITL form — now
shows the human what they're approving, instead of "(no action details provided)" (gh #95).**
The approval menu dropped the payload of a bare-string/scalar interrupt (dict payloads rendered
fine), so a confirmation like `interrupt("Approve deleting ALL files in /home?")` asked the user
to Approve/Reject blind — defeating the point of HITL. The renderer already handled a scalar
(`format_interrupt_request` returns `str(action)`); the payload was dropped upstream in
`langstage-core`'s `on_interrupt` handler, which `json.loads()`'d the string, failed on a
non-JSON question, and fell back to an empty request. Fixed in **langstage-core 1.0.28** on the
chunk wire the CLI consumes; this raises the floor to `>= 1.0.28` so a fresh install gets it.

## 0.6.23 - 2026-07-23

### Fixed
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "langstage-cli"
version = "0.6.23"
version = "0.6.24"
description = "The terminal stage for your LangGraph agent — Claude Code-style CLI for any CompiledGraph"
readme = "README.md"
requires-python = ">=3.11"
Expand All @@ -31,7 +31,7 @@ dependencies = [
# is a HARD dep. A bare `pip install langstage-cli` must be able to run a turn.
# >=1.0.6 for the shared preflight primitive core.verify() used by --verify.
# >=1.0.14 for describe(configurable=) — the single complete config diagnostic.
"langstage-core[agui]>=1.0.24",
"langstage-core[agui]>=1.0.28",
"click>=8.0.0",
"python-dotenv",
]
Expand Down
31 changes: 31 additions & 0 deletions tests/test_agui_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,34 @@ def test_non_streaming_tool_agent_surfaces_tool_call_and_result():
assert results and results[0]["tool_result"] == "Sunny, 24C", (
"tool result dropped on the CLI snapshot path (needs langstage-core >= 1.0.24, gh #91)"
)


def test_plain_string_interrupt_reaches_the_renderer_with_its_payload():
"""gh #95: a bare-string interrupt("Approve X?") must surface its text to the CLI,
not "(no action details provided)". The fix is in langstage-core (>= 1.0.28, on the
chunk wire the CLI consumes); this pins that the frame carries the string and the
CLI renderer shows it."""
from langgraph.checkpoint.memory import MemorySaver
from langgraph.types import interrupt

from langstage_cli.cli import format_interrupt_request

def ask(state):
interrupt("Approve deleting ALL files in /home? (y/n)")
return {"messages": [AIMessage(content="done")]}

b = StateGraph(MessagesState)
b.add_node("ask", ask)
b.add_edge(START, "ask")
b.add_edge("ask", END)
agent = build_session_agent(b.compile(checkpointer=MemorySaver()))

chunks = _collect(agent, "go")
interrupts = [c for c in chunks if c.get("status") == "interrupt"]
assert interrupts, chunks
ars = interrupts[0]["interrupt"]["action_requests"]
assert ars == ["Approve deleting ALL files in /home? (y/n)"], (
"string interrupt dropped (needs core >= 1.0.28, gh #95)"
)
# the renderer surfaces the string (not "(no action details provided)")
assert format_interrupt_request(ars[0])[0] == "Approve deleting ALL files in /home? (y/n)"
Loading