Skip to content

Add Anthropic Claude OAuth support and enhance Factory tab features#720

Open
Avi-Bendetsky wants to merge 41 commits into
fathah:mainfrom
BAS-More:main
Open

Add Anthropic Claude OAuth support and enhance Factory tab features#720
Avi-Bendetsky wants to merge 41 commits into
fathah:mainfrom
BAS-More:main

Conversation

@Avi-Bendetsky

Copy link
Copy Markdown

No description provided.

Avi-Bendetsky and others added 30 commits June 8, 2026 02:40
…lans

Adds an 'Anthropic Claude (OAuth)' card to the OAuth Plans panel with a
native paste-a-code PKCE sign-in flow.

The existing OAuth handler spawns 'hermes auth add <provider> --type oauth'
with stdin ignored and only auto-handles device-code flows. Anthropic uses
a paste-a-code PKCE flow that needs the code fed back, which the CLI refuses
on a non-tty stdin. This implements the PKCE dance natively in the Electron
main process and persists the resulting token into the engine credential
pool, so repeated sign-ins add multiple Claude accounts.

- constants.ts: add anthropic OAuth card
- hermes-auth.ts: buildAnthropicAuthUrl + exchangeAnthropicCode (native PKCE,
  dual token endpoint, pool persistence via engine)
- index.ts: anthropic-oauth-start / -submit IPC handlers
- preload: anthropicOauthStart / anthropicOauthSubmit bridge
- OAuthLoginModal.tsx: paste-code UI branch for anthropic
- i18n: anthropicDesc (all locales) + common.submit

Verified: typecheck clean; PKCE challenge=S256(verifier); token exchange
reaches both endpoints and is correctly rejected on a bad code.
Add Anthropic Claude (OAuth) to Subscription/OAuth Plans
Addresses Codex review: the Anthropic submit path didn't pass the
selected profile, so credentials always landed in the default profile's
pool. Now profile flows renderer → preload → IPC → exchangeAnthropicCode
→ persistAnthropicToken, which points HERMES_HOME at the profile home
(<HERMES_HOME>/profiles/<name>) for non-default profiles. Default profile
behavior unchanged.
fix: thread selected profile through Anthropic OAuth persistence
Surface the engine's existing multi-account credential rotation in the
Providers UI. Each OAuth sign-in already appends a new pool entry that the
engine rotates across (skipping rate-limited ones), but nothing in the UI
communicated this.

- OAuthLoginModal: add an onSuccess callback fired when an account is
  successfully added, so the Providers screen can reload the pool
  immediately. Previously a new account stayed invisible until the user
  navigated away and back (onClose did not reload).
- Providers OAuth card: show an "N connected" badge and an "Add account"
  button (instead of "Sign in") once a provider has >=1 account, plus a
  hint explaining automatic rotation. Generic across all OAuth providers.
- Credential-pool list: per-account Active / Rate-limited / Failed badges
  computed from the engine selection strategy and last_status, so users
  can see which account is live and which are exhausted.
- i18n (en): addAccount, accountsConnected (plural), rotationHint, and the
  pool status badge/hint strings. Other locales fall back to en.

Motivation: adding multiple Anthropic accounts is the fix for hitting
Claude Pro/Max rate limits - this makes that capability discoverable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A local TLS-intercepting proxy (corporate MITM, the 9Router dev proxy,
Fiddler/mitmproxy) presents a self-signed root CA that the Python gateway's
bundled certifi store does not trust. Electron itself is unaffected because
it launches with NODE_EXTRA_CA_CERTS, but the gateway is a separate Python
process whose httpx/requests/SDK calls fail with CERTIFICATE_VERIFY_FAILED
against every upstream (Anthropic, Gemini, MCP servers).

buildGatewayEnv now detects such a proxy CA (NODE_EXTRA_CA_CERTS or the
9Router rootCA), builds a combined bundle (certifi + the proxy CA) under
HERMES_HOME, and points HERMES_CA_BUNDLE / SSL_CERT_FILE / REQUESTS_CA_BUNDLE
/ CURL_CA_BUNDLE at it. No-op when no proxy CA is present or when the user
already set those vars, so default installs are unaffected. The bundle is
regenerated only when missing or stale.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… in the UI

Surfaces the autonomous dev-factory's engine-side governance (which until now
lived only in config.yaml + CLI) as a first-class Hermes One sidebar tab,
between Kanban and Models.

Four sections, editable:
- GOVERNANCE: oversight level selector (monitor/warn/gate/strict, factory-wide),
  secret-scan enable/disable (shows the live pattern count: 23 + entropy),
  a per-profile table (level/secrets/hybrid/protected), and a protected-paths
  chip editor (add/remove globs).
- BUDGET: kill-switch halt/resume + the breaker dimensions.
- ORCHESTRATION: the kanban orchestrator/assignee/auto-decompose knobs (read).
- ACTIVITY: the live governance-block feed (decision/code/path) + recent builds
  — the proof the governor is actually firing.

