Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions packages/hermes-tlon-adapter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,13 +293,15 @@ The seven keys above are the full "dashboard edit works" set. Everything else Tl

```
*Harness*: **Hermes**
*Harness Version*: **0.17.0 (2026.6.19)**
*Adapter Version*: **0.1.0**
*Tlon Skill*: **0.3.2**
*Fingerprint*: **fp1:3f9a2c1b8d02**
*Source*: **lb/hermes-init @ d8ae0c4e (clean)**
```

- **Harness** — always `Hermes`; identifies which bot framework is running this node at a glance.
- **Harness Version** — the running Hermes Agent's own version, read from its `__version__` and `__release_date__` constants (the same source its `/version` command uses), with the installed distribution's metadata as a fallback. Reads `unknown` when the host reports neither, so the row is never dropped and the reply stays line-for-line comparable with OpenClaw's.
- **Adapter Version** — semver from this package's `package.json`, bumped at releases.
- **Tlon Skill** — version of the packaged `@tloncorp/tlon-skill` CLI (first line of `tlon --version`).
- **Fingerprint** — sha256 over the runtime files (non-test `*.py`, `plugin.yaml`, `prompts/`), so copied or hand-patched installs are still identifiable. To match a fingerprint to a commit, recompute it at a candidate checkout: `python3 -c "import version; print(version.content_fingerprint())"` from this directory.
Expand Down
2 changes: 2 additions & 0 deletions packages/hermes-tlon-adapter/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -1052,6 +1052,7 @@ async def connect(self, *, is_reconnect: bool = False) -> bool:
source=source,
fingerprint=fingerprint,
cli_version=cli_version,
harness_version=self._harness_version(),
markdown=False,
).replace("\n", " | "),
)
Expand Down Expand Up @@ -2272,6 +2273,7 @@ async def _version_reply(self) -> str:
source=await git_source(),
fingerprint=content_fingerprint(),
cli_version=await self._cli_version(),
harness_version=self._harness_version(),
)

async def _cli_version(self) -> str:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ The platform adapter handles these owner-only chat commands deterministically, s
- `/owner-listen [on|off|status|list] [<nest>|<~host/group>]`, `/owner-listen all [on|off]`, `/owner-listen default [owned|all]` — owner-listen lets the owner be heard without a mention. Channels hosted by the bot or owner are on by default; any channel or whole group can be opted in or out, and `default all` extends the default to every monitored channel.
- `/channel-access [open|restricted|status|list] [<nest>]` — open lets anyone in that channel address the bot with a mention; restricted (default) limits it to authorized or approved ships.
- `/pending`, `/allow <id>`, `/reject <id>`, `/ban <id|~ship>`, `/unban ~ship`, `/banned` — access is deny-by-default. Unknown ships that DM the bot, mention it in restricted channels, or invite it to a group queue for owner approval (approving a group invite joins it). The owner gets a DM with an approval card; the buttons send these same commands.
- `/tlon version` — reports the running adapter version, source commit, content fingerprint, and `tlon` CLI version. (`/tlon-version` is a legacy alias for the same output.)
- `/tlon version` — reports the running Hermes harness version, adapter version, source commit, content fingerprint, and `tlon` CLI version. (`/tlon-version` is a legacy alias for the same output.)
- `/tlon status storage` — reports image-upload storage debug info: node URL, whether it looks hosted, the `TLON_HOSTING` override, storage service, S3 credentials, `%genuine` reachability, and the resolved upload path.
- `/tlon status binary` — identifies the exact `tlon` CLI the adapter invokes: version, a content hash (so two builds of one version are distinguishable), size, and build time.
- `/tlon status telemetry`, `/tlon status telemetry test` — reports telemetry status (whether it is enabled and why, the PostHog identity events are sent under, and any delivery failures); `test` sends a test event and confirms whether PostHog accepted it.
Expand Down
22 changes: 15 additions & 7 deletions packages/hermes-tlon-adapter/test_adapter_owner_listen.py
Original file line number Diff line number Diff line change
Expand Up @@ -1167,20 +1167,26 @@ def test_migrate_command_from_non_owner_is_not_intercepted(self):
def test_version_command_replies_with_field_lines(self):
adapter = self.make_adapter({})
adapter._cli = FakeCLI()
# A value nothing else in the tree can produce, so the row can only be
# right if the reply is actually wired to the resolver. A regex on
# "some nonempty string" passes even when the wiring is dropped and
# every bot silently reports `unknown`.
adapter._harness_version_cache = "harness-sentinel-9.9.9"

events = self.dispatches(adapter, channel_event("/tlon-version"))

