mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-30 21:44:50 +02:00
[eric] gitleaks org-license fix; picker filter footer; trim+cache MCP gate prompt; losses compression caching on llm response (20% improvement avg)
This commit is contained in:
@@ -1,10 +1,8 @@
|
||||
name: gitleaks
|
||||
|
||||
# Block PRs that introduce hardcoded credentials. Runs the upstream gitleaks
|
||||
# action against the diff (PR) or full history (push to main). False
|
||||
# positives in the working tree are caught by the gitleaks-action's own
|
||||
# allowlist mechanism — extend .gitleaks.toml at repo root rather than
|
||||
# editing this workflow.
|
||||
# Block PRs that introduce hardcoded credentials. Runs the gitleaks CLI
|
||||
# directly (rather than gitleaks-action) because the action requires a
|
||||
# paid license on GitHub Orgs. Same scanner, same rules, same .gitleaks.toml.
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -14,22 +12,54 @@ on:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
scan:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GITLEAKS_VERSION: '8.21.2'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Full history needed so gitleaks can scan all new commits in a PR.
|
||||
fetch-depth: 0
|
||||
fetch-depth: 0 # Full history so PR-diff scanning works.
|
||||
|
||||
- name: Run gitleaks
|
||||
uses: gitleaks/gitleaks-action@v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Treat any high-confidence finding as a hard fail.
|
||||
GITLEAKS_ENABLE_UPLOAD_ARTIFACT: 'true'
|
||||
GITLEAKS_ENABLE_SUMMARY: 'true'
|
||||
- name: Install gitleaks
|
||||
run: |
|
||||
set -euo pipefail
|
||||
curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
|
||||
| tar -xz -C /tmp gitleaks
|
||||
sudo install -m 0755 /tmp/gitleaks /usr/local/bin/gitleaks
|
||||
gitleaks version
|
||||
|
||||
- name: Run gitleaks (PR diff)
|
||||
if: github.event_name == 'pull_request'
|
||||
run: |
|
||||
gitleaks detect \
|
||||
--source . \
|
||||
--redact \
|
||||
--verbose \
|
||||
--no-banner \
|
||||
--log-opts="${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}"
|
||||
|
||||
- name: Run gitleaks (push)
|
||||
if: github.event_name == 'push'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
BEFORE="${{ github.event.before }}"
|
||||
AFTER="${{ github.sha }}"
|
||||
# New-branch push: GH sends 40 zeros for `before`. Diff against
|
||||
# main's merge-base instead so we only scan commits unique to the
|
||||
# branch — fast and matches the gitleaks-action default.
|
||||
if [ "$BEFORE" = "0000000000000000000000000000000000000000" ]; then
|
||||
git fetch --no-tags --depth=1 origin main:refs/remotes/origin/main 2>/dev/null || true
|
||||
if git rev-parse --verify origin/main >/dev/null 2>&1; then
|
||||
BEFORE=$(git merge-base origin/main "$AFTER" 2>/dev/null || echo "")
|
||||
fi
|
||||
fi
|
||||
if [ -n "$BEFORE" ] && [ "$BEFORE" != "$AFTER" ]; then
|
||||
gitleaks detect --source . --redact --verbose --no-banner --log-opts="${BEFORE}..${AFTER}"
|
||||
else
|
||||
# Couldn't establish a range — full scan as fallback.
|
||||
gitleaks detect --source . --redact --verbose --no-banner
|
||||
fi
|
||||
|
||||
@@ -692,55 +692,46 @@ class AgentManager:
|
||||
if not active_lines and not available_lines:
|
||||
return None
|
||||
|
||||
# Static preamble first (kept byte-identical across users so it caches),
|
||||
# then the per-session server list. Worked-example uses generic
|
||||
# placeholders so a Pro Anthropic prompt-cache hit isn't broken by
|
||||
# one user's connector names differing from another's.
|
||||
sections = ["<mcp_servers>"]
|
||||
sections.append(
|
||||
"MCP servers are gated: the model cannot call any MCP tool until "
|
||||
"the user approves an MCPActivate request for that server. To use "
|
||||
"a server below, first call MCPSearch (to confirm the right server "
|
||||
"for the task), then call MCPActivate(server_name) — the user will "
|
||||
"be prompted to approve activation. After approval, the server's "
|
||||
"tools (`mcp__<server>__<tool>`) become callable on the next turn."
|
||||
"MCP servers are gated: their tools are uncallable until the user "
|
||||
"approves an MCPActivate request. To use one below, call MCPSearch "
|
||||
"(if unsure which) then MCPActivate(server_name); after approval the "
|
||||
"server's tools (`mcp__<server>__<tool>`) become callable next turn."
|
||||
)
|
||||
sections.append("")
|
||||
sections.append("## CRITICAL behavioral rules (follow these exactly)")
|
||||
sections.append("## Rules")
|
||||
sections.append(
|
||||
"1. If the user's request implies an integration listed below "
|
||||
"(email, calendar, slack, notion, etc.) and that server is NOT "
|
||||
"in the Active section, your FIRST tool call MUST be MCPSearch "
|
||||
"or MCPActivate. Do NOT call any other tool that looks like an "
|
||||
"auth/login helper (e.g. `mcp__*__authenticate`, "
|
||||
"`mcp__claude_ai_*__authenticate`) — those are legacy shims "
|
||||
"and will not work. Always go through MCPActivate."
|
||||
"1. If the user's request needs a server below that isn't Active, "
|
||||
"your FIRST tool call must be MCPSearch or MCPActivate. Ignore any "
|
||||
"`mcp__*__authenticate` helpers — those are legacy shims; always go "
|
||||
"through MCPActivate."
|
||||
)
|
||||
sections.append(
|
||||
"2. After MCPActivate returns, end your turn cleanly. The "
|
||||
"system will automatically run a follow-up turn with the "
|
||||
"newly-activated tools available — you do NOT need the user "
|
||||
"to re-prompt. Just stop and let the next turn fire."
|
||||
"2. After MCPActivate returns, end the turn — a follow-up turn fires "
|
||||
"automatically with the new tools available."
|
||||
)
|
||||
sections.append(
|
||||
"3. Do not ask the user 'should I activate X?' before calling "
|
||||
"MCPActivate — MCPActivate already triggers an explicit user "
|
||||
"approval prompt via the standard tool-approval UI. Asking "
|
||||
"again wastes a round-trip."
|
||||
"3. Don't ask 'should I activate X?' first — MCPActivate already "
|
||||
"triggers an approval prompt."
|
||||
)
|
||||
sections.append("")
|
||||
sections.append("## Worked example")
|
||||
sections.append("## Example")
|
||||
sections.append(
|
||||
"User: \"check my email\"\n"
|
||||
"Active MCPs: (none)\n"
|
||||
"Available MCPs: google-workspace, microsoft-365\n"
|
||||
"→ Your first tool call is `MCPActivate(server_name=\"google-workspace\", reason=\"checking inbox\")`.\n"
|
||||
" After it returns, end your turn. Next turn, call "
|
||||
"`mcp__google-workspace__query_gmail_emails(...)` to actually "
|
||||
"fetch the email."
|
||||
"User asks for email; no email server is Active. First tool call: "
|
||||
"`MCPActivate(server_name=\"<email-server>\", reason=\"...\")`. End "
|
||||
"turn. Next turn: call the activated server's email tool."
|
||||
)
|
||||
sections.append("")
|
||||
if active_lines:
|
||||
sections.append("Active (already approved this session — tools callable now):")
|
||||
sections.append("Active (callable now):")
|
||||
sections.extend(active_lines)
|
||||
if available_lines:
|
||||
sections.append("\nAvailable (installed but not yet activated):")
|
||||
sections.append("\nAvailable (not yet activated):")
|
||||
sections.extend(available_lines)
|
||||
sections.append("</mcp_servers>")
|
||||
return "\n".join(sections)
|
||||
@@ -2045,6 +2036,13 @@ class AgentManager:
|
||||
|
||||
try:
|
||||
level = getattr(session, "thinking_level", "auto") or "auto"
|
||||
# Trivially short prompts ("hi", "thanks") don't benefit from
|
||||
# 5-30s of hidden reasoning. Override per-turn only — session
|
||||
# setting is untouched so the UI pill keeps reflecting the
|
||||
# user's choice.
|
||||
_prompt_len = len((prompt or "").strip())
|
||||
if 0 < _prompt_len < 50 and level != "off":
|
||||
level = "off"
|
||||
# gc/gemini-3* without Antigravity 400s every multi-step turn
|
||||
# on thoughtSignature continuity. Force-disable thinking.
|
||||
if (
|
||||
|
||||
@@ -35,11 +35,9 @@ TOOLS = [
|
||||
{
|
||||
"name": "MCPList",
|
||||
"description": (
|
||||
"List the MCP servers installed on this machine. Returns one entry "
|
||||
"per server with its name, one-sentence purpose, and current "
|
||||
"activation status (active/available). Costs almost nothing — the "
|
||||
"registry is a flat list, no schemas. Use this when you want a "
|
||||
"broad survey before picking a server."
|
||||
"List installed MCP servers (name, one-sentence purpose, "
|
||||
"active/available status). Cheap. Use for a broad survey before "
|
||||
"picking a server."
|
||||
),
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
@@ -50,18 +48,16 @@ TOOLS = [
|
||||
{
|
||||
"name": "MCPSearch",
|
||||
"description": (
|
||||
"Find MCP servers relevant to a query. Returns the top matches "
|
||||
"ranked by description match against the query (e.g. 'email', "
|
||||
"'calendar', 'spreadsheet'). Use this BEFORE MCPActivate when you "
|
||||
"are not sure which server to enable. The server's tools are NOT "
|
||||
"callable yet — you still need MCPActivate after picking one."
|
||||
"Rank MCP servers by relevance to a query. Use before MCPActivate "
|
||||
"when unsure which server fits. Tools are NOT callable until you "
|
||||
"also MCPActivate."
|
||||
),
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "Free-form description of what you need (e.g. 'send email', 'read inbox', 'post to slack').",
|
||||
"description": "What you need (e.g. 'send email', 'post to slack').",
|
||||
},
|
||||
},
|
||||
"required": ["query"],
|
||||
@@ -71,24 +67,21 @@ TOOLS = [
|
||||
{
|
||||
"name": "MCPActivate",
|
||||
"description": (
|
||||
"Request activation of an MCP server for this session. The user is "
|
||||
"prompted via the standard tool-approval UI; on approve, the "
|
||||
"server's tools become callable on the NEXT turn (the current turn "
|
||||
"ends after this call). On deny, the server stays unavailable and "
|
||||
"you should ask the user how to proceed. Always call MCPSearch or "
|
||||
"MCPList first to confirm the server name — invalid names return "
|
||||
"a list of valid alternatives instead of activating."
|
||||
"Request activation of an MCP server for this session. Triggers a "
|
||||
"user approval prompt; on approve the server's tools become callable "
|
||||
"next turn. Always confirm the server name via MCPList/MCPSearch first — "
|
||||
"invalid names return alternatives instead of activating."
|
||||
),
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"server_name": {
|
||||
"type": "string",
|
||||
"description": "Sanitized server name as returned by MCPList/MCPSearch (e.g. 'gmail', 'slack', 'discord').",
|
||||
"description": "Sanitized name from MCPList/MCPSearch (e.g. 'gmail', 'slack').",
|
||||
},
|
||||
"reason": {
|
||||
"type": "string",
|
||||
"description": "One-sentence explanation of why you need this server, shown to the user in the approval prompt to help them decide.",
|
||||
"description": "Why you need it — shown to the user in the approval prompt.",
|
||||
},
|
||||
},
|
||||
"required": ["server_name"],
|
||||
|
||||
@@ -494,6 +494,24 @@ const ChatInput = forwardRef<ChatInputHandle, Props>(({ onSend, disabled, mode,
|
||||
return out;
|
||||
}, [modelSearch, allModelOptions.grouped, capFilters, ctxIdx, costIdx]);
|
||||
|
||||
// Footer summary — counts reflect what the user actually has access to
|
||||
// right now (post-filter, post-credentials).
|
||||
const pickerSummary = useMemo(() => {
|
||||
let total = 0, free = 0, reasoning = 0, subscription = 0, apiKey = 0, paid = 0, longContext = 0;
|
||||
for (const ms of Object.values(filteredModelGroups)) {
|
||||
for (const m of ms as any[]) {
|
||||
total += 1;
|
||||
if (m.reasoning) reasoning += 1;
|
||||
if ((m.context_window ?? 0) >= 1_000_000) longContext += 1;
|
||||
if (m.billing_kind === 'free') free += 1;
|
||||
else if (m.billing_kind === 'subscription') subscription += 1;
|
||||
else if (m.billing_kind === 'api_key') apiKey += 1;
|
||||
else if (m.billing_kind === 'paid') paid += 1;
|
||||
}
|
||||
}
|
||||
return { total, free, reasoning, subscription, apiKey, paid, longContext };
|
||||
}, [filteredModelGroups]);
|
||||
|
||||
// Recents materialised against current catalog so removed models drop out.
|
||||
const recentMaterialised = useMemo(() => {
|
||||
const flatByValue = new Map(allModelOptions.flat.map((m) => [m.value, m]));
|
||||
@@ -2009,11 +2027,64 @@ const ChatInput = forwardRef<ChatInputHandle, Props>(({ onSend, disabled, mode,
|
||||
px: 1.25, py: 0.5,
|
||||
fontSize: '0.65rem', color: c.text.ghost,
|
||||
display: 'flex', justifyContent: 'space-between',
|
||||
pointerEvents: 'none',
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
<span>Type to search · Esc to close</span>
|
||||
<span>{Object.values(filteredModelGroups).reduce((sum, ms) => sum + (ms as any[]).length, 0)} models</span>
|
||||
<Box component="span" sx={{ flexShrink: 0, pointerEvents: 'none' }}>
|
||||
Type to search · Esc to close
|
||||
</Box>
|
||||
{(() => {
|
||||
const breakdown: Array<[string, number]> = ([
|
||||
['Free', pickerSummary.free],
|
||||
['Subscription', pickerSummary.subscription],
|
||||
['API key', pickerSummary.apiKey],
|
||||
['Pay-per-use', pickerSummary.paid],
|
||||
['Reasoning', pickerSummary.reasoning],
|
||||
['1M+ context', pickerSummary.longContext],
|
||||
] as Array<[string, number]>).filter(([, n]) => n > 0);
|
||||
const breakdownTooltip = breakdown.length > 0 ? (
|
||||
<Box sx={{ fontSize: '0.74rem', lineHeight: 1.6, minWidth: 180 }}>
|
||||
<Box sx={{
|
||||
fontWeight: 600, fontSize: '0.78rem',
|
||||
color: c.text.primary,
|
||||
pb: 0.6, mb: 0.6,
|
||||
borderBottom: `1px solid ${c.border.subtle}`,
|
||||
}}>
|
||||
{pickerSummary.total} model{pickerSummary.total === 1 ? '' : 's'} available
|
||||
</Box>
|
||||
<Box sx={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr auto',
|
||||
columnGap: 1.5, rowGap: 0.3,
|
||||
color: c.text.muted,
|
||||
}}>
|
||||
{breakdown.map(([label, n]) => (
|
||||
<React.Fragment key={label}>
|
||||
<span>{label}</span>
|
||||
<span style={{ fontVariantNumeric: 'tabular-nums', color: c.text.secondary }}>{n}</span>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
) : null;
|
||||
return (
|
||||
<Tooltip
|
||||
title={breakdownTooltip || ''}
|
||||
placement="top-end"
|
||||
enterDelay={300}
|
||||
slotProps={tooltipSlotProps}
|
||||
disableHoverListener={!breakdownTooltip}
|
||||
>
|
||||
<Box component="span" sx={{
|
||||
cursor: breakdownTooltip ? 'help' : 'default',
|
||||
fontVariantNumeric: 'tabular-nums',
|
||||
whiteSpace: 'nowrap',
|
||||
}}>
|
||||
{pickerSummary.total} model{pickerSummary.total === 1 ? '' : 's'}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
);
|
||||
})()}
|
||||
</Box>
|
||||
</Menu>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user