Skip to content
Open
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
d4732b3
feat: add protocol.js
liamhelmer Jan 19, 2026
7853613
feat: add client.js
liamhelmer Jan 19, 2026
a78d2a3
feat: add status-tracker.js
liamhelmer Jan 19, 2026
d1b7392
feat: add proxy.js
liamhelmer Jan 19, 2026
ec114eb
feat: add index.js
liamhelmer Jan 19, 2026
4cc88ca
feat: add index.js
liamhelmer Jan 19, 2026
694fea0
feat: add index.js
liamhelmer Jan 19, 2026
fdb4a45
feat: add index.js
liamhelmer Jan 19, 2026
e04e347
feat: add index.js
liamhelmer Jan 19, 2026
10da083
feat: add index.js
liamhelmer Jan 19, 2026
7368c38
feat: add index.js
liamhelmer Jan 19, 2026
307bdc7
feat: add cli.js
liamhelmer Jan 19, 2026
a4cc597
feat: add cli.js
liamhelmer Jan 19, 2026
08267ce
feat: add cli.js
liamhelmer Jan 19, 2026
5cf5720
feat: add cli.js
liamhelmer Jan 19, 2026
1947227
feat: add cli.js
liamhelmer Jan 19, 2026
1b83340
feat: add .env.example
liamhelmer Jan 19, 2026
f216a07
feat(orchestrator): implement client mode for claudecodeui orchestrat…
liamhelmer Jan 19, 2026
2a8be94
feat: add github-auth.js
liamhelmer Jan 19, 2026
65606ae
feat: add protocol.js
liamhelmer Jan 19, 2026
df5ab89
feat: add proxy.js
liamhelmer Jan 19, 2026
599f9d6
feat: add proxy.js
liamhelmer Jan 19, 2026
04075a7
feat: add .env.example
liamhelmer Jan 19, 2026
3878b5b
feat: add cli.js
liamhelmer Jan 19, 2026
04ff458
feat: add cli.js
liamhelmer Jan 19, 2026
0fba3b0
feat: add cli.js
liamhelmer Jan 19, 2026
1b548e0
feat: add cli.js
liamhelmer Jan 19, 2026
8420337
feat: add index.js
liamhelmer Jan 19, 2026
5ee0456
feat(orchestrator): implement github oauth and websocket proxy for cl…
liamhelmer Jan 19, 2026
8f85682
feat: progressive app support through orchestrator
liamhelmer Jan 19, 2026
c03b915
feat(orchestrator): implement github oauth and websocket proxy for cl…
liamhelmer Jan 20, 2026
4eae09a
feat(orchestrator): implement github oauth and websocket proxy for cl…
liamhelmer Jan 20, 2026
f6c6565
feat(orchestrator): implement github oauth and websocket proxy for cl…
liamhelmer Jan 20, 2026
8eb5944
docs: add orchestrator client documentation
liamhelmer Jan 20, 2026
f8a2d5a
feat(orchestrator): implement client mode with github oauth and webso…
liamhelmer Jan 20, 2026
97116c7
chore: ignore .fork-join/ directory
liamhelmer Jan 20, 2026
7f0f0d6
fix(orchestrator): wire up follow-up message routing
liamhelmer Jan 20, 2026
ff7d30c
fix: address CodeRabbit security and correctness issues
liamhelmer Jan 20, 2026
09e576a
chore: address CodeRabbit nitpicks
liamhelmer Jan 20, 2026
01770de
feat(orchestrator): implement client mode with github oauth and webso…
liamhelmer Jan 21, 2026
5de3c9e
feat(server): orchestrator client mode
liamhelmer Jan 21, 2026
952f0f4
feat(orchestrator): implement client mode with github oauth and webso…
liamhelmer Jan 21, 2026
585391f
feat(orchestrator): implement client mode with github oauth and webso…
liamhelmer Jan 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,56 @@ VITE_CONTEXT_WINDOW=160000
CONTEXT_WINDOW=160000

# VITE_IS_PLATFORM=false

# =============================================================================
# ORCHESTRATOR CONFIGURATION
# =============================================================================

