Skip to content

fmt(js): npm run fix auto-fix - #80725

Merged
hermes-seaeye[bot] merged 1 commit into
mainfrom
bot/js-autofix
Aug 7, 2026
Merged

hermes-seaeye[bot] merged 1 commit into
mainfrom
bot/js-autofix

Conversation

@hermes-seaeye

@hermes-seaeye hermes-seaeye Bot commented Aug 7, 2026

Copy link
Copy Markdown

Auto-generated by the auto-fix lint issues & formatting workflow. Auto-merges (squash) once CI passes. If CI fails or main moves, the PR is auto-closed and the branch deleted — the next run re-applies on the current state.

@hermes-seaeye
hermes-seaeye Bot enabled auto-merge (squash) August 7, 2026 02:35
@github-actions

github-actions Bot commented Aug 7, 2026 •

Copy link
Copy Markdown

૮ >ﻌ< ა ci review

ran on 4b97639

⚠️ Warnings

OSV vulnerability scan · View job

50 known vulnerabilities found in pinned dependencies.

How to fix:

Review the findings in the Security tab. Update the affected dependencies if a patched version is available.


debug info

CI timings

CI timings · View report · View job

Wall time 6m50s vs 6m49s (+0.2%). 7 job(s) slower, 8 faster, 2 unchanged.

  • JS & TS checks / ui-tui / check: -20.0s
  • JS & TS checks / apps/desktop / check:lint: +13.0s
  • JS & TS checks / apps/desktop / check:test:desktop:platforms: -10.0s
  • JS & TS checks / web / check: +8.0s
  • JS & TS checks / tests-js / check: -8.0s

