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
60 changes: 60 additions & 0 deletions src/lib/styles/pages/_agent.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2089,6 +2089,66 @@ $panel-radius: 0.5rem;
}


.ari-row-secondary {
margin-top: 0.625rem;
padding-top: 0.625rem;
border-top: 1px dotted color-mix(in srgb, var(--color-primary) 40%, transparent);
}


.ari-compile-icon {
cursor: pointer;
font-size: 1rem;
color: var(--color-primary);
transition: filter 0.15s ease;

&:hover {
filter: brightness(1.15);
}
}


.ari-textarea {
width: 100%;
padding: 0.375rem 0.5rem;
font-size: 0.875rem;
line-height: 1.4;
color: rgb(55 65 81);
background-color: rgb(255 255 255);
border: 1px solid rgb(209 213 219);
border-radius: 0.375rem;
resize: none;
transition: border-color 0.15s ease, box-shadow 0.15s ease;

&:focus {
outline: none;
border-color: var(--color-primary);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 15%, transparent);
}

&:disabled {
background-color: rgb(243 244 246);
cursor: not-allowed;
}
}


@media (max-width: 1250px) {
.ari-row-secondary { flex-direction: column; }
}


.dark .ari-textarea {
color: rgb(229 231 235);
background-color: rgb(17 24 39);
border-color: rgb(75 85 99);

&:disabled {
background-color: rgb(31 41 55);
}
}


/* ========================================================================
* src/routes/page/agent/[agentId]/agent-components/rules/agent-rule.svelte
* ======================================================================== */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
<script>
import { slide } from 'svelte/transition';
import Markdown from '$lib/common/markdown/Markdown.svelte';
import BotsharpTooltip from '$lib/common/tooltip/BotsharpTooltip.svelte';
import Select from '$lib/common/dropdowns/Select.svelte';

const textLimit = 1024;

/**
* @type {{
* rule: import('$agentTypes').AgentRule,
Expand All @@ -13,7 +16,8 @@
* ontoggle?: (data: { ruleIdx: number, field: string, checked: boolean }) => void,
* onchange?: (data: { ruleIdx: number, field: string, value: string }) => void,
* ondelete?: (data: { ruleIdx: number, field: string }) => void,
* oncollapse?: (data: { ruleIdx: number, collapsed: boolean }) => void
* oncollapse?: (data: { ruleIdx: number, collapsed: boolean }) => void,
* oncompile?: (data: { ruleIdx: number, rule: import('$agentTypes').AgentRule }) => void
* }}
*/
let {
Expand All @@ -25,9 +29,15 @@
ontoggle,
onchange,
ondelete,
oncollapse
oncollapse,
oncompile
} = $props();

// Code script can only be generated by admins, once a trigger is picked and criteria text exists.
let canCompile = $derived(
!!rule.trigger_name && !!rule.config?.criteria?.trim()
);
Comment on lines +36 to +39

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

2. Compile allowed when disabled 🐞 Bug ≡ Correctness

canCompile ignores rule.disabled, so a disabled rule can still show and execute the compile action
even though the Criteria textarea is disabled, allowing code generation/overwrite for a rule the UI
marks as disabled.
Agent Prompt
### Issue description
The Criteria textarea is disabled when `rule.disabled` is true, but the compile icon’s visibility/enabled state is driven only by `canCompile` which does not include `rule.disabled`. This allows generating code scripts for disabled rules.

### Issue Context
- `canCompile` currently checks `trigger_name` and `criteria.trim()` only.
- The compile icon is rendered when `canCompile` is true.
- The Criteria textarea is disabled via `disabled={rule.disabled}`.

### Fix Focus Areas
- src/routes/page/agent/[agentId]/agent-components/rules/agent-rule-item.svelte[36-40]
- src/routes/page/agent/[agentId]/agent-components/rules/agent-rule-item.svelte[186-213]

Suggested fix:
- Update `canCompile` to include `!rule.disabled`.
- Additionally (defense-in-depth), in the parent compile handler (agent-rule.svelte) refuse to open the confirm modal / call the API when `rule.disabled` is true.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


/**
* @param {any} e
* @param {string} field
Expand Down Expand Up @@ -69,6 +79,24 @@
collapsed: !collapsed
});
}

/**
* @param {any} e
*/
function changeCriteria(e) {
onchange?.({
ruleIdx: ruleIndex,
field: 'criteria',
value: e?.target?.value || ''
});
}

function compile() {
oncompile?.({
ruleIdx: ruleIndex,
rule: rule
});
}
</script>

<div class="ari-wrapper">
Expand Down Expand Up @@ -148,6 +176,45 @@
</div>
</div>
</div>

