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
5 changes: 0 additions & 5 deletions src/agent/agent-search.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,14 @@ const fixtures: AgentProfile[] = [
{
id: "greybeard",
description: "Seasoned architect — reviews for design and backwards compatibility",
tier: "clever",
},
{
id: "critique",
description: "Code quality reviewer — tests assumptions and security smells",
tier: "standard",
},
{
id: "scout",
description: "Fast codebase explorer — maps structure and entry points",
tier: "fast",
},
];

Expand Down Expand Up @@ -71,13 +68,11 @@ describe("formatAgentSearchResults", () => {
id: "draper",
description: "PR design reviewer from marketplace",
source: "claude",
tier: "standard",
systemPromptRole: body,
},
]);
expect(text).toContain("### draper");
expect(text).toContain("[source: claude]");
expect(text).toContain("[tier: standard]");
expect(text).toContain("System prompt / body:");
expect(text).toContain(body);
expect(text).toContain("do not need read_file on plugin roots");
Expand Down
5 changes: 2 additions & 3 deletions src/agent/agent-search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,12 @@ function truncateAgentBody(body: string): string {
// MAX_AGENT_SEARCH_BODY_CHARS are truncated with an ellipsis marker.
function formatAgentProfileEntry(p: AgentProfile): string {
const desc = (p.description ?? "").trim();
const tier = p.tier !== undefined ? ` [tier: ${p.tier}]` : "";
const orch = p.orchestrator === true ? " [orchestrator]" : "";
const source = p.source !== undefined ? ` [source: ${p.source}]` : "";
const header =
desc.length > 0
? `### ${p.id}${tier}${orch}${source}\n${desc}`
: `### ${p.id}${tier}${orch}${source}`;
? `### ${p.id}${orch}${source}\n${desc}`
: `### ${p.id}${orch}${source}`;
const body = (p.systemPromptRole ?? "").trim();
if (body.length === 0) return header;
return `${header}\n\nSystem prompt / body:\n${truncateAgentBody(body)}`;
Expand Down
2 changes: 0 additions & 2 deletions src/agent/default-agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export const defaultAgentsPlugin: AgentPlugin = {
{
id: "greybeard",
description: "Seasoned architect — reviews for design, constraint ownership, and backwards compatibility",
tier: "clever",
systemPromptRole:
"You are a seasoned software architect with decades of experience. " +
"You review code and designs for architectural soundness, constraint ownership " +
Expand All @@ -19,7 +18,6 @@ export const defaultAgentsPlugin: AgentPlugin = {
{
id: "critique",
description: "Code quality reviewer — tests assumptions, finds edge cases and security smells",
tier: "standard",
systemPromptRole:
"You are a critical code reviewer focused on code quality, test coverage, " +
"edge cases, and security-adjacent issues. You challenge assumptions, look for " +
Expand Down
14 changes: 5 additions & 9 deletions src/agent/profile-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,8 @@ export type CapabilityFilter = {
tools: string[];
};

// A single provider/model/effort combo an agent can run on. Mirrors a tier leg
// but carries an optional reasoningEffort so an agent can pin "Sonnet + medium"
// or "Grok + high" without going through the tier abstraction.
// A single provider/model/effort combo an agent can run on, so an agent can
// pin "Sonnet + medium" or "Grok + high".
export type InferenceLeg = {
provider: string;
model: string;
Expand All @@ -50,12 +49,9 @@ export type AgentProfile = {
// Unique identifier, used in workflow steps as `agent: "greybeard"`.
id: string;
description?: string;
// Provider tier alias for this agent. Resolved via settings.tiers to a
// concrete provider and model assignment. Used when `inference` is absent.
tier?: "fast" | "standard" | "clever";
// Explicit per-agent model selection. Takes precedence over `tier` when set,
// so an agent can declare "Sonnet + medium reasoning" without going through
// the user's tier config. See InferenceSpec for resolution rules.
// Explicit per-agent model selection. When absent, the agent runs on the
// parent session's active provider/model. See InferenceSpec for
// resolution rules.
inference?: InferenceSpec;
// Optional tool restriction. Controls which tools the sub-agent can call.
capabilities?: CapabilityFilter;
Expand Down
1 change: 0 additions & 1 deletion src/agent/profiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ const InferenceSpecSchema = type({
const AgentProfileSchema = type({
id: "string",
"description?": "string",
"tier?": "'fast' | 'standard' | 'clever'",
"inference?": InferenceSpecSchema,
"capabilities?": CapabilityFilterSchema,
"systemPromptRole?": "string",
Expand Down
7 changes: 1 addition & 6 deletions src/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -538,14 +538,11 @@ describe("buildProviderCatalog", () => {
});
});

test("runtimeSettingsWithCatalog overlays OAuth catalog entries for tier resolution", () => {
test("runtimeSettingsWithCatalog overlays OAuth catalog entries for provider resolution", () => {
const disk = {
providers: {
openai: { baseURL: "https://api.openai.com/v1", apiKey: "sk", models: ["gpt-4o"] },
},
tiers: {
clever: { provider: "xai/work", model: "grok-4" },
},
};
const catalog = [
{
Expand All @@ -568,7 +565,6 @@ describe("buildProviderCatalog", () => {
apiKey: "xai-token",
models: ["grok-4"],
});
expect(runtime.tiers).toEqual(disk.tiers);
// Disk persist path still strips OAuth.
expect(providerCatalogToSettings(catalog, "openai", disk).providers["xai/work"]).toBeUndefined();
});
Expand Down Expand Up @@ -633,7 +629,6 @@ describe("buildProviderCatalog", () => {
agentModelFallback: "none",
shell: { timeoutMs: 30_000, maxTimeoutMs: 120_000 },
tools: { timeoutMs: 60_000 },
tiers: { fast: { provider: "fp", model: "fp-large" } },
workflowProfiles: { fast: { implement: "fp-large" } },
};
const settings = providerCatalogToSettings(
Expand Down
12 changes: 3 additions & 9 deletions src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,6 @@ import {
normalizeOpenAICompatibleBaseURL,
resolveProvider,
type MCPServerConfig,
type ProviderTier,
type TierAssignment,
type TierConfig,
type TierDefinition,
type ResolvedProvider,
type Settings,
type ProviderSettings,
Expand Down Expand Up @@ -306,9 +302,8 @@ export type Config = {
workflow?: string;
// Deprecated no-op retained for CLI compatibility.
noWorkflow: boolean;
tiers?: Partial<Record<ProviderTier, import("./settings.js").TierConfig>>;
/**
* Runtime settings view for tier/provider resolution. Includes OAuth provider
* Runtime settings view for provider resolution. Includes OAuth provider
* projections from the live catalog that are never written to settings.json.
* Do not pass this object to saveGlobalSettings — rebuild with
* providerCatalogToSettings (or re-read disk) before any persist.
Expand Down Expand Up @@ -580,8 +575,7 @@ export async function loadConfig(
: settings?.mcpServers !== undefined
? { mcpServers: settings.mcpServers, mcpServersSource: "global" as const }
: { mcpServersSource: "none" as const }),
...(settings?.tiers !== undefined ? { tiers: settings.tiers } : {}),
// Runtime view includes OAuth projections so tier resolution can see
// Runtime view includes OAuth projections so inference resolution can see
// Codex/xAI providers that are never written to settings.json. Not safe
// to persist as-is — use providerCatalogToSettings or re-read disk.
...(settingsForResolution !== null ? { settings: settingsForResolution } : {}),
Expand Down Expand Up @@ -613,7 +607,7 @@ export function catalogEntryAsProviderSettings(entry: ProviderCatalogEntry): Pro
}

// Overlay the full live catalog (including OAuth profiles) onto settings for
// runtime tier/provider resolution. OAuth credentials live in home auth stores
// runtime provider resolution. OAuth credentials live in home auth stores
// and are stripped from settings.json; the catalog is the source of truth for
// which OAuth providers are available right now. Never pass the result to a
// disk write path — use providerCatalogToSettings for persistence.
Expand Down
Loading
Loading