# Orchestrator mode determines how this instance operates:
# standalone - Runs independently (default)
# client - Connects to a central orchestrator server
ORCHESTRATOR_MODE=standalone

# Orchestrator WebSocket URL (required when ORCHESTRATOR_MODE=client)
# This is the URL of the orchestrator server to connect to
# ORCHESTRATOR_URL=wss://orchestrator.example.com/ws/connect

# Orchestrator authentication token (required when ORCHESTRATOR_MODE=client)
# Generate this token from the orchestrator dashboard
# ORCHESTRATOR_TOKEN=

# Optional: Custom client ID for this instance
# If not set, defaults to hostname-pid
# ORCHESTRATOR_CLIENT_ID=

# Optional: Reconnection interval in milliseconds (default: 5000)
# ORCHESTRATOR_RECONNECT_INTERVAL=5000

# Optional: Heartbeat interval in milliseconds (default: 30000)
# ORCHESTRATOR_HEARTBEAT_INTERVAL=30000

# =============================================================================
# ORCHESTRATOR PASS-THROUGH AUTHENTICATION
# =============================================================================
# When in client mode, you can configure which GitHub users are allowed to
# access this instance via the orchestrator. The orchestrator passes the user's
# GitHub OAuth token, and claudecodeui validates it against these rules.
#
# Set ONE OR MORE of the following to enable pass-through auth:

# Allow users from a specific GitHub organization
# ORCHESTRATOR_GITHUB_ORG=your-org-name

# Allow users from a specific GitHub team (format: org/team-slug)
# ORCHESTRATOR_GITHUB_TEAM=your-org/your-team

# Allow specific GitHub usernames (comma-separated for multiple users)
# ORCHESTRATOR_GITHUB_USERS=username1,username2,username3

