Skip to content

Feat/claude oauth login#58

Open
jeewoo-lee wants to merge 254 commits into
mainfrom
feat/claude-oauth-login
Open

Feat/claude oauth login#58
jeewoo-lee wants to merge 254 commits into
mainfrom
feat/claude-oauth-login

Conversation

@jeewoo-lee

Copy link
Copy Markdown
Collaborator

No description provided.

jeewoo-lee and others added 30 commits April 4, 2026 23:12
- Add VERIFY_MAX_CONCURRENT_JOBS env var (default: 1, preserves current behavior)
- Add VERIFY_DB_POOL_MIN/MAX env vars with auto-sizing from concurrency
- When concurrency > 1, N worker coroutines each claim jobs independently
  via FOR UPDATE SKIP LOCKED; each creates its own AsyncDaytona client
- Single-worker mode (default) keeps shared client for zero overhead
- Coordinator coroutine handles stale-job recovery separately
- Add duration logging and worker ID prefixes to all log lines
- Fix daytona_sdk import to try daytona_sdk before daytona
- Add deployment docs: services, env vars, scaling modes
- Add 7 tests: pool sizing, _run_one_job, daytona crash recovery,
  concurrent claim independence

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Transient Daytona errors (CPU quota, rate limits) now retry up to
VERIFY_SANDBOX_MAX_RETRIES (default 3) with exponential backoff
(VERIFY_SANDBOX_RETRY_BACKOFF * attempt, default 30s base).
Non-retryable errors (no fork, disabled) still fail immediately.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of giving up after N retries and marking runs as error,
the verifier now retries indefinitely until Daytona has capacity.
Backoff caps at SANDBOX_RETRY_BACKOFF * SANDBOX_MAX_RETRIES (default 90s).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Daytona free tier doesn't allow programmatic sandbox deletion.
Now falls back to stopping the sandbox, which frees CPU immediately.
Stopped sandboxes auto-delete via Daytona's auto_delete_interval.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
These were lost during a linter revert:
- git fetch all refs + checkout specific commit for agent fork
- HIVE_SETUP_CMD sandbox setup step before prepare.sh
Also change default VERIFY_MAX_CONCURRENT_JOBS from 1 to 3.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Keep both sides: deployment's auth/private-tasks/items changes
and verify-runs' verification system. All conflict markers resolved.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
feat: server-side verification via Daytona sandboxes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix: restore /verify-old endpoint
…se_sha

Old forks created before SHA tracking have base_sha=NULL. The admin can
now pass task_repo_sha in the request body as a fallback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix: restore /verify-old endpoint + base_sha fallback
- API: GET /tasks accepts ?type=public|private query param
- CLI: hive task list --public and --private flags
- UI: main page and Public Tasks use type=public, profile uses /tasks/mine
Backend:
- Add verification_enabled, leaderboard_verified, leaderboard_unverified
  to /context endpoint
- Add section=all query support for mixed verified+unverified leaderboard

UI:
- All/Verified toggle on graph and leaderboard
- Green checkmark on verified runs, pending/failed badges
- Effective score display (verified_score if available, else self-reported)
- Non-verified tasks show default UI unchanged

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
feat: verification UI + context endpoint
- Graph tooltip shows checkmark (verified), pending/running/error badges
- Graph endpoint returns verification_status for each node
- useGraph maps verification_status to Run objects
- Verified leaderboard uses /runs?section=verified (paginated) instead
  of context endpoint (was limited to 5)
- Leaderboard uses verified_score for section=verified

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
feat: graph tooltip badges + verified leaderboard pagination
Graph was filtering by verifiedRunIds from context (limited to 5).
Now filters by r.verified on each graph node directly, showing all
verified runs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix: graph shows all verified runs instead of 5
Users can create one sandbox per task via POST /api/tasks/{task_id}/sandbox.
The sandbox uses an existing Daytona snapshot, bootstraps Node.js + Claude Code
at startup, clones the task repo, and returns SSH credentials for the browser
terminal. GET refreshes expired SSH tokens, DELETE cleans up both Daytona and DB.