Data path mirrors the existing Kanban tab: renderer ->
window.hermesAPI.kanbanGovern{Status,Set,KillSwitch} -> IPC -> main/kanban.ts
-> execFile(hermes, ["kanban","govern","--json"]). The engine command does all
config reads/writes (surgical, comment-preserving); the UI is a thin client.

Files: main/kanban.ts (govern bridge fns), main/index.ts (3 IPC handlers),
preload/index.ts + .d.ts (api + GovernStatus/GovernSetChange types),
screens/Factory/Factory.tsx (new), Layout.tsx (View+NAV_ITEMS+pane),
icons (ShieldCheck), en/navigation.ts (factory key), main.css (factory-* styles).

Verified: npm run typecheck (node+web) exit 0; electron-vite build exit 0.
Remote-only mode shows the RemoteNotice (governance needs local/SSH).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Implements the agreed PRD on top of the v2 engine (autonomy semantics +
govern extensions, hermes-agent 4c464f7f7).

LAYOUT (Nielsen-applied):
- Pinned status strip: oversight level (+MIXED badge), Running/HALTED,
  secrets+pattern count, hybrid X/6, blocks-today.
- User-selectable section order (localStorage): Control-first / Monitoring-first
  / Classic.

NEW CONTROLS:
- Per-profile table now fully editable inline: level dropdown, secrets toggle,
  hybrid toggle — instant-save via govern set --for-profile. Factory-wide level
  buttons + Enable/Disable-all remain as bulk shortcuts.
- Budget: default max-iterations + wall-clock inputs (0=unlimited, inherited by
  new cards), shows the per-block retry cap. Kill-switch relabeled "Halt all
  agents".
- Orchestration: read-only knob table.

ACTIVITY:
- Auto-refresh (15s, toggleable, pauses when tab hidden).
- Filters: decision / code / profile.
- Columns: when, decision, code, profile, finding message.
- Click a block row -> jump to the Kanban tab (v1 cross-tab; task-focus is a
  fast-follow).
- Settings change-log panel (from the engine's reversible changelog).

BEHAVIOR (Nielsen #3/#5/#9):
- Confirm dialogs before the 4 risky actions: Halt all agents, Disable secret
  scanning, Set level to monitor, Remove a protected path.
- Undo toast on changes (wired for level changes — captures prior value and
  re-applies; other changes toast-only).
- Failed writes surface the engine's actual error.

IPC: governSet gains hybrid/defaultMaxIterations/defaultWallclock; GovernStatus
type gains budget.default_*/per_block_retry_cap + activity.change_log. The
bridge already uses --for-profile (not --profile) to dodge hermes's global -p.

Verified: npm run typecheck (node+web) exit 0; electron-vite build exit 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GOVERNANCE table: new Model column — a per-agent LLM picker. Dropdown is the
engine's curated cc/ + ag/ list (governModels → 9Router /v1/models, filtered to
adapter-compatible shapes; fail-soft to configured models). A "+ custom…"
option prompts for any id; a cx/ id is warned in the UI and hard-rejected by the
engine (govern set --model). Instant-save with an undo toast.

ORCHESTRATION section is now editable:
- Orchestrator profile + Default assignee: dropdowns over the 6 profiles.
- Auto-decompose: on/off toggle.
- Auto-decompose per-tick + Max in-progress / profile: number inputs.
- failure_limit + dispatch_in_gateway stay read-only (lower-level, by design).
All instant-save via govern set, change-logged.

IPC: new kanbanGovernModels (kanban-govern-models); governSet gains model,
autoDecomposePerTick, maxInProgress. GovernProfileState gains `model`.

Verified: npm run typecheck (node+web) exit 0; electron-vite build exit 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…loop controls

Surfaces the engine closed-loop (verify→correct→done) in the Factory tab.

BUILDS section (the "oversees what they're doing" pane): per-build cards showing
goal/title (click-through to the Kanban task), loop-state badge (Building /
Verifying / Correcting / Done / Escalated), orchestrator profile, verify round
N/max, last verdict (PASS green / FAIL red), collapsible acceptance criteria, and
on escalation the "why escalated" diagnosis. Empty + loop-off states explained.
Added to all three layout orders.

ORCHESTRATION gains a "Closed-loop oversight" group:
- Orchestrator loop on/off toggle — turning ON is confirmed (it changes how builds
  complete); turning OFF is instant.
- Max verify rounds input (1–10, default 3).

Bridge/types: GovernSetChange + governSet gain orchestratorLoop + maxVerifyRounds
(--orchestrator-loop / --max-verify-rounds). GovernStatus gains builds: GovernBuild[]
and orchestration.orchestrator_loop / max_verify_rounds. New GovernBuild interface
mirrored in preload .d.ts and Factory.tsx.

Verified: npm run typecheck (node+web) exit 0; electron-vite build exit 0. Engine
side (044bc79a3, e591bb13d) already shipped + loop OFF by default, so this UI shows
an empty/off state until the user enables the loop.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Previously clicking a build in the Factory Builds pane only switched to the
Kanban tab. Now it opens that build's task detail.

- Kanban gains focusTaskId + onFocusHandled props; an effect opens the task
  detail (setDetailTaskId) once the screen is visible, then notifies the parent
  to clear the one-shot request (so re-clicking the same task re-focuses).
- Layout holds kanbanFocusTaskId; focusKanbanTask(taskId) sets it + goTo('kanban')
  (which also marks the view visited so the pane mounts and the effect fires).
  Factory's onNavigateToTask now routes through it instead of a bare goTo.

Verified: typecheck (node+web) exit 0; electron-vite build exit 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat(factory): Factory tab — governance/budget/orchestration + orchestrator closed-loop UI
Adds a toolbar toggle inside a chat session that activates "factory mode"
(enables the orchestrator closed-loop) AND opens a live side panel showing what
the factory is doing — active builds, loop state, verify rounds, escalations —
without leaving the chat for the Factory tab.

Renderer-only; every engine capability + IPC already exists
(kanbanGovernStatus / kanbanGovernSet).

- useFactoryStatus hook: polls kanbanGovernStatus every 15s ONLY while the panel
  is open; setLoop flips kanban.orchestrator_loop optimistically (revert + error
  on failure). Mirrors the Factory tab's load/poll + reuses its loop-state read.
- FactoryPanel: pure-presentational right-hand column (mirrors WorktreePanel
  placement). Chat owns the single hook instance and passes data down, so the
  toggle and panel share one source of truth (no double polling). Reuses the
  Factory tab's build-card / chip / loop-state vocabulary. Remote/loading/error
  guards. Carries an explicit "Factory mode ON/OFF" switch.
- FactoryToggle: toolbar chip (ShieldCheck) in Chat's toolbarExtras, beside the
  model/reasoning pickers. Hidden in remote mode. Opening it enables the loop if
  it's off; closing the panel leaves the loop AS-IS (closing a viewer must not
  silently kill an in-flight autonomous build) — matching the engine's
  loop-off-by-default safety posture.
