langstage-cli 0.6.5__tar.gz → 0.6.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {langstage_cli-0.6.5/langstage_cli.egg-info → langstage_cli-0.6.7}/PKG-INFO +3 -3
- {langstage_cli-0.6.5 → langstage_cli-0.6.7}/langstage_cli/agui_stream.py +8 -3
- {langstage_cli-0.6.5 → langstage_cli-0.6.7}/langstage_cli/cli.py +21 -12
- {langstage_cli-0.6.5 → langstage_cli-0.6.7/langstage_cli.egg-info}/PKG-INFO +3 -3
- {langstage_cli-0.6.5 → langstage_cli-0.6.7}/langstage_cli.egg-info/SOURCES.txt +3 -1
- {langstage_cli-0.6.5 → langstage_cli-0.6.7}/langstage_cli.egg-info/requires.txt +2 -2
- {langstage_cli-0.6.5 → langstage_cli-0.6.7}/pyproject.toml +3 -3
- langstage_cli-0.6.7/tests/test_configurable.py +37 -0
- langstage_cli-0.6.7/tests/test_workspace.py +48 -0
- {langstage_cli-0.6.5 → langstage_cli-0.6.7}/LICENSE +0 -0
- {langstage_cli-0.6.5 → langstage_cli-0.6.7}/README.md +0 -0
- {langstage_cli-0.6.5 → langstage_cli-0.6.7}/deepagent_code/__init__.py +0 -0
- {langstage_cli-0.6.5 → langstage_cli-0.6.7}/langstage_cli/__init__.py +0 -0
- {langstage_cli-0.6.5 → langstage_cli-0.6.7}/langstage_cli/config.py +0 -0
- {langstage_cli-0.6.5 → langstage_cli-0.6.7}/langstage_cli.egg-info/dependency_links.txt +0 -0
- {langstage_cli-0.6.5 → langstage_cli-0.6.7}/langstage_cli.egg-info/entry_points.txt +0 -0
- {langstage_cli-0.6.5 → langstage_cli-0.6.7}/langstage_cli.egg-info/top_level.txt +0 -0
- {langstage_cli-0.6.5 → langstage_cli-0.6.7}/setup.cfg +0 -0
- {langstage_cli-0.6.5 → langstage_cli-0.6.7}/tests/test_agui_stream.py +0 -0
- {langstage_cli-0.6.5 → langstage_cli-0.6.7}/tests/test_checkpointer.py +0 -0
- {langstage_cli-0.6.5 → langstage_cli-0.6.7}/tests/test_cli.py +0 -0
- {langstage_cli-0.6.5 → langstage_cli-0.6.7}/tests/test_cli_help.py +0 -0
- {langstage_cli-0.6.5 → langstage_cli-0.6.7}/tests/test_codeconfig.py +0 -0
- {langstage_cli-0.6.5 → langstage_cli-0.6.7}/tests/test_config.py +0 -0
- {langstage_cli-0.6.5 → langstage_cli-0.6.7}/tests/test_help_render.py +0 -0
- {langstage_cli-0.6.5 → langstage_cli-0.6.7}/tests/test_no_interactive_approve.py +0 -0
- {langstage_cli-0.6.5 → langstage_cli-0.6.7}/tests/test_quiet_output.py +0 -0
- {langstage_cli-0.6.5 → langstage_cli-0.6.7}/tests/test_rename_shim.py +0 -0
- {langstage_cli-0.6.5 → langstage_cli-0.6.7}/tests/test_show_config.py +0 -0
- {langstage_cli-0.6.5 → langstage_cli-0.6.7}/tests/test_spec_resolution.py +0 -0
- {langstage_cli-0.6.5 → langstage_cli-0.6.7}/tests/test_stream_mode.py +0 -0
- {langstage_cli-0.6.5 → langstage_cli-0.6.7}/tests/test_streaming_marker.py +0 -0
- {langstage_cli-0.6.5 → langstage_cli-0.6.7}/tests/test_turn_exit_and_render.py +0 -0
- {langstage_cli-0.6.5 → langstage_cli-0.6.7}/tests/test_unicode_console.py +0 -0
- {langstage_cli-0.6.5 → langstage_cli-0.6.7}/tests/test_verify_flag.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langstage-cli
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.7
|
|
4
4
|
Summary: The terminal stage for your LangGraph agent — Claude Code-style CLI for any CompiledGraph
|
|
5
5
|
Author-email: Kedar Dabhadkar <kdabhadk@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -19,7 +19,7 @@ Requires-Python: >=3.11
|
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
License-File: LICENSE
|
|
21
21
|
Requires-Dist: langgraph>=0.2.0
|
|
22
|
-
Requires-Dist: langstage-core[agui]>=1.0.
|
|
22
|
+
Requires-Dist: langstage-core[agui]>=1.0.7
|
|
23
23
|
Requires-Dist: click>=8.0.0
|
|
24
24
|
Requires-Dist: python-dotenv
|
|
25
25
|
Provides-Extra: dev
|
|
@@ -32,7 +32,7 @@ Requires-Dist: deepagents>=0.3; extra == "dev"
|
|
|
32
32
|
Requires-Dist: ag-ui-langgraph>=0.0.41; extra == "dev"
|
|
33
33
|
Requires-Dist: fastapi; extra == "dev"
|
|
34
34
|
Provides-Extra: agui
|
|
35
|
-
Requires-Dist: langstage-core[agui]>=1.0.
|
|
35
|
+
Requires-Dist: langstage-core[agui]>=1.0.7; extra == "agui"
|
|
36
36
|
Dynamic: license-file
|
|
37
37
|
|
|
38
38
|
# langstage-cli
|
|
@@ -30,13 +30,18 @@ def ensure_agui_available() -> None:
|
|
|
30
30
|
raise RuntimeError(_IMPORT_HINT) from e
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
def build_session_agent(graph: Any, *, name: str = "langstage-cli") -> Any:
|
|
33
|
+
def build_session_agent(graph: Any, *, name: str = "langstage-cli", config: Any = None) -> Any:
|
|
34
34
|
"""Wrap a compiled graph once per session (checkpointer attached by the core
|
|
35
|
-
bridge), so multi-turn memory persists across turns.
|
|
35
|
+
bridge), so multi-turn memory persists across turns.
|
|
36
|
+
|
|
37
|
+
``config`` (a ``RunnableConfig``, e.g. ``{"configurable": {...}}`` seeded from
|
|
38
|
+
``langstage.toml``'s ``[configurable]`` table) is forwarded to the graph on
|
|
39
|
+
every turn, so keys beyond ``thread_id`` actually reach the agent. (gh #57)
|
|
40
|
+
"""
|
|
36
41
|
ensure_agui_available()
|
|
37
42
|
from langstage_core.agui import build_agent
|
|
38
43
|
|
|
39
|
-
return build_agent(graph, name=name)
|
|
44
|
+
return build_agent(graph, name=name, config=config)
|
|
40
45
|
|
|
41
46
|
|
|
42
47
|
async def agui_stream_updates(
|
|
@@ -18,7 +18,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
|
|
18
18
|
|
|
19
19
|
import click
|
|
20
20
|
|
|
21
|
-
from langstage_core import load_agent_spec
|
|
21
|
+
from langstage_core import apply_workspace, load_agent_spec
|
|
22
22
|
from langstage_cli import config as config_module
|
|
23
23
|
|
|
24
24
|
# Platform-specific imports for keyboard input
|
|
@@ -1328,12 +1328,21 @@ def run_conversation_loop(
|
|
|
1328
1328
|
# the core bridge) so multi-turn memory persists. Since langstage-core 1.0 the
|
|
1329
1329
|
# AG-UI adapter is the only streaming path.
|
|
1330
1330
|
thread_id = ""
|
|
1331
|
+
configurable: Dict[str, Any] = {}
|
|
1331
1332
|
if isinstance(config, dict):
|
|
1332
|
-
|
|
1333
|
+
configurable = dict(config.get("configurable", {}))
|
|
1334
|
+
thread_id = configurable.get("thread_id", "") or ""
|
|
1333
1335
|
from langstage_cli.agui_stream import build_session_agent
|
|
1334
1336
|
|
|
1337
|
+
# Forward the resolved `[configurable]` table (minus thread_id, which the
|
|
1338
|
+
# adapter sets per-run) to the graph, so keys beyond thread_id — the documented
|
|
1339
|
+
# way to parameterize an agent — actually reach it instead of being silently
|
|
1340
|
+
# dropped while /config advertises them. (gh #57)
|
|
1341
|
+
configurable.pop("thread_id", None)
|
|
1342
|
+
session_config = {"configurable": configurable} if configurable else None
|
|
1343
|
+
|
|
1335
1344
|
try:
|
|
1336
|
-
agui_agent = build_session_agent(graph, name=agent_name)
|
|
1345
|
+
agui_agent = build_session_agent(graph, name=agent_name, config=session_config)
|
|
1337
1346
|
except RuntimeError as e:
|
|
1338
1347
|
_status(f"{RED}⏺ {e}{RESET}")
|
|
1339
1348
|
return
|
|
@@ -1673,10 +1682,9 @@ def main(
|
|
|
1673
1682
|
sys.exit(2)
|
|
1674
1683
|
use_async = cfg.async_mode
|
|
1675
1684
|
verbose = cfg.verbose
|
|
1676
|
-
#
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
)
|
|
1685
|
+
# Whether a workspace root was explicitly configured (vs the default cwd);
|
|
1686
|
+
# cli chdirs into it only when it was, matching prior behavior.
|
|
1687
|
+
workspace_explicit = cfg.sources.get("workspace_root") != "default"
|
|
1680
1688
|
|
|
1681
1689
|
# If no spec provided, try the default agent
|
|
1682
1690
|
if not final_spec:
|
|
@@ -1696,11 +1704,12 @@ def main(
|
|
|
1696
1704
|
# under workspace_root, not where the user is and put the file. (gh #30)
|
|
1697
1705
|
final_spec = _absolutize_file_spec(final_spec)
|
|
1698
1706
|
|
|
1699
|
-
#
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1707
|
+
# Apply the resolved workspace as the single source of truth (ADR 0005):
|
|
1708
|
+
# publish it (env + active) so the agent's tools can read workspace_root(),
|
|
1709
|
+
# and chdir into it (cli is single-process) when one was explicitly
|
|
1710
|
+
# configured. Runs AFTER _absolutize_file_spec so `-a my_agent.py` still
|
|
1711
|
+
# resolves against the invocation cwd, not the workspace (gh #30).
|
|
1712
|
+
apply_workspace(Path(cfg.workspace_root).expanduser(), chdir=workspace_explicit)
|
|
1704
1713
|
|
|
1705
1714
|
# Load the graph with a spinner (both are chrome; quiet mode stays silent
|
|
1706
1715
|
# until the reply). (gh #53)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langstage-cli
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.7
|
|
4
4
|
Summary: The terminal stage for your LangGraph agent — Claude Code-style CLI for any CompiledGraph
|
|
5
5
|
Author-email: Kedar Dabhadkar <kdabhadk@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -19,7 +19,7 @@ Requires-Python: >=3.11
|
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
License-File: LICENSE
|
|
21
21
|
Requires-Dist: langgraph>=0.2.0
|
|
22
|
-
Requires-Dist: langstage-core[agui]>=1.0.
|
|
22
|
+
Requires-Dist: langstage-core[agui]>=1.0.7
|
|
23
23
|
Requires-Dist: click>=8.0.0
|
|
24
24
|
Requires-Dist: python-dotenv
|
|
25
25
|
Provides-Extra: dev
|
|
@@ -32,7 +32,7 @@ Requires-Dist: deepagents>=0.3; extra == "dev"
|
|
|
32
32
|
Requires-Dist: ag-ui-langgraph>=0.0.41; extra == "dev"
|
|
33
33
|
Requires-Dist: fastapi; extra == "dev"
|
|
34
34
|
Provides-Extra: agui
|
|
35
|
-
Requires-Dist: langstage-core[agui]>=1.0.
|
|
35
|
+
Requires-Dist: langstage-core[agui]>=1.0.7; extra == "agui"
|
|
36
36
|
Dynamic: license-file
|
|
37
37
|
|
|
38
38
|
# langstage-cli
|
|
@@ -18,6 +18,7 @@ tests/test_cli.py
|
|
|
18
18
|
tests/test_cli_help.py
|
|
19
19
|
tests/test_codeconfig.py
|
|
20
20
|
tests/test_config.py
|
|
21
|
+
tests/test_configurable.py
|
|
21
22
|
tests/test_help_render.py
|
|
22
23
|
tests/test_no_interactive_approve.py
|
|
23
24
|
tests/test_quiet_output.py
|
|
@@ -28,4 +29,5 @@ tests/test_stream_mode.py
|
|
|
28
29
|
tests/test_streaming_marker.py
|
|
29
30
|
tests/test_turn_exit_and_render.py
|
|
30
31
|
tests/test_unicode_console.py
|
|
31
|
-
tests/test_verify_flag.py
|
|
32
|
+
tests/test_verify_flag.py
|
|
33
|
+
tests/test_workspace.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "langstage-cli"
|
|
7
|
-
version = "0.6.
|
|
7
|
+
version = "0.6.7"
|
|
8
8
|
description = "The terminal stage for your LangGraph agent — Claude Code-style CLI for any CompiledGraph"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -30,7 +30,7 @@ dependencies = [
|
|
|
30
30
|
# AG-UI runtime (ag-ui-langgraph[fastapi] + uvicorn, via core's [agui] extra)
|
|
31
31
|
# is a HARD dep. A bare `pip install langstage-cli` must be able to run a turn.
|
|
32
32
|
# >=1.0.6 for the shared preflight primitive core.verify() used by --verify.
|
|
33
|
-
"langstage-core[agui]>=1.0.
|
|
33
|
+
"langstage-core[agui]>=1.0.7",
|
|
34
34
|
"click>=8.0.0",
|
|
35
35
|
"python-dotenv",
|
|
36
36
|
]
|
|
@@ -54,7 +54,7 @@ dev = [
|
|
|
54
54
|
]
|
|
55
55
|
# Redundant since AG-UI moved into base deps (core 1.0); kept as a no-op alias so
|
|
56
56
|
# existing `pip install langstage-cli[agui]` scripts/READMEs still resolve.
|
|
57
|
-
agui = ["langstage-core[agui]>=1.0.
|
|
57
|
+
agui = ["langstage-core[agui]>=1.0.7"]
|
|
58
58
|
|
|
59
59
|
[project.scripts]
|
|
60
60
|
langstage-cli = "langstage_cli.cli:main"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""The langstage.toml [configurable] table reaches the graph (gh #57).
|
|
2
|
+
|
|
3
|
+
`/config` and `--show-config` advertise that `[configurable]` seeds LangGraph's
|
|
4
|
+
`RunnableConfig.configurable`, but the AG-UI streaming path used to forward only
|
|
5
|
+
`thread_id` — every other key was silently dropped. The session agent now carries
|
|
6
|
+
the resolved configurable, so a node's `config["configurable"]` sees it.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from click.testing import CliRunner
|
|
10
|
+
|
|
11
|
+
from langstage_cli.cli import main
|
|
12
|
+
|
|
13
|
+
_CFG_AGENT = (
|
|
14
|
+
"from langgraph.graph import StateGraph, START, END\n"
|
|
15
|
+
"from langgraph.graph.message import MessagesState\n"
|
|
16
|
+
"from langchain_core.messages import AIMessage\n"
|
|
17
|
+
"def respond(state, config):\n"
|
|
18
|
+
" c = (config or {}).get('configurable', {})\n"
|
|
19
|
+
" return {'messages': [AIMessage(content='custom_key=' + str(c.get('custom_key')))]}\n"
|
|
20
|
+
"g = StateGraph(MessagesState)\n"
|
|
21
|
+
"g.add_node('respond', respond)\n"
|
|
22
|
+
"g.add_edge(START, 'respond')\n"
|
|
23
|
+
"g.add_edge('respond', END)\n"
|
|
24
|
+
"graph = g.compile()\n"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_configurable_toml_reaches_the_graph(tmp_path, monkeypatch):
|
|
29
|
+
monkeypatch.chdir(tmp_path)
|
|
30
|
+
(tmp_path / "langstage.toml").write_text('[configurable]\ncustom_key = "from-toml"\n')
|
|
31
|
+
agent = tmp_path / "cfg_agent.py"
|
|
32
|
+
agent.write_text(_CFG_AGENT)
|
|
33
|
+
|
|
34
|
+
r = CliRunner().invoke(main, ["-a", f"{agent}:graph", "hi"])
|
|
35
|
+
assert r.exit_code == 0, r.output
|
|
36
|
+
# The node saw the [configurable] value, not None.
|
|
37
|
+
assert "custom_key=from-toml" in r.output, r.output
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""cli applies the resolved workspace via core.apply_workspace (ADR 0005).
|
|
2
|
+
|
|
3
|
+
The acceptance behavior all the workspace bugs were about: with a workspace
|
|
4
|
+
configured, a turn whose agent writes a *relative* file must land that file in the
|
|
5
|
+
workspace, not the launch cwd.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from click.testing import CliRunner
|
|
12
|
+
|
|
13
|
+
from langstage_cli.cli import main
|
|
14
|
+
|
|
15
|
+
_WRITER_AGENT = (
|
|
16
|
+
"from pathlib import Path\n"
|
|
17
|
+
"from langgraph.graph import StateGraph, START, END, MessagesState\n"
|
|
18
|
+
"from langchain_core.messages import AIMessage\n"
|
|
19
|
+
"def node(s):\n"
|
|
20
|
+
" Path('marker.txt').write_text('hi')\n"
|
|
21
|
+
" return {'messages': [AIMessage(content='wrote marker')]}\n"
|
|
22
|
+
"b = StateGraph(MessagesState)\n"
|
|
23
|
+
"b.add_node('n', node)\n"
|
|
24
|
+
"b.add_edge(START, 'n')\n"
|
|
25
|
+
"b.add_edge('n', END)\n"
|
|
26
|
+
"graph = b.compile()\n"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_configured_workspace_is_where_the_agent_writes(tmp_path, monkeypatch):
|
|
31
|
+
ws = tmp_path / "ws"
|
|
32
|
+
agent = tmp_path / "writer.py"
|
|
33
|
+
agent.write_text(_WRITER_AGENT)
|
|
34
|
+
# Workspace comes from the env (cli has no --workspace flag); track both names
|
|
35
|
+
# so monkeypatch restores them (apply_workspace also sets the legacy one).
|
|
36
|
+
monkeypatch.setenv("LANGSTAGE_WORKSPACE_ROOT", str(ws))
|
|
37
|
+
monkeypatch.setenv("DEEPAGENT_WORKSPACE_ROOT", "")
|
|
38
|
+
|
|
39
|
+
origin = Path.cwd()
|
|
40
|
+
try:
|
|
41
|
+
r = CliRunner().invoke(main, ["-a", f"{agent}:graph", "go"])
|
|
42
|
+
finally:
|
|
43
|
+
os.chdir(origin) # cli chdir'd into the workspace; restore for other tests
|
|
44
|
+
|
|
45
|
+
assert r.exit_code == 0, r.output
|
|
46
|
+
# The relative write landed in the configured workspace, not the launch cwd.
|
|
47
|
+
assert (ws / "marker.txt").read_text() == "hi"
|
|
48
|
+
assert not (origin / "marker.txt").exists()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|