Access control: sandboxes are scoped to (task_id, user_id) — users can only
see and manage their own sandboxes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add auth section (16 endpoints: signup, login, OAuth, API keys)
- Fix agent token: UUID, not equal to agent_id
- Add POST /tasks (archive upload), POST /tasks/private, GET /tasks/mine
- Add DELETE /tasks, DELETE /runs, POST /verify-old endpoints
- Add verification fields to run responses and Task Verification Config
- Add search type filter, agent/since params
- Add context verification_enabled, split leaderboards
- Add Deployment section with server/verifier env vars
- Update CLI: auth login/claim/switch, task --public/--private
- Update CLI: feed --run, comment --parent-type, search inline filters
- Update CLI: swarm --dangerously-skip-permissions, config structure
- Remove outdated Items/kanban references per prior discussion
- Add auth section (16 endpoints: signup, login, OAuth, API keys)
- Fix agent token: UUID, not equal to agent_id
- Add POST /tasks (archive upload), POST /tasks/private, GET /tasks/mine
- Add DELETE /tasks, DELETE /runs, POST /verify-old endpoints
- Add verification fields to run responses and Task Verification Config
- Add search type filter, agent/since params
- Add context verification_enabled, split leaderboards
- Add Deployment section with server/verifier env vars
- Update CLI: auth login/claim/switch, task --public/--private
- Update CLI: feed --run, comment --parent-type, search inline filters
- Update CLI: swarm --dangerously-skip-permissions, config structure
- Remove outdated Items/kanban references per prior discussion
Replace TEXT task IDs with SERIAL integer PKs and owner-scoped slugs,
following GitHub's owner/repo URL model. Public tasks are owned by
the platform namespace ('hive'); private tasks are owned by the
creating user's UUID. Slugs are unique per owner, allowing duplicates
across owners.

- db: tasks.id TEXT -> SERIAL, add slug + owner columns with
  UNIQUE(owner, slug); migrate FK columns on forks/runs/posts/claims/
  skills/items to INTEGER; one-shot migration backfills existing rows
- server: all 24+ task-scoped routes change from /tasks/{task_id}/...
  to /tasks/{owner}/{slug}/...; resolve owner+slug to integer task_id
  internally; fork naming uses task['slug']
- cli: helpers expose _task_ref()/split_task_ref(); .hive/task stores
  owner/slug; bare slugs auto-prefix with 'hive/' for legacy compat
- ui: Task type gains slug/owner; routes restructured to
  /task/[owner]/[slug]; profile page links to /task/{uuid}/{slug}
  instead of /me/{uuid}/{slug}
- tests: update fixtures and assertions for new schema and routes
Chanbinski2 and others added 30 commits April 19, 2026 18:56
claude-agent-acp prefixes MCP tool names with mcp__{server}__. Use
endsWith("ask_user") instead of exact match.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tool input may be JSON string, nested under arguments/input, or a
direct object. Try all formats. Also extract input from classified.data
.input and .arguments in addition to .rawInput.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move the AskUserWidget from inline message rendering to above the text
input field — matching Cursor's UX where interactive questions appear
at the bottom near the input, not buried in the chat history.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When user clicks an "Other..." option in the ask_user widget, it
expands to show a text input inline. Detects options starting with
"other" (case-insensitive). Enter submits the custom text.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reduce container padding (px-4/py-4 → px-3/py-3), option padding
(px-3/py-2.5 → px-2.5/py-1.5), letter badge size (w-6/h-6 → w-5/h-5),
and spacing (space-y-2 → space-y-1.5) for a more compact widget.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ging

Streamable HTTP transport may use GET for SSE stream or hit sub-paths.
Added handlers to log what claude-agent-acp actually requests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Other option renders as always-visible inline text field
- Questions header at top of card
- Skip and Continue buttons at bottom right
- Compact paddings throughout
Streamable HTTP transport requires:
1. Long-lived GET SSE stream (was ending immediately, causing reconnect
   loop and blocking POST requests)
2. mcp-session-id header on all responses for session tracking
Remove the aggressive reorder that moved all thinking parts to the top
of each assistant message. Log events are already chronological — just
use them as-is.
Log stores reasoning after text, but runtime shows thinking before
text. Swap only adjacent (text, thinking) pairs — not all thinking
to top (which broke multi-cycle turns).
ACP uses SSE transport which requires an 'endpoint' event to know
where to POST tool calls. Without it, the client connects via GET
but never sends POSTs.
SSE transport checks that endpoint origin matches connection origin.
Using the host header gave the internal backend URL, causing origin
mismatch. Use relative path /api/mcp instead — resolves correctly
against the client's connection URL.
tool_call arrives with empty input, but tool_result has rawInput with
question/options/mode. Save rawInput as input on the tool part during
tool_call_update. Show widget whenever input has question data,
regardless of tool status.
MCP blocking doesn't work, so the widget now sends the user's answer
as a regular chat message via sendMessage. The agent receives it in
the next turn.

Removed apiFetch/apiPostJson dependency from the widget.
All modes now use the same visual language:
- select: A/B/C badges, click to submit
- confirm: A) Yes, B) No badges, click to submit
- multi_select: A/B/C badges, toggle multiple, Submit button
- text: badge + inline text field (like Other...)

Extracted OptionButton helper for reuse across modes.
MCP server simplified to just initialize + tools/list + immediate
tools/call response. No blocking, no questions endpoints, no asyncio.

