Commit graph

2087 commits

Author SHA1 Message Date
thewillhuang
e286e68756 feat(kanban): stale detection for running tasks in dispatcher
Salvages #23790 by @thewillhuang. Adds detect_stale_running() to
the dispatcher cycle. Running tasks that have been started for longer
than dispatch_stale_timeout_seconds (default 14400 = 4h) without a
heartbeat in the last hour are auto-reclaimed to ready.

- New config kanban.dispatch_stale_timeout_seconds (default 14400, 0 disables)
- New 'stale' field on DispatchResult
- detect_stale_running() in kanban_db.py with heartbeat freshness check
- Records outcome='stale' on run close + 'stale' event; ticks failure counter
- Wires config through gateway embedded dispatcher
- Updates _cmd_dispatch verbose/JSON output and daemon logging

Resolved test-file end-of-file conflict by appending both halves.
2026-05-18 21:20:56 -07:00
thewillhuang
f55d94a1e0 feat(kanban): wire dispatcher to dispatch review agents from review column
Salvages #23772 by @thewillhuang. Adds 'review' as a valid kanban task
status and extends dispatch_once to monitor the review column as a
second dispatch source (in addition to the existing ready column).

- Adds 'review' to VALID_STATUSES
- Adds claim_review_task() — atomically transitions review → running
- Adds has_spawnable_review() — health telemetry mirror
- Extends dispatch_once with a review column dispatch loop
- Review agents get 'sdlc-review' skill auto-loaded

Resolved 2 conflicts (VALID_STATUSES merge with main's 'scheduled' state,
test file additions). Adapted claim_review_task to main's
ttl_seconds: Optional[int] = None convention (matches claim_task).
2026-05-18 21:19:51 -07:00
awizemann
31fe229039 feat(kanban): stamp originating ACP session_id on tasks
Salvages #23208 by @awizemann. Tracks which chat session created a
kanban task so clients can render a per-session board without falling
back to tenant + time-window heuristics.

- Schema: tasks gains nullable session_id TEXT column with index
  (additive migration in _migrate_add_optional_columns).
- ACP: server.py exposes the originating session id via HERMES_SESSION_ID
  with save/restore around the agent loop.
- Tool: kanban_create reads HERMES_SESSION_ID (with explicit override).
- CLI: 'hermes kanban list --session <id>' filter; JSON output exposes
  session_id.
2026-05-18 21:15:21 -07:00
Niraven
3ee7a5546d feat(cli): add kanban swarm topology helper
Salvages #26791 by @Niraven. Adds 'hermes kanban swarm' to create a
durable Kanban Swarm v1 graph: a completed root/blackboard card,
parallel worker cards, a verifier gated on all workers, and a
synthesizer gated on the verifier. Stores shared swarm blackboard
updates as structured JSON comments on the root card.

Self-contained: new hermes_cli/kanban_swarm.py module + CLI wiring +
unit tests.
2026-05-18 21:10:12 -07:00
loicnico96
79f6654d16 feat(kanban): surface per-task model_override in show + tool output
Salvages #26897 by @loicnico96. The per-task model_override DB column
already exists on main, but it wasn't exposed in user-facing surfaces.
This adds:
- 'kanban show' prints 'model: <name>' when model_override is set
- kanban_show / kanban_list tool responses include the model_override field

Original branch was stale (PR was authored against an older field name
'model'); applied the substantive surface exposure manually using the
current 'model_override' field name.
2026-05-18 21:09:02 -07:00
LizerAIDev
a846e500b0 feat(kanban): add --sort option to 'hermes kanban list'
Salvages #25745 by @LizerAIDev. Adds --sort {created,created-desc,
priority,priority-desc,status,assignee,title,updated} to 'hermes kanban
list'. Validated against VALID_SORT_ORDERS map; invalid values raise
ValueError. Default behaviour (priority DESC, created ASC) is unchanged
when --sort is omitted.
2026-05-18 20:58:43 -07:00
Bartok9
365da2d2df fix: 4 small surgical bugs
Salvages #23302 by @Bartok9. Four independent one-area fixes:

1. kanban boards delete alias now hard-deletes (not archives) — the
   alias didn't carry --delete, so getattr(args, 'delete', False)
   returned False. Detect boards_action=='delete' explicitly.
2. Gateway auto-title failures no longer leak as user-visible
   warnings — debug-log only since they're not actionable.
3. Background process completion notification snaps truncation to
   the next newline boundary, prepends a marker when content is
   dropped.