self.assertEqual(events, [])
self.assertEqual(len(adapter._cli.messages), 1)
self.assertEqual(adapter._cli.messages[0][0], "chat/~pen/general")
lines = adapter._cli.messages[0][1].splitlines()
self.assertEqual(len(lines), 5)
self.assertEqual(len(lines), 6)
self.assertEqual(lines[0], "*Harness*: **Hermes**")
# exact version is covered in test_version; here we pin field + format
self.assertRegex(lines[1], r"^\*Adapter Version\*: \*\*.+\*\*$")
self.assertEqual(lines[2], "*Tlon Skill*: **0.3.2**")
self.assertRegex(lines[3], r"^\*Fingerprint\*: \*\*fp1:[0-9a-f]{12}\*\*$")
self.assertTrue(lines[4].startswith("*Source*: **"))
self.assertEqual(lines[1], "*Harness Version*: **harness-sentinel-9.9.9**")
# exact versions are covered in test_version; here we pin field + format
self.assertRegex(lines[2], r"^\*Adapter Version\*: \*\*.+\*\*$")
self.assertEqual(lines[3], "*Tlon Skill*: **0.3.2**")
self.assertRegex(lines[4], r"^\*Fingerprint\*: \*\*fp1:[0-9a-f]{12}\*\*$")
self.assertTrue(lines[5].startswith("*Source*: **"))
self.assertIn(("--version",), adapter._cli.commands)

def test_version_command_works_from_dm_and_with_mention(self):
Expand Down Expand Up @@ -1223,13 +1229,15 @@ def test_version_command_reports_cli_failure(self):
def test_tlon_version_subcommand(self):
adapter = self.make_adapter({})
adapter._cli = FakeCLI()
adapter._harness_version_cache = "harness-sentinel-9.9.9"

events = self.dispatches(adapter, channel_event("/tlon version"))

self.assertEqual(events, [])
lines = adapter._cli.messages[0][1].splitlines()
self.assertEqual(lines[0], "*Harness*: **Hermes**")
self.assertRegex(lines[1], r"^\*Adapter Version\*: \*\*.+\*\*$")
self.assertEqual(lines[1], "*Harness Version*: **harness-sentinel-9.9.9**")
self.assertRegex(lines[2], r"^\*Adapter Version\*: \*\*.+\*\*$")

def test_tlon_status_telemetry_subcommand(self):
adapter = self.make_adapter({})
Expand Down
20 changes: 20 additions & 0 deletions packages/hermes-tlon-adapter/test_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,18 +117,38 @@ def test_field_per_line_format(self):
source="main @ abc1234 (clean)",
fingerprint="fp1:0123456789ab",
cli_version="0.3.2",
harness_version="0.17.0 (2026.6.19)",
)
self.assertEqual(
reply.splitlines(),
[
"*Harness*: **Hermes**",
"*Harness Version*: **0.17.0 (2026.6.19)**",
"*Adapter Version*: **0.1.0**",
"*Tlon Skill*: **0.3.2**",
"*Fingerprint*: **fp1:0123456789ab**",
"*Source*: **main @ abc1234 (clean)**",
],
)

def test_absent_harness_version_renders_unknown(self):
# The row is never dropped: OpenClaw always emits it, and the README
# promises the two harnesses answer with the same field-per-line
# summary. A host that reports nothing degrades the value, not the
# shape.
for absent in (None, "", " "):
with self.subTest(harness_version=absent):
reply = version.format_version_reply(
adapter_version="0.1.0",
source=None,
fingerprint="fp1:0123456789ab",
cli_version="0.3.2",
harness_version=absent,
)
self.assertEqual(
reply.splitlines()[1], "*Harness Version*: **unknown**"
)

def test_plain_keys_for_logs(self):
reply = version.format_version_reply(
adapter_version="0.1.0",
Expand Down
9 changes: 8 additions & 1 deletion packages/hermes-tlon-adapter/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,18 +125,25 @@ def format_version_reply(
source: Optional[str],
fingerprint: str,
cli_version: str,
harness_version: Optional[str] = None,
markdown: bool = True,
) -> str:
"""Field-per-line version summary. For the chat reply keys are italicized
and values bolded (Tlon markdown) for scannability; pass ``markdown=False``
for plain log output."""
for plain log output.

``harness_version`` is the running Hermes Agent's own version, taken by the
caller (the adapter resolves and caches it). It renders as ``unknown`` when
the host reports nothing, matching OpenClaw: the row always appears, so the
two harnesses' replies stay line-for-line comparable."""

def row(label: str, value: str) -> str:
return f"*{label}*: **{value}**" if markdown else f"{label}: {value}"

return "\n".join(
[
row("Harness", "Hermes"),
row("Harness Version", (harness_version or "").strip() or "unknown"),
row("Adapter Version", adapter_version),
row("Tlon Skill", cli_version),
row("Fingerprint", fingerprint),
Expand Down
Loading