Widget now tracks answered tool IDs — disappears after user submits.
Answer sent as chat message to the agent.
Remove font-medium from question text so it matches the regular
chat message weight.
Update tool schema from single question to questions array.
Each question has its own mode, options, and question text.
Agent can ask multiple questions at once in one tool call.
- Paginated view with "1 of N" navigation and progress dots
- Selecting an answer auto-advances to next question
- Can go back and change previous answers via arrows or dots
- Submit button only on last question, sends all answers at once
- Supports both questions array and single question format
- All modes (select, confirm, multi_select, text) use consistent
  lettered badge style
- Skip button styled as grey button matching Submit
- Remove progress dots
- Add Next button for multi_select and text modes
- Text mode uses plain input without letter badge
- Click same option to unselect in select/confirm modes
- Auto-advance only on select, not deselect
- Multi-select supports Other... inline text field
- Fix React setState-during-render warning in multi_select
Submit enabled when at least one question is answered. Skipped
questions send empty string instead of blocking submission.
Sandbox lifecycle is now tied to the workspace, not the first agent.
Creating a cloud workspace provisions the agent-sdk sandbox synchronously
and rolls back (502) on failure. Agents create/delete only flip their
session on the existing sandbox, which stays alive when there are zero
agents and is destroyed only when the workspace is deleted.

Also fixes the display bug where cloud-workspace agents were stored as
type='local' and rendered as "Local" in the UI. Agent type now inherits
from the workspace type. Reserves the "persistent" type slot for a
future admin-owned long-lived sandbox.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
POST /workspaces returns immediately and fires a background task to
provision the sandbox. The workspace page navigates instantly and
shows a loading overlay while polling GET /workspaces/{id} every 2s;
the overlay clears once sdk_sandbox_id is populated.

_workspace_sdk_connect still has the NULL-sandbox fallback so an agent
created before the background task finishes gets provisioned on-demand.
Both paths are idempotent (check-before-provision; UPDATE ... WHERE
sdk_sandbox_id IS NULL).

On provision failure, the workspace stays in pending state (sdk_sandbox_id
remains null) rather than rolling back — the user can delete it or retry
creating an agent to re-trigger provisioning.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cloud workspaces created before async provisioning landed — or whose
provisioning errored and was silently swallowed by the previous
implementation — had type='cloud' + sdk_sandbox_id=NULL with no
background task running. The new loading overlay revealed these
permanently, spinning forever.

GET /workspaces/{id} now nudges _provision_workspace_sandbox when it
sees a cloud workspace with no sandbox, so visiting the page revives
the provision. Added a module-level in-flight set so concurrent polls
coalesce into one provision call; the early sdk_sandbox_id check and
UPDATE ... WHERE sdk_sandbox_id IS NULL remain as second-line guards.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Users connect their Claude account once from the profile settings page;
hive runs `claude setup-token` under a PTY on the server, surfaces the
OAuth URL to the UI, accepts the pasted code, and persists the resulting
token — encrypted with the existing Fernet helper — in a new
claude_oauth_tokens table keyed to user_id.

Subsequent cloud workspaces (and their agents) are authenticated with
that user's Claude subscription instead of the server's shared
ANTHROPIC_API_KEY, via the oauth_token field already wired into the
agent-sdk /sessions call.

If a user tries to create a cloud workspace without having connected
Claude yet, the backend returns 402 + {auth_required: true} and the
CreateWorkspaceModal embeds the connect flow inline, then retries.

Changes:
- db.py: new table claude_oauth_tokens (INTEGER PK REFERENCES users(id),
  token_encrypted, created_at, expires_at). Strictly additive; zero
  ALTER TABLE on existing tables.
- claude_oauth.py (new): PTY broker around `claude setup-token`. Spawns
  via pty.fork(), scrapes the OAuth URL from the Ink TUI hyperlink
  escape, accepts a user-pasted code via a second RPC, captures the
  final sk-ant-oat01-… token. Per-user supersede on concurrent starts;
  10-min TTL.
- main.py: POST /auth/claude/start, POST /auth/claude/code,
  GET /auth/claude/status, DELETE /auth/claude — modeled on the
  existing github OAuth routes, reusing _encrypt/_decrypt. Workspace
  creation short-circuits to 402 + {auth_required: true} when the
  user has no token. _workspace_sdk_connect now decrypts + passes
  oauth_token to agent-sdk.
- Dockerfile.server: adds nodejs + `npm i -g @anthropic-ai/claude-code`
  so the PTY broker has the CLI available.
- ui: four new hooks in auth.tsx (claudeStatus / Start / SubmitCode /
  Disconnect); new ClaudeConnectModal; ProfilePanel shows a "Claude
  Account" row next to GitHub with Connect / Reconnect / Disconnect
  buttons. CreateWorkspaceModal handles 402 by opening the connect
  modal inline and retrying on success.
- tests/server/test_claude_oauth.py: URL + token regex coverage and
  the session-store guards (TTL reap, per-user isolation).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

4 participants