Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
a3ce6f9
fix(cron): lifecycle guard — never crash on binary referenced paths, …
teknium1 Aug 6, 2026
767e7b1
fix(db): never downgrade journal mode on a database with concurrent o…
teknium1 Aug 6, 2026
7053692
fix(tests): fail hard when pytest resolves the production state.db (l…
teknium1 Aug 6, 2026
52b7d7c
fix(tests): resolve guard's production root via expanduser, immune to…
teknium1 Aug 6, 2026
654a299
fix(delegation): surface a child's undelivered steer instead of dropp…
TheSmokeDev Aug 3, 2026
1cb96f4
fix(delegation): harden steer lifecycle ownership
TheSmokeDev Aug 6, 2026
674e73a
fix(delegation): bind steering to session generation
TheSmokeDev Aug 6, 2026
8bb4a3c
refactor(skills): move polymarket to optional-skills/finance
teknium1 Aug 6, 2026
ad4d270
fix(launchd): stop stranding gateway label on plist reload
rjhilgefort Aug 6, 2026
da72620
fix(launchd): require a supervised PID to call a reload successful
rjhilgefort Aug 6, 2026
b4c3ffb
chore: map rjhilgefort@gmail.com -> rjhilgefort in contributor directory
kshitijk4poor Aug 6, 2026
c60d2a3
fix: align helper PID check with Python parser + dedupe drain-wait
kshitijk4poor Aug 6, 2026
a8bcb9a
fix(agent): the desktop's tools reach it on remote and cloud backends…
OutThisLife Aug 7, 2026
75f1331
docs(agents): surface capability belongs to the session, not the proc…
OutThisLife Aug 7, 2026
ec94072
docs(reference): document the desktop_ui toolset
OutThisLife Aug 7, 2026
92237b1
Fireworks user agent (#80422)
rob-maron Aug 7, 2026
bb0d76d
refactor(desktop): share how a tool row renders
OutThisLife Aug 7, 2026
c791c19
feat(desktop): price a turn by what it paints
OutThisLife Aug 7, 2026
c77bbd1
feat(desktop): stop hiding a session behind Show earlier
OutThisLife Aug 7, 2026
2da61df
fix(api): persist session pins instead of 400ing them
OutThisLife Aug 7, 2026
6f5a39f
fix(desktop): stop dropping pinned sessions past the page limit
OutThisLife Aug 7, 2026
486a1b8
fix(desktop): hold the pin write guard until a page confirms it
OutThisLife Aug 7, 2026
539c0ef
fix(desktop): show a pinned session once, and keep its drag order
OutThisLife Aug 7, 2026
6fd31b1
fix(desktop): rank dragged sessions inside their date group
OutThisLife Aug 7, 2026
2847d2b
fix(desktop): show every pinned session, however many there are
OutThisLife Aug 7, 2026
57525f8
fix(desktop): keep elapsed status text from overlapping
OutThisLife Aug 7, 2026
109adff
fmt(js): `npm run fix` on merge (#80725)
hermes-seaeye[bot] Aug 7, 2026
ab03321
fix(desktop): drop the gateway-pill dogfood plugin
OutThisLife Aug 7, 2026
2bfd756
fix(reasoning): keep gpt-5.x summary parts as separate blocks on the …
OutThisLife Aug 7, 2026
b674c8f
fix(codex): split reasoning summary parts on summary_index
OutThisLife Aug 7, 2026
a79c153
fix(desktop): render already-glued reasoning as separate blocks
OutThisLife Aug 7, 2026
4ff64da
fix: preserve session history when a turn crashes
OutThisLife Aug 7, 2026
a276a4a
fix(dashboard): derive the stale-schema read probe from SCHEMA_SQL
jquesnelle Aug 7, 2026
230c33d
fix(skills): align hermes-agent-skill-authoring with hardline authori…
teknium1 Aug 7, 2026
4d7af94
feat(/learn): expansive knowledge-base skills for books and large cor…
teknium1 Aug 7, 2026
d4b0dc2
fix: dispatch cronjob(action='run') to the background like delegate_task
teknium1 Aug 7, 2026
21855e8
fix: share in-flight cron dedupe between ticker and manual runs
teknium1 Aug 7, 2026
7b6a73d
fix(cron): deliver manual runs on gateway loop
Fly-onlyone Jul 13, 2026
da1ddb2
test(cron): add _build_job_prompt extra_prompt regression tests
liuhao1024 Jul 2, 2026
39ac2be
fix(cron): thread per-run prompt through cronjob(action='run') (#57331)
liuhao1024 Jul 2, 2026
cb51ce8
fix(plugins): use asyncio.wait_for instead of ClientTimeout in Matrix…
liuhao1024 Jul 9, 2026
40765f7
docs(cron): document async manual runs and per-run prompt context
teknium1 Aug 7, 2026
d9feb44
feat(pmoves-bootstrap): CGP consumer + tools_bridge + subscriber stub…
POWERFULMOVES Aug 8, 2026
6d2935c
fix(pmoves-bootstrap): address review findings — packaging, lazy reso…
POWERFULMOVES Aug 26, 2026
e65b182
fix(ci): sync audit_pr_attribution.py with the base-ref + first-paren…
POWERFULMOVES Aug 26, 2026
557d048
fix(ci): contributor gate scans the PR's actual base, first-parent only
POWERFULMOVES Aug 26, 2026
b5b0671
fix(ci): close review findings on the attribution-gate fix — env indi…
POWERFULMOVES Aug 26, 2026
4595a54
Merge pull request #5 from POWERFULMOVES/feat/pmoves-bootstrap-unstrand
POWERFULMOVES Aug 31, 2026
875aba9
ci(hardened-sync): registry-first sync pilot — merge main into Harden…
POWERFULMOVES Sep 21, 2026
890627a
Merge remote-tracking branch 'origin/main' into PMOVES.AI-Edition-Har…
Sep 21, 2026
81a2768
sync: drop pre-migration wal-gate copy — upstream moved it to tests/h…
POWERFULMOVES Sep 21, 2026
6d03058
ci: downgrade unservable runner labels to hosted defaults
POWERFULMOVES Sep 21, 2026
faa3077
fix(sync): deduplicate merge-synthesized definitions (JS parse blocker)
POWERFULMOVES Sep 21, 2026
ac85cd2
Merge pull request #10 from POWERFULMOVES/sync/hardened-resolved-2
POWERFULMOVES Sep 21, 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: 2 additions & 2 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ jobs:
matrix:
include:
- arch: amd64
runner: ubuntu-latest-32-core
runner: ubuntu-latest
platform: linux/amd64
cache-from: type=gha,scope=docker-amd64
cache-to: type=gha,mode=max,scope=docker-amd64
Expand Down Expand Up @@ -193,7 +193,7 @@ jobs:
matrix:
include:
- arch: amd64
runner: ubuntu-latest-32-core
runner: ubuntu-latest
platform: linux/amd64
cache-from: type=gha,scope=docker-amd64
cache-to: type=gha,mode=max,scope=docker-amd64
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/e2e-desktop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
# This job builds the renderer and the electron bundle, then drives a real
# Electron app under xvfb. vite, tsc and the Playwright workers all scale
# with the core count.
runs-on: ubuntu-latest-32-core
runs-on: ubuntu-latest
timeout-minutes: 20
outputs:
review_status: ${{ steps.review-status.outputs.review_status }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/js-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
#
# One larger runner installs one time. vitest, tsc and eslint each size
# their own worker pool from the core count.
runs-on: ubuntu-latest-32-core
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/nix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:
# takes minutes and not seconds when the cache misses. `nix flake check`
# builds 21 checks, and --max-jobs defaults to the core count. It uses the
# wider runner with no more configuration.
runs-on: ubuntu-latest-32-core
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout code
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/rust-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
# cargo builds codegen units and test binaries in parallel across the
# cores. This lane also builds the crate from the start when Cargo.toml
# changes.
runs-on: ubuntu-latest-32-core
runs-on: ubuntu-latest
timeout-minutes: 30
defaults:
run:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tests-os.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jobs:
runner: macos-latest
marker: macos_only
- name: Windows-only tests
runner: windows-latest-32-core
runner: windows-latest
marker: windows_only
steps:
- name: Checkout code
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
# 96 cores clear the floor that the slowest single test file sets (about
# 82s). A second slice divides work that is already at that floor, and
# adds a second setup.
runs-on: ubuntu-latest-96-core
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout code
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,7 @@ youtube-transcript-api = false
# and the drift broke installed wheels. Do not add the list back.

[tool.setuptools.packages.find]
include = ["agent", "agent.*", "tools", "tools.*", "hermes_cli", "hermes_cli.*", "gateway", "gateway.*", "tui_gateway", "tui_gateway.*", "cron", "cron.*", "acp_adapter", "plugins", "plugins.*", "providers", "providers.*"]
include = ["agent", "agent.*", "tools", "tools.*", "hermes_cli", "hermes_cli.*", "gateway", "gateway.*", "tui_gateway", "tui_gateway.*", "cron", "cron.*", "acp_adapter", "plugins", "plugins.*", "providers", "providers.*", "pmoves_bootstrap", "pmoves_bootstrap.*"]

[tool.setuptools.package-data]
hermes_cli = ["observability/schemas/*.json", "data/*.json", "local_runtime/*.json"]
Expand Down
1 change: 1 addition & 0 deletions tests/agent/test_run_agent_codex_responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -2626,6 +2626,7 @@ def test_consume_codex_stream_leaves_unindexed_reasoning_untouched():

assert "".join(reasoning_streamed) == "Need to inspect files."

reasoning_streamed = []

def _codex_compaction_checkpoint_response(blob: str = "compaction_blob_1"):
"""A turn that returns ONLY a server-side native-compaction checkpoint.
Expand Down
16 changes: 16 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -888,6 +888,22 @@ def _state_db_write_guard(request, monkeypatch):
yield


# ── Live state.db write guard ───────────────────────────────────────────────
# Companion to the kanban guard above, for the MAIN state database.
# ``hermes_state._ensure_test_isolation`` (the single choke point every
# ``SessionDB()`` construction goes through) refuses, under pytest, any DB
# path that resolves inside the REAL Hermes root. This fixture wires the
# test-side knobs:
# • honors ``@pytest.mark.live_system_guard_bypass`` (the established
# escape-hatch marker) by disabling the state-db guard for that test;
# • injects the pre-sandbox CUSTOM production root (Docker/portable
# installs where HERMES_HOME is not ~/.hermes) into the guard's
# deny-list, mirroring the kanban deny-list capture above.
# The guard itself is env-activated (PYTEST_CURRENT_TEST / PYTEST_VERSION),
# so subprocess children that import hermes_state directly are covered even
# without this fixture.


# ── Module-level state reset — replaced by per-file process isolation ──────
#
# Each test FILE runs in a freshly-spawned ``python -m pytest <file>``
Expand Down
4 changes: 4 additions & 0 deletions tests/plugins/model_providers/test_fireworks_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ def test_user_agent_identifies_hermes(self, fireworks_profile):
# Prefix, not the full string — the version moves every release.
assert fireworks_profile.default_headers["User-Agent"].startswith("HermesAgent/")

def test_user_agent_identifies_hermes(self, fireworks_profile):
# Prefix, not the full string — the version moves every release.
assert fireworks_profile.default_headers["User-Agent"].startswith("HermesAgent/")


class TestFireworksAliases:
@pytest.mark.parametrize("alias", ["fireworks-ai", "fw"])
Expand Down
100 changes: 100 additions & 0 deletions tests/test_schema_read_probe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"""Contract tests for schema_read_probe_statements().

Read-only SessionDB opens skip _reconcile_columns() by design, so dashboard
read paths heal stale stores via a probe-then-writable-reopen dance in
``hermes_cli.web_server._open_session_db_at_path``. These tests pin the
probe's contract: it is DERIVED from SCHEMA_SQL (any column added there is
covered automatically — the previous hand-written probe went stale within
days) and it must fail at prepare time on a store missing any declared
column or table.
"""

import sqlite3

import pytest

from hermes_state_common import DEFERRED_INDEX_SQL, SCHEMA_SQL
from hermes_state_schema import SessionSchemaMixin, schema_read_probe_statements


def _fresh_schema_conn() -> sqlite3.Connection:
conn = sqlite3.connect(":memory:")
conn.executescript(SCHEMA_SQL)
conn.executescript(DEFERRED_INDEX_SQL)
return conn


class TestSchemaReadProbeStatements:
def test_probes_cover_every_declared_column(self):
"""Invariant: every column SCHEMA_SQL declares appears in a probe.

This is the anti-staleness contract — a column added to SCHEMA_SQL
must be probed without anyone remembering to update a list.
"""
expected = SessionSchemaMixin._parse_schema_columns(SCHEMA_SQL)
statements = schema_read_probe_statements()
by_table = {}
for statement in statements:
for table in expected:
if f'FROM "{table}"' in statement:
by_table[table] = statement
for table, cols in expected.items():
assert table in by_table, f"no probe statement for table {table}"
for col in cols:
assert f'"{table}"."{col}"' in by_table[table], (
f"column {table}.{col} declared in SCHEMA_SQL but not probed"
)

def test_probes_pass_on_fresh_schema(self):
conn = _fresh_schema_conn()
try:
for statement in schema_read_probe_statements():
conn.execute(statement).fetchone()
finally:
conn.close()

def test_probes_fail_on_missing_column(self):
"""The shipped regression: a store predating sessions.last_activity_at

(#72424) passed the old hand-written probe, then 500'd inside
list_sessions_rich on every sidebar poll until the user's first
message forced a writable open.
"""
conn = _fresh_schema_conn()
try:
conn.execute("ALTER TABLE sessions DROP COLUMN last_activity_at")
# The failure must come from the sessions probe naming the exact
# column — not incidentally from some other statement — so a
# probe-generation bug that misassigns columns to tables can't
# sneak through.
sessions_probe = next(
s
for s in schema_read_probe_statements()
if 'FROM "sessions"' in s
)
with pytest.raises(sqlite3.OperationalError) as excinfo:
conn.execute(sessions_probe)
assert "no such column: sessions.last_activity_at" in str(
excinfo.value
)
finally:
conn.close()

def test_probes_fail_on_missing_table(self):
conn = sqlite3.connect(":memory:")
try:
conn.executescript(SCHEMA_SQL)
conn.executescript("DROP TABLE gateway_routing")
gateway_probe = next(
s
for s in schema_read_probe_statements()
if 'FROM "gateway_routing"' in s
)
with pytest.raises(sqlite3.OperationalError) as excinfo:
conn.execute(gateway_probe)
assert "no such table" in str(excinfo.value).lower()
finally:
conn.close()

def test_probe_statements_are_cached(self):
assert schema_read_probe_statements() is schema_read_probe_statements()
37 changes: 37 additions & 0 deletions tests/test_tui_gateway_server_crash_history.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Regression coverage for crashed TUI gateway turns."""

import threading
import types

from tui_gateway import server


def test_turn_error_restores_agent_transcript_to_gateway_history():
agent = types.SimpleNamespace(
_session_messages=[
{"role": "user", "content": "first"},
{"role": "assistant", "content": "completed work"},
]
)
session = {
"history": [{"role": "user", "content": "first"}],
"history_lock": threading.Lock(),
"history_version": 0,
}

assert server._restore_agent_history_after_turn_error(session, agent) is True
assert session["history"] == agent._session_messages
assert session["history_version"] == 1


def test_turn_error_with_no_agent_transcript_does_not_overwrite_history():
agent = types.SimpleNamespace(_session_messages=None)
session = {
"history": [{"role": "user", "content": "first"}],
"history_lock": threading.Lock(),
"history_version": 4,
}

assert server._restore_agent_history_after_turn_error(session, agent) is False
assert session["history"] == [{"role": "user", "content": "first"}]
assert session["history_version"] == 4
46 changes: 46 additions & 0 deletions tui_gateway/methods_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -2180,6 +2180,52 @@ def _(rid, params: dict) -> dict:
return _ok(rid, {"status": "queued" if queued else "rejected", "subagent_id": subagent_id, "text": text})


@method("subagent.steer")
def _(rid, params: dict) -> dict:
"""Queue steering text into a live delegated child without stopping it.

The redirection-side mirror of subagent.interrupt: resolves the child in
the delegation registry and calls AIAgent.steer(), which appends the text
to the child's last tool result at its next iteration boundary — the
in-flight tool call is never cut. "queued" is not "delivered": a child
already past its final tool batch has no boundary left to drain into,
and that race surfaces as ``missed_steer`` on the parent's completion
entry instead of being silently dropped.
"""
from tools.delegate_tool import steer_subagent

subagent_id = str(params.get("subagent_id") or "").strip()
if not subagent_id:
return _err(rid, 4000, "subagent_id required")
text = (params.get("text") or "").strip()
if not text:
return _err(rid, 4002, "text is required")
_invoking_session, err = _sess_nowait(params, rid)
if err:
return err
invoking_session_id = str(params.get("session_id") or "").strip()
invoking_transport, invoking_session = _current_session_steer_authority(
invoking_session_id
)
queued = False
if invoking_transport is not None and invoking_session is not None:
queued = steer_subagent(
subagent_id,
text,
owner_session_id=invoking_session_id,
owner_transport=invoking_transport,
owner_session_record=invoking_session,
)
return _ok(
rid,
{
"status": "queued" if queued else "rejected",
"subagent_id": subagent_id,
"text": text,
},
)


@method("spawn_tree.save")
def _(rid, params: dict) -> dict:
session_id = _str_param(params, "session_id")
Expand Down
Loading