Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
574a3fd
feat: helix-org prototype with MCP, prompt-driven CLI, and Role/Ident…
philwinder Apr 25, 2026
e60b4bf
feat: add MLOps newsletter demo (3 roles, 1 prompt, no scaffolding)
philwinder Apr 26, 2026
c2821fe
feat: add helix-org tail command for streaming events with channel globs
philwinder Apr 26, 2026
dbc23b7
docs: switch newsroom and getting-started demos to helix-org tail
philwinder Apr 26, 2026
9f451c2
refactor: unify Channel and Stream into single Stream abstraction wit…
philwinder Apr 26, 2026
65d1041
refactor: remove /tail HTTP endpoint and helix-org CLI, unify on MCP
philwinder Apr 27, 2026
e96291c
feat: add helix-org chat subcommand and update demos to interactive flow
philwinder Apr 27, 2026
385cb4f
fix: only pass --continue when claude has a prior session for the cwd
philwinder Apr 27, 2026
e50307e
fix: resume chat by explicit session ID instead of --continue
philwinder Apr 27, 2026
d79a392
feat: add dm and invite_workers tools for direct messaging
philwinder Apr 27, 2026
1fd048b
feat: per-Worker activation streams and worker_log tool
philwinder Apr 27, 2026
027138e
feat: add webhook transport with secretary demo and comprehensive tests
philwinder Apr 27, 2026
4766b7b
feat: add outbound webhook transport with bidirectional bridge demo
philwinder Apr 27, 2026
1cf2cb6
feat: canonical Message envelope as the only Event.Body shape
philwinder Apr 27, 2026
92fae06
feat: email transport via Postmark + DB-stored operational config
philwinder Apr 27, 2026
6567cc2
feat: two-worker email demo (Sam + Lee email each other)
philwinder Apr 28, 2026
638fa01
feat: render full Message envelope in worker activation prompts
philwinder Apr 28, 2026
ecfb525
feat: add inbound github transport with HMAC-verified webhooks
philwinder Apr 28, 2026
b01d634
docs: add github transport demo and design doc
philwinder Apr 28, 2026
6fd1302
refactor: move Role and Worker identity to domain (DB source of truth)
philwinder Apr 28, 2026
e01d8a6
feat: add MCP prompts system with /help and /role commands, chat type…
philwinder Apr 29, 2026
ee842ba
feat: fix cascading AI activations, add markdown rendering, real-time…
philwinder Apr 30, 2026
59ce87a
chore: add opinionated defaults to make targets and improve developer…
philwinder Apr 30, 2026
88f4d37
feat: batch queued triggers into single spawner activation
philwinder Apr 30, 2026
074c563
docs: add github-engineer demo and escalate setup-level issues via DM
philwinder Apr 30, 2026
2253f24
feat: per-Worker Helix projects with desktops, MCP wiring, and warm s…
philwinder May 1, 2026
4a6cb33
feat: republish role/identity on every activation + always git-pull i…
philwinder May 2, 2026
284d86b
docs: sharpen role.md template with default-quiet, trigger naming, an…
philwinder May 4, 2026
63e7a78
refactor: split helix runtime out of domain + tools, unify session shape
philwinder May 6, 2026
3d58083
docs: add manufacturing NCR demo with webhook cascades
philwinder May 6, 2026
da6a783
docs: harden manufacturing demo against local-default stream creation
philwinder May 7, 2026
a1b90f2
fix(tools): surface valid TransportKind values in create_stream schema
philwinder May 7, 2026
15ade47
docs: simplify manufacturing hire prompt now that schema enums kinds
philwinder May 7, 2026
8d19b7a
docs: rewrite manufacturing hire prompt in plain English
philwinder May 7, 2026
4f94775
fix(tools): accept bare-string transport shorthand in create_stream
philwinder May 7, 2026
09b114f
docs: inline the role markdown in the manufacturing hire prompt
philwinder May 7, 2026
2e6850f
fix(tools): clear Types when narrowing transport schema to object form
philwinder May 7, 2026
e835d6d
chore(gitignore): anchor `helix` rule to specific build-artifact paths
philwinder May 10, 2026
088e2b4
feat(helix-org): use new /sessions/{id}/messages queue + validate pro…
philwinder May 10, 2026
56620db
feat(helix-org): pre-flight desktop quota check before opening sessions
philwinder May 10, 2026
0548792
demo fix
philwinder May 10, 2026
80135b9
feat(helix-org): embed as alpha SaaS surface with in-process MCP gate…
philwinder May 19, 2026
5b3670b
feat(helix-org): zed_external workers via Claude Code subscription, e…
philwinder May 19, 2026
d2102d6
fix(helix-org): stop /ui/org polling loop from stalling clicks
philwinder May 19, 2026
79e864b
feat(helix-org): wire prompts registry so slash commands work
philwinder May 19, 2026
c7f79af
feat(helix-org): style alpha-agents picker as a proper helix-org UI page
philwinder May 19, 2026
807c26c
refactor(helix-org): w-owner is a Worker — drop the picker, one setti…
philwinder May 19, 2026
3ed40c5
fix(helix-org): worker projects land under the actual user, and the b…
philwinder May 19, 2026
f421fd3
fix(helix-org): stop double-rendering assistant replies in owner chat
philwinder May 19, 2026
a8921ab
feat(helix-org): label owner-chat sessions as `exploratory`
philwinder May 19, 2026
9510839
fix(helix-org): /ui/streams 20s freeze — drop runaway polling triggers
philwinder May 19, 2026
f10fcb7
fix(helix-org): recover from evicted owner-chat session after restart
philwinder May 20, 2026
a709b51
fix(helix-org): owner-chat survives session eviction, recovery renders
philwinder May 20, 2026
e6d6bee
feat(helix-org): chat survives refresh, "New chat" truly starts fresh
philwinder May 20, 2026
c43d21e
refactor(helix-org): one code path for owner-chat and worker activations
philwinder May 20, 2026
61304a9
fix(helix-org): re-add cold-start retry inside EnsureAndSend
philwinder May 20, 2026
03ad126
fix(helix-org): owner is just-another-Worker — activation stream + co…
philwinder May 20, 2026
4364410
feat(helix-org): live /ui/streams via SSE — kills manual refresh
philwinder May 20, 2026
23b6a68
feat(helix-org): HELIX_ORG_ENABLED kill switch (default off)
philwinder May 20, 2026
ebdfe81
chore(helix-org): drop saas-alpha design doc
philwinder May 20, 2026
9946047
Merge remote-tracking branch 'origin/main' into feat/helix-org-prompt…
philwinder May 20, 2026
ad5f1bf
ci: bump unit-test wrapper timeout from 300s to 600s
philwinder May 20, 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
10 changes: 7 additions & 3 deletions .drone.yml
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,12 @@ steps:
- git config --global user.email "ci@helix.ml"
- git config --global user.name "CI"
- chmod +x ./scripts/run-tests-with-timeout.sh
# Run tests excluding GStreamer-dependent packages (tested separately with CGO disabled)
- go list ./api/... | grep -v '/desktop' | grep -v '/desktop-bridge' | xargs ./scripts/run-tests-with-timeout.sh -tags ORT -timeout 8m -v
# Run tests excluding GStreamer-dependent packages (tested separately with CGO disabled).
# First arg "600" sets the script's wall-clock timeout — it parses
# the first numeric arg. Without it the script defaults to 300s,
# which is shorter than the inner `-timeout 8m` and kills the
# suite during pkg/tools (real TogetherAI calls take ~280s).
- go list ./api/... | grep -v '/desktop' | grep -v '/desktop-bridge' | xargs ./scripts/run-tests-with-timeout.sh 600 -tags ORT -timeout 8m -v
when:
event:
- push
Expand Down Expand Up @@ -268,7 +272,7 @@ steps:
- which helix && helix version
- timeout 5 helix serve 2>&1 || echo "Server exited (expected - checking for startup errors)"
# Run tests
- pwd && ./scripts/run-tests-with-timeout.sh -tags ORT -timeout 8m -v ./integration-test/api/...
- pwd && ./scripts/run-tests-with-timeout.sh 600 -tags ORT -timeout 8m -v ./integration-test/api/...
when:
event:
- push
Expand Down
10 changes: 6 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@ charts/helix-controlplane/charts/*.tgz
# Source-of-truth is the .tmpl file; the rendered Chart.yaml is build output.
charts/*/Chart.yaml
# Anchor helix-runner to repo root so it does not shadow charts/helix-runner/.
# Other unanchored patterns below (helix, zed-build, tmp) are left as-is to
# avoid regressing the ignoring of build artifacts in subdirectories such as
# api/cmd/helix/helix or runner-cmd/helix-runner/helix-runner.
# `helix` and friends are anchored to specific build-artifact paths because a
# bare `helix` pattern matches any directory named `helix` at any depth — which
# accidentally swallowed helix-org/helix/ and helix-org/agent/helix/ entirely.
/helix-runner
zed-build
helix
/helix
/api/helix
/api/cmd/helix/helix
tmp
zed-config/development_credentials

