Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
de664a8
spec: dimos spy task package — universal transport spy
leshdaemon Jul 6, 2026
7c7720c
feat: dimos spy — universal transport spy over LCM + Zenoh
leshdaemon Jul 6, 2026
fae1dc7
chore: drop internal task package file before publishing
leshdaemon Jul 6, 2026
1ed1963
refactor: harden spy lifecycle + transport selection
leshdaemon Jul 6, 2026
e55a1a2
refactor: make lcmspy a thin alias for spy --transport lcm
leshdaemon Jul 6, 2026
d29c740
fix: degrade default spy on missing backends, forward web-mode filters
leshdaemon Jul 6, 2026
22f5442
fix: route lcmspy web through spy web mode
leshdaemon Jul 6, 2026
bf1dac1
revert: keep zenoh subscribe_all conflating; spy stays protocol-agnostic
leshdaemon Jul 6, 2026
851d864
feat: remove spy web mode; fix lcmspy console entry arg handling
leshdaemon Jul 6, 2026
3f4337f
Merge branch 'main' into feat/ivan/spy
leshy Jul 6, 2026
1937f19
fix: spy rejects stray positionals; default_sources degrades on any e…
leshdaemon Jul 6, 2026
2d710f7
fix: spy default path survives per-source start failures
leshdaemon Jul 6, 2026
d517f28
docs: fold dimos spy page into the transports doc
leshdaemon Jul 6, 2026
a2e02c1
comment correction
leshy Jul 6, 2026
00071ad
refactor: move spy core out of protocol/pubsub into the spy tool package
leshdaemon Jul 6, 2026
506a3a0
Merge branch 'main' into feat/ivan/spy
leshy Jul 6, 2026
c97b24f
docs: document dimos spy in cli.md, mark lcmspy as deprecated alias
leshdaemon Jul 6, 2026
3608002
refactor(spy): rename _lcm_only_argv to lcm_only_argv
paul-nechifor Jul 6, 2026
4f7a6f6
refactor(spy): move inline imports to the top of their modules
paul-nechifor Jul 6, 2026
92695be
refactor(spy): share the default history window as one constant
paul-nechifor Jul 6, 2026
7ab39df
refactor(spy): name the gradient saturation magic numbers
paul-nechifor Jul 6, 2026
6cc7b26
fix(spy): resolve type ignores in run_spy with real types
paul-nechifor Jul 6, 2026
9a19f03
refactor(spy): extract duplicated unknown-transport validation
paul-nechifor Jul 6, 2026
2e0df3e
refactor(spy): stop doing work in SpyApp.__init__
paul-nechifor Jul 6, 2026
a01cbe1
fix(spy): emit degrade warnings via the project logger
paul-nechifor Jul 6, 2026
e914ff9
fix(spy): don't abort TransportSpy.stop() on the first error
paul-nechifor Jul 6, 2026
095feec
test: guarantee spy subscription/lifecycle cleanup on assertion failure
leshdaemon Jul 6, 2026
79967db
refactor(spy): remove unused TopicStats.avg_size
paul-nechifor Jul 6, 2026
f705bce
fix(spy): read shared TopicStats fields under the lock, in one pass
paul-nechifor Jul 6, 2026
0329aaf
chore(spy): drop stale TASK.md reference from test docstring
paul-nechifor Jul 6, 2026
510a8ea
refactor(spy): tear down test resources via fixtures
paul-nechifor Jul 6, 2026
fe5859e
fix: dedupe sys import and type SpyApp's App generic in run_spy
leshdaemon Jul 6, 2026
86c3f56
refactor(spy): poll for subscription establishment instead of sleeping
paul-nechifor Jul 6, 2026
bab0fe4
fix: reject root-level --transport before the spy subcommand
leshdaemon Jul 6, 2026
52e6de0
add fix
paul-nechifor Jul 6, 2026
bf819c1
merge: Paul's spy autofixes (PR #2748) into feat/ivan/spy
leshdaemon Jul 6, 2026
9224503
Merge branch 'main' into feat/ivan/spy
leshy Jul 6, 2026
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
4 changes: 4 additions & 0 deletions dimos/codebase_checks/test_get_logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
"dimos/visualization/rerun/websocket_server.py",
'ws_logger = logging.getLogger("websockets.server")',
),
(
"dimos/utils/cli/spy/conftest.py",
"lg = logging.getLogger(_CORE_LOGGER)",
),
]


Expand Down
29 changes: 25 additions & 4 deletions dimos/robot/cli/dimos.py
Original file line number Diff line number Diff line change
Expand Up @@ -619,13 +619,34 @@ def list_blueprints() -> None:
typer.echo(blueprint_name)


