diff --git a/.forgejo/workflows/master.yml b/.forgejo/workflows/master.yml index 7c959ba40..ccdede22d 100644 --- a/.forgejo/workflows/master.yml +++ b/.forgejo/workflows/master.yml @@ -3,8 +3,6 @@ name: CI on: push: branches: [master, develop] - pull_request: - branches: [master, develop] vars: docker_prefix: "http://harbor.cleverthis.com/docker/" diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c9d662fd..e490ab29d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,7 +75,16 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). `provisioning_state: discovered`. Named configurations (`.devcontainer//devcontainer.json`) are also discovered and carry the configuration name in the `config_name` property. This wires the previously-isolated `discover_devcontainers()` function into the production - code path, enabling the spec's zero-configuration devcontainer experience. + code path, enabling the spec's zero-configuration devcontainer experience. + +- **`agents session list --format json` empty-case emits correct summary structure** (#6695): + Fixed the empty-sessions early-return path in `session.py` to reuse `_session_list_dict([])` + instead of passing `{\"sessions\": [], \"total\": 0}` directly. The previous code emitted + `total` at the top level and omitted the required `summary` sub-object entirely. Now + `agents session list --format json` with zero sessions produces `{\"sessions\": [], + \"summary\": {\"total\": 0, \"most_recent\": null, \"oldest\": null, \"total_messages\": 0, + \"storage\": \"0 KB\"}}`, which matches the spec-aligned structure used by all non-empty + output paths. (#6316) ### Changed diff --git a/features/session_list_error.feature b/features/session_list_error.feature index ea38934ad..09fa75ec5 100644 --- a/features/session_list_error.feature +++ b/features/session_list_error.feature @@ -70,6 +70,7 @@ Feature: Session list command handles missing database gracefully Then the session-list-error command should exit successfully And the session-list-error output should be valid JSON containing "sessions" And the session-list-error output should not contain "AttributeError" + And the session-list-error JSON summary should represent an empty result @tdd_issue @tdd_issue_554 @tdd_issue @tdd_issue_4270 @tdd_expected_fail diff --git a/features/steps/session_list_error_steps.py b/features/steps/session_list_error_steps.py index 4126b0a67..2f72f4499 100644 --- a/features/steps/session_list_error_steps.py +++ b/features/steps/session_list_error_steps.py @@ -256,6 +256,50 @@ def step_output_json_key(context: Context, key: str) -> None: ) +@then("the session-list-error JSON summary should represent an empty result") +def step_json_empty_summary(context: Context) -> None: + result = context.sle_result + assert result is not None, "No command was invoked" + try: + parsed = json.loads(result.output) + except json.JSONDecodeError as exc: + raise AssertionError(f"Output is not valid JSON:\n{result.output}") from exc + + data = _unwrap_envelope(parsed) + assert isinstance(data, dict), f"Expected JSON object, got {type(data)}: {data}" + + sessions = data.get("sessions") + assert isinstance(sessions, list), ( + f"Expected 'sessions' to be a list, got {type(sessions)}: {sessions}" + ) + assert not sessions, f"Expected no sessions but found: {sessions}" + + assert "total" not in data, ( + "Top-level 'total' key should not be present; expected value nested in 'summary'." + ) + + summary = data.get("summary") + assert isinstance(summary, dict), ( + f"Expected 'summary' to be a dict, got {type(summary)}: {summary}" + ) + + expected_keys = {"total", "most_recent", "oldest", "total_messages", "storage"} + missing_keys = expected_keys - set(summary) + assert not missing_keys, f"Missing summary keys: {sorted(missing_keys)}" + + assert summary["total"] == 0, f"Expected summary total 0, got {summary['total']}" + assert summary["total_messages"] == 0, ( + f"Expected total_messages 0, got {summary['total_messages']}" + ) + assert summary["most_recent"] is None, ( + f"Expected most_recent None, got {summary['most_recent']}" + ) + assert summary["oldest"] is None, f"Expected oldest None, got {summary['oldest']}" + assert summary["storage"] == "0 KB", ( + f"Expected storage '0 KB', got {summary['storage']}" + ) + + @then('the session-list-error output should be valid YAML containing "{key}"') def step_output_yaml_key(context: Context, key: str) -> None: result = context.sle_result diff --git a/src/cleveragents/cli/commands/session.py b/src/cleveragents/cli/commands/session.py index 27a9f1c99..aa720be2f 100644 --- a/src/cleveragents/cli/commands/session.py +++ b/src/cleveragents/cli/commands/session.py @@ -329,7 +329,7 @@ def list_sessions( # For machine-readable formats, always emit a structured empty list # so that callers parsing JSON/YAML receive valid output. if fmt not in (OutputFormat.RICH.value, OutputFormat.COLOR.value): - typer.echo(format_output({"sessions": [], "total": 0}, fmt)) + typer.echo(format_output(_session_list_dict([]), fmt)) return console.print("[yellow]No sessions found.[/yellow]") console.print("Create one with 'agents session create'")