Expand Down
8 changes: 8 additions & 0 deletions api/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ type ServerConfig struct {
Edition string `envconfig:"HELIX_EDITION" default:""`

SBMessage string `envconfig:"SB_MESSAGE" default:""`

// HelixOrgEnabled is the deployment-wide kill switch for the
// embedded helix-org alpha. When false (the default), none of
// the helix-org init runs and none of its HTTP surfaces
// (/api/v1/org/, /ui/, /api/v1/mcp/helix-org/) are mounted —
// the per-user alpha feature flag in the DB has no effect.
// Set HELIX_ORG_ENABLED=true to opt in.
HelixOrgEnabled bool `envconfig:"HELIX_ORG_ENABLED" default:"false"`
}

// Sandboxes configures the user-facing Sandboxes API.
Expand Down
3 changes: 3 additions & 0 deletions api/pkg/server/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,7 @@ func (s *HelixAPIServer) user(w http.ResponseWriter, r *http.Request) {
Admin: dbUser.Admin,
OnboardingCompleted: dbUser.OnboardingCompleted,
Waitlisted: dbUser.Waitlisted,
AlphaFeatures: []string(dbUser.AlphaFeatures),
}
writeResponse(w, response, http.StatusOK)
return
Expand Down Expand Up @@ -780,6 +781,7 @@ func (s *HelixAPIServer) user(w http.ResponseWriter, r *http.Request) {
Admin: user.Admin,
OnboardingCompleted: user.OnboardingCompleted,
Waitlisted: user.Waitlisted,
AlphaFeatures: []string(user.AlphaFeatures),
}
writeResponse(w, response, http.StatusOK)
return
Expand Down Expand Up @@ -863,6 +865,7 @@ func (s *HelixAPIServer) user(w http.ResponseWriter, r *http.Request) {
Admin: user.Admin,
OnboardingCompleted: user.OnboardingCompleted,
Waitlisted: user.Waitlisted,
AlphaFeatures: []string(user.AlphaFeatures),
}
writeResponse(w, response, http.StatusOK)
}
Expand Down
23 changes: 23 additions & 0 deletions api/pkg/server/auth_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,29 @@ func requireAdmin(next http.Handler) http.Handler {
return http.HandlerFunc(f)
}

