Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
5f2ac10
feat: skills-based architecture for agent prompt system
Zarlanx Apr 4, 2026
81014b6
merge: resolve conflicts with main, keep skills architecture
Zarlanx Apr 4, 2026
85c2902
Merge branch 'main' into worktree/skills-architecture
Zarlanx Apr 14, 2026
dd822d6
feat: add OpsGenie skill + fix merge variable refs
Zarlanx Apr 14, 2026
adc4d54
fix: connection checks for bitbucket and kubectl_onprem skills
Zarlanx Apr 14, 2026
3838f32
refactor: extract build_system_invariant into modular markdown files
Zarlanx Apr 14, 2026
aad9bf5
refactor: decompose prompt_builder into modular sub-packages
Zarlanx Apr 14, 2026
f070455
fix: address PR review comments — critical bugs, perf, and cleanup
beng360 Apr 16, 2026
83842c0
Merge remote-tracking branch 'origin/main' into worktree/skills-archi…
beng360 Apr 16, 2026
c089e9b
fix: add logging to bare except in _get_recent_deploys + org_id index…
beng360 Apr 16, 2026
370848f
random fixes
beng360 Apr 16, 2026
85d2ce1
fix: non-blocking mark-executed + auto-send retry for Next Steps
beng360 Apr 17, 2026
fadba49
fix: connection check caching, dynamic skill context, structured resp…
beng360 Apr 17, 2026
4578b5a
fix: address remaining PR review comments — prompt content and docs
beng360 Apr 17, 2026
62f8de6
fix: replace non-existent OVH CLI kubeconfig command with correct syntax
beng360 Apr 17, 2026
b8b0de1
fix: auto-load full skill content for interactive chat sessions
beng360 Apr 17, 2026
e31a008
fix: revert auto-load, enforce on-demand load_skill with stronger pro…
beng360 Apr 17, 2026
11597f7
fix: load_skill UI, session dedup, Flask-free connection checks, rema…
beng360 Apr 17, 2026
23448ad
fix: coderabbit review — bitbucket display_name, template rendering, …
beng360 Apr 17, 2026
6b52753
docs: add RCA_SKILLS_TOKEN_BUDGET to .env.example
beng360 Apr 17, 2026
4ed012a
docs: add RCA_TOKEN_BUDGET fallback to .env.example
beng360 Apr 17, 2026
4224a08
fix: uncomment RCA token budget vars so CI env check passes
beng360 Apr 17, 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
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ SEARXNG_SECRET=
RCA_OPTIMIZE_COSTS=false
GEMINI_DISABLE_THINKING=

# Optional: Token budget for auto-loaded integration skills during background RCA.
# Defaults to 12000. Set higher to include more integration context, lower to save tokens.
RCA_SKILLS_TOKEN_BUDGET=
RCA_TOKEN_BUDGET=

# Model overrides — defaults vary by setting; see each variable below.
# Use provider/model format (e.g., openai/gpt-4.1, google/gemini-2.5-pro, ollama/llama3.1).