@main.command(context_settings={"allow_extra_args": True, "ignore_unknown_options": True})
Comment thread
leshy marked this conversation as resolved.
def spy(ctx: typer.Context) -> None:
"""Universal transport spy: topics, rates, sizes across all pubsub transports."""
# A root-level `--transport` (before the subcommand) sets the stack's pubsub
# backend — which single transport dimos processes participate on. The spy is an
# observer: it watches every transport and takes its own repeatable `--transport`
# filter *after* the subcommand. The two look alike but mean different things, so
# reject the root placement rather than silently ignoring the requested filter.
if (ctx.obj or {}).get("transport") is not None:
typer.echo(
"Error: `--transport` before `spy` sets the stack backend, which the spy "
"ignores. Put the filter after the subcommand: `dimos spy --transport <name>`.",
err=True,
)
raise typer.Exit(2)
from dimos.utils.cli.spy.run_spy import main as spy_main

sys.argv = ["spy", *ctx.args]
Comment thread
paul-nechifor marked this conversation as resolved.
spy_main()


@main.command(context_settings={"allow_extra_args": True, "ignore_unknown_options": True})
def lcmspy(ctx: typer.Context) -> None:
"""LCM spy tool for monitoring LCM messages."""
from dimos.utils.cli.lcmspy.run_lcmspy import main as lcmspy_main
"""Alias for `dimos spy --transport lcm`."""
from dimos.utils.cli.spy.run_spy import lcm_only_argv, main as spy_main

sys.argv = ["lcmspy", *ctx.args]
lcmspy_main()
sys.argv = lcm_only_argv(list(ctx.args))
spy_main()


@main.command(context_settings={"allow_extra_args": True, "ignore_unknown_options": True})
Expand Down
54 changes: 54 additions & 0 deletions dimos/robot/cli/test_dimos.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import sys
from typing import Literal

import pytest
Expand All @@ -21,6 +22,7 @@
from dimos.core.global_config import global_config
from dimos.core.module import Module, ModuleConfig
from dimos.robot.cli.dimos import _normalize_simulation_argv, arg_help, main
import dimos.utils.cli.spy.run_spy as run_spy


@pytest.mark.parametrize(
Expand Down Expand Up @@ -160,3 +162,55 @@ class TestModule(Module):
" * testmodule.spam: str (default: eggs)",
"",
]


@pytest.fixture
def spy_main_argv(monkeypatch):
"""Stub run_spy.main and capture the sys.argv the lcmspy alias hands it."""
captured: list[list[str]] = []
monkeypatch.setattr(sys, "argv", ["dimos"])
monkeypatch.setattr(run_spy, "main", lambda: captured.append(list(sys.argv)))
return captured


def test_lcmspy_alias_prepends_lcm_transport(spy_main_argv):
result = CliRunner().invoke(main, ["lcmspy"])
assert result.exit_code == 0, result.output
assert spy_main_argv == [["spy", "--transport", "lcm"]]


def test_lcmspy_alias_rejects_transport_override(spy_main_argv):
result = CliRunner().invoke(main, ["lcmspy", "--transport", "zenoh"])
assert result.exit_code == 1
assert "LCM-only" in result.output
assert spy_main_argv == [] # never reaches the spy


def test_spy_cmd_rejects_stray_positional(monkeypatch):
# A stray positional must fail loudly, not silently launch the TUI.
monkeypatch.setattr(sys, "argv", ["dimos"])
result = CliRunner().invoke(main, ["spy", "foo"])
assert result.exit_code == 1
assert "unexpected" in result.output.lower()


def test_lcmspy_rejects_stray_positional(monkeypatch):
monkeypatch.setattr(sys, "argv", ["dimos"])
result = CliRunner().invoke(main, ["lcmspy", "foo"])
assert result.exit_code == 1
assert "unexpected" in result.output.lower()


def test_spy_rejects_root_transport(monkeypatch, spy_main_argv):
# A root-level `--transport` (before the subcommand) sets the stack backend,
# which the spy ignores. Rather than silently show all transports, error and
# point at the subcommand-level filter.
monkeypatch.setattr(sys, "argv", ["dimos"])
original = global_config.transport
try:
result = CliRunner().invoke(main, ["--transport", "zenoh", "spy"])
finally:
global_config.update(transport=original)
assert result.exit_code == 2
assert "dimos spy --transport" in result.output
assert spy_main_argv == [] # never reaches the spy
196 changes: 0 additions & 196 deletions dimos/utils/cli/lcmspy/lcmspy.py

This file was deleted.

Loading
Loading