4. _cprint() schedules the run_in_terminal coroutine via
   asyncio.ensure_future so output isn't silently dropped from
   background threads (fixes #23185 Bug A). Skips the
   double-print fallback that would fire for mock paths.
2026-05-18 20:54:52 -07:00
SimbaKingjoe
5fdcfd851f feat(kanban): add max_in_progress config to cap concurrent running tasks
Salvages #22981 by @SimbaKingjoe. Adds 'kanban.max_in_progress' config
that caps simultaneously running tasks. When the board already has N
running, dispatcher skips spawning so slow workers (local LLMs,
resource-constrained hosts) don't pile up and time out.

Threads through dispatch_once(max_in_progress=) and gateway dispatcher
config parsing with validation (warns on invalid/below-1 values).
2026-05-18 20:50:13 -07:00
LeonSGP43
a94ddd8073 fix(kanban): honor severity thresholds in diagnostics
Salvages #26431 by @LeonSGP43. Dashboard plugin_api list_diagnostics
was using exact-match (severity == filter), so '--severity warning'
hid 'error' and 'critical' diagnostics. Adds severity_at_or_above()
helper to kanban_diagnostics and uses it in the dashboard endpoint
(CLI already used SEVERITY_ORDER comparison correctly).
2026-05-18 20:47:01 -07:00
LeonJS
9f008bcd5c fix(kanban): release scratch workspace and tmux session on task completion
Salvages #27369 by @LeonJS. complete_task() now calls _cleanup_workspace()
and _cleanup_worker_tmux() after marking a task complete.

Scratch workspaces (used by swarm agents) accumulate on disk — hundreds
of MB per task, never released. Stale tmux sessions from completed
agents also persist indefinitely.

Both gates are safe:
- workspace_kind == 'scratch' gate preserves user worktree/dir workspaces
- tmux #{pane_dead} == 1 gate only kills sessions where the worker has
  already exited
- best-effort: cleanup failures never block task completion
2026-05-18 20:45:29 -07:00
shunsuke-hikiyama
fb96208892 feat(kanban): add initial-status for human-ops cards
Salvages #27526 by @shunsuke-hikiyama. Adds an --initial-status flag
(running|blocked, default running) to 'kanban create', threaded through
kanban_db.create_task() and the kanban_create tool schema. 'blocked'
parks the task directly in the blocked column for R3 human-ops review,
skipping the brief running-to-blocked transition.

Dropped the unrelated 'add' alias, WIFEXITED Windows compat, and
slash-handler error formatting changes that were bundled in the
original PR — those should ship as their own focused changes if still
wanted.
2026-05-18 20:44:02 -07:00
uzunkuyruk
666b66a066 fix(oneshot): pass fallback_providers from profile config to AIAgent
Salvages #23368 by @uzunkuyruk. Oneshot workers (e.g. kanban workers
spawned via 'hermes -p <profile> chat -q ...') were not honouring the
profile's fallback_providers / fallback_model chain because oneshot.py
never read the config and never passed fallback_model= to AIAgent.

Reads cfg.get('fallback_providers') (new list format) or
cfg.get('fallback_model') (legacy single-dict) with the same
normalization cli.py applies, then forwards as fallback_model=_fb.
2026-05-18 20:37:23 -07:00
oemtalks
b9d38a56dd fix(kanban): don't crash dispatched workers when kanban-worker skill is absent
Salvages #27372 by @oemtalks. The dispatcher unconditionally injected
`--skills kanban-worker` into every worker spawn, but worker profiles
sometimes don't have that bundled skill in their skills dir, which is
fatal at CLI startup (`ValueError: Unknown skill(s): kanban-worker`).