- Lifted the duplicated GovernStatus/GovernBuild interfaces out of Factory.tsx
  into screens/Factory/types.ts (single source of truth; the hook + panel import
  them).
- i18n: chat.factory.* keys in en + es + zh-CN.

typecheck + build clean. No new test failures (the 15 in hermes-api /
hermes-cli-session-id are pre-existing on main — a getConfigValue mock gap).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat(chat): in-chat Factory toggle + live Factory panel
# Conflicts:
#	src/main/hermes.ts
#	src/renderer/src/screens/Chat/Chat.tsx
#	src/renderer/src/screens/Providers/Providers.tsx
Upstream's auto-updater (publish → fathah/hermes-desktop) overwrote the
installed fork build (0.5.8, with the Factory tab + in-chat panel) with
upstream 0.6.1, which also fails to launch, and deleted the local backups.

- Version 0.6.1 → 0.6.2 so the fork build outranks upstream's latest release
  and won't be replaced by an upstream auto-update.
- Repoint publish (electron-builder.yml) + dev-app-update.yml from
  fathah/hermes-desktop to BAS-More/hermes-desktop-Working-. The fork has no
  published releases, so the app finds no auto-update (no overwrite) until we
  deliberately publish one.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
One-shot, manually-triggered workflow that builds an UNSIGNED macOS .dmg
on a hosted macOS runner — no Apple secrets, no notarization, no publishing.
Just uploads the .dmg as a workflow artifact for download.

Why: electron-builder 26.x removed cross-platform Mac builds (requires real
macOS), and the existing release.yml mac job needs Apple secrets for
notarization. This is the minimal recipe to produce a runnable .dmg.

Caveats noted in the workflow comment: Gatekeeper blocks unsigned apps on
first launch; user does right-click -> Open or 'xattr -dr com.apple.quarantine'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
chore: merge upstream v0.6.1 + bump to 0.6.2 with auto-update feed fix
…rough, multi-profile Sessions overhaul

Three follow-ups on the v0.6.2 Factory work:

1) Factory → Office bot animation
   - The Office 3D scene now consumes the same kanbanGovernStatus the in-chat
     Factory panel reads (via a lifted useFactoryStatus hook in screens/shared).
   - New pure `enrichOfficeAgentsWithBuilds` overlays factory state onto bots:
     active builds drive the orchestrator bot to "working", parked builds
     paint it red, done builds tag a celebrateUntil deadline (4s).
   - AgentsLayer honours celebrateUntil inside its existing useFrame — the
     responsible bot dances for ~4s when a build completes.

2) In-chat Factory panel → Kanban clickthrough
   - Thread Layout's focusKanbanTask through Chat → FactoryPanel via a new
     onNavigateToTask prop. The build-card link is now live; the rest of the
     plumbing already existed.

