Add content-only workspace read modes

Make the human workspace read commands easier to use in chat transcripts and shell pipelines by adding CLI-only --content-only to workspace file read and workspace disk read.

Keep JSON, SDK, and MCP behavior unchanged while fixing the default human rendering so content without a trailing newline is cleanly separated from the summary footer.

Update the 3.9.0 docs and roadmap status, and add CLI regression coverage plus a real guest-backed smoke for live and stopped-disk reads.
This commit is contained in:
Thales Maciel 2026-03-13 11:43:40 -03:00
parent 407c805ce2
commit 22d284b1f5
13 changed files with 314 additions and 46 deletions

View file

@ -159,6 +159,7 @@ def test_cli_subcommand_help_includes_examples_and_guidance() -> None:
_subparser_choice(_subparser_choice(parser, "workspace"), "file"), "read"
).format_help()
assert "--max-bytes" in workspace_file_read_help
assert "--content-only" in workspace_file_read_help
workspace_file_write_help = _subparser_choice(
_subparser_choice(_subparser_choice(parser, "workspace"), "file"), "write"
@ -207,6 +208,7 @@ def test_cli_subcommand_help_includes_examples_and_guidance() -> None:
_subparser_choice(_subparser_choice(parser, "workspace"), "disk"), "read"
).format_help()
assert "--max-bytes" in workspace_disk_read_help
assert "--content-only" in workspace_disk_read_help
workspace_diff_help = _subparser_choice(
_subparser_choice(parser, "workspace"), "diff"
@ -637,6 +639,32 @@ def test_cli_shortcut_flags_are_mutually_exclusive() -> None:
]
)
with pytest.raises(SystemExit):
parser.parse_args(
[
"workspace",
"file",
"read",
"workspace-123",
"note.txt",
"--json",
"--content-only",
]
)
with pytest.raises(SystemExit):
parser.parse_args(
[
"workspace",
"disk",
"read",
"workspace-123",
"note.txt",
"--json",
"--content-only",
]
)
def test_cli_workspace_create_prints_json(
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
@ -1523,6 +1551,184 @@ def test_cli_workspace_disk_commands_print_human_and_json(
assert read_payload["content"] == "hello\n"
def test_cli_workspace_file_read_human_separates_summary_from_content(
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str],
) -> None:
class StubPyro:
def read_workspace_file(
self,
workspace_id: str,
path: str,
*,
max_bytes: int,
) -> dict[str, Any]:
assert workspace_id == "workspace-123"
assert path == "note.txt"
assert max_bytes == 4096
return {
"workspace_id": workspace_id,
"path": "/workspace/note.txt",
"size_bytes": 5,
"max_bytes": max_bytes,
"content": "hello",
"truncated": False,
}
class StubParser:
def parse_args(self) -> argparse.Namespace:
return argparse.Namespace(
command="workspace",
workspace_command="file",
workspace_file_command="read",
workspace_id="workspace-123",
path="note.txt",
max_bytes=4096,
content_only=False,
json=False,
)
monkeypatch.setattr(cli, "Pyro", StubPyro)
monkeypatch.setattr(cli, "_build_parser", lambda: StubParser())
cli.main()
captured = capsys.readouterr()
assert captured.out == "hello\n"
assert "[workspace-file-read] workspace_id=workspace-123" in captured.err
def test_cli_workspace_file_read_content_only_suppresses_summary(
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str],
) -> None:
class StubPyro:
def read_workspace_file(
self,
workspace_id: str,
path: str,
*,
max_bytes: int,
) -> dict[str, Any]:
return {
"workspace_id": workspace_id,
"path": "/workspace/note.txt",
"size_bytes": 5,
"max_bytes": max_bytes,
"content": "hello",
"truncated": False,
}
class StubParser:
def parse_args(self) -> argparse.Namespace:
return argparse.Namespace(
command="workspace",
workspace_command="file",
workspace_file_command="read",
workspace_id="workspace-123",
path="note.txt",
max_bytes=4096,
content_only=True,
json=False,
)
monkeypatch.setattr(cli, "Pyro", StubPyro)
monkeypatch.setattr(cli, "_build_parser", lambda: StubParser())
cli.main()
captured = capsys.readouterr()
assert captured.out == "hello"
assert captured.err == ""
def test_cli_workspace_disk_read_human_separates_summary_from_content(
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str],
) -> None:
class StubPyro:
def read_workspace_disk(
self,
workspace_id: str,
path: str,
*,
max_bytes: int,
) -> dict[str, Any]:
assert workspace_id == "workspace-123"
assert path == "note.txt"
assert max_bytes == 4096
return {
"workspace_id": workspace_id,
"path": "/workspace/note.txt",
"size_bytes": 5,
"max_bytes": max_bytes,
"content": "hello",
"truncated": False,
}
class StubParser:
def parse_args(self) -> argparse.Namespace:
return argparse.Namespace(
command="workspace",
workspace_command="disk",
workspace_disk_command="read",
workspace_id="workspace-123",
path="note.txt",
max_bytes=4096,
content_only=False,
json=False,
)
monkeypatch.setattr(cli, "Pyro", StubPyro)
monkeypatch.setattr(cli, "_build_parser", lambda: StubParser())
cli.main()
captured = capsys.readouterr()
assert captured.out == "hello\n"
assert "[workspace-disk-read] workspace_id=workspace-123" in captured.err
def test_cli_workspace_disk_read_content_only_suppresses_summary(
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str],
) -> None:
class StubPyro:
def read_workspace_disk(
self,
workspace_id: str,
path: str,
*,
max_bytes: int,
) -> dict[str, Any]:
return {
"workspace_id": workspace_id,
"path": "/workspace/note.txt",
"size_bytes": 5,
"max_bytes": max_bytes,
"content": "hello",
"truncated": False,
}
class StubParser:
def parse_args(self) -> argparse.Namespace:
return argparse.Namespace(
command="workspace",
workspace_command="disk",
workspace_disk_command="read",
workspace_id="workspace-123",
path="note.txt",
max_bytes=4096,
content_only=True,
json=False,
)
monkeypatch.setattr(cli, "Pyro", StubPyro)
monkeypatch.setattr(cli, "_build_parser", lambda: StubParser())
cli.main()
captured = capsys.readouterr()
assert captured.out == "hello"
assert captured.err == ""
def test_cli_workspace_diff_prints_human_output(
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str],
@ -2618,6 +2824,17 @@ def test_chat_host_docs_and_examples_recommend_workspace_core() -> None:
assert "Recommended default for most chat hosts: `workspace-core`." in mcp_config
def test_content_only_read_docs_are_aligned() -> None:
readme = Path("README.md").read_text(encoding="utf-8")
install = Path("docs/install.md").read_text(encoding="utf-8")
first_run = Path("docs/first-run.md").read_text(encoding="utf-8")
assert 'workspace file read "$WORKSPACE_ID" note.txt --content-only' in readme
assert 'workspace file read "$WORKSPACE_ID" note.txt --content-only' in install
assert 'workspace file read "$WORKSPACE_ID" note.txt --content-only' in first_run
assert 'workspace disk read "$WORKSPACE_ID" note.txt --content-only' in first_run
def test_cli_workspace_shell_write_signal_close_json(
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str],