mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 09:41:04 +01:00
Fix configuration section composition and model fallback
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
8818747b3a
commit
3d1fa75b60
3 files changed
+110
-115
No files matched your search
@@ -13,7 +13,7 @@ describe("agent configuration shell", () => {
|
||||
expect([...filterAgentConfigurationSections("sandbox")]).toEqual(["danger"]);
|
||||
});
|
||||
|
||||
it("resolves adapter defaults to a concrete model label", () => {
|
||||
it("does not guess an inherited adapter model from available choices", () => {
|
||||
const agent = {
|
||||
adapterType: "claude_local",
|
||||
adapterConfig: { modelReasoningEffort: "high" },
|
||||
@@ -22,8 +22,8 @@ describe("agent configuration shell", () => {
|
||||
defaultEnvironmentId: null,
|
||||
} as unknown as Agent;
|
||||
|
||||
expect(resolveEffectiveConfiguration(agent, [{ id: "claude-fable-5", label: "Claude Fable 5" }], 2)).toMatchObject({
|
||||
model: "Claude Fable 5 · high",
|
||||
expect(resolveEffectiveConfiguration(agent, 2)).toMatchObject({
|
||||
model: "Adapter default · high",
|
||||
modelInherited: true,
|
||||
environmentInherited: true,
|
||||
apiKeyCount: 2,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { Search, Zap } from "lucide-react";
|
||||
import type { Agent, AgentEnvConfig } from "@paperclipai/shared";
|
||||
import type { AdapterModel } from "../api/agents";
|
||||
import { adapterLabels } from "./agent-config-primitives";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { cn } from "../lib/utils";
|
||||
@@ -44,7 +43,7 @@ function readString(value: unknown): string {
|
||||
return typeof value === "string" ? value : "";
|
||||
}
|
||||
|
||||
export function resolveEffectiveConfiguration(agent: Agent, adapterModels: AdapterModel[] = [], apiKeyCount = 0) {
|
||||
export function resolveEffectiveConfiguration(agent: Agent, apiKeyCount = 0) {
|
||||
const adapterConfig = (agent.adapterConfig ?? {}) as Record<string, unknown>;
|
||||
const runtimeConfig = (agent.runtimeConfig ?? {}) as Record<string, unknown>;
|
||||
const heartbeat = (runtimeConfig.heartbeat ?? {}) as Record<string, unknown>;
|
||||
@@ -52,7 +51,7 @@ export function resolveEffectiveConfiguration(agent: Agent, adapterModels: Adapt
|
||||
const cheap = (profiles.cheap ?? {}) as Record<string, unknown>;
|
||||
const cheapConfig = (cheap.adapterConfig ?? {}) as Record<string, unknown>;
|
||||
const explicitModel = readString(adapterConfig.model);
|
||||
const resolvedModel = explicitModel || adapterModels[0]?.label || adapterModels[0]?.id || "Adapter default";
|
||||
const resolvedModel = explicitModel || "Adapter default";
|
||||
const effort = readString(adapterConfig.modelReasoningEffort || adapterConfig.reasoningEffort || adapterConfig.effort || adapterConfig.variant || adapterConfig.mode);
|
||||
const env = (adapterConfig.env ?? {}) as AgentEnvConfig;
|
||||
return {
|
||||
|
||||
+105
-109
@@ -1758,14 +1758,9 @@ function AgentConfigurePage({
|
||||
queryKey: queryKeys.agents.keys(agentId),
|
||||
queryFn: () => agentsApi.listKeys(agentId, companyId),
|
||||
});
|
||||
const { data: adapterModels = [] } = useQuery({
|
||||
queryKey: companyId ? queryKeys.agents.adapterModels(companyId, agent.adapterType) : ["agents", "none", "adapter-models", agent.adapterType],
|
||||
queryFn: () => agentsApi.adapterModels(companyId!, agent.adapterType),
|
||||
enabled: Boolean(companyId),
|
||||
});
|
||||
const effectiveConfig = useMemo(
|
||||
() => resolveEffectiveConfiguration(agent, adapterModels, keys.filter((key) => !key.revokedAt).length),
|
||||
[adapterModels, agent, keys],
|
||||
() => resolveEffectiveConfiguration(agent, keys.filter((key) => !key.revokedAt).length),
|
||||
[agent, keys],
|
||||
);
|
||||
const handleDirtyDetailsChange = useCallback((details: { count: number; sections: string[] }) => {
|
||||
setDirtyDetails(details);
|
||||
@@ -1817,20 +1812,6 @@ function AgentConfigurePage({
|
||||
hideInstructionsFile
|
||||
visibleSections={visibleSections}
|
||||
/>
|
||||
{visibleSections.has("keys") ? (
|
||||
<ConfigurationSection id="keys" title="API Keys" instant>
|
||||
<p className="text-xs text-muted-foreground">Long-lived keys for external processes acting as this agent. Runs receive short-lived tokens automatically.</p>
|
||||
<KeysTab agentId={agentId} companyId={companyId} />
|
||||
</ConfigurationSection>
|
||||
) : null}
|
||||
{visibleSections.has("danger") ? (
|
||||
<ConfigurationSection id="danger" title="Danger & Legacy">
|
||||
<div className="rounded-lg border border-border p-4 text-sm text-muted-foreground">
|
||||
Adapter-specific sandbox bypass, permission skipping, and deprecated values remain draftable in the runtime form. They are grouped here for navigation while the field internals stay unchanged in P1.
|
||||
</div>
|
||||
</ConfigurationSection>
|
||||
) : null}
|
||||
|
||||
{/* Configuration Revisions — collapsible at the bottom */}
|
||||
{visibleSections.has("history") ? <ConfigurationSection id="history" title="History" instant>
|
||||
<button
|
||||
@@ -2000,6 +1981,108 @@ function ConfigurationTab({
|
||||
? "Enabled by simple company-wide task assignment defaults."
|
||||
: "Disabled unless explicitly granted.";
|
||||
|
||||
const configurationShellAfterSchedule = (
|
||||
<>
|
||||
{visibleSections.has("access") ? (
|
||||
<ConfigurationSection id="access" title="Access & Governance" instant>
|
||||
<TrustPresetSection
|
||||
permissions={agent.permissions}
|
||||
disabled={updatePermissions.isPending}
|
||||
companyId={companyId}
|
||||
projectCandidates={(boundaryProjects ?? []).map((project) => ({
|
||||
id: project.id,
|
||||
label: project.name,
|
||||
}))}
|
||||
issueCandidates={(boundaryIssues ?? []).map((issue) => ({
|
||||
id: issue.id,
|
||||
label: `${issue.identifier ?? issue.id.slice(0, 8)} · ${issue.title}`,
|
||||
}))}
|
||||
candidatesLoading={boundaryProjectsLoading || boundaryIssuesLoading}
|
||||
onChange={(nextPermissions) =>
|
||||
updatePermissions.mutate({
|
||||
canCreateAgents,
|
||||
canCreateSkills,
|
||||
canAssignTasks,
|
||||
...buildPermissionsForTrustPreset(
|
||||
nextPermissions,
|
||||
nextPermissions.trustPreset === "low_trust_review" ? "low_trust_review" : "standard",
|
||||
),
|
||||
})
|
||||
}
|
||||
/>
|
||||
<div>
|
||||
<h3 className="text-sm font-medium mb-3">Permissions</h3>
|
||||
<div className="border border-border rounded-lg p-4 space-y-4">
|
||||
<div className="flex items-center justify-between gap-4 text-sm">
|
||||
<div className="space-y-1">
|
||||
<div>Can create new agents</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Lets this agent create or hire agents. This also grants task assignment authority.
|
||||
</p>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
checked={canCreateAgents}
|
||||
onCheckedChange={() =>
|
||||
updatePermissions.mutate({
|
||||
canCreateAgents: !canCreateAgents,
|
||||
canCreateSkills,
|
||||
canAssignTasks: !canCreateAgents ? true : canAssignTasks,
|
||||
})
|
||||
}
|
||||
disabled={updatePermissions.isPending}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-4 text-sm">
|
||||
<div className="space-y-1">
|
||||
<div>Can create/import skills</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Lets this agent install, import, create, and scan company skills without creating agents.
|
||||
</p>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
checked={canCreateSkills}
|
||||
onCheckedChange={() =>
|
||||
updatePermissions.mutate({
|
||||
canCreateAgents,
|
||||
canCreateSkills: !canCreateSkills,
|
||||
canAssignTasks,
|
||||
})
|
||||
}
|
||||
disabled={updatePermissions.isPending}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-4 text-sm">
|
||||
<div className="space-y-1">
|
||||
<div>Can assign tasks</div>
|
||||
<p className="text-xs text-muted-foreground">{taskAssignHint}</p>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
checked={canAssignTasks}
|
||||
onCheckedChange={() =>
|
||||
updatePermissions.mutate({
|
||||
canCreateAgents,
|
||||
canCreateSkills,
|
||||
canAssignTasks: !canAssignTasks,
|
||||
})
|
||||
}
|
||||
disabled={updatePermissions.isPending || taskAssignLocked}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ConfigurationSection>
|
||||
) : null}
|
||||
{visibleSections.has("keys") ? (
|
||||
<ConfigurationSection id="keys" title="API Keys" instant>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Long-lived keys for external processes acting as this agent. Runs receive short-lived tokens automatically.
|
||||
</p>
|
||||
<KeysTab agentId={agent.id} companyId={companyId} />
|
||||
</ConfigurationSection>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<AgentConfigForm
|
||||
@@ -2018,99 +2101,12 @@ function ConfigurationTab({
|
||||
hideIdentity
|
||||
configurationShell
|
||||
visibleConfigurationSections={visibleSections}
|
||||
configurationShellAfterSchedule={configurationShellAfterSchedule}
|
||||
sectionLayout="cards"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Saved adapter config affects the next run. Active runs keep the config they started with, and config changes may start a fresh adapter session.
|
||||
</p>
|
||||
|
||||
{visibleSections.has("access") ? <ConfigurationSection id="access" title="Access & Governance" instant>
|
||||
<TrustPresetSection
|
||||
permissions={agent.permissions}
|
||||
disabled={updatePermissions.isPending}
|
||||
companyId={companyId}
|
||||
projectCandidates={(boundaryProjects ?? []).map((project) => ({
|
||||
id: project.id,
|
||||
label: project.name,
|
||||
}))}
|
||||
issueCandidates={(boundaryIssues ?? []).map((issue) => ({
|
||||
id: issue.id,
|
||||
label: `${issue.identifier ?? issue.id.slice(0, 8)} · ${issue.title}`,
|
||||
}))}
|
||||
candidatesLoading={boundaryProjectsLoading || boundaryIssuesLoading}
|
||||
onChange={(nextPermissions) =>
|
||||
updatePermissions.mutate({
|
||||
canCreateAgents,
|
||||
canCreateSkills,
|
||||
canAssignTasks,
|
||||
...buildPermissionsForTrustPreset(nextPermissions, nextPermissions.trustPreset === "low_trust_review" ? "low_trust_review" : "standard"),
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<h3 className="text-sm font-medium mb-3">Permissions</h3>
|
||||
<div className="border border-border rounded-lg p-4 space-y-4">
|
||||
<div className="flex items-center justify-between gap-4 text-sm">
|
||||
<div className="space-y-1">
|
||||
<div>Can create new agents</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Lets this agent create or hire agents. This also grants task assignment authority.
|
||||
</p>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
checked={canCreateAgents}
|
||||
onCheckedChange={() =>
|
||||
updatePermissions.mutate({
|
||||
canCreateAgents: !canCreateAgents,
|
||||
canCreateSkills,
|
||||
canAssignTasks: !canCreateAgents ? true : canAssignTasks,
|
||||
})
|
||||
}
|
||||
disabled={updatePermissions.isPending}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-4 text-sm">
|
||||
<div className="space-y-1">
|
||||
<div>Can create/import skills</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Lets this agent install, import, create, and scan company skills without creating agents.
|
||||
</p>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
checked={canCreateSkills}
|
||||
onCheckedChange={() =>
|
||||
updatePermissions.mutate({
|
||||
canCreateAgents,
|
||||
canCreateSkills: !canCreateSkills,
|
||||
canAssignTasks,
|
||||
})
|
||||
}
|
||||
disabled={updatePermissions.isPending}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-4 text-sm">
|
||||
<div className="space-y-1">
|
||||
<div>Can assign tasks</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{taskAssignHint}
|
||||
</p>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
checked={canAssignTasks}
|
||||
onCheckedChange={() =>
|
||||
updatePermissions.mutate({
|
||||
canCreateAgents,
|
||||
canCreateSkills,
|
||||
canAssignTasks: !canAssignTasks,
|
||||
})
|
||||
}
|
||||
disabled={updatePermissions.isPending || taskAssignLocked}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ConfigurationSection> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user