18 Commits

Author SHA1 Message Date
ouyangbo 012c9c6bed chore: anchor fresh-start history to upstream 2026-09-16 15:06:54 +08:00
Teknium fd1a0594c1 refactor(tui_gateway): X2 wave3 — compute_host guarded frame handlers + session-build split, groups profile/kwargs folds, project_tree placement/auto-bucket compaction, projects row/policy folds, entry+oauth plumbing trims (2653->2437 LOC) 2026-09-03 04:15:06 -07:00
Teknium de227bdc34 refactor(tui_gateway): final compaction pass on r3-36 group G
- mcp_oauth_sessions: lift the probe+rollback block out of _worker (_probe_with_rollback).
- project_tree: lane group built from a field table; stamp_profile/_lane_key/_disambiguate
  flattening. methods_projects: policy loader + scan loop tightened. agent_callbacks:
  preview-history filter via _PREVIEW_HISTORY_ROLES. Blank-line squeeze after in-body
  lazy imports (AST-identical). All goldens identical; WIRE-PARITY-OK.
2026-09-02 23:54:51 -07:00
Teknium aef127ad46 refactor(tui_gateway): projects mutator table, mcp_startup call helper, kwargs table in agent_callbacks
- methods_projects: update/add_folder/remove_folder/set_primary handlers registered from a
  _PROJECT_MUTATORS table (same @method names, same 5062/5063/5061 mapping); _pick() for
  params->kwargs; old-vs-new RPC golden (27 calls incl. error paths) identical.
- entry: _mcp_startup_call() replaces 5 lazy import+try/except ladders on hermes_cli.mcp_startup
  (tests monkeypatch mcp_startup module attrs — still resolved at call time).
- agent_callbacks: _background_agent_kwargs built from key tables; drop unused _registry.
- docstring compaction (module docstrings <= 8 lines, WHYs kept).
WIRE-PARITY-OK.
2026-09-02 23:44:18 -07:00
Teknium 79d0d3b600 refactor(tui_gateway): compact project_tree/methods_projects/agent_callbacks/entry/mcp_oauth_sessions (-16%)
- project_tree: split build_tree (207 LOC) into _auto_buckets/_home_project phase helpers;
  _field() replaces 9 '(x.get(k) or "").strip()' ladders; _project_node takes wire-shaped
  **flags; drop unused placement 'repo_path' and _strip_trailing_sep; _FolderIndex/
  _project_for_session collapsed. Old-vs-new golden (67 synthetic build_tree runs) identical.
- methods_projects: _project_ok/_path_param unify 4 handler tails; _project_tree_row via
  dict comprehensions; discovered-cache read via contextlib.nullcontext; policy loader
  helpers. Golden over rows/policy/junk predicates identical.
- agent_callbacks: subagent mirror dispatch on a delta table; drop _render_personality_prompt
  (single caller); getattr shorthand in _background_agent_kwargs.
- entry: _write_or_exit unifies 3 write-fail exits; suppress(); heartbeat/sweep start loop.
- mcp_oauth_sessions: suppress(), gc/listener/redirect compaction, docstrings.
WIRE-PARITY-OK; tests green.
2026-09-02 23:24:24 -07:00
Teknium 93fead86dd refactor(tui): compact peripheral modules (compute_host, entry, hosted_room_*, ws, project_tree, ...); add _env knobs and method_ctx.bind_module
Docstring/comment compaction plus small structural dedupe across the
tui_gateway peripheral modules. Originally landed as an outage-recovery
snapshot; reviewed and verified afterwards (import smokes, cluster tests).
2026-09-02 14:09:00 -07:00
evan-bradford 208549432e fix(gateway): make profile-scoped project session rows self-describing
A scoped projects.tree / projects.project_sessions response is built from
ONE profile's state.db, so the request scope is authoritative even for
legacy rows whose persisted profile_name is NULL. Without the stamp those
rows reach the renderer ownerless, and every owner lookup off them — the
branch path included — falls back to whichever backend is active.

Co-authored-by: evan-bradford <evan-bradford@users.noreply.github.com>
2026-09-01 22:58:48 -05:00
briandevans f7de2ca416 fix(desktop): order project-tree lanes by recency in the overview, not alphabetically
_build_repos emptied each lane's sessions array for the overview (hydrate=False)
payload BEFORE _sort_lanes ran. _lane_sort_key derives a lane's activity from
max(_session_time(s) for s in group["sessions"]), so with the rows already gone
every non-trunk lane scored activity=0.0 and the sort key
(is_trunk, is_kanban, -activity, label) collapsed to alphabetical-by-label. The
documented intent — branches and linked worktrees sort by most-recent activity,
then label — was silently defeated on the projects.tree RPC that feeds the
desktop sidebar overview, while the drill-in path (hydrate=True) kept the rows
and sorted correctly. Any repo with two or more non-trunk lanes showed a
different order in the overview than when opened.

Move the session-clearing to after _sort_lanes/_disambiguate_labels so the sort
reads real recency. Lane counts are still captured before clearing, so
sessionCount and the slim overview payload are unchanged — only the order is
fixed, and the overview now matches the drill-in.
2026-08-15 00:33:11 -07:00
Tranquil-Flow 0d07fe63f9 fix(projects): use _branch_lane_id for non-git folders to prevent duplicate lanes (#53329)
_place_by_heuristic used the raw path as the lane key for non-git
project folders, while the desktop overlay independently computed
::branch::main for the same session (since git_branch was null).
The ID mismatch caused duplicate lanes — one from the backend with
the folder name, one from the overlay labeled 'main'.

Use _branch_lane_id(path, DEFAULT_BRANCH_LABEL) so the backend's
lane key matches the overlay's expected ::branch::main scheme,
eliminating the duplicate lane.
2026-08-15 00:33:11 -07:00
Brooklyn Nicholson 5b68d2271b feat(profiles): serve a cross-profile project tree and per-profile usage totals
`projects.tree` answers for the backend's own profile, so the grouped
sidebar had nothing to draw once the user asked to see every profile.
Run the same authoritative builder once per profile against that
profile's state.db and merge the results by folder, so one checkout is
one group no matter how many profiles work in it, and the owning profile
rides on each session row where the badge and filter can read it.

Group totals are summed in SQL rather than over the loaded page — a
number that shrank as you scrolled would be worse than no number.

Scope the batched sidebar slices while we're here: cron and messaging
came back cross-profile unconditionally, which is why a concrete profile
showed another profile's Telegram threads and cronjobs.

Closes #65710
Closes #42651
Closes #70629
2026-08-10 03:13:08 -05:00
bb 4cefba3ec9 fix(desktop): stop rendering a repo's main checkout as a duplicate sidebar lane
The main-checkout test compared the two probe roots with raw string
equality. When they differed only in separator spelling, the repo's own
checkout was misclassified as a linked worktree: it fell through to the
worktree branch and was labeled by directory basename. The sidebar then
showed one checkout twice — a dir-labeled lane plus the branch-labeled
`main` lane built from the same sessions.

Compare with `_path_key` so platform path identity decides, matching how
every other path comparison in this module is already keyed.

Tests cover the single-checkout case and the main + linked-worktree case;
both fail before this change (the lane comes back labeled `repo`, not
`main`).
2026-08-07 12:48:41 -05:00
Brooklyn Nicholson 60b6ea237f feat(gateway): group unplaced sessions into a Home bucket in the project tree
Sessions with no cwd — or whose folder can't be promoted to a project (the
bare home dir, a deleted workspace, HERMES state) — were dropped from the
project tree entirely, so the grouped sidebar silently showed fewer chats
than flat Recents. Collect them into a synthetic `__no_project__` node at
the head of the list. It carries one lane purely to hold the rows, and is
omitted when empty so a project-less install stays blank.
2026-07-27 16:04:57 -05:00
Brooklyn Nicholson b572ec3dda fix(projects): don't promote a deleted workspace to a project
The name-based sibling probe can't reach every dead worktree: a dir renamed away
from its repo's prefix (`hermes-salvage-drafts` next to `hermes-agent`) shares
nothing to trim back to, and a scratch dir under /tmp was never a worktree at
all. Those fall through to the path-only heuristic, which reports the cwd as its
own repo root, and Tier 2 promotes it to a top-level project — one that can't be
opened and can only be dismissed by hand.

Gate auto-project promotion on the directory still existing, threaded in as an
injected predicate to keep the builder pure. The guard keys on the directory,
not on git-ness, so a plain non-git folder that's still on disk keeps its
project; callers that can't stat (remote backends) omit it and keep every
candidate. A stale persisted `git_repo_root` gets the same treatment, so a
deleted repo can't resurrect from the recorded value alone.
2026-07-26 00:57:17 -05:00
Brooklyn Nicholson c7fcb73e3d fix(projects): fold deleted-worktree subdirs into their parent repo
`_probe_sibling_worktree` recovers a deleted `<repo>-<suffix>` worktree by
trimming its name back to a sibling that still resolves, but it only trimmed
the LEAF segment. A session's cwd is usually a subdir of the worktree
(`<repo>-<suffix>/apps/desktop`), whose basename shares nothing with the repo,
so the probe no-oped and the dead path fell through to the path-only heuristic
— which minted it as its own main repo root, and then as a top-level project.

Walk the ancestors, deepest first, with the probe budget shared across the whole
walk so a deeply nested cwd can't fan out into a probe storm.
2026-07-26 00:56:19 -05:00
Brooklyn Nicholson 47c95130f7 fix(project-tree): absorb deleted-worktree sessions into the parent home checkout
A linked worktree at <repo>-<suffix> that has been deleted leaves its
sessions with a dangling cwd: the git probe fails and no git_repo_root was
persisted, so the path-only heuristic promoted each one to its own
standalone project. Every abandoned worktree added another phantom entry to
the sidebar, and they accumulate indefinitely.

Recover the parent by trimming one -<segment> at a time off the basename and
returning the first sibling that resolves. A deleted worktree has no checkout
to return to, so its sessions land in the parent's trunk lane rather than a
lane keyed by the dead path.

Live worktrees are unaffected — they resolve through the git probe and keep
their own lane.
2026-07-25 20:31:17 -05:00
Brooklyn Nicholson ceb179163d fix(desktop): mirror Windows path identity in live overlay + WSL spelling
Addresses @teknium1's review of #61950:

- The desktop live overlay (workspace-groups.ts) matched cwd membership
  case-sensitively, so a fresh mixed-case/separator Windows session missed
  its explicit/auto project until the next backend tree refresh. Mirror the
  backend identity (isWindowsPath/comparisonSegments/pathKey) in isPathUnder,
  liveSessionProjectId, and overlayRepoLanes lane matching. Comparison-only —
  emitted ids/labels keep their spelling. POSIX stays case-sensitive.
- Backend _is_windows_path missed root-relative `\wsl.localhost\...` (single
  leading backslash), leaving that historical spelling case-sensitive. Classify
  any backslash-rooted path as Windows.

Tests: WSL-spelling collapse + explicit-project precedence (project_tree),
Windows/WSL live-overlay membership + POSIX case-sensitivity (workspace-groups).
2026-07-12 04:33:05 -04:00
Esthon Jr fff1769bd1 fix(desktop): preserve legacy non-git workspace groups 2026-07-12 04:30:37 -04:00
Brooklyn Nicholson 4e023f5bc9 feat(gateway): build authoritative project tree 2026-06-25 16:40:27 -05:00