Skip to content

feat: add backend workspace git APIs - #11

Merged
stocky789 merged 76 commits into
masterfrom
feat/workspace-git-backend
May 21, 2026
Merged

stocky789 merged 76 commits into
masterfrom
feat/workspace-git-backend

Conversation

@stocky789

Copy link
Copy Markdown
Owner

Summary

  • Adds the backend workspace Git API surface for status, diff, staging, discarding, commit, fetch/pull/push, branch checkout, and structured Git errors.
  • Includes path/workspace validation, nested workspace scoping, ignored-file reporting, CRLF/filemode noise handling, and selected-file commit support.
  • Keeps this PR backend-only as the first slice of the workspace Git UI work requested after review on upstream PR feat(workspace): add integrated Git controls nesquena/hermes-webui#2578.

Scope

Backend slice only. The frontend/UI wiring is intentionally kept for a follow-up PR after this lands cleanly.

Verification

  • Python 3.11: pytest tests/ -v --timeout=60
  • Python 3.12: pytest tests/ -v --timeout=60
  • Python 3.13.13 via uv: pytest tests/ -v --timeout=60

Local Python 3.13 result:

6060 passed, 47 skipped, 3 xpassed, 8 subtests passed

Notes

This is an internal fork PR first so we can verify the GitHub Actions matrix before opening the upstream backend slice.

bjb2 and others added 30 commits May 18, 2026 21:40
Adds a "Download Folder" item to the workspace file-tree right-click
menu and a GET /api/folder/download endpoint that streams the
directory as a zip with Content-Disposition: attachment.

Configurable caps:
  HERMES_WEBUI_FOLDER_ZIP_MAX_MB    (default 1024)
  HERMES_WEBUI_FOLDER_ZIP_MAX_FILES (default 50000)

Pre-flights the walk so cap-exceeded returns 413 + JSON BEFORE any
zip bytes are sent. Symlinks resolving outside the workspace are
skipped. Mirrors the existing _handle_file_raw shape (session_id
resolution, safe_resolve, RFC 5987 filename via
_content_disposition_value). Stdlib zipfile only; no new dependencies.

Tests: 11 static-inspection tests matching the style of
tests/test_issue1867_upload_size_preflight.py. All passing on
Python 3.11/3.12/3.13.
When API server runs append messages directly to state.db, reconcile WebUI sidecar sessions with those canonical rows across API responses, model-facing streaming context, and active browser refresh.

Add append-only state.db merge helpers, metadata-only counts for refresh polling, and regression coverage for API visibility, context incorporation, and frontend refresh behavior.
Force same-session external refreshes to dismiss stale approval and clarification prompts immediately so completed state.db updates do not leave the composer blocked.
When a queued message was waiting for the active stream to finish,
the 120ms setTimeout drain in setBusy(false) would write the queued
text to the shared #msg composer and call send(), which reads
S.session.session_id at call time. If the user switched to a different
chat during the 120ms window, the queued message was sent to the
wrong session.

Two fixes:

1. setBusy(false) drain: guard the setTimeout callback — if the
   currently viewed session no longer matches the drain session,
   put the message back into the original session's queue instead
   of sending it.

2. _sendInProgress re-queue: track _sendInProgressSid alongside
   _sendInProgress so that when a concurrent send() is caught by the
   guard, the re-queued message targets the in-flight session rather
   than the currently viewed one.
# Conflicts:
#	CHANGELOG.md
# Conflicts:
#	CHANGELOG.md
# Conflicts:
#	api/streaming.py
# Conflicts:
#	CHANGELOG.md
CI parity tests enforce that every key in the English locale block exists
in zh, ja, ko, ru, and es. The PR introducing download_folder added it to
en only, which broke the 5 hard-parity test files. Adds the English
fallback to all 10 non-en blocks (it/ja/ru/es/de/zh/zh-Hant/pt/ko/fr) with
the project's // TODO: translate marker so translators can refine later.

Tests: tests/test_chinese_locale.py, test_japanese_locale.py,
test_korean_locale.py, test_russian_locale.py, test_spanish_locale.py —
26/26 passing locally.
AJV20 and others added 20 commits May 19, 2026 18:41
# Conflicts:
#	CHANGELOG.md
Unreleased section now reflects:
- PR nesquena#2598 live tool event dedup (AJV20)
- PR nesquena#2533 browser dashboard links (AJV20)
- PR nesquena#2607 messaging transcript dedup (AJV20)
- PR nesquena#2521 Geist Contrast skin (intellectronica)
- PR nesquena#2524 SSE runtime diagnostics endpoint (AJV20)

Removed merge markers and consolidated stray entries that leaked into the v0.51.94 release block.
PR nesquena#2521 (Geist Contrast skin) legitimately adds a scoped
`:root[data-skin="geist-contrast"] .theme-pick-btn.active` override that
appears earlier in style.css than the global `#mainSettings .theme-pick-btn.active`
rule. The naive substring search in tests/test_1059_settings_picker_active_state.py
found the skin-specific override first (which correctly uses --border2 for its
palette), failing the global assertion that wanted --accent.

Tighten both assertions to anchor on the `#mainSettings` selector prefix so
they always match the global rule regardless of how many skin-specific
overrides land in the file.
…assertion

PR nesquena#2521 (Geist Contrast skin) adds a scoped
`:root[data-skin="geist-contrast"] .session-item.active .session-title` rule
that legitimately uses its own palette values. The existing assertion in
test_sprint40_ui_polish.py matched on any line containing the
`.session-item.active .session-title` substring, picking up the skin-scoped
override and asserting against its palette.

Exclude lines containing `:root[data-skin=` from the base-rule scan so
skin-scoped overrides are free to use their own design tokens, while the
global rule still enforces var(--gold) / var(--accent-text).
release: v0.51.95 (Release BS / stage-388 / 5-PR batch — live tool dedup + browser dashboard links + messaging dedup + Geist Contrast skin + SSE diagnostics)
@stocky789 stocky789 closed this May 20, 2026
@stocky789 stocky789 reopened this May 20, 2026
@stocky789 stocky789 closed this May 20, 2026
@stocky789 stocky789 reopened this May 20, 2026
stocky789 added 3 commits May 20, 2026 05:43
Distinguish CRLF-only working tree changes from filemode-only noise when the ignored-CR diff path set is empty on GitHub Actions.
@stocky789
stocky789 merged commit 45c7a69 into master May 21, 2026
0 of 3 checks passed
@stocky789
stocky789 deleted the feat/workspace-git-backend branch May 26, 2026 04:03
stocky789 pushed a commit that referenced this pull request Jun 4, 2026
…rs (Codex review #11)

Per-profile WebUI state lives at <root>/webui_state (api/workspace.py), so
<base>/profiles/<name>/webui_state/sessions/*.json was reachable — it is not a
direct child of the profile root, so the prior deny-subdir loop missed it. Add
<root>/webui_state/<state-subdir> to the deny dirs for every Hermes root. Adds a
regression assertion (profile webui_state/sessions/*.json → 403).
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.

9 participants