# Authentication priority (if multiple are set):
# 1. Specific users (ORCHESTRATOR_GITHUB_USERS) - checked first
# 2. Team membership (ORCHESTRATOR_GITHUB_TEAM) - checked second
# 3. Org membership (ORCHESTRATOR_GITHUB_ORG) - checked last
#
# If NONE of these are set, pass-through auth is disabled and requests
# from the orchestrator will be processed without user validation.
1 change: 1 addition & 0 deletions .fork-join/current_session
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
session-1768779775
25 changes: 25 additions & 0 deletions .fork-join/session-1768779775.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"session_id": "session-1768779775",
"feature_branch": "feat/orchestrator-client-mode",
"base_branch": "main",
"prompt": "Look in @~/repos/liamhelmer/ai-orchestrator/CLAUDE.md . I'm building an orchestrator that will act as a central server where instances of claudecodeui can connect to. We need to create a mode of claudecodeui that works with the orchestrator: it should create a websocket connection to the orchestrator at a URL defined in the config, and it should authenticate itself using github oauth authentication. Then it will accept user traffic over the websocket connection which will be authenticated with the github token, and it will send back app data that will be consumed by the orchestrator. Create a plan for building this functionality.\n",
"state": "STARTED",
"started_at": "2026-01-18T23:42:55Z",
"agents": [],
"merged_count": 0,
"conflict_count": 0,
"prompts": [
{
"prompt": "Fix all of the following. If any would break new functionality, ask before implementing the fix. Assign an agent to each fix for clarity: In `@server/database/db.js`:\n- Around line 229-268: The getOrCreateOrchestratorUser function currently\nignores githubId and can bind an orchestrator user to any local account with the\nsame username; update this logic to (1) validate githubUsername is\nnon-empty/defined before DB ops, (2) prefer finding users by a persisted\ngithub_id column (add/ensure users.github_id exists), (3) if a user is found by\nusername but github_id differs, reject or namespace the orchestrator user\ninstead of reusing the local account, and (4) when creating a new orchestrator\nuser insert the github_id (and a namespaced username if you choose that\napproach) and set last_login; update the SELECT, INSERT and UPDATE statements in\ngetOrCreateOrchestratorUser to use github_id checks and handle mismatch cases\nexplicitly.\n\nIn `@server/middleware/auth.js`:\n- Around line 9-112: The middleware logs sensitive token fragments, decoded\npayloads, and secret hashes unguarded (see secretHash, authenticateToken,\ngenerateToken), which must be disabled in production; wrap all\nconsole.log/console.error calls that print token/payload/secret information\nbehind an explicit AUTH_DEBUG env check (e.g., process.env.AUTH_DEBUG ===\n\"true\") so logs only run when AUTH_DEBUG is enabled, leaving non-sensitive error\nhandling intact and keeping validateApiKey, authenticateToken, and generateToken\nlogic unchanged except for gating their debug logging.\n\nIn `@server/orchestrator/client.js`:\n- Around line 255-264: In sendPing(), clear any existing heartbeat timeout\nbefore setting a new one to prevent stale timers from firing; call\nclearTimeout(this.heartbeatTimeoutTimer) (and reset it to null) right before\nassigning this.heartbeatTimeoutTimer = setTimeout(...), so overlapping pings\nwon’t cause a healthy connection to be terminated in sendPing(), keeping the\nexisting DEFAULTS.heartbeatTimeout and the ws?.terminate() behavior unchanged.\n- Around line 501-516: Remove logging of token material: keep the token format\nvalidation for orchestratorToken and still set\nfetchOptions.headers[\"authorization\"] = `Bearer ${orchestratorToken}` when\nvalid, but change the console.log to omit the raw token (do not use\norchestratorToken.substring). Instead log only non-sensitive context such as\norchestratorUsername or a fixed placeholder (e.g., \"[REDACTED]\" or masked\nindicator) so no bearer token characters are emitted; update the message near\nthe existing token-format check and header assignment in\nserver/orchestrator/client.js that references orchestratorToken,\norchestratorUsername, and fetchOptions.headers.\n- Around line 734-769: The issue is that '.' and other regex metacharacters in\nentries of urlPrefixes (e.g., \"socket.io\", \"manifest.json\") are being\ninterpreted by RegExp, causing unintended matches; in rewriteJsUrls(js,\nproxyBase) escape each prefix before constructing the RegExp (e.g., compute an\nescapedPrefix by replacing /[.*+?^${}()|[\\]\\\\]/g with '\\\\[Pasted text #1 +107 lines]') and use\nescapedPrefix in the three new RegExp(...) calls so the patterns match the\nliteral prefix characters.\n- Around line 525-539: The current code always calls response.text() which\ncorrupts binary payloads; change the logic around the response handling in\nserver/orchestrator/client.js (the block that builds responseHeaders,\ncontentType, and responseBody from response) to detect binary vs text via\ncontentType (inspect the contentType variable from the response.headers loop),\nand for binary types (non-text/* and not application/json or lacking utf-8) read\nthe body with response.arrayBuffer(), base64-encode it, set a sentinel header\n(e.g. add [\"x-orch-encoding\",\"base64\"] into responseHeaders) and set\nresponseBody to the base64 string; for text types continue to use\nresponse.text() and keep no encoding header; apply the same change to the second\nsimilar block later in the file where response.text() is used.\n\nIn `@server/orchestrator/github-auth.js`:\n- Around line 32-47: The githubFetch function currently can hang indefinitely;\nmodify githubFetch to use an AbortController with an 8 second timeout: create an\nAbortController, pass its signal to fetch, set a timeout (8000ms) that calls\ncontroller.abort(), and clear that timeout after the fetch completes (both\nsuccess and error) to avoid leaks; ensure the thrown error when aborted is\nsurfaced as a fetch failure so the existing response.ok handling still applies\n(update references to the controller and signal usage inside githubFetch).\n\nIn `@server/orchestrator/index.js`:\n- Around line 67-69: The function createOrchestratorClientFromEnv uses\nrequire(\"./client.js\") which fails in this ESM module; replace that with either\na static top-level import \"import { OrchestratorClient } from './client.js'\" or\nswitch to the existing dynamic-import pattern (use await import(\"./client.js\")\nand destructure OrchestratorClient) inside createOrchestratorClientFromEnv and\nmake the function async if you choose dynamic import so the destructuring works\ncorrectly.\n\nIn `@server/orchestrator/protocol.js`:\n- Around line 133-139: The createResponseCompleteMessage function currently\nomits valid falsy values because it spreads ...(data && { data }); change the\nconditional to only exclude when data is null or undefined (e.g., use a\nconditional like data !== null && data !== undefined) so that empty string, 0,\nfalse are preserved; update createResponseCompleteMessage to build the returned\nobject for OutboundMessageTypes.RESPONSE_COMPLETE and include the data property\nwhen data !== null && data !== undefined (leave it out when null/undefined).\n\nIn `@server/orchestrator/proxy.js`:\n- Around line 215-285: handleUserRequest may destructure payload as undefined\nand then do payload.user = ..., causing a crash when githubAuth is configured;\nfix by ensuring payload is initialized before mutation: change the\ndestructuring/assignment so payload is a defined object (e.g., let payload =\nmessage.payload || {}) prior to using githubAuth.validate and assigning\npayload.user, and then pass that initialized payload into\nOrchestratorProxySocket and handlers.handleChatMessage so subsequent code\n(OrchestratorProxySocket, OrchestratorProxyWriter, handlers.handleChatMessage)\ncan safely read/write payload without throwing.\n\nIn `@src/App.jsx`:\n- Around line 109-140: The mediaQuery listener leaks because\nwindow.matchMedia(\"(display-mode: standalone)\") is invoked multiple times;\ncapture the MediaQueryList once (e.g., const mql = window.matchMedia(...)) in\nthe useEffect, add the \"change\" listener to mql and remove it from the same mql\nin the cleanup, and stop calling matchMedia inside checkPWA. Also move the\ntouchstart listener setup out of checkPWA (or guard so it only attaches once),\nprovide a real handler function instead of {} (e.g., handleTouchStart) and\nremove that same handler in the useEffect cleanup; update references to\ncheckPWA, mql, and handleTouchStart accordingly so listeners are added/removed\nfrom the same objects.",
"timestamp": "2026-01-20 22:06:35 UTC"
},
{
"prompt": "close both PRs from this branch without merging. Force push the branch. Then change the origin to https://github.com/Epiphytic/claudecodeui and push all changes there. Then make new PRs to https://github.com/Epiphytic/claudecodeui and https://github.com/siteboon/claudecodeui/",
"timestamp": "2026-01-20 22:24:35 UTC"
},
{
"prompt": "add detail from this PR: https://github.com/siteboon/claudecodeui/pull/316 into documentation for the project in /docs/Orchestrator-Client.md, as well as in the PR description for https://github.com/siteboon/claudecodeui/pull/319. Make sure everything is up to date with the latest version of the PR. Put a link to the orchestrator client docs into the main README. Make sure the docs reference the new orchestrator project url: https://github.com/Epiphytic/duratii",
"timestamp": "2026-01-20 22:31:02 UTC"
}
]
}
11 changes: 11 additions & 0 deletions .fork-join/tracked_files.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/Users/liam.helmer/repos/epiphytic/claudecodeui/server/database/db.js
/Users/liam.helmer/repos/epiphytic/claudecodeui/server/middleware/auth.js
/Users/liam.helmer/repos/epiphytic/claudecodeui/server/orchestrator/client.js
/Users/liam.helmer/repos/epiphytic/claudecodeui/server/orchestrator/github-auth.js
/Users/liam.helmer/repos/epiphytic/claudecodeui/server/orchestrator/index.js
/Users/liam.helmer/repos/epiphytic/claudecodeui/server/orchestrator/protocol.js
/Users/liam.helmer/repos/epiphytic/claudecodeui/server/orchestrator/proxy.js
/Users/liam.helmer/repos/epiphytic/claudecodeui/src/App.jsx
/Users/liam.helmer/repos/epiphytic/claudecodeui/server/database/init.sql
/Users/liam.helmer/repos/epiphytic/claudecodeui/docs/Orchestrator-Client.md
/Users/liam.helmer/repos/epiphytic/claudecodeui/README.md
Loading