@hermes-seaeye
hermes-seaeye Bot merged commit eb8421b into main Aug 7, 2026
35 checks passed
@hermes-seaeye
hermes-seaeye Bot deleted the bot/js-autofix branch August 7, 2026 02:42
POWERFULMOVES pushed a commit to POWERFULMOVES/PMOVES-hermes-agent that referenced this pull request Aug 10, 2026
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
melon-xf added a commit to melon-xf/hermes-agent that referenced this pull request Sep 3, 2026
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
smfworks pushed a commit to smfworks/hermes-agent that referenced this pull request Sep 7, 2026
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
POWERFULMOVES added a commit to POWERFULMOVES/PMOVES-hermes-agent that referenced this pull request Sep 22, 2026
…9-20 + 48-commit overlay, AUDIT INSIDE (#11)

* fix(cron): lifecycle guard — never crash on binary referenced paths, stop matching lifecycle words inside SQL/text

Two live failures on the same guard (cron/lifecycle_guard.py), both of
which blocked legitimate diagnostics from inside the gateway:

1. Crash class: the referenced-script walk read compiled binaries as if
   they were shell scripts. Reading/inspecting a referenced file is now
   best-effort by construction: executable magic numbers (ELF, PE,
   Mach-O fat/thin) short-circuit before any full read via a 4KB sniff,
   NUL-bearing heads are skipped as non-scripts, and unreadable paths of
   every kind (NUL bytes in the token, ENAMETOOLONG, missing files)
   degrade to "nothing to scan" instead of raising. A second fail-safe
   layer wraps the pure-string fallback so the boundary function stays
   total even if the tokenizer itself fails.

2. False-positive class: the lifecycle regex matched its command shapes
   inside DATA arguments — SQL string literals passed to sqlite3/psql
   and grep/rg/journalctl patterns hunting for the lifecycle string in
   logs. Added a fail-closed second-pass exemption: on a raw regex hit,
   re-scan with data-sink executables' arguments masked; only a match
   that survives (i.e. sits in command position) blocks. Masking is
   skipped for pipes into shells/xargs, command/process substitution,
   sqlite3 dot-commands and psql backslash escapes, so it can only ever
   allow, never miss.

Behavioral tests: exact live false-positive shapes as negatives, the
smuggling shapes as positives, the kill-primitive positive catalog
unchanged, and an adversarial never-raises suite (NUL bytes, non-UTF-8,
/dev/*, directories, missing files, magic-prefix binaries).

* fix(db): never downgrade journal mode on a database with concurrent openers

The WAL-reset-vulnerability gate (#70055 lineage) could flip a LIVE WAL
database to journal_mode=DELETE while another process was writing to it.
Observed on state.db (Aug 5): a pytest process on the repo .venv (SQLite
3.50.4, vulnerable) opened the live ~/.hermes/state.db while the gateway
(SQLite 3.53.1, WAL) held it, downgraded the journal mode, and destroyed
the gateway's committed-but-uncheckpointed WAL transactions (disk rows
went 10 -> 0 while memory held 185). cron/executions.db already had the
"leave WAL in place, no live downgrade under concurrent openers" rule via
the on-disk WAL probe; state.db and every other store shared the hole
whenever the mode PROBE itself was blocked by a concurrent opener's locks
("could not read the mode" was treated as "not WAL" -> flip anyway).

Generalized in the single journal-mode owner (apply_wal_with_fallback),
covering ALL call sites (state.db, kanban.db, projects.db,
cron/executions.db, delivery_ledger, async_delegation,
verification_evidence, discord recovery, response_store.db,
memory_store.db):

- _set_journal_mode_no_wait(): the only journal-mode switch primitive for
  non-WAL targets. Forces busy_timeout=0 around the pragma so SQLite's own
  exclusivity requirement for leaving WAL becomes the concurrent-opener
  detector — any other opener (this process or another) makes the flip
  fail immediately instead of waiting out a busy timeout and sneaking the
  flip in under a live writer.
- Vulnerable-SQLite gate: an unreadable journal mode (probe blocked) now
  means "ownership not provably exclusive" — leave the mode untouched and
  warn, never flip. A lock conflict on the flip itself likewise leaves the
  mode alone.
- Configured journal_mode=delete: refuses (raises) rather than downgrading
  blind when the mode cannot be verified under a concurrent opener.
- Filesystem-incompat fallback: re-raises instead of downgrading when the
  on-disk mode cannot be verified.
- New/exclusively-owned DBs on vulnerable builds behave exactly as before
  (DELETE gate retained per #70055).

Behavioral tests use a REAL second process (and a real second connection
holding an exclusive lock) with the blocked-state assertions running WHILE
the holder owns the DB, plus exclusive-ownership downgrade-still-happens
coverage.

* fix(tests): fail hard when pytest resolves the production state.db (live-DB isolation guard)

Forensics on a live developer machine found pytest fixture rows inside the
REAL ~/.hermes/state.db — sessions with chat_id 'chat-1', '123', 'wx-chat',
and gateway_routing rows whose scope was literally under /tmp/pytest-of-*/.
A pytest-spawned process also opened the live DB and flipped its journal
mode (journal_mode=DELETE fallback on SQLite 3.50.4) under the WAL-mode
gateway writer, destroying committed transcripts ("Persisted transcript
lagged live cached history ... possible FTS write corruption", 15+
occurrences). The existing live-system guard covers kill primitives but not
the SessionDB/SessionStore write paths.

Root cause (leak vector): the session-level HERMES_HOME sandbox in
tests/conftest.py only created a tempdir when HERMES_HOME was UNSET. On a
machine where the shell (e.g. gateway-launched, or an exported
HERMES_HOME=~/.hermes) hands pytest the production home, the sandbox was
skipped entirely — every argless SessionDB()/SessionStore() and every
collection-time DEFAULT_DB_PATH froze onto the real state.db.

Fixes (fail the class, one owner):

* hermes_state._ensure_test_isolation(): single choke point wired into
  SessionDB.__init__ (every construction, incl. read_only). Under pytest
  (PYTEST_CURRENT_TEST / PYTEST_VERSION — inherited by subprocess
  children), a db path resolving to <real-root>/state.db or
  <real-root>/profiles/<name>/state.db raises RuntimeError('live-system
  guard: ...') before any connection, mkdir, or journal-mode pragma.
* tests/conftest.py: session sandbox now also tempdir-redirects a pre-set
  HERMES_HOME that points at the production root (the actual escape
  vector); kanban deny-list capture updated to match. New autouse
  _state_db_write_guard fixture honors the existing
  @pytest.mark.live_system_guard_bypass marker as the escape hatch and
  feeds custom (non-~/.hermes) production roots into the guard deny-list.
* gateway/session.py: SessionStore.__init__ no longer swallows the guard's
  RuntimeError into the JSONL fallback — guard trips are loud.
* tests/hermes_state/test_live_db_isolation_guard.py: behavioral
  regression tests — production paths (direct, profile, read-only,
  unnormalized, default-resolution) raise; tmp HERMES_HOME works; bypass
  marker works; SessionStore re-raises guard errors but still degrades on
  ordinary failures; subprocess child without HERMES_HOME is refused while
  a hermetic child succeeds.

No new HERMES_* env vars; no hardcoded ~/.hermes (platform root comes from
hermes_constants._get_platform_default_hermes_home()).

* fix(tests): resolve guard's production root via expanduser, immune to Path.home monkeypatches

Tests like tests/gateway/test_goal_verdict_send.py monkeypatch Path.home()
to a tmpdir; resolving the guard's 'real root' through Path.home() made the
test's own hermetic home look like production (false positive). Resolve via
os.path.expanduser/LOCALAPPDATA instead — the hermetic conftest never
rewrites HOME, so this always names the actual production root.

* fix(delegation): surface a child's undelivered steer instead of dropping it

The turn finalizer already hands back steer text that queued after the
final tool batch — result["pending_steer"], with the comment "hand it
back to the caller so it can be delivered as the next user turn instead
of being silently lost." Every interactive surface honors that contract
(cli.py, gateway/run.py, tui_gateway/server.py all requeue it). The
delegation layer doesn't: _run_single_child never reads it, so a steer
queued into a delegated child that finishes first vanishes with no trace
in the completion entry. There is also no sanctioned sender: the registry
has interrupt_subagent() but no redirection-side mirror, and session.steer
cannot reach children (lazy watch sessions have agent=None, so it 4010s).

Complete the contract for delegated children — both halves:

- steer_subagent(subagent_id, text): redirection-side mirror of
  interrupt_subagent(). Resolves the live child in _active_subagents and
  queues text via AIAgent.steer(). True means queued, not delivered.
- missed_steer retention: when the child's result carries pending_steer,
  _run_single_child names it on the completion entry (missed_steer field
  plus a summary note) so the parent can re-issue the guidance instead of
  trusting it landed. This is what makes adding a sender safe: without it
  the finish-before-drain race silently loses the text — the exact loss
  the finalizer contract exists to prevent.
- subagent.steer gateway RPC beside subagent.interrupt so programmatic
  hosts (dashboard, voice layers, ACP bridges) get an in-tree caller;
  catalogued in programmatic-integration.md.
- docs: "Steering a Running Subagent" section in delegation.md covering
  the queued-vs-delivered semantics.

Tests: registry-level steer coverage (delivery, unknown id, empty text,
dead record, raising agent), the finish-before-drain race retaining
missed_steer, and the RPC contract (4000/4002 validation, queued and
rejected envelopes).

* fix(delegation): harden steer lifecycle ownership

* fix(delegation): bind steering to session generation

* refactor(skills): move polymarket to optional-skills/finance

Per the 'when in doubt, optional' rule — niche prediction-market data
skill that sees no regular use; belongs alongside stocks in the finance
optional category rather than the default bundle.

Install via: hermes skills install official/finance/polymarket

* fix(launchd): stop stranding gateway label on plist reload

Reload chose the in-process bootout/bootstrap path based on POSIX
ancestry, but bootout tears down the job's process coalition, and
coalition membership is inherited at spawn and survives reparenting.
A gateway-spawned process reparented to PID 1 is no longer an ancestor
yet still dies with the coalition, so the retry loop was killed
mid-bootstrap and nothing re-registered the label (KeepAlive can't
revive a job launchd no longer knows about).

- always prefer the detached transient-job helper; it's also correct
  when genuinely outside the coalition, just asynchronous
- wait for the old gateway PID to exit before bootstrapping; bootout
  only sends SIGTERM and every bootstrap during the drain fails EIO
- fall through to the in-process path when the helper can't spawn
  instead of leaving the plist rewritten but never reloaded

* fix(launchd): require a supervised PID to call a reload successful

The reload retry loop treated `launchctl list <label>` exit 0 as success,
but exit 0 also covers a registered-but-not-running definition (macOS 26+
`state = not running`) — the same trap _probe_launchd_service_running
already guards against. Require a PID so success means launchd is
supervising a live process, in both the Python loop and the shell helper.

Verified against live launchd: a RunAtLoad=false job reports exit 0 with
no PID, which the old check accepted and the new one rejects.

Note this is NOT what distinguishes a draining instance — measured, the
label deregisters within ~1s of bootout while the old process drains on.
Waiting for the old PID to exit is what covers that.

* chore: map rjhilgefort@gmail.com -> rjhilgefort in contributor directory

* fix: align helper PID check with Python parser + dedupe drain-wait

Review follow-ups on the salvage:

- The helper script's success check accepted any "PID" line, including
  the "PID" = -1 a recently-crashed job reports — while the in-process
  path's _parse_launchd_pid_from_list_output rejects non-positive PIDs.
  Both bash sites now require a positive PID (grep -qE '"PID" = [0-9]+;')
  so the two paths enforce the same supervised-PID standard.
- _graceful_restart_via_sigusr1's drain-wait tail was a duplicate of the
  new _wait_for_pid_exit — now delegates to it.
- Stale comments: the ancestry-detection framing at the top of the reload
  block, and the exhaustion log's '(refresh ran outside gateway process
  tree)' which is false on the new helper-spawn-failure fallback path
  (now '(in-process fallback path)').

* fix(agent): the desktop's tools reach it on remote and cloud backends too

The pane, in-app browser, and reaction tools were gated on HERMES_DESKTOP=1 —
an env var set only on backends Electron spawns itself (local and SSH). A
desktop client connected to a plain URL gateway or Hermes Cloud lost all six:
they were stripped from the schema before the model saw them, on the same
backend whose platform hint was telling it "you are chatting inside the Hermes
desktop app". open_preview, read_preview, read_terminal, close_terminal,
focus_pane, and react_to_message were all silently absent.

The client is not the host. Capability now resolves from the session's own
source, which session.create already carries:

- The six tools move into a `desktop_ui` toolset, off _HERMES_CORE_TOOLS so no
  other platform pays their schema.
- _gui_surface_toolsets(platform) folds `desktop_ui` (and the existing
  `project` tools) into the GUI gateway's resolution when the session's
  platform is the desktop app — the same answer on every topology.
- check_fn drops the env probe. It kept the one thing that is genuinely a
  per-process/user fact: react_to_message's display.message_reactions opt-in,
  which the desktop mirrors onto whichever gateway it is connected to.

react_to_message was doubly broken: it read that toggle behind the env gate, so
even a local-backend user's Settings toggle could not reach a remote session.

The embedded terminal pane keeps working correctly the other way round: it runs
`hermes --tui` against a desktop-spawned backend, and a tui-sourced session
gets no GUI tools even though HERMES_DESKTOP=1 is set on that process.

* docs(agents): surface capability belongs to the session, not the process env

The rule the preview-tool bug broke, written down so the next GUI-adjacent tool
does not rediscover it: the client and the backend are separate machines, so
"was this process spawned by Electron?" cannot answer "is a GUI watching?".
Names the working pattern (toolset gates the surface, check_fn answers only
reachability or user opt-in), the process-wide check_fn TTL cache that makes it
the wrong home for a per-session answer, and the test that would have caught
it — assert the GUI session gets the tool with the env var absent.

* docs(reference): document the desktop_ui toolset

The six GUI tools moved out of `terminal` into their own toolset; the tables
still described them as check_fn-gated members of it, and as available to every
hermes-* platform bundle.

* Fireworks user agent (#80422)

* refactor(desktop): share how a tool row renders

The transcript decides what a tool call draws and the render budget has to
price it. Both sides need the same answer, so the classification moves out of
the tool renderer into its own module rather than the budget importing the
formatting and i18n weight of fallback-model to ask one question.

Adds isSilentTool for the rows that render nothing at all: todo is hoisted to
its own panel, and a reaction's UI is the emoji on the bubble.

* feat(desktop): price a turn by what it paints

One weight function served two budgets that protect different things. The
store window protects the heap: every message it admits is normalized into the
runtime repository whether or not the transcript collapses it, so it has to
price the payload it holds. The DOM budget protects the paint, and what a turn
mounts is decided by the grouping, not by the bytes behind it.

Charging the DOM budget for payload made it count work that never happens. A
settled run of twelve reads is one grey summary line, a thought is one
collapsed disclosure, a todo is hoisted out of the transcript, and an image is
one img however long its data URL — all of it priced as if fully expanded.

messageStoreWeight keeps the payload price for the window. messagePaintWeight
prices what mounts: collapsed rows flat, silent rows free, cards fixed, and
markdown and diffs by size, since those really do build DOM. Both share one
character ceiling per message rather than one per part.

* feat(desktop): stop hiding a session behind Show earlier

On real sessions the button showed up two or three turns from the bottom, over
a screen and a half of transcript that had barely painted anything.

The budget now spends paint weight, which is what the DOM actually mounts, and
600 units of it — 10-20 agentic turns measured, where a tool-heavy turn prices
at 30-90 and a plain exchange at 5-10. A floor of 8 turns covers the session of
enormous turns that a weight-only cut still truncates hard; it applies to a
real page only, so the small first-paint commit stays small and the backfill a
frame later fills the rest.

Measured on four stored sessions at the same budget: one went from 3 turns
visible to 12, another from 3 to 4, two unchanged. The store window still caps
what the DOM can reach at all.

* fix(api): persist session pins instead of 400ing them

PATCH /api/sessions/{id} only accepted title and end_reason, so the
`pinned` flag the desktop sends was rejected as an unsupported field —
and the client swallows that error. Pins lived in one app's localStorage
and never reached state.db, which also meant the server-side auto-archive
sweep was free to hide the chats a pin exists to keep.

Accept pinned and archived as booleans, route them to the SessionDB
setters that already existed, and include both in the serialized session
so clients can reconcile against server truth.

* fix(desktop): stop dropping pinned sessions past the page limit

The list endpoints deliberately back-fill pinned conversations past their
LIMIT, then the client sliced the response back down to that same limit
and threw them away — so only pins that happened to land inside the most
recent page ever rendered, which reads as a cap on how many sessions you
can pin.

Keep the back-filled rows when trimming, and discount them from the
"window came back full" test that drives Load more. Counting a back-fill
as a loaded row invented a page that could never be fetched, leaving a
Load more button that refetched the same rows forever.

* fix(desktop): hold the pin write guard until a page confirms it

The guard that stops a stale list page from reverting a fresh pin was
released on the PATCH's own ack. A list request issued just before the
write is slower than the write, so it lands after the ack still carrying
the old value, with no guard left to fence it: the pin flips back and the
next reconcile pushes that wrong value to the server, making it durable.

Keep the guard until a page actually confirms the value written, with a
cooldown so a row that never returns can't fence itself forever, and drop
it outright when the write fails — the server never changed, so it stays
authoritative.

Also reset the mirror bookkeeping on a gateway switch. mirrored/pending
are per-backend facts; carrying them across a re-home told us the pins
were already pushed to a backend that has never seen them.

* fix(desktop): show a pinned session once, and keep its drag order

Two ways a pin got misfiled. The duplicate: a pin is stored on the
durable lineage root, but recents, the messaging slice and the backend
project tree are three independent fetches and each can surface the same
conversation under either its live tip or its root — so the filter
compared one identity against the other, missed, and the session rendered
in both Pinned and its project group. Match on every id the pin is
reachable under.

The lost reorder: a drag only reports the pins whose row is loaded, and
setPinnedSessionOrder required that list to match the stored one in
length, so a single unresolved pin discarded the whole reorder. Treat it
as a permutation of a subset — re-slot the named ids, leave the rest.

* fix(desktop): rank dragged sessions inside their date group

Dragging one row switched the entire sidebar into a frozen manual mode
with no date dividers at all — permanently, for every session, because
the manual order replaced the recency sort outright instead of layering
on it. Chronology and ranking are separate concerns: keep the calendar
buckets where recency put them and apply the hand-picked order only
within a bucket, so a drag ranks a chat among its own day's chats and the
dividers survive.

Rows move as clusters, so a reorder can't strand a branch child from its
parent, and a session the saved order doesn't name keeps the slot recency
gave it. Two supporting fixes fall out: dnd-kit now receives the ids it
actually renders (it was handed the unrendered session order, so a drop
computed its target against a list the user wasn't looking at), and an
older page that loads no longer jumps above the hand-picked rows — new
ids fold in by position rather than all hoisting to the top.

* fix(desktop): show every pinned session, however many there are

Pinned was capped at half the viewport by its own nested scroller, so past
roughly a dozen pins the rest were reachable only by scrolling inside a
scroller — a pin you have to go hunting for isn't doing its job.

Drop the cap and let the section grow into the sidebar's existing scroll,
and stop virtualizing Pinned: virtualization needs a bounded viewport to
measure against, which is exactly what's being removed. No count badge, no
"show more" — pin as many as you want and they all render.

Also back-fill pins on the API-server list route, which was the one list
path still windowing purely on recency.

* fix(desktop): keep elapsed status text from overlapping

* fmt(js): `npm run fix` on merge (#80725)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* fix(desktop): drop the gateway-pill dogfood plugin

It was a 1:1 rebuild of the core statusbar gateway item and shipped
enabled by default, so the pill showed up twice. Core chrome stays in
shell; demos that clone it belong in hermes-example-plugins.

* fix(reasoning): keep gpt-5.x summary parts as separate blocks on the chat wire

Reasoning-summary models emit one reasoning_content delta per completed
summary part, each a self-contained bold heading. The Responses API delimits
those parts with summary_index; the OpenAI chat wire carries no such field —
verified live against Nous Portal, whose reasoning chunks contain nothing but
delta.reasoning_content — so concatenating them glued every part into one
unspaced, half-bold paragraph.

Re-derive the boundary from the signal the wire does carry: a delta opening a
closed bold heading against a mid-line tail. This matches Hermes own Responses
adapter, which already joins its summary parts with a blank line.

* fix(codex): split reasoning summary parts on summary_index

The native Responses stream does carry summary_index, so the part boundary is
structured data here rather than something to infer. Break on a change of
index, and leave streams that send no index (plain reasoning_text) untouched.

* fix(desktop): render already-glued reasoning as separate blocks

Repairs what is already in the transcript: reasoning persisted before the
backend fix, and any provider still gluing its parts. Handles both shapes —
heading-onto-heading (the **** run) and prose-onto-heading (vercel/ai#6742).
Verified against 46 real glued messages from a gpt-5.6-sol session; all repair
cleanly and idempotently.

* fix: preserve session history when a turn crashes

* fix(dashboard): derive the stale-schema read probe from SCHEMA_SQL

After `hermes update`, the desktop sidebar showed "No sessions yet" until
the user's first message. #72424 added sessions.last_activity_at, which
list_sessions_rich now selects — but column adds only land through
_reconcile_columns() in the writable _init_schema, and read-only opens
skip that by design. Every sidebar read path opens state.db read-only, so
each poll raised "no such column: s.last_activity_at" until the first
prompt's lazy session-row persist forced a writable open and reconciled.

A heal for exactly this class already existed (_open_session_db_for_profile
probes the read-only handle and does a one-time writable reopen on
staleness), but its probe was a hand-written four-column list that never
learned last_activity_at — it went stale three days after shipping. And the
batched sidebar route (/api/profiles/sessions/sidebar) bypassed the helper
entirely, swallowing per-profile failures into an errors array the desktop
never surfaces, so the incident produced an empty sidebar with clean logs.

The fix removes the maintenance burden instead of paying it once more:

- hermes_state_schema.schema_read_probe_statements() derives one
  `SELECT <every declared column> FROM <table> LIMIT 0` per table from
  SCHEMA_SQL via the existing _parse_schema_columns() — the same source of
  truth the writable reconciler diffs against, so any future ADD COLUMN is
  probed with no list to update. Column references are table-qualified:
  an unqualified double-quoted identifier that fails to resolve silently
  degrades to a string literal (SQLite's double-quoted-string misfeature)
  and would make the probe pass on exactly the store it exists to catch.

- web_server splits the heal into a path-level _open_session_db_at_path
  (semantics unchanged) so the cross-profile session routes can share it;
  both profiles.py loops and _count_status_active_sessions (the remaining
  raw read-only sibling) now open through it. The heal stays a helper
  rather than a SessionDB classmethod on purpose: escalation-to-writable
  must remain an explicit caller decision — update_cmd.py opens read-only
  mid-update and must never write.

- Exhaustion guard: if the writable heal SUCCEEDS and the re-probe still
  fails (a schema problem ADD COLUMN cannot express), the store is marked
  exhausted — warn once, skip the probe, serve reads probe-less — instead
  of re-running the full writable init on every poll against a possibly
  live DB. A FAILED writable open (transient lock) is deliberately not
  recorded, so the next poll retries the heal.

- The per-profile swallow sites in profiles.py now also log a deduplicated
  warning, so a persistent read failure is loud in errors.log even though
  the response errors array stays invisible to the sidebar.

Tests: probe/SCHEMA_SQL coverage invariants (tests/test_schema_read_probe.py),
last_activity_at added to the /api/sessions heal parametrize, a sidebar-route
heal test reproducing the shipped symptom (errors == [] and the session
returned against a store missing the column), and an exhaustion test pinning
exactly one writable open. The sidebar and last_activity_at tests fail on
main.

* fix(skills): align hermes-agent-skill-authoring with hardline authoring standards

The in-repo skill-authoring skill taught the validator's ceilings (1024-char
descriptions, 'Use when ...' phrasing) instead of the repo's review standards,
so agents following it produced skills that fail review: 240+ char
descriptions, author 'Hermes Agent' with no human credit, no bundled-vs-
optional decision, dangling related_skills, no platforms audit, no tests, no
docs regen, and machine-local /home/bb/... paths baked into prose.

Rewritten to teach the hardline standards from AGENTS.md:
- description <= 60 chars, one sentence, ends with period
- author credits the human contributor first
- bundled vs optional tier decision (5+ sessions/month bar; default optional)
- no router/index/hub skills
- platforms: audited against actual scripts, POSIX-signal table
- related_skills must resolve in-repo
- Hermes-tool framing instead of raw shell prose
- tests at tests/skills/ + docs regen with scope discipline
- removed machine-local paths; validator limits marked as NOT the standard

* feat(/learn): expansive knowledge-base skills for books and large corpora

Inspired by virgiliojr94/book-to-skill (MIT): /learn now picks the skill
shape by the source. Workflows and small sources still get one tight
SKILL.md; books, paper stacks, specs, and large doc corpora get a
knowledge-base layout — a lean always-loaded SKILL.md index plus one
distilled file per chapter/topic under references/, loaded on demand via
skill_view so query cost stays proportional to the answer.

- agent/learn_prompt.py: new _KNOWLEDGE_SKILL_STANDARDS block (index +
  per-chapter references/, structure-not-summary distillation, never
  reproduce source passages, fold-in instead of duplicating) and a
  _SOURCE_HYGIENE block pinning extracted source text as data and
  dropping invisible/bidi Unicode (Trojan Source class). Clarified that
  the ~200-line cap and hub-skill ban apply to SKILL.md itself, not a
  knowledge skill's own references/ files.
- tests: contracts for the knowledge-base layout, the three embedded
  standards blocks, and the source-hygiene coverage.
- docs: skills.md documents the knowledge-base shape.

* fix: dispatch cronjob(action='run') to the background like delegate_task

A manual cronjob run executed the job synchronously on the calling
agent's tool thread. A cron job is a full agent run that routinely
takes minutes to hours, so the parent turn sat inside ONE tool call
the whole time: uninterruptible (the interrupt flag is only checked
between loop iterations) and serial (a batch of manual runs executed
one by one). A Telegram session that kicked off dozens of new jobs
'right now' was wedged for hours ignoring every interrupt.

action='run' now rides the async-delegation rail delegate_task
background mode uses: the at-most-once claim is taken synchronously
(so paused/missing/already-firing jobs still report immediately),
the run executes on the shared daemon executor, the tool returns at
once with a delegation handle, and the job's outcome re-enters the
conversation as a type='async_delegation' completion event through
the existing completion-queue drains (CLI + gateway) — preserving
message-role alternation and the prompt cache.

Sync fallbacks preserved:
- no routable session (direct Python callers, hermes cron run)
- async delivery unsupported (hermes -z, cron child sessions,
  Kanban workers, stateless HTTP)
- dispatch pool at capacity (claim already taken — runs inline
  rather than stranding it)

The completion block reports ok/failure, delivery target, next
scheduled run, and an excerpt of the job's saved output.

* fix: share in-flight cron dedupe between ticker and manual runs

Salvaged from PR #53395 by @izumi0uu: the fire claim's 300s TTL is
routinely outlived by real cron jobs, so claim_job_for_fire alone
cannot stop a manual cronjob(action='run') from double-firing a job
the ticker (or another manual run) is still executing.

Extract the ticker's _submit_with_guard running-set check into shared
module-level helpers (try_register_running_job / release_running_job)
and register manual runs through the same set — one dedupe owner, no
drift. Manual runs also become visible to get_running_job_ids (the
gateway shutdown drain, #60432) and mark_running_jobs_interrupted,
which previously could not see them.

The background dispatch path pre-checks the running set so a mid-run
job reports 'already running' in the tool response immediately
instead of as a delayed error completion event; the authoritative
atomic check remains in _run_claimed_job on the worker.

Co-authored-by: izumi0uu <izumi0uu@gmail.com>

* fix(cron): deliver manual runs on gateway loop

* test(cron): add _build_job_prompt extra_prompt regression tests

Pins the scheduler-boundary contract: extra_prompt is appended under
'## Run Context', does not mutate job['prompt'], and the header is
absent when extra_prompt is omitted.

Addresses review feedback from harjothkhara on PR #57342.

* fix(cron): thread per-run prompt through cronjob(action='run') (#57331)

Salvaged from PR #57342 by @liuhao1024 (with the injection-scan half
from PR #57360 by @ghedeselmabot): cronjob(action='run', prompt=...)
silently discarded the prompt argument — per-run context never
reached the spawned cron session.

The prompt is now threaded as extra_prompt through the whole chain
(cronjob run action → _try_dispatch_background_run/_execute_job_now →
_run_claimed_job → run_one_job → run_job → _build_job_prompt) and
appended to the stored prompt under a '## Run Context' header for
that single fire only — never persisted to the job definition. It
passes the same strict _scan_cron_prompt injection scan as stored
prompts before firing, and works identically on the background and
sync fallback paths.

Test fakes across tests/cron/ updated to accept the new kwargs
(sibling-test blast radius from the signature change).

Co-authored-by: liuhao1024 <liuhao1024@users.noreply.github.com>

* fix(plugins): use asyncio.wait_for instead of ClientTimeout in Matrix standalone send

Fixes #61495

When manually triggering cron jobs from a live Matrix session, delivery
would fail with "Timeout context manager should be used inside a task"
because the aiohttp.ClientTimeout context manager requires a proper asyncio
task context.

Use asyncio.wait_for() instead of aiohttp.ClientTimeout to avoid this error,
following the same pattern as the Weixin platform (gateway/platforms/weixin.py).

Changes:
- Remove aiohttp.ClientTimeout(total=30) from ClientSession constructor
- Wrap the send operation in a nested async function (_do_send)
- Use asyncio.wait_for(_do_send(), timeout=30) for timeout handling
- Catch asyncio.TimeoutError explicitly and return clear error message

* docs(cron): document async manual runs and per-run prompt context

Covers the behavior shipped in #80807 (background dispatch for
cronjob action='run') and #80838 (per-run '## Run Context' prompt,
gateway-loop delivery): immediate return with handle, completion
re-entering the conversation, in-flight dedupe, transient context
injection with prompt scanning, and the sync fallbacks.

* feat(pmoves-bootstrap): CGP consumer + tools_bridge + subscriber stub for Hermes (#4)

* feat(pmoves-bootstrap): loader + tools_bridge + subscriber stub

CLAIM the Hermes-agent fork's slice of the Mavis harness v0 (3-repo
coordinated). Companion to POWERFULMOVES/PMOVES.AI PR #2477 and
POWERFULMOVES/PMOVES-pinokio PR #1 (feat/pmoves-app-launcher).
The CGP (pmoves.bootstrap/v1) is the contract that ties the 3
forks together: PMOVES.AI writes it, PMOVES-hermes-agent reads it
at session init + registers PMOVES tools alongside the native
toolset, PMOVES-pinokio reads it when launching a PMOVES-tagged
app.

What this commit ships:

- pmoves_bootstrap/loader.py - the CGP reader. Accepts both YAML
  and JSON (Hermes has pyyaml==6.0.3 in core deps, so YAML is the
  natural format; JSON is supported for PMOVES_BOOTSTRAP_CGP raw-
  string env vars). 4 input sources in priority order: path arg,
  source arg, PMOVES_BOOTSTRAP_CGP[_PATH] env var, vendored
  example. Validates structurally (the vendored v1.schema.json is
  the source of truth, but no jsonschema dep is added - the thin
  structural check covers the 80% case). Returns a typed Bootstrap
  object with has_tool/has_mcp/has_constraint/service/route_for
  accessors. Stub Bootstrap (safe defaults, all 6 constraints)
  when no CGP is present - the non-breaking fallback.

- pmoves_bootstrap/tools_bridge.py - the PMOVES tools bridge.
  Reads bootstrap.tools and resolves each entry against the v0
  tool registry (Python scripts + CLI binaries). Returns a
  BridgeResult with registered/skipped/disabled lists. The session
  init code (a future slice) merges registered tools into Hermes's
  active toolset. PMOVES_TOOLS_DISABLE env var is a per-tool deny
  list. Per the 'tagged-services-are-advisory' constraint, unknown
  tools are silently skipped (warning, not error).

- pmoves_bootstrap/subscriber.py - the optional NATS subscriber.
  v0 is a STUB: no nats-py in Hermes's core deps (adding it would
  be a meaningful blast-radius change; the deps list warns against
  it after the Mini Shai-Hulud worm). subscribe() always returns
  a SubscriberStatus with enabled=False and a clear reason. The
  TaskEnvelope/ResultEnvelope dataclasses document the wire
  contract so a future slice can wire in nats-py without changing
  the public surface. Subjects: pmoves.agent.task.v1 (input),
  pmoves.agent.result.v1 (output), pmoves.bpm.phase.v1 +
  pmoves.bpm.pomodoro.v1 (observability, not consumed by Hermes).

- pmoves_bootstrap/__init__.py - the public surface. Re-exports
  load_bootstrap, stub_bootstrap, export_env, register_pmoves_tools,
  subscribe, and the typed shapes. Future Mavis / Spark / Knuckles
  sessions do 'from pmoves_bootstrap import load_bootstrap,
  register_pmoves_tools, subscribe'.

- pmoves_bootstrap/cgp_schema/v1.schema.json - vendored copy of
  the PMOVES.AI schema. The hermes-agent fork doesn't depend on
  the PMOVES.AI repo at install time.

- pmoves_bootstrap/cgp_schema/example.cgp.yaml - vendored YAML
  example (the same data as the PMOVES.AI example.cgp.yaml).

Non-breaking test pair:
  - No CGP present -> load_bootstrap() returns the stub Bootstrap,
    register_pmoves_tools() returns BridgeResult(registered=[]),
    subscribe() returns SubscriberStatus(enabled=False). Existing
    Hermes behavior unchanged.
  - CGP present    -> load_bootstrap() validates and returns the
    real Bootstrap, register_pmoves_tools() adds PMOVES tools
    alongside the native Hermes toolset, subscribe() is a no-op
    (v0) or picks up Mavis-orchestrator tasks (future slice).

Cross-fork plan:
  - PMOVES.AI PR #2477 (writer)
  - PMOVES-hermes-agent PR feat/pmoves-bootstrap-consumer (agent,
    this PR)
  - PMOVES-pinokio PR feat/pmoves-app-launcher (app launcher)

All three read the same v1.schema.json - the schema is the contract.

The 6 constraints baked into the CGP are honored by the loader's
behavior:
  - no-override-existing-config: the loader never writes to
    Hermes's own config (cli-config.yaml, hermes_state, etc.)
  - tagged-services-are-advisory: missing services are skipped
    in tools_bridge, not failed
  - no-chit-bypass: no CHIT signing code in this package; the
    Mavis orchestrator does the signing
  - no-force-push: this PR's commits use rebase, never --force
  - no-ci-bypass: PR is in DRAFT, no --admin to skip CI
  - preserve-existing-tools: tools_bridge adds PMOVES tools
    alongside Hermes's native toolset, never in place of

Tests: 33/33 pass (tests/test_pmoves_bootstrap.py, run with
'python -m pytest tests/test_pmoves_bootstrap.py -o addopts=').

No new core dependencies. pyyaml is already a Hermes core dep
(see pyproject.toml); no new packages are added.

* test(pmoves-bootstrap): 33 pytest tests across 9 groups

The test suite for the hermes-side CGP consumer. Mirrors the
PMOVES.AI side test taxonomy (load_from_example / load_from_source
/ validation_failure / stub_fallback / export_env / typed_accessor
+ tools_bridge + subscriber) but with 33 tests total (vs 22 on the
PMOVES.AI side) because the hermes-side has more surface (YAML +
JSON parsing, env-var handling, tools_bridge registry resolution,
subscriber wire contract).

Test groups:

- A. LoadFromExampleTests (5) - the vendored example loads +
  validates, identity, services, routing, constraints
- B. LoadFromSourceTests (4) - raw YAML, raw JSON, PMOVES_BOOTSTRAP_CGP
  env var, PMOVES_BOOTSTRAP_CGP_PATH env var
- C. ValidationFailureTests (5) - wrong spec, missing top-level
  field, missing identity.agent, bad role, non-empty super_nodes
- D. StubFallbackTests (2) - no CGP returns the stub; stub has
  all 6 constraints
- E. ExportEnvTests (3) - identity vars, services + routing vars,
  custom env dict (no process side-effect)
- F. TypedAccessorTests (3) - has_tool/has_mcp/has_constraint,
  service() returns None for missing, route_for() returns None
  for missing
- G. ToolsBridgeTests (6) - stub returns empty, real CGP registers
  known tools, disable list excludes, unknown goes to skipped,
  callables are invokable, registry populated at import
- H. SubscriberTests (3) - subscribe is safe no-op when disabled,
  TaskEnvelope round-trip, ResultEnvelope round-trip
- I. Constants and subject surfaces (2) - subjects match the
  orchestrator, KNOWN_TARGETS contains the expected agents

Run with:

    python -m pytest tests/test_pmoves_bootstrap.py -v -o addopts=

(the -o addopts= is needed on Windows where the project-level
pytest-timeout addopts expects SIGALRM which doesn't exist on
Windows; the override disables the addopts so pytest-timeout
isn't required for these tests).

The autouse _isolate_env fixture strips PMOVES_BOOTSTRAP_*,
PMOVES_SUBSCRIBER_*, and PMOVES_TOOLS_* env vars before every
test, so the tests are order-independent and don't leak state
across test files.

* docs(pmoves-bootstrap): README + integration notes

The high-level map of the pmoves_bootstrap package + the 3 files
(loader, tools_bridge, subscriber) + the non-breaking contract +
the design choices (YAML+JSON, no jsonschema, nats-py as follow-up)
+ the cross-fork plan. Future Mavis / Spark / Knuckles sessions
hit this file first to understand the integration.

What's in the README:

- Why this exists - the 3-repo harness v0 slice, hermes-side role
  as the heaviest of the three (read CGP, register tools, optional
  subscriber)
- What this slice ships - 8 files (4 .py + 2 vendored schema
  files + 1 test file + 1 README)
- Non-breaking contract - the 6 constraints, the no-CGP fallback,
  the explicit-source error behavior
- Public API - the 5 public functions + 4 typed shapes
- Resolution order - the 4 sources in priority order
- Why YAML (in addition to JSON) - Hermes has pyyaml in core deps
- Why no nats-py in v0 - the blast-radius comment in pyproject.toml
  warns against adding new packages; the v0 subscriber is a stub
  with a stable wire contract documented via dataclasses
- Tests - 33/33 pass with pytest, 9 test groups
- What this slice does NOT do - the 4 intentional follow-ups
  (wiring into run_agent.py, real nats-py, CHIT trail signing,
  per-session tool allow-list)
- Cross-fork plan - the 3 PRs and the schema as the contract

* fix(bootstrap): rename Bootstrap.source to load_source, guard non-string tool_ids, drop dead imports

The verifier's review of PR #4 surfaced 6 pre-merge findings; this commit applies the cleanup for all 6:

1. Semantic-naming drift (Bootstrap.source to load_source): the field name 'source' collided with meta.source (the producer). Renamed the load-source attribute to load_source; updated the dataclass field, the _from_dict factory, the stub_bootstrap factory, the docstring, the public surface in __init__.py, and the 5 call sites in test_pmoves_bootstrap.py.

2. Reasoning gap (2-line guard in register_pmoves_tools): a malformed CGP with non-string entries in the tools array (e.g. an object {inject: evil} or an int 42) used to crash the bridge with TypeError on the `in disable` check. Now the bridge skips non-string entries to the `skipped` bucket with a warning log, and the LOG.info(skipped) call uses key=str to sort mixed-type lists.

3. Defense-in-depth (sort key=str): the LOG.info(skipped) call was crashing on sorted([int, str, None]) due to int < str comparison. Added key=str to handle mixed types. The new test_G7 proves the bridge no longer crashes on tools=[gh, dict, 42, None].

4. Cleanup (dead imports in loader.py): dropped unused re, sys, Iterable from the typing import.

5. Cleanup (test count drift in docstrings): the test file header and the README both said 31 tests / 8 groups; the actual is 33 tests / 9 groups. Updated to 33 / 9.

6. Nit (line endings on vendored JSON): added pmoves_bootstrap/cgp_schema/*.json text eol=lf to .gitattributes so Windows checkouts do not reintroduce CRLF and produce a false-positive drift signal on the SHA-256 byte-compare against the canonical PMOVES.AI copy.

Also re-vendored v1.schema.json with the PMOVES.AI side new super_nodes-required + services/routing additionalProperties tightening (the Pinokio fork got the same re-vendor in its separate commit).

Test count: 33 to 34 (added test_G7_non_string_tool_id_does_not_crash_bridge). All 34 pass.

* chore(mailmap): add Mavis@pmoves.local -> Mavis@users.noreply.github.com

The Contributor Attribution Check (CI) was failing because my
local commit author (Mavis@pmoves.local) wasn't in the .mailmap.
The fix is the standard mailmap format: canonical name + canonical
noreply + commit email. Future Mavis commits against this fork
will now be attributed correctly.

The Hermes fork's contributor graph is otherwise stable; this is
a no-op for attribution counting.

* chore(release): add Mavis@pmoves.local to AUTHOR_MAP

The Contributor Attribution Check (CI) was failing because my
commit author Mavis@pmoves.local isn't in scripts/release.py
AUTHOR_MAP. The check uses AUTHOR_MAP (not the .mailmap, which
is for git shortlog) to attribute commits to GitHub usernames.

Added the mapping Mavis@pmoves.local -> Mavis-PMOVES. The
.github/PULL_REQUEST_TEMPLATE.md / CONTRIBUTING.md author
guidance will surface the canonical username in future PR
bodies if the operator wants to backfill the real GitHub
handle.

Also added a .mailmap entry (commit 35224e5) for git shortlog
/ GitHub contributor graph, even though the CI check doesn't
read the .mailmap.

---------

Co-authored-by: Mavis <Mavis@pmoves.local>
(cherry picked from commit e68b4ceba09bd5970a0c96f2d75867d6e8634c53)

* fix(pmoves-bootstrap): address review findings — packaging, lazy resolution, item validation

Review follow-up on the un-strand PR (chatgpt-codex-connector findings):

1. Packaging (P1): pmoves_bootstrap was absent from
   [tool.setuptools.packages.find] include and its vendored schema/example
   from package-data. Verified with the same HERMES_NIX_BUILD=1 wheel path
   the reviewer used: 6/6 entries now present (4 modules + schema + example).
   Before: zero entries — sealed installs silently dropped the consumer.

2. Lazy candidate resolution (P1): the four CGP sources were read eagerly
   before the priority loop, so a malformed lower-priority source (bad
   PMOVES_BOOTSTRAP_CGP env var) could break a call that passed a valid
   explicit path. Candidates are now resolved inside the loop; read/parse
   failures fall through like validation failures.

3. String-item validation (P2): _validate_cgp only checked that
   tools/mcps/constraints were lists, so non-string items passed and
   TypeError'd later in export_env()'s comma-join. Now rejected at
   validation with a clear BootstrapError.

Tests: 38 passed (34 original + G7 rewritten to the new contract + J1-J4
regression tests covering all three fixes). Wheel-content check done.

* fix(ci): sync audit_pr_attribution.py with the base-ref + first-parent gate fix

Same fix as the contributor-check.yml change on the sync branch: scan the
PR's actual base (not hardcoded origin/main) and only the first-parent
commit line, so upstream history merged in via sync PRs is never flagged
for mapping in this fork.

* fix(ci): contributor gate scans the PR's actual base, first-parent only

The check-attribution gate hardcoded 'git merge-base origin/main HEAD' as
the scan root. That is wrong twice over on this fork, which has two
long-lived bases (main for upstream syncs, PMOVES.AI-Edition-Hardened for
the submodule pin):

1. Hardened-base PRs scanned the entire upstream delta embedded in
   hardened (~24k commits at the Aug 2026 sync) and demanded mappings for
   upstream authors who have never contributed to this fork.
2. Sync PRs to main scanned all upstream commits the sync brings in —
   again upstream authors, not fork contributors.

Fix (both in the workflow and in scripts/audit_pr_attribution.py, which
the gate says to keep in sync):

- Scan root is now the PR's actual base branch
  (github.event.pull_request.base.ref; falls back to main on push events).
- --first-parent: only the branch's own commit line counts. Upstream
  history arrives via a sync merge's SECOND parent and is excluded;
  fork-authored commits (including conflict-resolution commits after a
   sync merge) remain on the first-parent line and are still counted.

Verified locally by simulating both PR shapes:
- feature PR off hardened -> exactly the fork's 2 patch commits
- sync PR to main -> only merge + carry-forward commits

Upstream contributor emails are attributed in upstream's tree and do not
belong in this fork's contributors/ directory (the 17 mappings added in
1e8a69f9b9 were reverted in c2cd76c4db; only the fork owner's own commit
email mapping remains, and Mavis@pmoves.local from PR #4).

* fix(ci): close review findings on the attribution-gate fix — env indirection + explicit refspec

Self-review (delivery agent) with the diff treated as hostile:

P1: BASE_REF was interpolated inline via ${{ github.event.pull_request.base.ref }}
into the run: shell — the classic Actions expression-injection pattern. base.ref
is event-controlled; the workflow header itself warns this workflow runs
PR-controlled code. Moved to env: indirection so the value never touches the
shell parser as code.

P2: 'git fetch origin $BASE_REF' left refs/remotes/origin/$BASE_REF existence
to opportunistic ref updates. Explicit refspec
+refs/heads/$BASE_REF:refs/remotes/origin/$BASE_REF guarantees resolution.

Semantics unchanged: same scan root (PR base branch), same --first-parent
range, verified earlier against both real PR shapes.

* ci(hardened-sync): registry-first sync pilot — merge main into Hardened on runners (#9)

Provenance: 2026-09-20 session, PR #8 pilot, DARKXSIDE registry-first doctrine.
Local transports failed 5x (early-EOF, blobless negotiation, zombie packs);
the sync runs where connectivity is native. Resolution: -X theirs (main),
PMOVES allowlist keeps Hardened (.gitattributes .mailmap AGENTS.md
contributor-check.yml .github/scripts/*). Safety: MERGE_HEAD commit
conclusion + parent-count verification close the phantom-push hole;
unresolved conflicts fail loudly (exit 1, never push old tip).
Danger-room rooms applied: stale-checkout-heal, phantom-deliverable.

* sync: drop pre-migration wal-gate copy — upstream moved it to tests/hermes_state/

The migrated copy landed via the merge: 419 lines, targets hermes_state_wal
(0 stale setattr(hermes_state,) sites), covers all 11 scenarios of the old
copy. Carrying the stale duplicate trips upstream compat scanning (14
deprecated setattr sites) and is the stale-copy-carrying anti-pattern.

Provenance: hardened-sync run 35605169124 keep-ours resolution, superseded
by upstream migration receipt (main tests/hermes_state/ 131-file layout).

* ci: downgrade unservable runner labels to hosted defaults

The 5 heavy jobs starved 2.5h+ queued on labels this account cannot
serve (empty runner assignments; receipts in PR #10). Map: *-32-core
and 96-core -> ubuntu-latest, windows-32-core -> windows-latest.
Atomic commit via Git Data API: one CI trigger, no half-downgraded
intermediates. Provenance: hardened-sync run 35605169124, PR #10.

* fix(sync): deduplicate merge-synthesized definitions (JS parse blocker)

The Hardened<-main merge (run 35605169124) silently duplicated content in
files both sides touched - no textual conflict, so the duplicated export
surfaced only as a rolldown parse failure killing 106 desktop test files
(CI run 35654743553). Sweep found and fixes:
- session-pin-sync.ts: resetSessionPinMirror x2 -> main verbatim (1)
- conftest.py: _state_db_write_guard x2 -> 1 (ast-gated)
- cronjob_tools.py: _latest_job_output_excerpt +
  _try_dispatch_background_run x2 -> 1 each (kept main-matching blocks)
- AGENTS.md: session-capability doctrine section x2 -> 1
methods_session.py triaged separately (57 top-level 'def _' on both
sides compared; exclusion recorded in the follow-up lane).

Provenance: hardened-sync run 35605169124, PR #10 pilot, DARKXSIDE
registry-first doctrine. Danger-room rooms: phantom-deliverable,
template-brace-double-escape (ast gates not eyeballs).

---------

Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
Co-authored-by: SmokeDev <degensmoke@gmail.com>
Co-authored-by: Rob Hilgefort <rjhilgefort@gmail.com>
Co-authored-by: kshitij <kshitijkapoor0611@gmail.com>
Co-authored-by: Brooklyn Nicholson <brooklyn.bb.nicholson@gmail.com>
Co-authored-by: rob-maron <132852777+rob-maron@users.noreply.github.com>
Co-authored-by: hermes-seaeye[bot] <307254004+hermes-seaeye[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: emozilla <emozilla@nousresearch.com>
Co-authored-by: izumi0uu <izumi0uu@gmail.com>
Co-authored-by: Fly <172966293+Fly-onlyone@users.noreply.github.com>
Co-authored-by: liuhao1024 <sunsky.lau@gmail.com>
Co-authored-by: liuhao1024 <liuhao1024@users.noreply.github.com>
Co-authored-by: Mavis <Mavis@pmoves.local>
Co-authored-by: PMOVES <powerfulmoves@pmoves.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants