Skip to content
Closed
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
2 changes: 1 addition & 1 deletion core/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ export interface ChatHistoryItem {
toolCallStates?: ToolCallState[];
isGatheringContext?: boolean;
reasoning?: Reasoning;
appliedRules?: RuleWithSource[];
appliedRules?: Omit<RuleWithSource, "rule">[];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can keep the AppliedRule type and just say type AppliedRule = Omit<RuleWithSource, "rule"> to prevent having Omits all over

conversationSummary?: string;
}

Expand Down
13 changes: 8 additions & 5 deletions core/llm/rules/getSystemMessageWithRules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ export const getApplicableRules = (
return applicableRules;
};

export function getRuleId(rule: RuleWithSource): string {
export function getRuleId(rule: Omit<RuleWithSource, "rule">): string {
return rule.slug ?? rule.ruleFile ?? rule.name ?? rule.source;
}

Expand All @@ -342,17 +342,17 @@ export const getSystemMessageWithRules = ({
rulePolicies?: RulePolicies;
}): {
systemMessage: string;
appliedRules: RuleWithSource[];
appliedRules: Omit<RuleWithSource, "rule">[];
} => {
const appliedRules = getApplicableRules(
const rules = getApplicableRules(
userMessage,
availableRules,
contextItems,
rulePolicies,
);
let systemMessage = baseSystemMessage ?? "";

for (const rule of appliedRules) {
for (const rule of rules) {
if (systemMessage) {
systemMessage += "\n\n";
}
Expand All @@ -361,6 +361,9 @@ export const getSystemMessageWithRules = ({

return {
systemMessage,
appliedRules,
appliedRules: rules.map(fullRule => {
const { rule, ...rest } = fullRule // destruct rule key
return rest;
}),
Comment on lines +364 to +367
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit funky as well.

Ideally this method would not take availableRules: Omit<RuleWithSource, "rule">[] and read rules from the file.
This would also fix the issue of rules sometimes being outdated <- maybe a FLUP.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function could still just return RuleWithSource, shouldn't effect storage if omitted at the point of redux

note that config should reload if a rule file is updated, so should be up to date in most cases

};
};
2 changes: 1 addition & 1 deletion gui/src/components/mainInput/ContinueInputBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ interface ContinueInputBoxProps {
) => void;
editorState?: JSONContent;
contextItems?: ContextItemWithId[];
appliedRules?: RuleWithSource[];
appliedRules?: Omit<RuleWithSource, "rule">[];
hidden?: boolean;
inputId: string; // used to keep track of things per input in redux
}
Expand Down
56 changes: 28 additions & 28 deletions gui/src/components/mainInput/Lump/sections/RulesSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,33 @@ interface RuleCardProps {
rule: RuleWithSource;
}

export const openRules = async (rule: Omit<RuleWithSource, "rule">) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could take RuleWithSource | AppliedRule

const ideMessenger = useContext(IdeMessengerContext);
Copy link
Author

@etherandrius etherandrius Aug 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ideMessenger this was define outside the function before. I assume the useContext(IdeMessengerContext) call is basically free and we can call it inside the function.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will need to pass ideMessenger for this one since can't use hooks outside of top level of component

if (rule.slug) {
void ideMessenger.request("controlPlane/openUrl", {
path: `${rule.slug}/new-version`,
orgSlug: undefined,
});
} else if (rule.ruleFile) {
ideMessenger.post("openFile", {
path: rule.ruleFile,
});
} else if (
rule.source === "default-chat" ||
rule.source === "default-plan" ||
rule.source === "default-agent"
) {
ideMessenger.post("openUrl", DEFAULT_SYSTEM_MESSAGES_URL);
} else {
ideMessenger.post("config/openProfile", {
profileId: undefined,
element: { sourceFile: (rule as any).sourceFile },
});
}
};

const RuleCard: React.FC<RuleCardProps> = ({ rule }) => {
const dispatch = useAppDispatch();
const ideMessenger = useContext(IdeMessengerContext);
const mode = useAppSelector((store) => store.session.mode);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused.

const policy = useAppSelector((state) =>
rule.name
? state.ui.ruleSettings[rule.name] || DEFAULT_RULE_SETTING
Expand All @@ -41,29 +64,6 @@ const RuleCard: React.FC<RuleCardProps> = ({ rule }) => {

const isDisabled = policy === "off";

const handleOpen = async () => {
if (rule.slug) {
void ideMessenger.request("controlPlane/openUrl", {
path: `${rule.slug}/new-version`,
orgSlug: undefined,
});
} else if (rule.ruleFile) {
ideMessenger.post("openFile", {
path: rule.ruleFile,
});
} else if (
rule.source === "default-chat" ||
rule.source === "default-plan" ||
rule.source === "default-agent"
) {
ideMessenger.post("openUrl", DEFAULT_SYSTEM_MESSAGES_URL);
} else {
ideMessenger.post("config/openProfile", {
profileId: undefined,
element: { sourceFile: (rule as any).sourceFile },
});
}
};

const handleTogglePolicy = () => {
if (rule.name) {
Expand Down Expand Up @@ -139,12 +139,12 @@ const RuleCard: React.FC<RuleCardProps> = ({ rule }) => {
<ArrowsPointingOutIcon className="h-3 w-3 text-gray-400" />
</HeaderButtonWithToolTip>{" "}
{rule.source === "default-chat" ||
rule.source === "default-agent" ? (
<HeaderButtonWithToolTip onClick={handleOpen} text="View">
rule.source === "default-agent" ? (
<HeaderButtonWithToolTip onClick={() => openRules(rule)} text="View">
<EyeIcon className="h-3 w-3 text-gray-400" />
</HeaderButtonWithToolTip>
) : (
<HeaderButtonWithToolTip onClick={handleOpen} text="Edit">
<HeaderButtonWithToolTip onClick={() => openRules(rule)} text="Edit">
<PencilIcon className="h-3 w-3 text-gray-400" />
</HeaderButtonWithToolTip>
)}
Expand Down
48 changes: 10 additions & 38 deletions gui/src/components/mainInput/belowMainInput/RulesPeek.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import { DocumentTextIcon, GlobeAltIcon } from "@heroicons/react/24/outline";
import { RuleWithSource } from "core";
import { getLastNPathParts } from "core/util/uri";
import { ComponentType, useMemo, useState } from "react";
import { ComponentType, useContext, useMemo } from "react";
import ToggleDiv from "../../ToggleDiv";
import { IdeMessengerContext } from "../../../context/IdeMessenger";
import { DEFAULT_SYSTEM_MESSAGES_URL } from "core/llm/defaultSystemMessages";
import { RuleWithSource } from "core";
import { openRules } from "../Lump/sections/RulesSection";

interface RulesPeekProps {
appliedRules?: RuleWithSource[];
appliedRules?: Omit<RuleWithSource, "rule">[];
icon?: ComponentType<React.SVGProps<SVGSVGElement>>;
}

interface RulesPeekItemProps {
rule: RuleWithSource;
rule: Omit<RuleWithSource, "rule">;
}

// Convert technical source to user-friendly text
const getSourceLabel = (rule: RuleWithSource): string => {
const getSourceLabel = (rule: Omit<RuleWithSource, "rule">): string => {
switch (rule.source) {
case "default-chat":
return "Default Chat";
Expand Down Expand Up @@ -45,29 +48,11 @@ const getSourceLabel = (rule: RuleWithSource): string => {

export function RulesPeekItem({ rule }: RulesPeekItemProps) {
const isGlobal = rule.alwaysApply ?? !rule.globs;
const [expanded, setExpanded] = useState(false);

// Define maximum length for rule text display
const maxRuleLength = 100;
const isRuleLong = rule.rule.length > maxRuleLength;

// Get the displayed rule text based on expanded state
const displayedRule =
isRuleLong && !expanded
? `${rule.rule.slice(0, maxRuleLength)}...`
: rule.rule;

const toggleExpand = () => {
if (isRuleLong) {
setExpanded(!expanded);
}
};

return (
<div
className={`group mr-2 flex flex-col overflow-hidden rounded px-1.5 py-1 text-xs hover:bg-white/10 ${isRuleLong ? "cursor-pointer hover:text-gray-200" : ""}`}
className={`group mr-2 flex flex-col overflow-hidden rounded px-1.5 py-1 text-xs hover:bg-white/10`}
data-testid="rules-peek-item"
onClick={toggleExpand}
onClick={() => openRules(rule)}
>
<div className="flex w-full items-center">
{isGlobal ? (
Expand All @@ -88,19 +73,6 @@ export function RulesPeekItem({ rule }: RulesPeekItemProps) {
</div>
</div>
</div>
<div
className={`mt-1 whitespace-pre-line pl-6 pr-2 text-xs italic text-gray-300`}
title={
isRuleLong ? (expanded ? "Click to collapse" : "Click to expand") : ""
}
>
{displayedRule}
{isRuleLong && (
<span className="text-description-muted ml-1 opacity-0 transition-opacity group-hover:opacity-100">
{expanded ? "(collapse)" : "(expand)"}
</span>
)}
</div>
<div className="mt-1 pl-6 pr-2 text-xs text-gray-500">
Source: {getSourceLabel(rule)}
</div>
Expand Down
2 changes: 1 addition & 1 deletion gui/src/redux/slices/sessionSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,7 @@ export const sessionSlice = createSlice({
payload,
}: PayloadAction<{
index: number;
appliedRules: RuleWithSource[];
appliedRules: Omit<RuleWithSource, "rule">[];
}>,
) => {
if (state.history[payload.index]) {
Expand Down
2 changes: 1 addition & 1 deletion gui/src/redux/thunks/streamNormalInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ export const streamNormalInput = createAsyncThunk<
...(appliedRules.length > 0 && {
rules: appliedRules.map((rule) => ({
id: getRuleId(rule),
rule: rule.rule,
rule: "", // TODO: remove rule key from type. The contents should be present in the prompt
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a bit funky.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any specific reason to remove rules from dev data? Have you had issues with verbose dev data?

slug: rule.slug,
})),
}),
Expand Down
2 changes: 1 addition & 1 deletion gui/src/redux/util/constructMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function constructMessages(
useSystemToolsFramework?: SystemMessageToolsFramework,
): {
messages: ChatMessage[];
appliedRules: RuleWithSource[];
appliedRules: Omit<RuleWithSource, "rule">[];
appliedRuleIndex: number;
} {
// Find the most recent conversation summary and filter history accordingly
Expand Down
Loading