Expand Down
5 changes: 4 additions & 1 deletion client/src/app/chat/components/ChatClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ export default function ChatClient({ initialSessionId, shouldStartNewChat, initi
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [handleChatSessionSelect, handleNewChat, currentSessionId]);

// Auto-send initial message from URL
// Auto-send initial message from URL or sessionStorage (e.g., Next Steps execution)
useEffect(() => {
if (initialMessage && chatWebSocket.isReady && userId && !isLoadingSessionMessages && !isSending && !initialMessageSentRef.current) {
initialMessageSentRef.current = true;
Expand All @@ -384,6 +384,9 @@ export default function ChatClient({ initialSessionId, shouldStartNewChat, initi
sessionStorage.removeItem('pendingChatMessage');
const sessionIdToUse = currentSessionId;
window.history.replaceState({}, '', `/chat${sessionIdToUse ? `?sessionId=${sessionIdToUse}` : ''}`);
} else {
// Reset so the effect can retry when conditions are met again
initialMessageSentRef.current = false;
}
});
}, 500);
Expand Down
5 changes: 1 addition & 4 deletions client/src/app/incidents/components/SuggestionModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export default function SuggestionModal({
if (!suggestion.command || isExecuting) return;
setIsExecuting(true);

// Mark suggestion as executed (non-blocking — don't let this prevent navigation)
try {
const res = await fetch(`/api/incidents/suggestions/${suggestion.id}/mark-executed`, {
method: 'POST',
Expand All @@ -97,13 +98,9 @@ export default function SuggestionModal({
});
if (!res.ok) {
console.error('Failed to mark suggestion as executed:', res.status);
setIsExecuting(false);
return;
}
} catch (err) {
console.error('Failed to mark suggestion as executed:', err);
setIsExecuting(false);
return;
}

const message = `Execute this command and report the output:\n\n\`\`\`\n${suggestion.command}\n\`\`\`\n\nRun ONLY this command. Report the output, then stop. Do not run follow-up commands or investigate further.`;
Expand Down
23 changes: 23 additions & 0 deletions client/src/components/tool-calls/CommandLogo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,24 @@ const logos = {
<path strokeLinecap="round" strokeLinejoin="round" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
</svg>
),
loadSkill: (
<svg
className="w-4 h-4 min-w-4 min-h-4 text-teal-500 dark:text-teal-400"
fill="none"
stroke="currentColor"
strokeWidth="2"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="12" cy="12" r="3" />
<line x1="12" y1="3" x2="12" y2="9" />
<line x1="5.5" y1="17.5" x2="9.5" y2="14" />
<line x1="18.5" y1="17.5" x2="14.5" y2="14" />
<circle cx="12" cy="3" r="1.5" fill="currentColor" />
<circle cx="5.5" cy="17.5" r="1.5" fill="currentColor" />
<circle cx="18.5" cy="17.5" r="1.5" fill="currentColor" />
</svg>
),
rcaUpdate: (
<svg
className="w-4 h-4 min-w-4 min-h-4 text-red-600 dark:text-red-400"
Expand Down Expand Up @@ -305,6 +323,11 @@ const getLogoForCommand = (command: string | any, toolName: string, provider?: s
return 'tailscale'
}

// Load skill tool
if (tool === 'load_skill') {
return 'loadSkill'
}

// Web search tool
if (tool === 'web_search') {
return 'web'
Expand Down
10 changes: 10 additions & 0 deletions client/src/components/tool-calls/ToolExecutionWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,16 @@ const ToolExecutionWidget = ({ tool, className, sendMessage, sendRaw, onToolUpda
command = "Knowledge Base"
}

if (tool.tool_name === "load_skill") {
try {
const parsed = JSON.parse(normalizedInput)
const skillId = parsed.skill_id || parsed.kwargs?.skill_id || ''
command = skillId ? `Loading ${skillId} skill` : "Loading integration skill"
} catch {
command = "Loading integration skill"
}
}

// terminal_exec parsing - extract command from input or output
if (tool.tool_name === "terminal_exec") {
try {
Expand Down
17 changes: 17 additions & 0 deletions client/src/components/tool-calls/tool-output-renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ const isIacTool = (toolName: string) => {
return toolName.includes('iac') || toolName.includes('terraform')
}

const isLoadSkillTool = (toolName: string) => {
return toolName === 'load_skill'
}

const isWebSearchTool = (toolName: string) => {
return toolName.includes('web_search')
}
Expand Down Expand Up @@ -97,6 +101,19 @@ export function RenderOutput({

// If not JSON, check tool type for appropriate rendering
if (!parsed) {
// load_skill - compact one-liner, no raw content
if (isLoadSkillTool(toolName)) {
const alreadyLoaded = output.includes('already loaded')
return (
<div className="flex items-center gap-2 py-1">
<span className={`inline-block w-1.5 h-1.5 rounded-full ${alreadyLoaded ? 'bg-gray-400 dark:bg-gray-500' : 'bg-teal-500 dark:bg-teal-400'}`} />
<span className="text-xs text-gray-500 dark:text-gray-400">
{alreadyLoaded ? 'Already in context' : 'Integration guidance ready'}
</span>
</div>
)
}

// IAC tools with HCL content - use Monaco Editor
if (isIacTool(toolName) && isHclContent(output)) {
const trimmedOutput = output.trim()
Expand Down
7 changes: 3 additions & 4 deletions server/chat/backend/agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,16 +205,15 @@ async def agentic_tool_flow(self, state: State) -> State:
logging.error(f"Invalid user_id format: '{state.user_id}' - must be a non-empty string")
# Don't fail completely, but log the error

# Get connected providers from database (always fetch from DB, no preferences stored)
# Get verified providers (cloud + SkillRegistry-validated integrations)
provider_preference = getattr(state, 'provider_preference', None)
if provider_preference is None:
try:
from utils.auth.stateless_auth import get_connected_providers
provider_preference = get_connected_providers(state.user_id)
from chat.background.rca_prompt_builder import get_user_providers
provider_preference = get_user_providers(state.user_id)
except Exception as e:
logging.error(f"Error getting connected providers: {e}")
provider_preference = []
# Update state so downstream code can access it
state.provider_preference = provider_preference

selected_project_id = getattr(state, 'selected_project_id', None)
Expand Down
Loading
Loading