Adds `_kanban_worker_skill_available(hermes_home)` and only injects the
flag when the skill resolves. The MANDATORY lifecycle still ships via
KANBAN_GUIDANCE in the system prompt, so omitting the flag is safe.
2026-05-18 20:32:20 -07:00
Ade5954
0392cf53b5 fix(kanban): close sqlite connection on init failure to prevent fd leak
Salvages #28301 by @Ade5954. If WAL setup, PRAGMA application, or schema
init raises after sqlite3.connect() succeeds, the new connection was
leaking. Wrap the body in try/except so the connection is closed before
the exception propagates.
2026-05-18 20:30:56 -07:00
DoGMaTiiC
4da4133d34 fix: assign single-task kanban decompositions 2026-05-18 20:26:02 -07:00
roycepersonalassistant
6c4f11c64a fix: show scheduled kanban tasks in dashboard 2026-05-18 20:25:45 -07:00
ACR27
a5c2836b07 feat(kanban): allow trimmed task comments
SS-1647 live SHIP validation: real code + tests for kanban comment --max-len.
2026-05-18 20:25:29 -07:00
hanzckernel
5d079fee17 fix: harden Kanban worker Hermes command resolution 2026-05-18 20:25:09 -07:00
ht1072
0b547aea03 fix(kanban): make legacy task migration idempotent
(cherry picked from commit 293f1c3a7241b0117669e049d9aa746c9645ac90)
2026-05-18 20:24:53 -07:00
zccyman
fe5e0bf5a3 feat(kanban): add board-level default workdir (#25430) 2026-05-18 20:24:04 -07:00
LeonSGP43
8bfb456948 fix(kanban): pass accept-hooks to worker chat subprocess 2026-05-18 20:23:47 -07:00
LeonSGP43
0f620138b0 fix(kanban): make claim ttl configurable
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-18 20:23:31 -07:00
Interstellar-code
d8ad431de8 fix(kanban): task_age() tolerates ISO-8601 timestamps
Prevents ValueError crash in dashboard get_board() when a task has
an ISO timestamp (e.g. "2026-05-10T15:00:00Z") instead of a unix epoch
int. Adds _to_epoch() helper that normalises both formats.
2026-05-18 20:18:04 -07:00
psionic73
ca8126bd53 fix(kanban): serialize DB initialization 2026-05-18 20:17:48 -07:00
soynchux
9281599b6f fix(kanban): align board_exists with board discovery rules 2026-05-18 20:17:10 -07:00
bradhallett
de9bcfc6a0 fix(kanban): fingerprint crash errors to prevent fleet-wide retry exhaustion
When a systemic failure (provider outage, auth expiry, OOM) crashes
multiple workers simultaneously, detect_crashed_workers increments
each task failure counter independently. The circuit breaker only
trips after N × failure_limit retries across the fleet.

Fingerprint crash errors by normalizing host-specific details (PIDs,
timestamps). When 3+ tasks crash with the same fingerprint in a
single detection cycle, immediately trip the circuit breaker
(failure_limit=1) instead of waiting for repeated failures.

Isolated crashes (unique fingerprints) retain their normal retry
budget. Protocol violations continue to trip immediately.

Includes regression tests for systemic and isolated crash paths.
2026-05-18 20:16:50 -07:00
bradhallett
f042931852 fix(kanban): reset failure counters on unblock_task
When a task is manually unblocked (blocked → ready/todo), the
consecutive_failures counter and last_failure_error were left intact.
The next failure would immediately re-trip the circuit breaker because
the counter was still at or above the failure limit.

Reset both fields on unblock so the task gets a fresh retry budget.

Includes a regression test that verifies counters are zeroed.
2026-05-18 20:16:32 -07:00
sprmn24
5db0d72c90 fix(kanban): use 'is not None' check for max_runtime_seconds in create_task
max_runtime_seconds=0 was being silently coerced to None due to a falsy
check (if max_runtime_seconds). Zero is a valid value that causes the
dispatcher to immediately time out a task. The adjacent max_retries
parameter already used the correct 'is not None' pattern.

Fixes the inconsistency by aligning max_runtime_seconds with max_retries.
2026-05-18 20:16:15 -07:00
bradhallett
40c1decb3b fix(kanban): promote blocked tasks when parent dependencies complete
recompute_ready only scanned 'todo' tasks for promotion, ignoring
'blocked' tasks entirely. When a task was blocked (e.g. by the circuit
breaker) and its parent dependencies later completed, the task stayed
stuck in 'blocked' forever unless manually unblocked.

Now recompute_ready also scans 'blocked' tasks. When all parents are
done/archived, the blocked task is promoted to 'ready' with failure
counters reset — equivalent to an automatic unblock.

Includes a regression test for the blocked-parent-done promotion path.
2026-05-18 20:15:55 -07:00
Que0x
bc961c13f3 fix(kanban): sync slash subcommands with live parser 2026-05-18 20:15:38 -07:00
argabor
f149e1e567 fix(cli): make kanban specify max_tokens configurable 2026-05-18 20:15:20 -07:00
Zyrixtrex
b7ea62e5d3 fix(kanban): promote dependents when a parent is archived 2026-05-18 20:15:03 -07:00
Zyrixtrex
326c15d955 fix(kanban): preserve notifier_profile for dashboard home subscriptions 2026-05-18 20:14:45 -07:00
QuenVix
afae2dd9ec fix(kanban): keep board-management commands independent from board override 2026-05-18 20:14:27 -07:00
QuenVix
8a64e1580b fix(kanban): ignore stale HERMES_KANBAN_BOARD for removed boards 2026-05-18 20:14:10 -07:00
ms-alan
97ac94fe56 fix(kanban): seed bundled skills (e.g. kanban-worker) on kanban init
Closes #23725
2026-05-18 20:13:52 -07:00
momowind
4519d2b476 fix(web): add Cache-Control: no-store to plugin static file serving
Prevents browser caching of stale dashboard plugin JS files that may
contain bugs already fixed upstream (e.g. COLUMN_LABEL undefined).
2026-05-18 20:13:35 -07:00
briandevans
d62964cdfa fix(kanban): clear _INITIALIZED_PATHS in remove_board so recycled DBs re-init schema
Archiving or deleting a board via remove_board() leaves the path's
"schema already initialized" entry in the module-level cache. A
concurrent connect(board=<slug>) call (e.g. the dashboard event-stream
poll loop) then:

  1. resolves the same kanban.db path,
  2. recreates the directory + an empty sqlite file because
     connect() does mkdir(parents=True, exist_ok=True),
  3. skips the CREATE TABLE pass because the cache entry says the
     schema is already in place,
  4. errors on the next read with `no such table: task_events`.

Drop the cache entry before mutating the filesystem so the fresh file
gets a proper schema init on next connect(). Applies to both
archive=True (rename) and archive=False (rmtree) branches.

Fixes #23833.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 20:13:17 -07:00
hongchen1993
f01ee0b575 feat: per-task model override for kanban workers
- Add model_override field to Task class and tasks schema
- Add migration for existing databases
- Spawn worker with -m model when model_override is set
2026-05-18 20:12:28 -07:00
xxxigm
cafbc9a734 feat(cli): wire --manual-paste into `hermes auth add and hermes model`
Register the new ``--manual-paste`` flag on both entry points and
thread it through to the xAI loopback login:

* ``hermes auth add xai-oauth --manual-paste`` — pool-add path,
  forwarded inside ``auth_commands.handle_auth_add``.
* ``hermes model --manual-paste`` — model-picker path, forwarded
  by ``_model_flow_xai_oauth`` into the synthetic ``argparse.Namespace``
  it passes to ``_login_xai_oauth``.  The picker also now forwards
  ``--no-browser`` and ``--timeout`` for consistency (previously
  hardcoded to defaults regardless of CLI flags).

Help text on both flags points at #26923 and names the
browser-only remote consoles (Cloud Shell, Codespaces, EC2
Instance Connect) so users searching ``hermes --help`` can find
the workaround.
2026-05-18 20:10:52 -07:00
xxxigm
5a5c265bcf fix(oauth): add manual-paste fallback for browser-only remote consoles
xAI Grok OAuth (and Spotify) use a loopback redirect to
``http://127.0.0.1:<port>/callback`` to capture the authorization
code. That works when the browser and Hermes run on the same
machine, and the SSH tunnel recipe handles the regular remote
case. It breaks completely on **browser-only remote consoles**
(GCP Cloud Shell, GitHub Codespaces, AWS EC2 Instance Connect,
Gitpod, Replit, …) where the user has a browser but no real SSH
client to forward a port — the redirect to 127.0.0.1 on the
remote VM simply isn't reachable from the laptop, and there's
nothing the existing flow can do about it (#26923).

This commit adds the foundation for a manual-paste fallback:

* ``_is_remote_session`` now also recognises Cloud Shell,
  Codespaces, Gitpod, Replit, StackBlitz (in addition to SSH),
  so the existing tunnel hint at least fires in those
  environments.
* ``_parse_pasted_callback`` accepts any of: a full
  ``http(s)://...?code=...&state=...`` URL, a bare ``?code=...``
  query string, a bare ``code=...&state=...`` fragment, or a
  bare opaque code value.  Returns the same dict shape the HTTP
  callback handler produces, so the caller's state / error
  validation works unchanged (no CSRF bypass).
* ``_prompt_manual_callback_paste`` reads stdin with a clear
  multi-line explanation of what's happening and what to paste.
* ``_xai_oauth_loopback_login`` gains a ``manual_paste`` kwarg
  that skips the HTTP listener entirely.  The redirect_uri,
  PKCE verifier, state, and nonce are byte-identical to the
  loopback path so xAI's token endpoint can't tell the
  difference at the protocol level.
* ``_print_loopback_ssh_hint`` now also mentions
  ``--manual-paste`` so users without a real SSH client see a
  path forward instead of a dead-end tunnel recipe.
* ``_login_xai_oauth`` threads ``args.manual_paste`` into the
  loopback helper.
2026-05-18 20:10:52 -07:00
yannsunn
1d6f3753de feat(proxy): add xai upstream adapter for Grok via OAuth 2026-05-18 20:09:32 -07:00
Beandon13
bde6313e34 feat(kanban): archive --rm to hard-delete archived tasks
Salvages #19964 by @Beandon13. Adds `hermes kanban archive --rm` to
permanently remove already-archived tasks with cascading cleanup of
links, comments, events, runs, and notify-subs. Safety guard: only
archived tasks can be deleted; active/blocked/done must be archived
first.

Cherry-picked from #19964 onto current main (severe stale base, applied
manually to preserve substance only).
2026-05-18 20:09:26 -07:00
xxxigm
60ef368792 fix(xai-oauth): split 403 (tier/entitlement) from 400/401 in token endpoint
xAI's token endpoint returns HTTP 403 to the OAuth grant when the
account isn't on the allowlist for API access (e.g. standard
SuperGrok subscribers — see #26847). Treating it like a stale-token
400/401 made ``format_auth_error`` append "Run ``hermes model`` to
re-authenticate", which is misleading because re-login can't change
xAI's tier decision.

Split 403 off in both ``refresh_xai_oauth_pure`` and the loopback
login token exchange:

* New error code ``xai_oauth_tier_denied`` with ``relogin_required=False``
* Message explains the entitlement gate and points at the
  ``XAI_API_KEY`` + ``provider: xai`` fallback
* 400/401 still set ``relogin_required=True`` as before
* 5xx still set ``relogin_required=False`` as before
2026-05-18 20:08:09 -07:00
iqdoctor
4229facc01 docs(windows): avoid piping installer directly into iex 2026-05-18 20:05:47 -07:00
houenyang-momo
50158a60f9 fix(tui): improve charizard completion menu contrast 2026-05-18 20:05:23 -07:00
zccyman
2e09d2567c feat(kanban): add auto_promote_children config toggle
When the kanban auto-decomposer fans a triage task into child tasks,
recompute_ready() immediately promotes parent-free children to 'ready'
so the dispatcher picks them up. Some users want a manual workflow
where children stay in 'todo' for review before dispatch.

Add 'kanban.auto_promote_children' config key (default: true):
- false: children stay in 'todo' after decomposition
- true: existing behavior (auto-promote to 'ready')

Changes:
- kanban_db.py: decompose_triage_task() gains auto_promote param
- kanban_decompose.py: reads auto_promote_children from config
- kanban dashboard API: exposes the new setting in GET/PUT /orchestration

Closes #28016
2026-05-18 20:04:32 -07:00
EloquentBrush0x
b3e714e8b7 fix(xai-oauth): quarantine dead tokens on terminal refresh failure
resolve_xai_oauth_runtime_credentials() called _refresh_xai_oauth_tokens()
with no try/except. A terminal refresh failure (HTTP 400/401/403 —
invalid_grant, token revoked) propagated without clearing the dead
access_token / refresh_token from auth.json, causing every subsequent
session to retry the same doomed network request.

Add a try/except around the refresh call that mirrors the existing
credential_pool.py quarantine: when _is_terminal_xai_oauth_refresh_error
identifies a non-retryable failure, clear the dead token fields from
auth.json and write a last_auth_error diagnostic marker so future calls
fail fast with a clear relogin_required error instead of hitting the
network.

active_provider is preserved (set_active=False) so multi-provider users
whose chosen provider is not xai-oauth are unaffected.

Tests: two new cases in test_auth_xai_oauth_provider.py cover terminal
quarantine and transient pass-through.
2026-05-18 20:02:11 -07:00
hehehe0803
87ace43f1e fix(aux): remove stale session_search model menu entry 2026-05-18 20:01:34 -07:00
zccyman
4e9df52d60 fix: elevate plugin discovery failures from debug to warning
Plugin discovery exceptions in gateway startup (gateway/run.py) and
CLI startup (hermes_cli/main.py) are caught and logged at DEBUG
level, making them invisible at the default INFO log level.

If any plugin import fails — syntax error, missing dependency, import
cycle — operators get zero indication unless they bump the log level
to DEBUG. This makes broken plugins appear enabled but silently
non-functional.

Change both locations to logger.warning() so failures are visible at
production log levels.

Closes #28137
2026-05-18 19:35:41 -07:00