3) Sessions tab overhaul (root cause: ~/.hermes/active_profile file missing
   on this machine, so the tab opened the empty default state.db while 26
   real sessions lived in 5 named-profile DBs)
   - utils.ts: listAllStateDbPaths + stateDbPathForProfile + defensive
     writeActiveProfileName (so a profile switch never leaves the file
     missing again).
   - sessions.ts: listAllSessions / searchAllSessions / per-profile resume
     and delete; new desktop-owned tables `desktop_session_meta` (pin,
     status, group) and `desktop_session_group` created lazily via
     ensureDesktopSessionTables — fully additive, no engine schema change.
   - session-cache.ts: per-profile sync helpers.
   - profiles.ts: mirror active_profile to disk on every setActiveProfile
     so the regression cannot recur.
   - IPC + preload: rename-session, archive, pin, status, move-to-group,
     groups CRUD, plus typings.
   - Sessions.tsx: unified list across all profile DBs, profile chip on
     every card, pinned section, group filter, Show archived toggle, and
     a per-card ⋯ overflow menu with 10 actions: pin / pause+continue /
     mark complete / rename / move-to-group (with new-group inline) /
     copy link (hermes://session/<profile>/<id>) / share (OS share sheet
     with copy-link fallback) / archive / delete (hard-delete via engine
     path, with confirm).
   - Layout.tsx: handleResumeSession now takes (sessionId, profile) and
     setActiveProfile follows the resumed session so it runs under the
     right gateway — fixes a quiet correctness bug.
   - SidebarRecentSessions: same aggregator + profile threading.

Tests
- New unit tests for enrichOfficeAgentsWithBuilds (7/7 pass).
- Sessions.test.tsx updated to the new aggregator API + ⋯ menu interaction
  (14/14 pass).
- npm run typecheck clean; npm run build exit 0.
- Full vitest: 24 pre-existing infra failures (commandProvider, config-health,
  tui-gateway-stream, api-server-key, hermes-cli-session-id) — none new.

i18n: en/es/zh-CN parity for sessions.actions.*, sessions.status.*,
sessions.pinnedSection, sessions.showArchived, sessions.filterGroup,
group dialogs.

Co-Authored-By: Claude <noreply@anthropic.com>
…ions-overhaul

