feat: Add Batch Input Mode to run an agent across multiple inputs at once#615
feat: Add Batch Input Mode to run an agent across multiple inputs at once#615ida-jemi wants to merge 1 commit into
Conversation
Closes AditthyaSS#612 - Add batchRunner.js: parses pasted/CSV/TXT input, runs items with limited concurrency via runAgent() - Add exportBatch.js: export batch results as CSV or Markdown - Add BatchModeRunner.jsx: batch field picker, paste/upload UI, progress, per-item results - Wire always-visible Batch Mode toggle into AgentRunner.jsx for agents with a text/textarea/code field
|
@ida-jemi is attempting to deploy a commit to the aditthyass' projects Team on Vercel. A member of the Team first needs to authorize it. |
|
Note
|
| Layer / File(s) | Summary |
|---|---|
Core batch runner utilities src/lib/batchRunner.js |
Exports parsePastedLines, parseCSV, looksLikeHeader, buildBatchUserMessage, and runBatch. runBatch processes items with bounded concurrency (default 3), reports per-item running/done/failed status via callback, and supports early termination via AbortSignal. |
Batch result export utilities src/lib/exportBatch.js |
Exports exportBatchAsCSV and exportBatchAsMarkdown. Each derives a safe filename from the agent name, builds the output text (with CSV escaping or Markdown status sections), and triggers a browser download via Blob + URL.createObjectURL. |
BatchModeRunner component src/components/BatchModeRunner.jsx |
New component that lets users select which input field varies per batch item, edit shared fixed inputs, supply items via paste or .csv/.txt file upload, select a CSV column, and run/stop the batch. Renders per-item status icons and output/error after completion, with CSV and Markdown export buttons. |
AgentRunner batch mode integration src/components/AgentRunner.jsx |
Adds batchMode state (reset on agent change), computes supportsBatchMode from agent.inputs types, renders a "Batch Mode" toggle when supported, conditionally replaces the single-run form with BatchModeRunner, and corrects the JSX closing structure around ScheduleAgentModal. |
Sequence Diagram(s)
sequenceDiagram
participant User
participant AgentRunner
participant BatchModeRunner
participant runBatch
participant runAgent
User->>AgentRunner: clicks "Batch Mode" toggle
AgentRunner->>BatchModeRunner: renders with agent/provider/apiKey/model/systemPrompt
User->>BatchModeRunner: pastes items or uploads CSV/TXT
User->>BatchModeRunner: clicks "Run Batch"
BatchModeRunner->>runBatch: items, fixedInputs, batchFieldId, concurrency=3, signal
loop per item (bounded concurrency)
runBatch->>BatchModeRunner: onItemUpdate(i, {status: running})
runBatch->>runAgent: prompt + provider/model/apiKey
runAgent-->>runBatch: content or error
runBatch->>BatchModeRunner: onItemUpdate(i, {status: done|failed})
end
BatchModeRunner->>User: shows per-item results
User->>BatchModeRunner: clicks "Export CSV" or "Export Markdown"
BatchModeRunner->>BatchModeRunner: exportBatchAsCSV / exportBatchAsMarkdown triggers download
Estimated code review effort
🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related issues
- [Feature]: Add Batch Input Mode to run an agent across multiple inputs at once #612: This PR directly implements the Batch Mode feature described in that issue, including the toggle in AgentRunner, multi-item input via paste/file upload, per-item status display, and CSV/Markdown export.
Suggested labels
level:intermediate, type:feature, gssoc:approved
Suggested reviewers
- AditthyaSS
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
| Check name | Status | Explanation |
|---|---|---|
| Title check | ✅ Passed | The title accurately summarizes the main change: adding a Batch Input Mode feature to run agents across multiple inputs simultaneously, which is the primary objective of the PR. |
| Docstring Coverage | ✅ Passed | No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. |
| Linked Issues check | ✅ Passed | Check skipped because no linked issues were found for this pull request. |
| Out of Scope Changes check | ✅ Passed | Check skipped because no linked issues were found for this pull request. |
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
✏️ Tip: You can configure your own custom pre-merge checks in the settings.
✨ Finishing Touches
🧪 Generate unit tests (beta)
- Create PR with unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands.
|
Hey @ida-jemi! 👋
|
|
hey @ida-jemi! 👋 |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/components/AgentRunner.jsx (1)
751-774: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winSchedule button is rendered twice.
There are two identical “Schedule” buttons in the action row, which duplicates UI and behavior.
Suggested fix
- {/* Schedule button */} - <button - onClick={() => setScheduleModalOpen(true)} - title="Schedule this agent to run automatically" - className="flex items-center gap-1.5 px-3 py-2 rounded-lg text-sm font-medium transition-colors - dark:text-text-secondary dark:hover:text-text-primary dark:hover:bg-surface-hover - text-gray-500 hover:text-gray-900 hover:bg-gray-100" - > - <CalendarClock size={14} /> - Schedule - </button>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/AgentRunner.jsx` around lines 751 - 774, The Schedule button is rendered twice consecutively in the action row of AgentRunner.jsx with identical onClick handlers calling setScheduleModalOpen(true), titles, styling, and CalendarClock icons. Remove the duplicate button block and keep only one Schedule button to eliminate the redundant UI element and prevent duplicate functionality.
🧹 Nitpick comments (1)
src/lib/batchRunner.js (1)
83-86: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAlign
looksLikeHeader()behavior with its documented heuristic.The comment says numeric-looking cells should disqualify header detection, but current logic only checks non-empty and length. Keep docs and behavior in sync to avoid false positives.
Suggested fix
export function looksLikeHeader(firstRow) { if (!firstRow) return false - return firstRow.every((cell) => cell.trim().length > 0 && cell.trim().length < 80) + const numericLike = /^-?\d+(\.\d+)?$/ + return firstRow.every((cell) => { + const v = cell.trim() + return v.length > 0 && v.length < 80 && !numericLike.test(v) + }) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/batchRunner.js` around lines 83 - 86, The looksLikeHeader function currently does not implement its documented heuristic that numeric-looking cells should disqualify header detection. Update the condition inside the every() method in looksLikeHeader to add a check that returns false when a trimmed cell contains only digits or appears to be numeric. This ensures the function behavior matches its documentation and prevents false positives where numeric rows are mistakenly identified as headers.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/AgentRunner.jsx`:
- Line 88: The keyboard shortcut handler for Ctrl+Enter does not check the
current batchMode state before executing handleRun(), causing single-run API
calls to be triggered even when Batch Mode is active. Modify the
useKeyboardShortcuts implementation (around lines 95-103) to check if batchMode
is true before routing Ctrl+Enter to handleRun(). When batchMode is active,
either skip the shortcut handler or route it to a batch-specific execution
function instead of the standard handleRun() function to ensure keyboard
shortcuts respect the current UI mode.
In `@src/components/BatchModeRunner.jsx`:
- Around line 66-77: The CSV parsing logic in the BatchModeRunner component uses
the looksLikeHeader function to auto-detect whether the first row contains
headers, but this auto-detection can silently drop the first data row if it
incorrectly matches the heuristic. Replace the auto-detection logic with an
explicit user-controlled setting or flag that determines whether the first row
should be treated as headers, rather than relying on looksLikeHeader() to make
this decision. This ensures data integrity and gives users explicit control over
header handling instead of risking silent data loss from auto-detection
heuristics.
- Around line 42-47: The `canRun()` function only validates the `fixedInputs`
state, but shared fields with `defaultValue` displayed in the UI are not
automatically stored in `fixedInputs` when users don't interact with them,
causing required fields with defaults to be treated as empty and omitted from
the batch payload. Initialize the `fixedInputs` state to include default values
from shared fields so that `canRun()` properly validates all required fields and
ensures defaults are included when building the batch payload.
- Around line 184-233: The fixed field renderer in the conditional block
starting with the select/multiselect check only handles 'select' and
'multiselect' types, causing all other input types including 'textarea' and
'code' to fall back to a single-line text input. Add additional else-if
conditions after the multiselect block to specifically handle input.type ===
'textarea' by rendering a textarea element instead of an input element, and
input.type === 'code' by rendering an appropriate code editor component, each
reading from and updating fixedInputs using the same updateFixedInput callback
pattern as the existing handlers.
In `@src/lib/batchRunner.js`:
- Around line 158-162: In the catch block of the try-catch statement, when
signal?.aborted is true, the function returns immediately without updating the
item status, leaving it stuck as "running". Instead of returning directly when
signal?.aborted is true, call onItemUpdate with a terminal status (such as
'aborted' or 'cancelled') for the item before returning. This ensures the item
receives a proper terminal status that matches the success case pattern where
onItemUpdate is called with status 'done'.
In `@src/lib/exportBatch.js`:
- Around line 25-31: The csvEscape function is vulnerable to formula injection
when handling untrusted input that starts with formula trigger characters like
equals sign, plus sign, at sign, or hyphen. Before applying the existing CSV
escape logic in the csvEscape function, add a check to detect if the string
value starts with any of these dangerous characters and prefix it with a single
quote if detected. This will prevent formulas from being executed when the CSV
is opened in Excel or Google Sheets while maintaining proper CSV formatting.
---
Outside diff comments:
In `@src/components/AgentRunner.jsx`:
- Around line 751-774: The Schedule button is rendered twice consecutively in
the action row of AgentRunner.jsx with identical onClick handlers calling
setScheduleModalOpen(true), titles, styling, and CalendarClock icons. Remove the
duplicate button block and keep only one Schedule button to eliminate the
redundant UI element and prevent duplicate functionality.
---
Nitpick comments:
In `@src/lib/batchRunner.js`:
- Around line 83-86: The looksLikeHeader function currently does not implement
its documented heuristic that numeric-looking cells should disqualify header
detection. Update the condition inside the every() method in looksLikeHeader to
add a check that returns false when a trimmed cell contains only digits or
appears to be numeric. This ensures the function behavior matches its
documentation and prevents false positives where numeric rows are mistakenly
identified as headers.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 9d05c20e-5c83-41c3-9f66-0373e96ae385
📒 Files selected for processing (4)
src/components/AgentRunner.jsxsrc/components/BatchModeRunner.jsxsrc/lib/batchRunner.jssrc/lib/exportBatch.js
| const [modelRecommendation, setModelRecommendation] = useState(null); | ||
| const [analyserLoading, setAnalyserLoading] = useState(false); | ||
| const [scheduleModalOpen, setScheduleModalOpen] = useState(false); | ||
| const [batchMode, setBatchMode] = useState(false); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Ctrl+Enter still executes single-run logic while Batch Mode is active.
useKeyboardShortcuts always routes Control+Enter to handleRun(). In batch view, this can trigger unintended single-run API calls (invisible to the batch UI path).
Suggested fix
useKeyboardShortcuts({
'Control+Enter': () => {
- if (canRun() && !loading) handleRun();
+ if (batchMode) return;
+ if (canRun() && !loading) handleRun();
},
'Escape': () => {
handleClear();
setPlaygroundOpen(false);
},
});Also applies to: 95-103, 411-422
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/AgentRunner.jsx` at line 88, The keyboard shortcut handler for
Ctrl+Enter does not check the current batchMode state before executing
handleRun(), causing single-run API calls to be triggered even when Batch Mode
is active. Modify the useKeyboardShortcuts implementation (around lines 95-103)
to check if batchMode is true before routing Ctrl+Enter to handleRun(). When
batchMode is active, either skip the shortcut handler or route it to a
batch-specific execution function instead of the standard handleRun() function
to ensure keyboard shortcuts respect the current UI mode.
| const [fixedInputs, setFixedInputs] = useState({}) | ||
| const [rawPaste, setRawPaste] = useState('') | ||
| const [items, setItems] = useState([]) // string[] | ||
| const [csvColumns, setCsvColumns] = useState(null) // {headers, rows} when a CSV is uploaded | ||
| const [csvColumnIndex, setCsvColumnIndex] = useState(0) | ||
| const [results, setResults] = useState([]) // [{input, status, output, error}] |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Default values for fixed fields are not part of validation/run state.
canRun() (Line 100-108) checks only fixedInputs, but shared fields display defaultValue in the UI (Line 186/195). If the user doesn’t touch them, required fields can be treated as empty and defaults are omitted from the batch payload.
Suggested fix
+ const resolveFixedValue = (input) => {
+ const current = fixedInputs[input.id]
+ if (current !== undefined) return current
+ if (input.defaultValue !== undefined) return input.defaultValue
+ return input.type === 'multiselect' ? [] : ''
+ }
const canRun = () => {
if (!apiKey || items.length === 0 || !batchFieldId) return false
return agent.inputs
.filter((i) => i.required && i.id !== batchFieldId)
.every((i) => {
- const v = fixedInputs[i.id]
+ const v = resolveFixedValue(i)
if (Array.isArray(v)) return v.length > 0
return v && String(v).trim() !== ''
})
}
+ const mergedFixedInputs = Object.fromEntries(
+ agent.inputs
+ .filter((i) => i.id !== batchFieldId)
+ .map((i) => [i.id, resolveFixedValue(i)])
+ )
await runBatch({
items,
agent,
- fixedInputs,
+ fixedInputs: mergedFixedInputs,Also applies to: 100-108, 184-187, 225-226
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/BatchModeRunner.jsx` around lines 42 - 47, The `canRun()`
function only validates the `fixedInputs` state, but shared fields with
`defaultValue` displayed in the UI are not automatically stored in `fixedInputs`
when users don't interact with them, causing required fields with defaults to be
treated as empty and omitted from the batch payload. Initialize the
`fixedInputs` state to include default values from shared fields so that
`canRun()` properly validates all required fields and ensures defaults are
included when building the batch payload.
| if (file.name.toLowerCase().endsWith('.csv')) { | ||
| const parsed = parseCSV(text) | ||
| if (looksLikeHeader(parsed.headers) && parsed.rows.length > 0) { | ||
| setCsvColumns(parsed) | ||
| setCsvColumnIndex(0) | ||
| setItems(parsed.rows.map((r) => r[0]).filter(Boolean)) | ||
| } else { | ||
| // Treat as headerless — every row's first cell is an item | ||
| const allRows = [parsed.headers, ...parsed.rows].filter(Boolean) | ||
| setCsvColumns(null) | ||
| setItems(allRows.map((r) => r[0]).filter(Boolean)) | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
CSV header auto-detection can silently drop the first data row.
At Line 68, the UI infers headers with looksLikeHeader(...) and excludes the first row when it “looks” like a header. This contradicts the explicit “First row is a header” user-controlled behavior and risks data loss for free-text CSVs.
Suggested fix
+ const [firstRowIsHeader, setFirstRowIsHeader] = useState(false)
if (file.name.toLowerCase().endsWith('.csv')) {
const parsed = parseCSV(text)
- if (looksLikeHeader(parsed.headers) && parsed.rows.length > 0) {
- setCsvColumns(parsed)
- setCsvColumnIndex(0)
- setItems(parsed.rows.map((r) => r[0]).filter(Boolean))
- } else {
- const allRows = [parsed.headers, ...parsed.rows].filter(Boolean)
- setCsvColumns(null)
- setItems(allRows.map((r) => r[0]).filter(Boolean))
- }
+ setCsvColumns(parsed)
+ setCsvColumnIndex(0)
+ const sourceRows = firstRowIsHeader ? parsed.rows : [parsed.headers, ...parsed.rows]
+ setItems(sourceRows.map((r) => r?.[0]).filter(Boolean))
}+ <label className="flex items-center gap-2 text-[11px] dark:text-text-muted text-gray-400 mt-2">
+ <input
+ type="checkbox"
+ checked={firstRowIsHeader}
+ onChange={(e) => setFirstRowIsHeader(e.target.checked)}
+ disabled={running}
+ />
+ First row is a header
+ </label>Also applies to: 282-293
🧰 Tools
🪛 ast-grep (0.44.0)
[warning] 68-68: Avoid using the initial state variable in setState
Context: setCsvColumns(parsed)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.
(setstate-same-var)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/BatchModeRunner.jsx` around lines 66 - 77, The CSV parsing
logic in the BatchModeRunner component uses the looksLikeHeader function to
auto-detect whether the first row contains headers, but this auto-detection can
silently drop the first data row if it incorrectly matches the heuristic.
Replace the auto-detection logic with an explicit user-controlled setting or
flag that determines whether the first row should be treated as headers, rather
than relying on looksLikeHeader() to make this decision. This ensures data
integrity and gives users explicit control over header handling instead of
risking silent data loss from auto-detection heuristics.
| {input.type === 'select' ? ( | ||
| <CustomSelect | ||
| value={fixedInputs[input.id] || input.defaultValue || ''} | ||
| onChange={(val) => updateFixedInput(input.id, val)} | ||
| options={input.options || []} | ||
| className="w-full sm:w-64" | ||
| triggerClassName="h-9" | ||
| /> | ||
| ) : input.type === 'multiselect' ? ( | ||
| <div className="flex flex-wrap gap-2"> | ||
| {input.options?.map((opt) => { | ||
| const selected = (fixedInputs[input.id] || input.defaultValue || []).includes(opt) | ||
| return ( | ||
| <button | ||
| key={opt} | ||
| type="button" | ||
| onClick={() => { | ||
| const current = fixedInputs[input.id] || input.defaultValue || [] | ||
| updateFixedInput( | ||
| input.id, | ||
| current.includes(opt) | ||
| ? current.filter((o) => o !== opt) | ||
| : [...current, opt] | ||
| ) | ||
| }} | ||
| className={`px-3 py-1.5 rounded-md text-xs font-medium transition-all border | ||
| ${ | ||
| selected | ||
| ? 'bg-accent/15 text-accent border-accent/30' | ||
| : 'dark:bg-surface-input dark:text-text-secondary dark:border-border bg-white text-gray-500 border-gray-200' | ||
| }`} | ||
| > | ||
| {selected && '✓ '} | ||
| {opt} | ||
| </button> | ||
| ) | ||
| })} | ||
| </div> | ||
| ) : ( | ||
| <input | ||
| type="text" | ||
| value={fixedInputs[input.id] || ''} | ||
| onChange={(e) => updateFixedInput(input.id, e.target.value)} | ||
| placeholder={input.placeholder} | ||
| className="w-full h-9 px-3 rounded-md text-sm transition-colors | ||
| dark:bg-surface-input dark:border-border dark:text-text-primary | ||
| bg-white border border-gray-200 text-gray-900 | ||
| focus:ring-1 focus:ring-accent focus:border-accent outline-none" | ||
| /> | ||
| )} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Shared textarea/code fields are rendered as single-line text inputs.
The fixed-field renderer only special-cases select and multiselect; everything else falls back to <input type="text">. For textarea/code fields, this removes multiline/code editing and breaks expected agent configuration.
Suggested fix
- ) : (
+ ) : input.type === 'textarea' || input.type === 'code' ? (
+ <textarea
+ value={fixedInputs[input.id] || input.defaultValue || ''}
+ onChange={(e) => updateFixedInput(input.id, e.target.value)}
+ placeholder={input.placeholder}
+ rows={input.type === 'code' ? 8 : 4}
+ spellCheck={input.type !== 'code'}
+ className="w-full px-3 py-2 rounded-md text-sm transition-colors resize-y
+ dark:bg-surface-input dark:border-border dark:text-text-primary
+ bg-white border border-gray-200 text-gray-900
+ focus:ring-1 focus:ring-accent focus:border-accent outline-none"
+ />
+ ) : (
<input
type="text"
- value={fixedInputs[input.id] || ''}
+ value={fixedInputs[input.id] || input.defaultValue || ''}
onChange={(e) => updateFixedInput(input.id, e.target.value)}
placeholder={input.placeholder}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {input.type === 'select' ? ( | |
| <CustomSelect | |
| value={fixedInputs[input.id] || input.defaultValue || ''} | |
| onChange={(val) => updateFixedInput(input.id, val)} | |
| options={input.options || []} | |
| className="w-full sm:w-64" | |
| triggerClassName="h-9" | |
| /> | |
| ) : input.type === 'multiselect' ? ( | |
| <div className="flex flex-wrap gap-2"> | |
| {input.options?.map((opt) => { | |
| const selected = (fixedInputs[input.id] || input.defaultValue || []).includes(opt) | |
| return ( | |
| <button | |
| key={opt} | |
| type="button" | |
| onClick={() => { | |
| const current = fixedInputs[input.id] || input.defaultValue || [] | |
| updateFixedInput( | |
| input.id, | |
| current.includes(opt) | |
| ? current.filter((o) => o !== opt) | |
| : [...current, opt] | |
| ) | |
| }} | |
| className={`px-3 py-1.5 rounded-md text-xs font-medium transition-all border | |
| ${ | |
| selected | |
| ? 'bg-accent/15 text-accent border-accent/30' | |
| : 'dark:bg-surface-input dark:text-text-secondary dark:border-border bg-white text-gray-500 border-gray-200' | |
| }`} | |
| > | |
| {selected && '✓ '} | |
| {opt} | |
| </button> | |
| ) | |
| })} | |
| </div> | |
| ) : ( | |
| <input | |
| type="text" | |
| value={fixedInputs[input.id] || ''} | |
| onChange={(e) => updateFixedInput(input.id, e.target.value)} | |
| placeholder={input.placeholder} | |
| className="w-full h-9 px-3 rounded-md text-sm transition-colors | |
| dark:bg-surface-input dark:border-border dark:text-text-primary | |
| bg-white border border-gray-200 text-gray-900 | |
| focus:ring-1 focus:ring-accent focus:border-accent outline-none" | |
| /> | |
| )} | |
| {input.type === 'select' ? ( | |
| <CustomSelect | |
| value={fixedInputs[input.id] || input.defaultValue || ''} | |
| onChange={(val) => updateFixedInput(input.id, val)} | |
| options={input.options || []} | |
| className="w-full sm:w-64" | |
| triggerClassName="h-9" | |
| /> | |
| ) : input.type === 'multiselect' ? ( | |
| <div className="flex flex-wrap gap-2"> | |
| {input.options?.map((opt) => { | |
| const selected = (fixedInputs[input.id] || input.defaultValue || []).includes(opt) | |
| return ( | |
| <button | |
| key={opt} | |
| type="button" | |
| onClick={() => { | |
| const current = fixedInputs[input.id] || input.defaultValue || [] | |
| updateFixedInput( | |
| input.id, | |
| current.includes(opt) | |
| ? current.filter((o) => o !== opt) | |
| : [...current, opt] | |
| ) | |
| }} | |
| className={`px-3 py-1.5 rounded-md text-xs font-medium transition-all border | |
| ${ | |
| selected | |
| ? 'bg-accent/15 text-accent border-accent/30' | |
| : 'dark:bg-surface-input dark:text-text-secondary dark:border-border bg-white text-gray-500 border-gray-200' | |
| }`} | |
| > | |
| {selected && '✓ '} | |
| {opt} | |
| </button> | |
| ) | |
| })} | |
| </div> | |
| ) : input.type === 'textarea' || input.type === 'code' ? ( | |
| <textarea | |
| value={fixedInputs[input.id] || input.defaultValue || ''} | |
| onChange={(e) => updateFixedInput(input.id, e.target.value)} | |
| placeholder={input.placeholder} | |
| rows={input.type === 'code' ? 8 : 4} | |
| spellCheck={input.type !== 'code'} | |
| className="w-full px-3 py-2 rounded-md text-sm transition-colors resize-y | |
| dark:bg-surface-input dark:border-border dark:text-text-primary | |
| bg-white border border-gray-200 text-gray-900 | |
| focus:ring-1 focus:ring-accent focus:border-accent outline-none" | |
| /> | |
| ) : ( | |
| <input | |
| type="text" | |
| value={fixedInputs[input.id] || input.defaultValue || ''} | |
| onChange={(e) => updateFixedInput(input.id, e.target.value)} | |
| placeholder={input.placeholder} | |
| className="w-full h-9 px-3 rounded-md text-sm transition-colors | |
| dark:bg-surface-input dark:border-border dark:text-text-primary | |
| bg-white border border-gray-200 text-gray-900 | |
| focus:ring-1 focus:ring-accent focus:border-accent outline-none" | |
| /> | |
| )} |
🧰 Tools
🪛 ast-grep (0.44.0)
[warning] 184-190: A list component should have a key to prevent re-rendering
Context: <CustomSelect
value={fixedInputs[input.id] || input.defaultValue || ''}
onChange={(val) => updateFixedInput(input.id, val)}
options={input.options || []}
className="w-full sm:w-64"
triggerClassName="h-9"
/>
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.
(list-component-needs-key)
[warning] 222-231: A list component should have a key to prevent re-rendering
Context: <input
type="text"
value={fixedInputs[input.id] || ''}
onChange={(e) => updateFixedInput(input.id, e.target.value)}
placeholder={input.placeholder}
className="w-full h-9 px-3 rounded-md text-sm transition-colors
dark:bg-surface-input dark:border-border dark:text-text-primary
bg-white border border-gray-200 text-gray-900
focus:ring-1 focus:ring-accent focus:border-accent outline-none"
/>
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.
(list-component-needs-key)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/BatchModeRunner.jsx` around lines 184 - 233, The fixed field
renderer in the conditional block starting with the select/multiselect check
only handles 'select' and 'multiselect' types, causing all other input types
including 'textarea' and 'code' to fall back to a single-line text input. Add
additional else-if conditions after the multiselect block to specifically handle
input.type === 'textarea' by rendering a textarea element instead of an input
element, and input.type === 'code' by rendering an appropriate code editor
component, each reading from and updating fixedInputs using the same
updateFixedInput callback pattern as the existing handlers.
| if (signal?.aborted) return | ||
| onItemUpdate(index, { status: 'done', output: result.content }) | ||
| } catch (err) { | ||
| if (signal?.aborted) return | ||
| onItemUpdate(index, { |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Set a terminal status when an in-flight item is aborted.
If runAgent() rejects due abort, this branch returns immediately and leaves that item stuck as running, which makes post-stop progress/results inconsistent.
Suggested fix
try {
const userMessage = buildBatchUserMessage(agent, fixedInputs, batchFieldId, itemValue)
const result = await runAgent(
{ provider, model, apiKey, systemPrompt, userMessage },
{ signal }
)
if (signal?.aborted) return
onItemUpdate(index, { status: 'done', output: result.content })
} catch (err) {
- if (signal?.aborted) return
+ if (signal?.aborted) {
+ onItemUpdate(index, {
+ status: 'failed',
+ error: 'Batch run stopped by user.',
+ })
+ return
+ }
onItemUpdate(index, {
status: 'failed',
error: err?.message || err?.detail || 'Failed to process this item.',
})
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (signal?.aborted) return | |
| onItemUpdate(index, { status: 'done', output: result.content }) | |
| } catch (err) { | |
| if (signal?.aborted) return | |
| onItemUpdate(index, { | |
| if (signal?.aborted) return | |
| onItemUpdate(index, { status: 'done', output: result.content }) | |
| } catch (err) { | |
| if (signal?.aborted) { | |
| onItemUpdate(index, { | |
| status: 'failed', | |
| error: 'Batch run stopped by user.', | |
| }) | |
| return | |
| } | |
| onItemUpdate(index, { |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/batchRunner.js` around lines 158 - 162, In the catch block of the
try-catch statement, when signal?.aborted is true, the function returns
immediately without updating the item status, leaving it stuck as "running".
Instead of returning directly when signal?.aborted is true, call onItemUpdate
with a terminal status (such as 'aborted' or 'cancelled') for the item before
returning. This ensures the item receives a proper terminal status that matches
the success case pattern where onItemUpdate is called with status 'done'.
| function csvEscape(value) { | ||
| const str = String(value ?? '') | ||
| if (/[",\n]/.test(str)) { | ||
| return `"${str.replace(/"/g, '""')}"` | ||
| } | ||
| return str | ||
| } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Harden CSV export against formula injection.
input/output/error can contain untrusted text; if a value starts with formula trigger chars, opening the CSV in Excel/Sheets can execute it. Prefix dangerous cells before escaping.
Suggested fix
function csvEscape(value) {
const str = String(value ?? '')
- if (/[",\n]/.test(str)) {
- return `"${str.replace(/"/g, '""')}"`
+ const guarded = /^[\t\r ]*[=+\-@]/.test(str) ? `'${str}` : str
+ if (/[",\n]/.test(guarded)) {
+ return `"${guarded.replace(/"/g, '""')}"`
}
- return str
+ return guarded
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/exportBatch.js` around lines 25 - 31, The csvEscape function is
vulnerable to formula injection when handling untrusted input that starts with
formula trigger characters like equals sign, plus sign, at sign, or hyphen.
Before applying the existing CSV escape logic in the csvEscape function, add a
check to detect if the string value starts with any of these dangerous
characters and prefix it with a single quote if detected. This will prevent
formulas from being executed when the CSV is opened in Excel or Google Sheets
while maintaining proper CSV formatting.
Closes #612
What does this PR do?
Adds a Batch Input Mode to AgentRunner so agents can be run across many inputs at once instead of one at a time, closing #612.
When enabled, a "Batch Mode" toggle appears for any agent that has at least one
text/textarea/codeinput. For agents with more than one such field (e.g. Resume Screener'sjob_description+resume), the user picks which field varies per item, every other field stays fixed and is reused for every row in the batch.Users can either paste multiple items (one per line) or upload a
.csv/.txtfile. For CSV uploads, the first row is never assumed to be a header , content-based header detection is unreliable for free-text items (a line like"Resume of Alice, Engineer"is indistinguishable from a genuine header), so guessing wrong would silently drop the first real item. Instead there's an explicit "First row is a header" checkbox that defaults to unchecked, so no data is ever lost by default.Items run with limited concurrency (3 at a time) through the existing
runAgent()adapter, no changes tollmAdapter.jsitself. Each item shows a live status (waiting → running → done/failed), and a "X of Y complete" progress line updates as the batch runs. Results can be exported as CSV or Markdown, following the same Blob/download pattern already used inexportMarkdown.js. A Stop button aborts the in-flight batch viaAbortController, matching the existing single-run Stop behavior.Single-run mode is untouched, the existing input form, output rendering, Prompt Playground, Model Analyser, and Schedule flow are all wrapped in a
{batchMode ? ... : (...)}conditional with no changes to their internals, so toggling Batch Mode off restores the exact original UI and behavior.Files changed
src/lib/batchRunner.js(new) : dependency-free CSV/line parsing, builds per-item prompts, runs the batch with a concurrency-limited worker pool againstrunAgent()src/lib/exportBatch.js(new) : exports batch results as CSV or Markdownsrc/components/BatchModeRunner.jsx(new) : batch UI: field picker, fixed-field inputs, paste/upload, header checkbox, progress, per-item results, export buttonssrc/components/AgentRunner.jsx(modified) : adds an always-visible Batch Mode toggle and rendersBatchModeRunnerin place of the single-run form when activeType of change
Checklist
npm run buildlocally and it passed ✅import agents from '../agents/registry'✅Screenshots (if UI change)