langstage-cli 0.6.8__tar.gz → 0.6.10__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.
Files changed (37) hide show
  1. {langstage_cli-0.6.8/langstage_cli.egg-info → langstage_cli-0.6.10}/PKG-INFO +1 -7
  2. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/README.md +0 -6
  3. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/langstage_cli/cli.py +43 -42
  4. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/langstage_cli/config.py +6 -4
  5. {langstage_cli-0.6.8 → langstage_cli-0.6.10/langstage_cli.egg-info}/PKG-INFO +1 -7
  6. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/langstage_cli.egg-info/SOURCES.txt +1 -0
  7. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/pyproject.toml +1 -1
  8. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_cli_help.py +3 -1
  9. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_codeconfig.py +12 -9
  10. langstage_cli-0.6.10/tests/test_config_command.py +40 -0
  11. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_show_config.py +4 -7
  12. langstage_cli-0.6.10/tests/test_stream_mode.py +71 -0
  13. langstage_cli-0.6.8/tests/test_stream_mode.py +0 -76
  14. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/LICENSE +0 -0
  15. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/deepagent_code/__init__.py +0 -0
  16. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/langstage_cli/__init__.py +0 -0
  17. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/langstage_cli/agui_stream.py +0 -0
  18. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/langstage_cli.egg-info/dependency_links.txt +0 -0
  19. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/langstage_cli.egg-info/entry_points.txt +0 -0
  20. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/langstage_cli.egg-info/requires.txt +0 -0
  21. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/langstage_cli.egg-info/top_level.txt +0 -0
  22. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/setup.cfg +0 -0
  23. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_agui_stream.py +0 -0
  24. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_checkpointer.py +0 -0
  25. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_cli.py +0 -0
  26. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_config.py +0 -0
  27. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_configurable.py +0 -0
  28. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_help_render.py +0 -0
  29. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_no_interactive_approve.py +0 -0
  30. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_quiet_output.py +0 -0
  31. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_rename_shim.py +0 -0
  32. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_spec_resolution.py +0 -0
  33. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_streaming_marker.py +0 -0
  34. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_turn_exit_and_render.py +0 -0
  35. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_unicode_console.py +0 -0
  36. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_verify_flag.py +0 -0
  37. {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_workspace.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langstage-cli
3
- Version: 0.6.8
3
+ Version: 0.6.10
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
@@ -148,9 +148,6 @@ langstage-cli
148
148
 
149
149
  # Working directory
150
150
  export LANGSTAGE_WORKSPACE_ROOT="/path/to/workspace"
151
-
152
- # Stream mode (updates or messages)
153
- export LANGSTAGE_STREAM_MODE="updates"
154
151
  ```
155
152
 
156
153
  ## Configuration Files
@@ -179,7 +176,6 @@ root = "."
179
176
  [ui]
180
177
  verbose = true
181
178
  async_mode = false
182
- stream_mode = "auto" # auto | updates | messages
183
179
 
184
180
  [configurable]
185
181
  # seeds LangGraph RunnableConfig.configurable
@@ -200,8 +196,6 @@ Options:
200
196
  -f, --file PATH Read message from a file (any extension)
201
197
  --interactive/--no-interactive Handle interrupts (default: interactive)
202
198
  --async-mode/--sync-mode Async streaming (default: sync)
203
- --stream-mode [auto|updates|messages]
204
- Stream mode (default: auto)
205
199
  -v, --verbose Verbose output
206
200
  --demo Run with the built-in keyless demo agent
207
201
  --show-config Print the resolved configuration and exit
@@ -111,9 +111,6 @@ langstage-cli
111
111
 
112
112
  # Working directory
113
113
  export LANGSTAGE_WORKSPACE_ROOT="/path/to/workspace"
114
-
115
- # Stream mode (updates or messages)
116
- export LANGSTAGE_STREAM_MODE="updates"
117
114
  ```
118
115
 
119
116
  ## Configuration Files
@@ -142,7 +139,6 @@ root = "."
142
139
  [ui]
143
140
  verbose = true
144
141
  async_mode = false
145
- stream_mode = "auto" # auto | updates | messages
146
142
 
147
143
  [configurable]
148
144
  # seeds LangGraph RunnableConfig.configurable
@@ -163,8 +159,6 @@ Options:
163
159
  -f, --file PATH Read message from a file (any extension)
164
160
  --interactive/--no-interactive Handle interrupts (default: interactive)
165
161
  --async-mode/--sync-mode Async streaming (default: sync)
166
- --stream-mode [auto|updates|messages]
167
- Stream mode (default: auto)
168
162
  -v, --verbose Verbose output
169
163
  --demo Run with the built-in keyless demo agent
170
164
  --show-config Print the resolved configuration and exit
@@ -76,11 +76,12 @@ def _status(msg: str) -> None:
76
76
  print(msg, file=sys.stderr if _QUIET else sys.stdout)
77
77
 
78
78
 
79
- # Inherited HostConfig keys the terminal CLI never reads: it starts no server
80
- # (host/port/debug are inert) and the header box uses the loaded graph's name,
81
- # not `title`. `--show-config` / `/config` omit these so the diagnostic only
82
- # advertises knobs this surface actually honors. (gh #36)
83
- _INERT_KEYS = ["host", "port", "debug", "title"]
79
+ # Keys the terminal CLI never honors, so `--show-config` / `/config` omit them and
80
+ # the diagnostic only advertises knobs that actually do something. The inherited
81
+ # HostConfig server keys it starts no server (host/port/debug are inert) and the
82
+ # header box uses the loaded graph's name, not `title` (gh #36) — plus `stream_mode`,
83
+ # which has had no effect since the AG-UI streaming migration (gh #62).
84
+ _INERT_KEYS = ["host", "port", "debug", "title", "stream_mode"]
84
85
 
85
86
  # Spinner frames for thinking animation
86
87
  SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
@@ -865,20 +866,6 @@ def handle_interrupt_input(num_actions: int = 1) -> List[Dict[str, Any]]:
865
866
  sys.exit(0)
866
867
 
867
868
 
868
- def _resolve_stream_mode(stream_mode: str):
869
- """Map the user-facing stream mode to what LangGraph actually streams.
870
-
871
- 'auto' = dual-channel ``["updates", "messages"]``: the parser renders LLM
872
- tokens live when the agent streams them, and falls back to the finished
873
- message content otherwise — so a node that returns a complete (non-token-
874
- streamed) ``AIMessage`` still renders instead of an empty turn. (Bare
875
- 'messages' only carries LLM token streams, which is exactly why it was a
876
- silent-blank trap — gh #-dogfood.) 'updates'/'messages' pass through as the
877
- explicit single-mode power-user choices.
878
- """
879
- return ["updates", "messages"] if stream_mode == "auto" else stream_mode
880
-
881
-
882
869
  def print_help():
883
870
  """Print formatted help information."""
884
871
  print(f"\n{BOLD}{BRIGHT_CYAN}Commands{RESET}")
@@ -980,14 +967,12 @@ def cmd_status(args: str, context: Dict[str, Any]) -> Optional[str]:
980
967
  agent_name = context.get("agent_name", "Unknown")
981
968
  verbose = context.get("verbose", False)
982
969
  use_async = context.get("use_async", False)
983
- stream_mode = context.get("stream_mode", "updates")
984
970
 
985
971
  print(f"\n{BOLD}{BRIGHT_CYAN}Session Status{RESET}")
986
972
  print(f"{DIM}{'─' * 30}{RESET}")
987
973
  print(f" {DIM}Agent:{RESET} {agent_name}")
988
974
  print(f" {DIM}Thread ID:{RESET} {thread_id[:8]}...")
989
975
  print(f" {DIM}Mode:{RESET} {'async' if use_async else 'sync'}")
990
- print(f" {DIM}Stream:{RESET} {stream_mode}")
991
976
  print(f" {DIM}Verbose:{RESET} {'on' if verbose else 'off'}")
992
977
  print(f" {DIM}CWD:{RESET} {os.getcwd()}")
993
978
  print()
@@ -1017,14 +1002,17 @@ def cmd_config(args: str, context: Dict[str, Any]) -> Optional[str]:
1017
1002
  print(f" {DIM}TOML sources:{RESET} {DIM}(none — using defaults){RESET}")
1018
1003
 
1019
1004
  # Full resolved view: each value, where it came from, and the env var
1020
- # / TOML key that sets it.
1021
- from langstage_cli.config import CodeConfig
1022
-
1023
- # Omit the inherited server/web keys the terminal CLI never honors: it
1024
- # starts no server (host/port/debug inert) and the header uses the graph
1025
- # name, not `title`. Advertising them with source attribution is the
1026
- # "advertised != honored" trap. (gh #36)
1027
- for line in CodeConfig.resolve().describe(omit_keys=_INERT_KEYS).splitlines():
1005
+ # / TOML key that sets it. Prefer the snapshot captured at startup (before
1006
+ # apply_workspace self-published LANGSTAGE_WORKSPACE_ROOT), so workspace_root's
1007
+ # source is reported truthfully and /config agrees with --show-config (gh #64).
1008
+ # Fall back to a fresh resolve only if the snapshot is somehow absent.
1009
+ report = config.get("_resolved_config_report")
1010
+ if report is None:
1011
+ from langstage_cli.config import CodeConfig
1012
+
1013
+ # Omit the inherited server/web keys the terminal CLI never honors (gh #36).
1014
+ report = CodeConfig.resolve().describe(omit_keys=_INERT_KEYS)
1015
+ for line in report.splitlines():
1028
1016
  print(f" {line}")
1029
1017
 
1030
1018
  configurable = config.get("configurable", {})
@@ -1483,9 +1471,11 @@ def run_conversation_loop(
1483
1471
  @click.option(
1484
1472
  "--stream-mode",
1485
1473
  type=click.Choice(["auto", "updates", "messages"]),
1486
- help="Stream mode: 'auto' (default; token streaming when the agent emits "
1487
- "tokens, full-message fallback otherwise), 'updates' (whole-message), or "
1488
- "'messages' (token-level only a finished AIMessage renders nothing here).",
1474
+ # DEPRECATED (gh #62): a no-op since the AG-UI streaming migration all three
1475
+ # modes render identically. Kept hidden + accepted so existing `--stream-mode X`
1476
+ # invocations don't hard-error; a one-line notice fires when it is passed.
1477
+ hidden=True,
1478
+ help="(deprecated: no effect since the AG-UI streaming migration).",
1489
1479
  )
1490
1480
  @click.option(
1491
1481
  "--verbose",
@@ -1566,7 +1556,6 @@ def main(
1566
1556
  \b
1567
1557
  - LANGSTAGE_AGENT_SPEC: Agent location (same formats as above).
1568
1558
  - LANGSTAGE_WORKSPACE_ROOT: Working directory for the agent
1569
- - LANGSTAGE_STREAM_MODE: Stream mode for LangGraph (auto, updates, or messages)
1570
1559
 
1571
1560
  Reads ~/.langstage/config.toml (global) and langstage.toml (project,
1572
1561
  walks up from cwd). Precedence: CLI args > env vars > project TOML >
@@ -1629,6 +1618,14 @@ def main(
1629
1618
  "verbose": True if verbose else None,
1630
1619
  }
1631
1620
 
1621
+ # --stream-mode is deprecated and inert since the AG-UI streaming migration
1622
+ # (gh #62): accepted so scripts don't break, but say so once when it's passed.
1623
+ if stream_mode is not None:
1624
+ _status(
1625
+ f"{DIM}⏺ Note: --stream-mode is deprecated and has no effect "
1626
+ f"(streaming is uniform since the AG-UI migration).{RESET}"
1627
+ )
1628
+
1632
1629
  if show_config:
1633
1630
  # See _INERT_KEYS: hide the server/web keys this surface ignores. (gh #36)
1634
1631
  print(
@@ -1669,18 +1666,19 @@ def main(
1669
1666
  toml_start=Path.cwd(),
1670
1667
  overrides=cli_overrides,
1671
1668
  )
1669
+ # Snapshot the resolved-config diagnostic NOW, before apply_workspace() below
1670
+ # self-publishes LANGSTAGE_WORKSPACE_ROOT into os.environ (ADR 0005). The
1671
+ # interactive /config used to re-resolve at display time, see the tool's own
1672
+ # published var, and misreport workspace_root's source as [env:...] — diverging
1673
+ # from --show-config (which runs before apply_workspace). Reuse this snapshot so
1674
+ # /config shows the true provenance. (gh #64)
1675
+ resolved_config_report = cfg.describe(omit_keys=_INERT_KEYS)
1672
1676
  final_spec = cfg.agent_spec
1673
1677
  final_graph_name_default = cfg.graph_name
1678
+ # stream_mode is deprecated and inert (gh #62) — it only ever comes from the
1679
+ # accepted-and-ignored flag now (env/TOML no longer resolve it), so there is
1680
+ # nothing to validate; it changes no rendering.
1674
1681
  final_stream_mode = cfg.stream_mode
1675
- # The CLI's render path only supports 'updates' / 'messages'. Reject anything
1676
- # else (e.g. LANGSTAGE_STREAM_MODE=values, which bypasses the flag's Choice)
1677
- # with a clean error instead of a fatal crash deep in the stream worker.
1678
- if final_stream_mode not in ("auto", "updates", "messages"):
1679
- _status(
1680
- f"{RED}⏺ Error: unsupported stream mode {final_stream_mode!r} "
1681
- f"(use 'auto', 'updates', or 'messages')"
1682
- )
1683
- sys.exit(2)
1684
1682
  use_async = cfg.async_mode
1685
1683
  verbose = cfg.verbose
1686
1684
  # Whether a workspace root was explicitly configured (vs the default cwd);
@@ -1747,6 +1745,9 @@ def main(
1747
1745
 
1748
1746
  # Expose TOML sources to slash commands via the config dict
1749
1747
  config_dict["_toml_sources"] = [str(p) for p in toml_sources]
1748
+ # The resolved-config diagnostic snapshotted before apply_workspace, so /config
1749
+ # reports the true source of workspace_root instead of the self-published env (gh #64).
1750
+ config_dict["_resolved_config_report"] = resolved_config_report
1750
1751
 
1751
1752
  # Extract agent name and description from graph object
1752
1753
  agent_name = get_agent_name(graph)
@@ -84,16 +84,18 @@ class CodeConfig(HostConfig):
84
84
  resolver); the even-older ``DEEPAGENT_SPEC`` alias is reconciled below.
85
85
  """
86
86
 
87
+ # stream_mode is retained as an inert field (default only) so existing code and
88
+ # `--stream-mode` overrides don't break, but it is DEPRECATED: it has no effect
89
+ # since the AG-UI streaming migration (all three modes render identically). It is
90
+ # no longer resolved from LANGSTAGE_STREAM_MODE / [ui] stream_mode, and is omitted
91
+ # from `--show-config`. (gh #62)
87
92
  stream_mode: str = "auto"
88
93
  graph_name: str = "graph"
89
94
  verbose: bool = False
90
95
  async_mode: bool = False
91
96
 
92
- _ENV: ClassVar[dict] = {
93
- "stream_mode": ("LANGSTAGE_STREAM_MODE", str),
94
- }
97
+ _ENV: ClassVar[dict] = {}
95
98
  _TOML: ClassVar[dict] = {
96
- "stream_mode": "ui.stream_mode",
97
99
  "graph_name": "agent.graph_name",
98
100
  "verbose": "ui.verbose",
99
101
  "async_mode": "ui.async_mode",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langstage-cli
3
- Version: 0.6.8
3
+ Version: 0.6.10
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
@@ -148,9 +148,6 @@ langstage-cli
148
148
 
149
149
  # Working directory
150
150
  export LANGSTAGE_WORKSPACE_ROOT="/path/to/workspace"
151
-
152
- # Stream mode (updates or messages)
153
- export LANGSTAGE_STREAM_MODE="updates"
154
151
  ```
155
152
 
156
153
  ## Configuration Files
@@ -179,7 +176,6 @@ root = "."
179
176
  [ui]
180
177
  verbose = true
181
178
  async_mode = false
182
- stream_mode = "auto" # auto | updates | messages
183
179
 
184
180
  [configurable]
185
181
  # seeds LangGraph RunnableConfig.configurable
@@ -200,8 +196,6 @@ Options:
200
196
  -f, --file PATH Read message from a file (any extension)
201
197
  --interactive/--no-interactive Handle interrupts (default: interactive)
202
198
  --async-mode/--sync-mode Async streaming (default: sync)
203
- --stream-mode [auto|updates|messages]
204
- Stream mode (default: auto)
205
199
  -v, --verbose Verbose output
206
200
  --demo Run with the built-in keyless demo agent
207
201
  --show-config Print the resolved configuration and exit
@@ -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_config_command.py
21
22
  tests/test_configurable.py
22
23
  tests/test_help_render.py
23
24
  tests/test_no_interactive_approve.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "langstage-cli"
7
- version = "0.6.8"
7
+ version = "0.6.10"
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"
@@ -21,7 +21,9 @@ def test_help_leads_with_canonical_names():
21
21
  # canonical LANGSTAGE_* / langstage.toml are present...
22
22
  assert "LANGSTAGE_AGENT_SPEC" in out
23
23
  assert "LANGSTAGE_WORKSPACE_ROOT" in out
24
- assert "LANGSTAGE_STREAM_MODE" in out
25
24
  assert "langstage.toml" in out
25
+ # ...and the deprecated, no-op stream-mode knob is no longer advertised (gh #62)
26
+ assert "LANGSTAGE_STREAM_MODE" not in out
27
+ assert "--stream-mode" not in out
26
28
  # ...and the legacy names are no longer presented as the only config vocab
27
29
  assert "DEEPAGENT_AGENT_SPEC" not in out
@@ -28,24 +28,26 @@ def test_defaults(isolated, tmp_path):
28
28
  assert cfg.agent_spec is None
29
29
 
30
30
 
31
- def test_env_stream_mode(isolated, tmp_path):
32
- cfg = CodeConfig.resolve(env={"DEEPAGENT_STREAM_MODE": "values"}, toml_start=tmp_path)
33
- assert cfg.stream_mode == "values"
34
- assert cfg.sources["stream_mode"] == "env:DEEPAGENT_STREAM_MODE"
31
+ def test_stream_mode_env_and_toml_are_deprecated_and_ignored(isolated, tmp_path):
32
+ # gh #62: stream_mode has no effect since the AG-UI migration, so it is no longer
33
+ # resolved from LANGSTAGE_STREAM_MODE / [ui] stream_mode both are now ignored and
34
+ # the field stays at its default.
35
+ _toml(tmp_path, '[ui]\nstream_mode = "messages"\n')
36
+ cfg = CodeConfig.resolve(env={"LANGSTAGE_STREAM_MODE": "updates"}, toml_start=tmp_path)
37
+ assert cfg.stream_mode == "auto" # default, neither env nor TOML applied
38
+ assert cfg.sources["stream_mode"] == "default"
35
39
 
36
40
 
37
41
  def test_toml_keys(isolated, tmp_path):
38
42
  _toml(
39
43
  tmp_path,
40
- '[agent]\nspec = "a.py:g"\ngraph_name = "myg"\n'
41
- '[ui]\nverbose = true\nasync_mode = true\nstream_mode = "values"\n',
44
+ '[agent]\nspec = "a.py:g"\ngraph_name = "myg"\n[ui]\nverbose = true\nasync_mode = true\n',
42
45
  )
43
46
  cfg = CodeConfig.resolve(env={}, toml_start=tmp_path)
44
47
  assert cfg.agent_spec == "a.py:g"
45
48
  assert cfg.graph_name == "myg"
46
49
  assert cfg.verbose is True
47
50
  assert cfg.async_mode is True
48
- assert cfg.stream_mode == "values"
49
51
 
50
52
 
51
53
  def test_deepagent_spec_alias_is_deprecated(isolated, tmp_path):
@@ -75,5 +77,6 @@ def test_overrides_win(isolated, tmp_path):
75
77
  def test_describe_lists_var_names(isolated, tmp_path):
76
78
  text = CodeConfig.resolve(env={}, toml_start=tmp_path).describe()
77
79
  assert "DEEPAGENT_AGENT_SPEC" in text
78
- assert "DEEPAGENT_STREAM_MODE" in text
79
- assert "stream_mode" in text
80
+ # A field with a live TOML key is listed with its key (stream_mode's env/TOML
81
+ # were removed as a deprecated no-op — gh #62 — so it's no longer var-backed).
82
+ assert "graph_name" in text
@@ -0,0 +1,40 @@
1
+ """Interactive `/config` reports the true config source, matching `--show-config` (gh #64).
2
+
3
+ `/config` used to re-resolve `CodeConfig` at display time — but by then the startup path
4
+ had already called `apply_workspace()`, which self-publishes `LANGSTAGE_WORKSPACE_ROOT`
5
+ into `os.environ` (ADR 0005). The re-resolve saw the tool's own published var and reported
6
+ `workspace_root`'s source as `[env:LANGSTAGE_WORKSPACE_ROOT]` even when the user never set
7
+ it — diverging from `--show-config` (which runs before `apply_workspace`). `/config` now
8
+ reuses the report snapshotted at startup, before the self-publish.
9
+ """
10
+
11
+ from langstage_cli.cli import cmd_config
12
+
13
+
14
+ def test_config_uses_startup_snapshot_not_a_reresolve(monkeypatch, capsys):
15
+ # Reproduce the trigger: apply_workspace has self-published the workspace env.
16
+ monkeypatch.setenv("LANGSTAGE_WORKSPACE_ROOT", "/abs/self/published/ws")
17
+ monkeypatch.setenv("DEEPAGENT_WORKSPACE_ROOT", "/abs/self/published/ws")
18
+
19
+ # The snapshot captured at startup (before the self-publish) shows the true source.
20
+ snapshot = (
21
+ "agent_spec = a.py:graph [override]\n"
22
+ "workspace_root = . [default] (env: LANGSTAGE_WORKSPACE_ROOT, toml: workspace.root)"
23
+ )
24
+ ctx = {
25
+ "config": {
26
+ "_resolved_config_report": snapshot,
27
+ "_toml_sources": [],
28
+ "configurable": {},
29
+ }
30
+ }
31
+
32
+ cmd_config("", ctx)
33
+ out = capsys.readouterr().out
34
+
35
+ # /config prints the snapshot's true provenance...
36
+ assert "workspace_root" in out
37
+ assert "[default]" in out
38
+ # ...and does NOT misreport the self-published env var as the source (the #64 bug).
39
+ assert "[env:LANGSTAGE_WORKSPACE_ROOT]" not in out
40
+ assert "/abs/self/published/ws" not in out
@@ -14,13 +14,10 @@ from langstage_cli.cli import main
14
14
 
15
15
  def test_show_config_reflects_cli_flags():
16
16
  with CliRunner().isolated_filesystem(): # no stray toml
17
- r = CliRunner().invoke(
18
- main, ["-a", "fromcli.py:graph", "-v", "--stream-mode", "messages", "--show-config"]
19
- )
17
+ r = CliRunner().invoke(main, ["-a", "fromcli.py:graph", "-v", "--show-config"])
20
18
  assert r.exit_code == 0, r.output
21
19
  # CLI-set values appear with the [override] source, not [default].
22
20
  assert re.search(r"agent_spec\s*=\s*fromcli\.py:graph\s*\[override\]", r.output), r.output
23
- assert re.search(r"stream_mode\s*=\s*messages\s*\[override\]", r.output), r.output
24
21
  assert re.search(r"verbose\s*=\s*True\s*\[override\]", r.output), r.output
25
22
 
26
23
 
@@ -49,15 +46,15 @@ def test_show_config_without_flags_still_reports_env():
49
46
 
50
47
  def test_show_config_omits_server_only_keys():
51
48
  """The terminal CLI starts no server and titles the header from the graph
52
- name, so host/port/debug/title are inert and must not be advertised. (gh #36)"""
49
+ name, so host/port/debug/title are inert and must not be advertised (gh #36).
50
+ stream_mode is likewise omitted — it's a deprecated no-op (gh #62)."""
53
51
  with CliRunner().isolated_filesystem():
54
52
  r = CliRunner().invoke(main, ["--show-config"])
55
53
  assert r.exit_code == 0, r.output
56
- for key in ("host", "port", "debug", "title"):
54
+ for key in ("host", "port", "debug", "title", "stream_mode"):
57
55
  assert not re.search(rf"^\s*{key}\s*=", r.output, re.MULTILINE), f"{key} should be omitted"
58
56
  # ...but keys the CLI actually honors are still shown.
59
57
  assert re.search(r"^\s*agent_spec\s*=", r.output, re.MULTILINE), r.output
60
- assert re.search(r"^\s*stream_mode\s*=", r.output, re.MULTILINE), r.output
61
58
 
62
59
 
63
60
  def test_show_config_title_env_not_advertised_as_effective():
@@ -0,0 +1,71 @@
1
+ """--stream-mode is deprecated and inert (gh #62).
2
+
3
+ `--stream-mode {auto,updates,messages}` (and `LANGSTAGE_STREAM_MODE` / `[ui]
4
+ stream_mode`) was advertised as three streaming behaviors, but since the AG-UI
5
+ streaming migration all three render identically — the setting has no effect. The
6
+ dead knob is now: hidden from `--help`, omitted from `--show-config`, no longer
7
+ resolved from env / TOML, and accepted-and-ignored on the CLI (so existing
8
+ `--stream-mode X` invocations don't hard-error) with a one-line deprecation notice.
9
+
10
+ The unrelated "a finished AIMessage still renders" behavior (the reason the old
11
+ default was 'auto') is intrinsic to the AG-UI path now and is kept as a regression.
12
+ """
13
+
14
+ import textwrap
15
+
16
+ from click.testing import CliRunner
17
+
18
+ from langstage_cli.cli import main
19
+
20
+ # A graph whose node returns a *finished* AIMessage (no token streaming).
21
+ FINISHED_AIMESSAGE_AGENT = textwrap.dedent(
22
+ """
23
+ from langchain_core.messages import AIMessage
24
+ from langgraph.graph import START, END, StateGraph, MessagesState
25
+
26
+ def _respond(state):
27
+ return {"messages": [AIMessage(content="FINISHED_MARKER_42")]}
28
+
29
+ builder = StateGraph(MessagesState)
30
+ builder.add_node("respond", _respond)
31
+ builder.add_edge(START, "respond")
32
+ builder.add_edge("respond", END)
33
+ graph = builder.compile()
34
+ """
35
+ )
36
+
37
+
38
+ def test_finished_aimessage_still_renders(tmp_path, monkeypatch):
39
+ """Regression: a finished (non-token-streamed) AIMessage's content must render."""
40
+ (tmp_path / "fin_agent.py").write_text(FINISHED_AIMESSAGE_AGENT)
41
+ monkeypatch.chdir(tmp_path)
42
+ r = CliRunner().invoke(main, ["-a", "fin_agent.py:graph", "--no-interactive", "ping"])
43
+ assert r.exit_code == 0, r.output
44
+ assert "FINISHED_MARKER_42" in r.output
45
+
46
+
47
+ def test_stream_mode_is_omitted_from_show_config():
48
+ # gh #62: the deprecated no-op knob is no longer advertised in --show-config.
49
+ r = CliRunner().invoke(main, ["--show-config"])
50
+ assert r.exit_code == 0, r.output
51
+ assert "stream_mode" not in r.output
52
+
53
+
54
+ def test_stream_mode_flag_is_accepted_and_ignored_with_a_notice():
55
+ # An existing `--stream-mode X` invocation must not hard-error; it is accepted,
56
+ # ignored, and prints a one-line deprecation notice.
57
+ r = CliRunner().invoke(main, ["--demo", "--no-interactive", "--stream-mode", "messages", "hi"])
58
+ assert r.exit_code == 0, r.output
59
+ assert "deprecated" in r.output.lower()
60
+
61
+
62
+ def test_stream_mode_env_is_ignored_not_rejected():
63
+ # LANGSTAGE_STREAM_MODE used to be validated (and an unsupported value exited 2).
64
+ # It is now simply ignored — a run proceeds normally regardless of its value.
65
+ r = CliRunner().invoke(
66
+ main,
67
+ ["--demo", "--no-interactive", "hi"],
68
+ env={"LANGSTAGE_STREAM_MODE": "values"},
69
+ )
70
+ assert r.exit_code == 0, r.output
71
+ assert "unsupported stream mode" not in r.output.lower()
@@ -1,76 +0,0 @@
1
- """--stream-mode validation + the 'auto' default (gh #-dogfood).
2
-
3
- `values` was advertised but unsupported by the CLI's render path; passing it
4
- crashed with a fatal interpreter-shutdown error (a ValueError surfaced while the
5
- spinner daemon thread held stdout). Now: the flag is a Choice {auto,updates,
6
- messages}, and the resolved value (incl. LANGSTAGE_STREAM_MODE, which bypasses
7
- the flag) is validated up front with a clean error.
8
-
9
- Separately, single 'messages' mode only carries LLM *token* streams, so a node
10
- that returns a finished (non-token-streamed) AIMessage rendered an empty turn.
11
- The default is now 'auto' (dual updates+messages) so that agent shape — the one
12
- the README's own "Creating Your Own Agent" example produces — still renders.
13
- """
14
-
15
- import textwrap
16
-
17
- from click.testing import CliRunner
18
-
19
- from langstage_cli.cli import main
20
-
21
- # A graph whose node returns a *finished* AIMessage (no token streaming) — the
22
- # shape that was a silent blank turn under bare 'messages'.
23
- FINISHED_AIMESSAGE_AGENT = textwrap.dedent(
24
- """
25
- from langchain_core.messages import AIMessage
26
- from langgraph.graph import START, END, StateGraph, MessagesState
27
-
28
- def _respond(state):
29
- return {"messages": [AIMessage(content="FINISHED_MARKER_42")]}
30
-
31
- builder = StateGraph(MessagesState)
32
- builder.add_node("respond", _respond)
33
- builder.add_edge(START, "respond")
34
- builder.add_edge("respond", END)
35
- graph = builder.compile()
36
- """
37
- )
38
-
39
-
40
- def test_default_auto_renders_finished_aimessage(tmp_path, monkeypatch):
41
- """Regression: the default mode must render a finished AIMessage's content."""
42
- (tmp_path / "fin_agent.py").write_text(FINISHED_AIMESSAGE_AGENT)
43
- monkeypatch.chdir(tmp_path)
44
- r = CliRunner().invoke(main, ["-a", "fin_agent.py:graph", "--no-interactive", "ping"])
45
- assert r.exit_code == 0, r.output
46
- assert "FINISHED_MARKER_42" in r.output
47
-
48
-
49
- def test_default_stream_mode_is_auto():
50
- r = CliRunner().invoke(main, ["--show-config"])
51
- assert r.exit_code == 0, r.output
52
- assert "auto" in r.output
53
-
54
-
55
- def test_stream_mode_values_flag_rejected_cleanly():
56
- r = CliRunner().invoke(main, ["--demo", "--no-interactive", "--stream-mode", "values", "hi"])
57
- assert r.exit_code != 0
58
- assert "values" in r.output
59
- assert "Fatal" not in r.output # no interpreter-shutdown crash
60
-
61
-
62
- def test_stream_mode_messages_flag_accepted():
63
- # A valid mode must still be accepted by the Choice (parses, doesn't error on the flag).
64
- r = CliRunner().invoke(main, ["--stream-mode", "messages", "--show-config"])
65
- assert r.exit_code == 0, r.output
66
- assert "messages" in r.output
67
-
68
-
69
- def test_stream_mode_values_via_env_rejected():
70
- r = CliRunner().invoke(
71
- main,
72
- ["--demo", "--no-interactive", "hi"],
73
- env={"LANGSTAGE_STREAM_MODE": "values"},
74
- )
75
- assert r.exit_code == 2, r.output
76
- assert "unsupported stream mode" in r.output.lower()
File without changes
File without changes