{#if !collapsed}
<div class="ari-row ari-row-secondary" transition:slide={{ duration: 200 }}>
<div class="ari-label ari-label-strong">
<div class="ari-cell">
{'Criteria'}
</div>
{#if canCompile}
<div class="ari-cell">
<i
class="bx bx-code-alt ari-compile-icon"
id={`rule-compile-${ruleIndex}`}
role="link"
tabindex="0"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="Generate code script"
onkeydown={() => {}}
onclick={() => compile()}
></i>
Comment on lines +195 to +198

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. Compile icon not keyboardable 🐞 Bug ≡ Correctness

In agent-rule-item.svelte the new compile icon is focusable (tabindex=0) but its onkeydown handler
is a no-op, so keyboard users cannot trigger compilation via Enter/Space even though the UI presents
it as a link.
Agent Prompt
### Issue description
The new compile affordance is rendered as an `<i>` with `role="link"` + `tabindex="0"`, but `onkeydown={() => {}}` means there is no keyboard activation path.

### Issue Context
This was introduced with the new Criteria/compile UI. The click handler calls `compile()`, but keyboard events never do.

### Fix Focus Areas
- src/routes/page/agent/[agentId]/agent-components/rules/agent-rule-item.svelte[186-199]

Suggested implementation options:
- Prefer a semantic `<button type="button">` wrapping the icon (native keyboard behavior), or
- Implement `onkeydown` to call `compile()` when `e.key === 'Enter'` (and optionally `' '`), with `e.preventDefault()` for Space.
- Add `aria-label="Generate code script"` (tooltip `title` is not a reliable accessible name).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

</div>
{/if}
</div>
<div class="ari-value">
<div class="ari-input-wrap ari-cell">
<textarea
class="ari-textarea"
rows="5"
maxlength={textLimit}
placeholder="Describe when this rule should trigger..."
disabled={rule.disabled}
value={rule.config?.criteria || ''}
oninput={e => changeCriteria(e)}
></textarea>
</div>
<div class="ari-delete ari-cell"></div>
</div>
</div>
{/if}
</div>


Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
<script>
import { getAgentRuleOptionsById } from '$lib/services/agent-service';
import { getAgentRuleOptionsById, generateAgentCodeScript } from '$lib/services/agent-service';
import LoadingToComplete from '$lib/common/spinners/LoadingToComplete.svelte';
import ConfirmModal from '$lib/common/modals/ConfirmModal.svelte';
import { scrollToBottom } from '$lib/helpers/utils/common';
import { AgentCodeScriptType } from '$lib/helpers/enums';
import AgentRuleItem from './agent-rule-item.svelte';

const limit = 100;
const duration = 2000;

/**
* @type {{
Expand All @@ -30,6 +33,11 @@
/** @type {string} */
let errorText = $state('');

/** @type {boolean} */
let confirmOpen = $state(false);
/** @type {import('$agentTypes').AgentRule | null} */
let pendingRule = $state(null);

export const fetchRules = () => {
const candidates = innerRules?.filter(x => !!x.trigger_name)?.map(x => {
return {
Expand Down Expand Up @@ -157,6 +165,8 @@
if (field === 'rule') {
found.trigger_name = value;
innerRefresh(innerRules);
} else if (field === 'criteria') {
found.config = { ...(found.config || {}), criteria: value };
}

handleAgentChange();
Expand Down Expand Up @@ -220,6 +230,65 @@



/**
* @param {any} data
*/
function compileCodeScript(data) {
pendingRule = data.rule;
confirmOpen = true;
}

function closeConfirm() {
confirmOpen = false;
pendingRule = null;
}

function onConfirmCompile() {
const rule = pendingRule;
closeConfirm();
if (rule) {
generateCodeScript(rule);
}
}

/**
* @param {import('$agentTypes').AgentRule} rule
*/
function generateCodeScript(rule) {
isLoading = true;
generateAgentCodeScript(agent.id, {
options: {
Comment on lines +257 to +260

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

3. No in-flight compile guard 🐞 Bug ☼ Reliability

generateCodeScript() sets isLoading but never checks it, so users can trigger multiple confirmed
generateAgentCodeScript requests concurrently, causing duplicate work and racing success/error
banners/timeouts.
Agent Prompt
### Issue description
The code-generation flow does not enforce mutual exclusion:
- `generateCodeScript()` sets `isLoading = true`, but does not early-return if already loading.
- The UI path to invoke compile does not consult `isLoading`.
This allows concurrent requests and out-of-order UI state updates.

### Issue Context
`generateAgentCodeScript(...).then(...).catch(...)` toggles `isLoading/isComplete/isError` and schedules timeouts. With concurrent invocations, later responses can overwrite earlier UI status, and multiple writes may race on the backend.

### Fix Focus Areas
- src/routes/page/agent/[agentId]/agent-components/rules/agent-rule.svelte[257-290]
- src/routes/page/agent/[agentId]/agent-components/rules/agent-rule-item.svelte[186-199]

Suggested fix:
- Add a guard at the top of `generateCodeScript`: `if (isLoading) return;`
- Disable/hide the compile icon (or the confirm button) while `isLoading` is true.
- Optionally store/clear timeout IDs to avoid overlapping timers from successive runs.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

save_to_db: true,
script_name: `${rule.trigger_name}_criteria.py`,
script_type: AgentCodeScriptType.Src,
data: {
"user_request": rule.config?.criteria
}
}
}).then(res => {
isLoading = false;
if (res?.success) {
isComplete = true;
successText = 'Code script has been generated!';
setTimeout(() => {
isComplete = false;
successText = '';
}, duration);
} else {
throw new Error('error when generating code script.');
}
}).catch(() => {
isLoading = false;
isComplete = false;
isError = true;
errorText = 'Failed to generate code script.';
setTimeout(() => {
isError = false;
errorText = '';
}, duration);
});
}

/** @param {import('$agentTypes').AgentRule[]} list */
function innerRefresh(list) {
innerRules = list?.map(x => {
Expand Down Expand Up @@ -248,6 +317,20 @@
{errorText}
/>

<ConfirmModal
isOpen={confirmOpen}
icon="warning"
title="Are you sure?"
text={pendingRule
? `Are you sure you want to generate code script "${pendingRule.trigger_name}_criteria.py"? This will overwrite the existing code script if any.`
: ''}
confirmBtnText="Yes"
cancelBtnText="No"
confirm={onConfirmCompile}
cancel={closeConfirm}
toggleModal={closeConfirm}
/>

<div class="ar-card">
<div class="ar-card-body">
<div class="ar-header">
Expand All @@ -267,6 +350,7 @@
ondelete={data => deleteRule(data, uid)}
onchange={data => changeRule(data, uid)}
oncollapse={data => toggleCollapse(data, uid)}
oncompile={data => compileCodeScript(data)}
/>
{/each}

Expand Down
Loading