Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions internal/server/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,7 @@ func (i *Investigation) Start() error {
LinkedRepos: i.LinkedRepos,
LaunchCwd: i.LaunchCwd,
KubeconfigPath: i.KubeconfigPath,
Cluster: i.ActiveContext,
Profile: i.Profile,
})
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions internal/server/rehydrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ func (a *apiHandlers) rehydrate(inv *Investigation) error {
LinkedRepos: linked,
LaunchCwd: inv.LaunchCwd,
KubeconfigPath: inv.KubeconfigPath,
Cluster: inv.ActiveContext,
Profile: a.prof,
}
priorID := inv.ClaudeSessionID
Expand Down
31 changes: 20 additions & 11 deletions internal/sessions/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ import (
// Options describes how to launch one session. They mirror the bits of
// preflight.Result and program-level Deps that affect what claude sees.
type Options struct {
Namespace string
UserNotes string
IncidentURL string // operator-supplied incident.io incident URL; empty when not set
SlackChannelURL string // operator-supplied slack channel/thread URL; empty when not set
SlackChannelID string
SlackChannelName string
MCPConfigPath string
Namespace string
UserNotes string
IncidentURL string // operator-supplied incident.io incident URL; empty when not set
SlackChannelURL string // operator-supplied slack channel/thread URL; empty when not set
SlackChannelID string
SlackChannelName string
MCPConfigPath string
SlackMCPAvailable bool // triagent-slack MCP wired (slack token linked)
IncidentioMCPAvailable bool // triagent-incidentio MCP wired (incidentio token linked)
LinkedRepos []repos.LinkedRepo
Expand All @@ -51,7 +51,9 @@ type Options struct {
AlertKind string
// ProjectID is the deployment / tenant identifier (when known).
ProjectID string
// Cluster is the kube-context / cluster name (when known).
// Cluster is the kubeconfig context name the launcher seeded at
// preflight (when known). Rendered as kubernetes-context in the
// opening prompt and exposed to namespace_derivation as "cluster".
Cluster string
}

Expand Down Expand Up @@ -184,9 +186,17 @@ func clusterIDFromNamespace(ns string) string {
// from Options. Events are delivered on the returned channel; the channel
// closes when claude exits or ctx is cancelled.
func (s *Session) Start(ctx context.Context) (<-chan claude.Event, error) {
return s.inner.Start(ctx, s.startPrompt())
}

// startPrompt renders the opening prompt from Options. Cluster feeds the
// Environment block's kubernetes-context line so the agent sees the
// context the launcher already seeded instead of "<unset>".
func (s *Session) startPrompt() string {
clusterID := clusterIDFromNamespace(s.opts.Namespace)
prompt := prompts.Build(prompts.Env{
UserNotes: s.opts.UserNotes,
return prompts.Build(prompts.Env{
Context: s.opts.Cluster,
UserNotes: s.opts.UserNotes,
InputValues: map[string]map[string]any{
"cluster_id": {"value": clusterID},
"incident_url": {"value": s.opts.IncidentURL},
Expand All @@ -197,7 +207,6 @@ func (s *Session) Start(ctx context.Context) (<-chan claude.Event, error) {
IncidentioMCPAvailable: s.opts.IncidentioMCPAvailable,
LinkedRepos: s.opts.LinkedRepos,
}, s.opts.Profile)
return s.inner.Start(ctx, prompt)
}

// Resume continues the most-recent conversation with a follow-up prompt.
Expand Down
17 changes: 17 additions & 0 deletions internal/sessions/session_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,20 @@ func TestNew_ForwardsProfileInvestigationModel(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, "claude-opus-4-7", sess.inner.Model())
}

func TestStartPrompt_EmitsSeededKubeContext(t *testing.T) {
t.Parallel()
s := &Session{opts: Options{
Cluster: "camunda.teleport.sh-saas-int-worker-3",
Profile: &profile.Profile{},
}}
got := s.startPrompt()
assert.Contains(t, got, "kubernetes-context: camunda.teleport.sh-saas-int-worker-3\n")
assert.NotContains(t, got, "kubernetes-context: <unset>")
}

func TestStartPrompt_UnsetWhenNoContextSeeded(t *testing.T) {
t.Parallel()
s := &Session{opts: Options{Profile: &profile.Profile{}}}
assert.Contains(t, s.startPrompt(), "kubernetes-context: <unset>\n")
}
Loading