// requireFeature returns middleware that gates access on the given
// alpha-feature flag. The flag must be present in the authenticated
// user's AlphaFeatures slice; missing returns 403. This is the
// server-side gate — frontend toggles are cosmetic only.
func requireFeature(name string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user := getRequestUser(r)
if !hasUser(user) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
for _, f := range user.AlphaFeatures {
if f == name {
next.ServeHTTP(w, r)
return
}
}
http.Error(w, "Forbidden", http.StatusForbidden)
})
}
}

func requireRunner(next http.Handler) http.Handler {
f := func(w http.ResponseWriter, r *http.Request) {
user := getRequestUser(r)
Expand Down
83 changes: 83 additions & 0 deletions api/pkg/server/external_agent_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/gorilla/mux"
"github.com/rs/zerolog/log"

"github.com/helixml/helix/api/pkg/crypto"
"github.com/helixml/helix/api/pkg/proxy"
"github.com/helixml/helix/api/pkg/types"
)
Expand Down Expand Up @@ -90,6 +91,25 @@ func (apiServer *HelixAPIServer) addUserAPITokenToAgent(ctx context.Context, age
tokenEnvVars := types.DesktopAgentAPIEnvVars(apiKey)
agent.Env = append(agent.Env, tokenEnvVars...)

// Subscription-mode claude_code overrides: when the session's
// parent app runs Claude Code on the operator's OAuth, the
// in-sandbox `claude` process needs CLAUDE_CODE_OAUTH_TOKEN and
// an Anthropic-direct ANTHROPIC_BASE_URL — not the Helix proxy
// envs DesktopAgentAPIEnvVars sets above. Resolved here at
// session-start time so the claude process inherits them, rather
// than relying on Zed's `agent_servers.env` to propagate (it
// doesn't, for registry-type agents). The setup token lookup
// honours session ownership: each Worker session runs under the
// hiring user, so test@helix.ml's workers get test@helix.ml's
// subscription.
if extra := apiServer.subscriptionEnvForSession(ctx, session); len(extra) > 0 {
agent.Env = append(agent.Env, extra...)
log.Info().
Str("session_id", agent.SessionID).
Int("count", len(extra)).
Msg("✅ Added claude_code subscription env vars to agent (CLAUDE_CODE_OAUTH_TOKEN, ANTHROPIC_BASE_URL override)")
}

log.Info().
Str("user_id", userID).
Str("session_id", agent.SessionID).
Expand All @@ -101,6 +121,69 @@ func (apiServer *HelixAPIServer) addUserAPITokenToAgent(ctx context.Context, age
return nil
}

// subscriptionEnvForSession returns extra container env vars when
// the session's parent app uses claude_code runtime with subscription
// credentials. Each var "K=V" appears AFTER DesktopAgentAPIEnvVars in
// hydra_executor's env list, so process env honours these overrides
// (last write wins). Empty slice when the runtime is anything else or
// the lookup fails — callers degrade to the base API-key env.
func (apiServer *HelixAPIServer) subscriptionEnvForSession(ctx context.Context, session *types.Session) []string {
if session.ParentApp == "" {
return nil
}
app, err := apiServer.Store.GetApp(ctx, session.ParentApp)
if err != nil || len(app.Config.Helix.Assistants) == 0 {
return nil
}
asst := app.Config.Helix.Assistants[0]
if asst.CodeAgentRuntime != types.CodeAgentRuntimeClaudeCode {
return nil
}
if !asst.CodeAgentCredentialType.IsSubscription() {
return nil
}
sub, err := apiServer.Store.GetEffectiveClaudeSubscription(ctx, session.Owner, session.OrganizationID)
if err != nil || sub.Status != "active" {
log.Warn().Str("session_id", session.ID).Str("owner", session.Owner).
Msg("claude_code subscription mode but no active Claude subscription found for session owner")
return nil
}
out := []string{
// Anthropic-direct; overrides the helix-proxy URL baked into
// the container env list.
"ANTHROPIC_BASE_URL=https://api.anthropic.com",
// Clear ANTHROPIC_API_KEY so Claude Code falls back to OAuth.
"ANTHROPIC_API_KEY=",
}
encKey, err := crypto.GetEncryptionKey()
if err != nil {
log.Warn().Err(err).Str("session_id", session.ID).Msg("claude_code subscription mode: encryption key unavailable")
return out
}
plaintext, err := crypto.DecryptAES256GCM(sub.EncryptedCredentials, encKey)
if err != nil {
log.Warn().Err(err).Str("session_id", session.ID).Msg("claude_code subscription mode: decrypt subscription failed")
return out
}
credType := sub.CredentialType
if credType == "" {
credType = "oauth"
}
switch credType {
case "setup_token":
var tok types.ClaudeSetupTokenCredentials
if err := json.Unmarshal(plaintext, &tok); err == nil && tok.SetupToken != "" {
out = append(out, "CLAUDE_CODE_OAUTH_TOKEN="+tok.SetupToken)
}
case "oauth":
// OAuth credentials are normally written to
// ~/.claude/.credentials.json by settings-sync-daemon on
// container start. The daemon path is more correct for OAuth
// since it can refresh tokens; we don't duplicate it here.
}
return out
}

// RegisterRequestToSessionMapping registers a request_id to session_id mapping for external agent sessions
// This is used to route initial messages to Zed when it connects via WebSocket
func (apiServer *HelixAPIServer) RegisterRequestToSessionMapping(requestID, sessionID string) {
Expand Down
Loading