feat: Factory→Office bots · Factory-panel clickthrough · multi-profile Sessions with 10-action menu
Outranks any upstream auto-update so the locally-installed Factory+Office+
Sessions build (PR #6) isn't silently overwritten.

Co-Authored-By: Claude <noreply@anthropic.com>
Acts on a UI Designer + Accessibility Auditor + Persona Walkthrough audit of
the Sessions tab. Tier 1 (safe quick wins) + Tier 2 (high-value).

Tier 1
- Define missing --shadow-md / --shadow-lg / --danger at :root. They were
  referenced by the confirm modal, the per-card overflow popover, and the
  toast but defined nowhere, so all three rendered flat. One block fixes
  every consumer app-wide; themes can override.
- Rename input: --bg-base (undefined → transparent) → --bg-primary.
- Overflow (⋯) button: 24→28px (comfortable above WCAG 2.5.8), rest opacity
  0.45→0.7 (was near-invisible), and a real focus-visible outline (was
  opacity-only).
- Add :focus-visible rings to the session card + group filter (were invisible
  on dark themes — WCAG 2.4.7).
- Toast: role="status" aria-live="polite" (was silent to screen readers —
  WCAG 4.1.3).
- aria-label on the search-clear button and the group-filter select; add
  type="button" to clear (WCAG 4.1.2).
- Delete ~60 lines of dead CSS (.sessions-card-delete/.sessions-card-rename —
  the inline icons the ⋯ menu replaced).

Tier 2
- Undo on archive + mark-complete: both removed/hid a card silently with no
  recovery, re-creating the "session disappeared" anxiety this tab was built
  to fix. Now each shows a toast with an Undo that reverses both the UI patch
  and the engine write; longer 6s dwell when Undo is offered.
- ProfileChip: render as a filled pill (palette's intended white-on-colour
  contract) instead of colour-as-text on the dark card, which failed WCAG
  1.4.3 for several hues (slate ~2.0:1, purple ~2.6:1, gray ~3.2:1).
- Search results keep date grouping (today/yesterday/earlier) + a result
  count instead of collapsing to a flat list, preserving temporal scent for
  the most common job (re-finding "yesterday's" chat).

i18n: en/es/zh-CN — clearSearch, resultCount, actions.{undo,archived,
markedComplete}.

typecheck clean; Sessions + Office tests 21/21; npm run build exit 0.

Co-Authored-By: Claude <noreply@anthropic.com>
fix(sessions): 3-agent UI/UX audit — tokens, a11y, undo, contrast
…ps, card semantics

Completes the deferred structural items from the 3-agent audit (the Critical
keyboard/screen-reader barriers PR #7 left out because they needed real
component work, not token swaps).

1. Focus-managed action menu (WCAG 2.1.1 / 4.1.2)
   - The ⋯ menu was a role="menu" that never moved focus into the popover, so
     keyboard users could open it but reach NONE of the 9 actions (they Tab'd
     behind it). Rebuilt as a proper APG menu: opening focuses the first item;
     ArrowUp/Down roam with roving tabindex; Home/End jump; Escape closes and
     returns focus to the trigger; Tab closes. ArrowDown/Enter/Space on the
     trigger open onto the first item.
   - The "move to group" flyout (position:right:100%, clipped at the viewport
     edge and keyboard-unreachable) is replaced by an inline expanding section
     in the same flat list — no flyout, linear arrow roving, no edge clipping.

2. New-group modal (replaces window.prompt)
   - Group creation was buried under ⋯ → Move to group ▸ → New group… and used
     a raw window.prompt (unstyled, breaks in Electron, no focus management).
   - Added a visible "New group" button in the header and a styled modal with a
     labeled input, Enter-to-create, focus trap. Creating also filters the list
     to the new group.

3. Modal focus management (WCAG 2.4.3 / 2.1.2) — new useFocusTrap hook
   - Both confirm dialogs + the new-group modal now trap Tab inside, focus the
     first control on open, restore focus to the opener on close, and close on
     Escape. Previously focus could leak to the page behind the modal.

4. Card structural fix (WCAG 4.1.2 / 1.3.1)
   - The card was role="button" containing the menu button, checkbox, and
     rename input — an invalid a11y tree patched with stopPropagation. Now the
     row is a plain <li> in a <ul role="list">; the open/resume affordance is a
     real <button> wrapping the title; menu/checkbox/rename are siblings.
   - Date-group + Pinned labels are now <h3> inside <section aria-labelledby>
     (WCAG 1.3.1 / 2.4.6) so the list is navigable by heading.

New shared hook: screens/shared/useFocusTrap.ts.
i18n (en/es/zh-CN): newGroupTitle, newGroupPlaceholder, newGroupCreate.

typecheck clean; Sessions tests 19/19 (added 5: focus-into-menu, arrow roving,
Escape-returns-focus, real Open button, new-group modal not window.prompt);
full suite +5 passing, 24 pre-existing infra failures unchanged; build exit 0.

Co-Authored-By: Claude <noreply@anthropic.com>
fix(sessions): Tier-3 a11y structural — focus-managed menu, modal traps, card semantics
Ships the Sessions UI/UX audit work (PRs #7 + #8) in an installable build.

Co-Authored-By: Claude <noreply@anthropic.com>
…L engine)

Adds an LLM Council feature driven by PAL MCP: the running Opus 4.8 agent
orchestrates a panel of (free) models, one per named position, then synthesizes
as chairman. No remote service, no token in the renderer — the desktop authors
a convene instruction the agent fulfils via PAL, reusing the existing chat
pipeline.

Composer (ChatInput): new Council button submits the convene prompt through
onSubmit; the context gauge moves to the far right of the toolbar.

Models screen: new "LLM Council" tab (CouncilTab) with
- add/remove panel members + chairman selection
- positions CRUD with editable, self-learning descriptions (thumbs up/down →
  agent-proposed refinement the user accepts/rejects)
- model advisor: ranks the pool for a task with accuracy/speed shown as
  learning tiers (never fabricated %) and free/paid badges

Storage: per-profile council-config.json (never config.yaml, which is gated).
Types shared via src/shared/council.ts; 13 council-* IPC handlers; single en
council i18n namespace (other locales fall back per-key).

Verified: typecheck clean (web+node), 96/96 unit tests pass, electron-vite
build + electron-builder --dir pack succeed (council code confirmed inside the
packed asar), CDP screenshot confirms the tab + composer render without
clipping.
@greptile-apps

greptile-apps Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds Anthropic Claude OAuth (native PKCE paste-a-code flow), a multi-profile session aggregator with desktop-owned pin/status/group metadata, a Factory governance tab, and an LLM Council configuration panel.

  • Anthropic OAuth: A new PKCE flow in the main process (hermes-auth.ts) builds the authorize URL, exchanges the pasted code for tokens, and persists them into the engine credential pool via a Python one-liner — with two issues noted (token exposure via argv and a loose success-check substring match).
  • Multi-profile sessions: sessions.ts gains aggregated listing/search/delete/rename across every profile's state.db, with lazily-created desktop-owned tables for pin, status, and group metadata.
  • Factory & Council: New kanban govern wrappers and a council-config.ts JSON store power two new UI tabs, each backed by fresh IPC handlers in index.ts.

Confidence Score: 3/5

Safe to merge for most users, but the Anthropic OAuth token persistence passes live credentials through the process argv, which is readable by other same-user processes on the machine.

The multi-profile session aggregator and Factory/Council UI additions are well-structured. The Anthropic PKCE flow is the concerning path: access_token and refresh_token are handed to Python as positional sys.argv arguments, making them briefly visible in the OS process table to any process running under the same UID. The success detection also uses a substring check that could silently report success on a failed token-persistence run. These issues live on a high-value path (OAuth credential storage) and warrant a fix before the feature ships widely.

src/main/hermes-auth.ts — the persistAnthropicToken function and its subprocess invocation deserve the most attention.

Security Review

  • Credential exposure via process table (src/main/hermes-auth.ts ~line 318): accessToken and refreshToken are passed as positional CLI arguments to the Python subprocess. On Linux, /proc/<pid>/cmdline exposes them to any same-UID process for the subprocess's lifetime; on macOS they appear in ps aux output. Tokens should be delivered via stdin or an environment variable instead.

Important Files Changed

Filename Overview
src/main/hermes-auth.ts Adds Anthropic PKCE OAuth flow natively in the main process; tokens are passed as CLI argv to the Python persistence script (process table exposure), and the success check uses a substring match that could misfire on error messages.
src/main/sessions.ts Adds multi-profile session aggregation with desktop-owned metadata tables (pin, status, groups); the openProfileDb readonly parameter is a dead ternary but intentionally always opens writable. Logic is otherwise sound.
src/main/index.ts Wires up ~100 new IPC handlers for Anthropic OAuth, Council config, multi-profile sessions, and Factory governance; the pendingAnthropic state is scoped correctly and the Anthropic flow lacks an activeProc-style guard but this is not dangerous given Node's event-loop serialisation.
src/renderer/src/components/OAuthLoginModal.tsx Extended to handle the Anthropic paste-a-code PKCE path alongside the existing CLI-loopback flow; StrictMode double-invoke guard is in place and the success/cancel paths are correct.
src/main/kanban.ts Adds four Factory governance functions (governStatus, governSet, governKillSwitch, governModels) that delegate to hermes kanban govern; clean and consistent with existing kanban helpers.
src/main/council-config.ts New per-profile JSON-backed Council config store; well-structured with safe defaults and no cross-profile leakage.
src/renderer/src/screens/Factory/Factory.tsx New Factory governance UI with auto-refresh, toast notifications, and activity filters; no logic issues observed.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant UI as OAuthLoginModal (Renderer)
    participant IPC as Electron IPC (Main)
    participant Auth as hermes-auth.ts
    participant Anthropic as Anthropic OAuth
    participant Python as Python subprocess

    UI->>IPC: anthropic-oauth-start
    IPC->>Auth: buildAnthropicAuthUrl()
    Auth-->>IPC: "{ url, verifier, state }"
    IPC-->>UI: "{ url }"
    UI->>Anthropic: open browser → user authorizes
    Anthropic-->>UI: "user pastes code#state"
    UI->>IPC: anthropic-oauth-submit(code, profile)
    IPC->>Auth: exchangeAnthropicCode(code, verifier, state)
    Auth->>Anthropic: POST /v1/oauth/token (PKCE)
    Anthropic-->>Auth: "{ access_token, refresh_token, expires_in }"
    Auth->>Python: execFile(python, ["-c", script, access_token, refresh_token, expires_in])
    Note over Python: tokens visible in process table
    Python-->>Auth: stdout OK label
    Auth-->>IPC: "{ success: true, persisted: true }"
    IPC-->>UI: success → onSuccess()
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant UI as OAuthLoginModal (Renderer)
    participant IPC as Electron IPC (Main)
    participant Auth as hermes-auth.ts
    participant Anthropic as Anthropic OAuth
    participant Python as Python subprocess

    UI->>IPC: anthropic-oauth-start
    IPC->>Auth: buildAnthropicAuthUrl()
    Auth-->>IPC: "{ url, verifier, state }"
    IPC-->>UI: "{ url }"
    UI->>Anthropic: open browser → user authorizes
    Anthropic-->>UI: "user pastes code#state"
    UI->>IPC: anthropic-oauth-submit(code, profile)
    IPC->>Auth: exchangeAnthropicCode(code, verifier, state)
    Auth->>Anthropic: POST /v1/oauth/token (PKCE)
    Anthropic-->>Auth: "{ access_token, refresh_token, expires_in }"
    Auth->>Python: execFile(python, ["-c", script, access_token, refresh_token, expires_in])
    Note over Python: tokens visible in process table
    Python-->>Auth: stdout OK label
    Auth-->>IPC: "{ success: true, persisted: true }"
    IPC-->>UI: success → onSuccess()
Loading

Reviews (1): Last reviewed commit: "feat(council): LLM Council — composer co..." | Re-trigger Greptile

Comment thread src/main/hermes-auth.ts
Comment thread src/main/hermes-auth.ts
Comment thread src/main/sessions.ts
Avi-Bendetsky and others added 2 commits June 18, 2026 20:47
…-tools

feat(agent-detail): per-agent persona editor + skills/tools (WIP) — persona tested

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

…indows path mock (#11)

Two Windows-only failures (13 tests), both pre-existing on main, both real bugs:

1. CommandSecretsProvider hardcoded execFileSync("/bin/sh") — Node resolves that
   against the Win32 filesystem where it doesn't exist (the MSYS/Git-Bash /bin/sh
   is a shell-mount illusion), so every secret spawn returned ENOENT and degraded
   to null. Added resolveShell(): bare "sh" on POSIX (PATH-resolved), and on
   win32 prefer an existing Git-for-Windows sh.exe / $SHELL, else bare "sh" for
   WSL/Cygwin. The injection-safety + stderr-non-leak invariants now actually
   execute on Windows (they were silently dark). +5 resolveShell regression tests.

2. config-health.test.ts mocked installer HERMES_HOME as a hardcoded POSIX
   "/tmp/..." while the on-disk fixture uses join(tmpdir(),...). On Linux these
   coincide; on Windows join() yields backslashed %TEMP% paths, so the REAL
   existsSync(configFile) returned false and EMPTY_API_SERVER_KEY was gated off.
   Derive the mock HERMES_HOME from tmpdir() so mock and fixture stay in lockstep
   cross-platform.

Full suite: 1243 passed / 0 failed / 3 skipped (was 13 failed on Windows).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

… WIP typecheck fixes (#12)

* test(agent-detail): cover per-agent persona/skills/tools panel; fix WIP typechecks

- AgentDetail.test.tsx (5 tests): default persona tab, profile threading to
  Soul/Skills/Tools, initialTab deep-link (Office clickthrough), onClose,
  backdrop-vs-modal click. The per-agent config panel had zero coverage.
- sessions.ts: SearchResult requires lastActivity:number but the search
  mapping omitted it (TS2322). Map it to started_at (rows already recency-
  ordered; lastActivity is display/sort only).
- i18n/index.ts: annotate sharedI18n: i18n to fix TS2742 (inferred type not
  portable without an i18next node_modules reference).

Both tsc projects (node + web) clean. Full suite 1260 passed / 3 skipped;
only the known gateway-restart parallel-timing flake remains (17/17 isolated).

* feat(agent-detail): add per-agent persona/skills/tools panel component

Composes the profile-aware Soul (persona), Skills and Tools screens into one
tabbed overlay, so a single agent's persona, assigned skills and enabled
toolsets all live in one place — reachable per-agent. Each child already
takes a profile prop and reads/writes that profile's own files (SOUL.md,
skills/, config toolsets), so this is pure composition, no new backend.

Pairs with AgentDetail.test.tsx (committed prior) — that test imported this
file; committing it here so the import resolves and CI builds. The Agents-list
and Office-3D clickthrough wiring that render this panel are separate WIP edits
(Agents.tsx / Office.tsx) not included here.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

…gents (#13)

Click an agent in the Office tab to open the AgentDetail panel (persona/
skills/tools) for that profile — the settings are now reachable directly
from the Office, not only the Agents grid. Wires Office.tsx -> AgentDetail
(from #12), adds the Agents Settings affordance + onBrowseSkills passthrough
in Layout.

Verified: tsc --noEmit clean; Office/Agents/Layout suites 15/15 green.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

…) — DO NOT MERGE (#14)

Verbatim salvage of an uncommitted working-tree WIP that grew
SidebarRecentSessions 219 -> 847 lines: per-session context menu
(rename/delete/pin/archive) + keyboard a11y (roving tabindex, portal
popover), plus its 10KB test file. Committed UNREVIEWED to make it durable
after an earlier git-checkout clobber nearly lost it.

NOT verified, NOT reviewed, NOT for merge. Provenance uncertain (likely a
human dev's half-finished branch). Triage notes follow in the PR/handoff.
Bridge-pill + CSP allowlist edits deliberately kept on a SEPARATE branch.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

… MERGE (#15)

Verbatim salvage of uncommitted WIP: a live Bridge status pill in Settings
(up-to-date/syncing/error/off) and the CSP connect-src allowlist entry it
needs (http://127.0.0.1:8770 in src/main/index.ts + src/renderer/index.html).

SECURITY NOTE: the CSP change widens connect-src — must get its own focused
review before merge (flagged by the council's security seat). Committed
UNREVIEWED only to make it durable. NOT verified, NOT for merge.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

#16)

Resolves the divergence behind PR #9 (35 ahead / 55 behind). Union-merged
both sides across 13 conflicted files; typecheck (node+web) and full test
suite (1438 passed) green.

Kept from this fork (ours):
- Council (config + advisor + en/council locale)
- Factory tab/panel/toggle + orchestrator closed-loop + factory CSS
- Multi-profile sessions API (listAllSessions / syncAllSessionCaches /
  searchAllSessions / *InProfile / *ByProfile / session groups)
- AIR-008 config-health precedence tests + Windows cross-platform fix
- Version 0.6.5

Adopted from upstream (v0.6.2):
- Complete Hebrew (he) locale with RTL support
- Web Preview panel + chat toolbar toggle
- Follow-us / X-follow chat strings
- applySessionLocalOverlays session overlays
- onConnectionConfigChanged reconciliation (reset+reload on connection change)
- Session-load race guards (loadRequestId) + preserve-on-empty refresh
- DB connection caching (getDbConnection)

Semantic-merge fixes:
- remote-sessions.ts: add lastActivity to SearchResult builders (ours made
  the field required); update fixtures accordingly
- sessions.ts: drop now-unused activeStateDbPath import
- Sessions.tsx: cast listCachedSessions fallback to SessionRow[]
- Sessions.test.tsx: multi-profile mock + onConnectionConfigChanged; adapt
  upstream race-guard tests to the agg() helper

Signed-off-by: Avi-Bendetsky <146357256+Avi-Bendetsky@users.noreply.github.com>
@greptile-apps

greptile-apps Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Too many files changed for review. (287 files found, 100 file limit)

* fix(secrets): make command-provider timeout overridable to de-flake spawn tests

The secrets command provider used a hardcoded 3s timeout. Under a CPU-saturated
parallel test run, spawning a real `sh` helper can exceed 3s just to start,
tripping the timeout and resolving a spurious null (flaky, unrelated to the code).

- resolveCommandTimeoutMs() reads HERMES_SECRET_COMMAND_TIMEOUT_MS at CALL time
  (tests only); production never sets it and keeps the tight 3s UI-freeze ceiling.
  Parsed defensively: missing/blank/non-numeric -> 3s.
- Tests set a generous budget so spawn latency can't cause false nulls.
- vitest testTimeout/hookTimeout raised to 20s (runner deadline, not the spawn cap).
- New tests/command-timeout-override.test.ts covers the resolver.

Verified: 5 passed / 3 skipped on the affected suites.

* fix(test): de-flake full-suite secrets/gateway spawn tests via pool cap

Root cause (layered, verified):
1. ~27 test files spawn REAL child processes (sh helpers in the secrets
   provider, gateway lifecycle, CLI fallbacks). Vitest's default forks pool
   runs one worker PER CPU core (8 here) and each worker spawns its own
   children — oversubscribing the cores many times over. A test that takes
   ~2s isolated was observed taking 30s+ under that contention (16x), tripping
   deadlines and producing flaky failures unrelated to the code under test.
2. The secrets command-provider spawn budget wasn't overridable everywhere,
   so the provider's tight 3s production timeout could itself trip under load.

Fix:
- vitest.config: cap pool to maxForks=4 (half the cores) so spawned children
  get CPU headroom; add a 20s testTimeout/hookTimeout safety ceiling.
- commandProvider: resolveCommandTimeoutMs() reads HERMES_SECRET_COMMAND_TIMEOUT_MS
  at call time (TESTS ONLY); production keeps the 3s default.
- api-server-key + commandProvider tests set a generous spawn budget.
- Drop a broken/redundant throwaway (command-timeout-override.test.ts) whose
  internal plumbing is already exercised by the real spawn tests.

Verified: 3 consecutive full-suite runs 0 failures; 135 test files pass;
tsc clean both projects. (Was: 1-3 flaky failures per full run.)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
…+ worker-pool cap) (#18)

* fix(secrets): make command-provider timeout overridable to de-flake spawn tests

The secrets command provider used a hardcoded 3s timeout. Under a CPU-saturated
parallel test run, spawning a real `sh` helper can exceed 3s just to start,
tripping the timeout and resolving a spurious null (flaky, unrelated to the code).

- resolveCommandTimeoutMs() reads HERMES_SECRET_COMMAND_TIMEOUT_MS at CALL time
  (tests only); production never sets it and keeps the tight 3s UI-freeze ceiling.
  Parsed defensively: missing/blank/non-numeric -> 3s.
- Tests set a generous budget so spawn latency can't cause false nulls.
- vitest testTimeout/hookTimeout raised to 20s (runner deadline, not the spawn cap).
- New tests/command-timeout-override.test.ts covers the resolver.

Verified: 5 passed / 3 skipped on the affected suites.

* fix(test): de-flake full-suite secrets/gateway spawn tests via pool cap

Root cause (layered, verified):
1. ~27 test files spawn REAL child processes (sh helpers in the secrets
   provider, gateway lifecycle, CLI fallbacks). Vitest's default forks pool
   runs one worker PER CPU core (8 here) and each worker spawns its own
   children — oversubscribing the cores many times over. A test that takes
   ~2s isolated was observed taking 30s+ under that contention (16x), tripping
   deadlines and producing flaky failures unrelated to the code under test.
2. The secrets command-provider spawn budget wasn't overridable everywhere,
   so the provider's tight 3s production timeout could itself trip under load.

Fix:
- vitest.config: cap pool to maxForks=4 (half the cores) so spawned children
  get CPU headroom; add a 20s testTimeout/hookTimeout safety ceiling.
- commandProvider: resolveCommandTimeoutMs() reads HERMES_SECRET_COMMAND_TIMEOUT_MS
  at call time (TESTS ONLY); production keeps the 3s default.
- api-server-key + commandProvider tests set a generous spawn budget.
- Drop a broken/redundant throwaway (command-timeout-override.test.ts) whose
  internal plumbing is already exercised by the real spawn tests.

Verified: 3 consecutive full-suite runs 0 failures; 135 test files pass;
tsc clean both projects. (Was: 1-3 flaky failures per full run.)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* release(0.6.6): cross-platform release docs + version bump

- bump 0.6.5 -> 0.6.6 for GitHub Release + auto-update
- release/RELEASE-NOTES-0.6.6.md, BUILD-MAC.md, release.json
- bundles: LLM Council, sidebar session menu, CC Bridge pill, CSP localhost fix
- bridge is per-user (no Anthropic session ships); signing roadmap documented

* docs(release): add 0.6.6 release notes, Mac build guide, manifest
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.

2 participants