|
|
@@ -13,11 +13,11 @@ metadata:
|
|
|
|
|
|
## Where knowledge lives — what goes where
|
|
|
|
|
|
-Three homes, one rule each. Pick the destination by asking "does this travel with the plugin?":
|
|
|
+Three homes, one rule each. Pick the destination by asking "does this travel with the plugin?" — and note that only the first two ship with it:
|
|
|
|
|
|
- **This skill** — plugin behavior that holds on ANY installation: what the tools do, debugging procedures tied to plugin code (gates, realtime stream, tool contract), lessons a fresh checkout would rediscover the hard way. If it references `~/.hermes` paths of a particular machine, a particular server, or a local Hermes checkout's quirks, it does NOT belong here.
|
|
|
- **The repo root's `AGENTS.md`** — the binding rulebook for WORK ON THE REPO: editing, gates, vendoring, structure rules (e.g. how tools register through two doors). This skill does not repeat those rules; when a lesson belongs there, fix `AGENTS.md` instead.
|
|
|
-- **The operator's local skill (`chatto-local-preferences`)** — installation facts and personal workflow: server URL, room IDs, cron jobs, how to run the suite against THIS machine's Hermes checkout, baseline quirks of THIS dev environment (pre-existing failures, checker noise), the user's git/merge preferences.
|
|
|
+- **The operator's own local skill**, where the operator keeps one (`chatto-local-preferences` on this installation) — installation facts and personal workflow: server URL, room IDs, cron jobs, how to run the suite against THIS machine's Hermes checkout, baseline quirks of THIS dev environment (pre-existing failures, checker noise), the user's git/merge preferences. Nothing here depends on it existing.
|
|
|
|
|
|
When in doubt: a lesson written because of one bug is upstream-worthy only if another installation could hit the same bug from the plugin's own code — not from a stale local checkout.
|
|
|
|
|
|
@@ -54,7 +54,7 @@ Resolves the room (same `_resolve_room` as the send tools) and fetches the messa
|
|
|
|
|
|
### chatto_get_history(room_id|room_name, thread_root_event_id?, limit?) — read recent context
|
|
|
|
|
|
-Reads the newest messages of a room (`client.get_room_events`) or of one thread (`client.get_thread_events`), returned oldest-first (`_oldest_first`, the same ordering the thread seeding uses — the server's own ordering is not a contract). `limit` 1-100 (default 20), `has_older` in the result says whether more history exists. Author logins come from the timeline page's own `users_by_id` directory first; actors missing there are hydrated via `batch_get_room_members`. Same room resolution and the same thread-root rule as `chatto_send_room` — both go through `_resolve_thread_root`, so the two tools cannot disagree about what a root is; a thread page that omits its root gets it prepended (the root is already in hand from that check). Not gated on the mention lists either — the server's own visibility rules decide what the bot may read.
|
|
|
+Reads the newest messages of a room (`client.get_room_events`) or of one thread (`client.get_thread_events`), returned oldest-first (`_oldest_first`, the same ordering the thread seeding uses — the server's own ordering is not a contract). `limit` 1-100 (default 20), `has_older` in the result says whether more history exists — but there is no `before` argument, so the newest page is the only one reachable (`client.get_room_events` takes one; adding it to the schema is the whole change). Author logins come from the timeline page's own `users_by_id` directory first; actors missing there are hydrated via `batch_get_room_members`. Same room resolution and the same thread-root rule as `chatto_send_room` — both go through `_resolve_thread_root`, so the two tools cannot disagree about what a root is; a thread page that omits its root gets it prepended (the root is already in hand from that check). Not gated on the mention lists either — the server's own visibility rules decide what the bot may read.
|
|
|
|
|
|
### chatto_send_dm(user_login|user_id, message) — DM a user
|
|
|
|
|
|
@@ -108,5 +108,5 @@ A message that produced no dispatch, no reaction and no log line usually was not
|
|
|
2. **Gateway restart is REQUIRED after plugin code changes** — the running Hermes session keeps the old module in memory. The user's `/restart` chat command restarts in-band (gateway exits with code 75, the service manager relaunches); a restart issued from a tool terminal inside the gateway is blocked, because the gateway kills its own child processes on shutdown. Verify via `tool_call` afterwards.
|
|
|
3. **Before requesting a restart, verify config-dependent fixes in an isolated subprocess** simulating the cron scope: pop the relevant env vars (`CHATTO_TOKEN`, `CHATTO_BASE_URL`), `sys.path.insert` the Hermes kernel and plugin dir, set `HERMES_HOME`, call the handler directly. A green isolated test proves the cron path before burning a restart cycle.
|
|
|
4. **Bundled skills:** add a `skills/<name>/SKILL.md` and register it via `_register_bundled_skills(ctx)`, which passes the SKILL.md frontmatter `description` through — `register_skill` stores it verbatim and does not read the frontmatter itself, so an empty description leaves the skill listed with nothing explaining when to load it.
|
|
|
-5. **Run the suite against a Hermes checkout:** the Hermes kernel is not a plugin dependency and `yaml` is not in the dev group, so a plain run dies at import. The invocation — kernel on `PYTHONPATH`, the two `--with` packages, and the reason to re-check them when the checkout moves — is `AGENTS.md` → *Development*; which checkout sits where on this machine is the operator's local skill.
|
|
|
+5. **Run the suite against a Hermes checkout:** the Hermes kernel is not a plugin dependency and `yaml` is not in the dev group, so a plain run dies at import. The invocation — kernel on `PYTHONPATH`, the two `--with` packages, and the reason to re-check them when the checkout moves — is `AGENTS.md` → *Development*; which checkout sits where is machine-local, and belongs in the operator's local skill where there is one.
|
|
|
6. **Mock event payloads to the wire shape, not to the attributes the code happens to read:** a bare `MagicMock()` payload answers any attribute, so tests pass on fields the real event never carries — use `MagicMock(spec=[...])` with exactly the proto's fields. Same family: a MagicMock stub client auto-creates plain (non-async) Mocks for unassigned methods, so assign `AsyncMock` explicitly before any `assert_not_awaited`.
|