From 6335508eb7076e018bc88b72837eeb5aada7617f Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 4 May 2026 21:02:45 +0000 Subject: [PATCH 1/4] Add GUI task creation params input Co-authored-by: Michael Chou --- .../primary-content/prompt-input.tsx | 108 +++++++++++++++++- agentex-ui/lib/json-utils.test.ts | 31 ++++- agentex-ui/lib/json-utils.ts | 29 +++++ 3 files changed, 165 insertions(+), 3 deletions(-) diff --git a/agentex-ui/components/primary-content/prompt-input.tsx b/agentex-ui/components/primary-content/prompt-input.tsx index 5d872c4f..6be2e364 100644 --- a/agentex-ui/components/primary-content/prompt-input.tsx +++ b/agentex-ui/components/primary-content/prompt-input.tsx @@ -8,9 +8,11 @@ import { Prec } from '@codemirror/state'; import { EditorView, keymap } from '@codemirror/view'; import CodeMirror from '@uiw/react-codemirror'; import { DataContent, TextContent } from 'agentex/resources'; -import { ArrowUp } from 'lucide-react'; +import { ArrowUp, ChevronDown } from 'lucide-react'; import { useAgentexClient } from '@/components/providers'; +import { Button } from '@/components/ui/button'; +import { Collapsible } from '@/components/ui/collapsible'; import { IconButton } from '@/components/ui/icon-button'; import { Switch } from '@/components/ui/switch'; import { toast } from '@/components/ui/toast'; @@ -21,6 +23,7 @@ import { } from '@/hooks/use-safe-search-params'; import { useSendMessage } from '@/hooks/use-task-messages'; import { useTask } from '@/hooks/use-tasks'; +import { parseOptionalJsonObject } from '@/lib/json-utils'; import { TaskStatusEnum } from '@/lib/types'; type PromptInputProps = { @@ -49,6 +52,8 @@ export function PromptInput({ prompt, setPrompt }: PromptInputProps) { const { taskID, agentName, updateParams } = useSafeSearchParams(); const [isClient, setIsClient] = useState(false); const [isSendingJSON, setIsSendingJSON] = useState(false); + const [isTaskParamsOpen, setIsTaskParamsOpen] = useState(false); + const [taskParamsPrompt, setTaskParamsPrompt] = useState(''); const { agentexClient } = useAgentexClient(); @@ -58,6 +63,7 @@ export function PromptInput({ prompt, setPrompt }: PromptInputProps) { const textInputRef = useRef(null); const codeMirrorViewRef = useRef(null); + const taskParamsCodeMirrorViewRef = useRef(null); const isTaskTerminal = useMemo(() => { if (!taskID || !task) return false; @@ -116,12 +122,25 @@ export function PromptInput({ prompt, setPrompt }: PromptInputProps) { } } + let taskParams: Record | undefined; + if (!currentTaskId) { + try { + taskParams = parseOptionalJsonObject(taskParamsPrompt); + } catch (error) { + toast.error({ + title: 'Invalid task creation params', + message: error instanceof Error ? error.message : 'Invalid JSON object', + }); + return; + } + } + setPrompt(''); if (!currentTaskId) { const task = await createTaskMutation.mutateAsync({ agentName: agentName, - params: { + params: taskParams ?? { description: prompt, content: currentPrompt, }, @@ -159,10 +178,21 @@ export function PromptInput({ prompt, setPrompt }: PromptInputProps) { sendMessageMutation, setPrompt, isSendingJSON, + taskParamsPrompt, ]); return (
+ {!taskID && ( + + )}
@@ -211,6 +241,80 @@ export function PromptInput({ prompt, setPrompt }: PromptInputProps) { ); } +const TaskCreationParamsEditor = ({ + value, + setValue, + isOpen, + setIsOpen, + isDisabled, + codeMirrorViewRef, +}: { + value: string; + setValue: (value: string) => void; + isOpen: boolean; + setIsOpen: (isOpen: boolean) => void; + isDisabled: boolean; + codeMirrorViewRef: React.MutableRefObject; +}) => { + const handleToggle = useCallback(() => { + const nextIsOpen = !isOpen; + setIsOpen(nextIsOpen); + }, [isOpen, setIsOpen]); + + return ( +
+ + +
+ setValue(nextValue)} + onCreateEditor={view => { + codeMirrorViewRef.current = view; + }} + extensions={[json(), noOutlineTheme, closeBrackets()]} + placeholder='{ "container_id": "..." }' + basicSetup={{ + lineNumbers: false, + foldGutter: false, + highlightActiveLineGutter: false, + highlightActiveLine: false, + }} + editable={!isDisabled} + theme="none" + maxHeight="180px" + /> +
+

+ Optional JSON object sent as task/create params only when this GUI + starts a new task. Leave empty to use the prompt as the default + params. +

+
+
+ ); +}; + const TextInput = ({ prompt, setPrompt, diff --git a/agentex-ui/lib/json-utils.test.ts b/agentex-ui/lib/json-utils.test.ts index 02566925..3f5fd541 100644 --- a/agentex-ui/lib/json-utils.test.ts +++ b/agentex-ui/lib/json-utils.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; -import { serializeValue } from '@/lib/json-utils'; +import { parseOptionalJsonObject, serializeValue } from '@/lib/json-utils'; import type { JsonValue } from '@/lib/types'; describe('serializeValue', () => { @@ -77,3 +77,32 @@ describe('serializeValue', () => { expect(result).toBe('0'); }); }); + +describe('parseOptionalJsonObject', () => { + it('returns undefined for empty input', () => { + expect(parseOptionalJsonObject('')).toBeUndefined(); + expect(parseOptionalJsonObject(' ')).toBeUndefined(); + }); + + it('parses a valid JSON object', () => { + expect(parseOptionalJsonObject('{ "container_id": "abc123" }')).toEqual({ + container_id: 'abc123', + }); + }); + + it('rejects invalid JSON', () => { + expect(() => parseOptionalJsonObject('{ bad json }')).toThrow( + 'Invalid JSON' + ); + }); + + it('rejects arrays', () => { + expect(() => parseOptionalJsonObject('[]')).toThrow('Expected a JSON object'); + }); + + it('rejects null', () => { + expect(() => parseOptionalJsonObject('null')).toThrow( + 'Expected a JSON object' + ); + }); +}); diff --git a/agentex-ui/lib/json-utils.ts b/agentex-ui/lib/json-utils.ts index ee173388..d4b08e34 100644 --- a/agentex-ui/lib/json-utils.ts +++ b/agentex-ui/lib/json-utils.ts @@ -1,5 +1,7 @@ import type { JsonValue } from '@/lib/types'; +export type JsonObject = { [key: string]: JsonValue }; + export function serializeValue(data: JsonValue): string { if (typeof data === 'object' && data !== null) { return JSON.stringify(data, null, 2); @@ -9,3 +11,30 @@ export function serializeValue(data: JsonValue): string { } return String(data); } + +export function parseJsonObject(input: string): JsonObject { + let parsedValue: unknown; + try { + parsedValue = JSON.parse(input) as unknown; + } catch (error) { + throw new Error('Invalid JSON', { cause: error }); + } + + if ( + parsedValue === null || + typeof parsedValue !== 'object' || + Array.isArray(parsedValue) + ) { + throw new Error('Expected a JSON object'); + } + + return parsedValue as JsonObject; +} + +export function parseOptionalJsonObject(input: string): JsonObject | undefined { + if (!input.trim()) { + return undefined; + } + + return parseJsonObject(input); +} From 86f7fd82f9a60294fd4f678387e9a2e0430684cb Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 5 May 2026 17:51:02 +0000 Subject: [PATCH 2/4] Refine task params prompt UI Co-authored-by: Michael Chou --- .../primary-content/prompt-input.tsx | 90 +++++++++++-------- 1 file changed, 55 insertions(+), 35 deletions(-) diff --git a/agentex-ui/components/primary-content/prompt-input.tsx b/agentex-ui/components/primary-content/prompt-input.tsx index 6be2e364..2679fe7e 100644 --- a/agentex-ui/components/primary-content/prompt-input.tsx +++ b/agentex-ui/components/primary-content/prompt-input.tsx @@ -8,11 +8,10 @@ import { Prec } from '@codemirror/state'; import { EditorView, keymap } from '@codemirror/view'; import CodeMirror from '@uiw/react-codemirror'; import { DataContent, TextContent } from 'agentex/resources'; -import { ArrowUp, ChevronDown } from 'lucide-react'; +import { ArrowUp } from 'lucide-react'; import { useAgentexClient } from '@/components/providers'; import { Button } from '@/components/ui/button'; -import { Collapsible } from '@/components/ui/collapsible'; import { IconButton } from '@/components/ui/icon-button'; import { Switch } from '@/components/ui/switch'; import { toast } from '@/components/ui/toast'; @@ -52,7 +51,7 @@ export function PromptInput({ prompt, setPrompt }: PromptInputProps) { const { taskID, agentName, updateParams } = useSafeSearchParams(); const [isClient, setIsClient] = useState(false); const [isSendingJSON, setIsSendingJSON] = useState(false); - const [isTaskParamsOpen, setIsTaskParamsOpen] = useState(false); + const [isTaskParamsEnabled, setIsTaskParamsEnabled] = useState(false); const [taskParamsPrompt, setTaskParamsPrompt] = useState(''); const { agentexClient } = useAgentexClient(); @@ -123,7 +122,7 @@ export function PromptInput({ prompt, setPrompt }: PromptInputProps) { } let taskParams: Record | undefined; - if (!currentTaskId) { + if (!currentTaskId && isTaskParamsEnabled) { try { taskParams = parseOptionalJsonObject(taskParamsPrompt); } catch (error) { @@ -178,6 +177,7 @@ export function PromptInput({ prompt, setPrompt }: PromptInputProps) { sendMessageMutation, setPrompt, isSendingJSON, + isTaskParamsEnabled, taskParamsPrompt, ]); @@ -187,8 +187,8 @@ export function PromptInput({ prompt, setPrompt }: PromptInputProps) { @@ -244,22 +244,34 @@ export function PromptInput({ prompt, setPrompt }: PromptInputProps) { const TaskCreationParamsEditor = ({ value, setValue, - isOpen, - setIsOpen, + isEnabled, + setIsEnabled, isDisabled, codeMirrorViewRef, }: { value: string; setValue: (value: string) => void; - isOpen: boolean; - setIsOpen: (isOpen: boolean) => void; + isEnabled: boolean; + setIsEnabled: (isEnabled: boolean) => void; isDisabled: boolean; codeMirrorViewRef: React.MutableRefObject; }) => { - const handleToggle = useCallback(() => { - const nextIsOpen = !isOpen; - setIsOpen(nextIsOpen); - }, [isOpen, setIsOpen]); + const handleAdd = useCallback(() => { + setIsEnabled(true); + }, [setIsEnabled]); + + const handleRemove = useCallback(() => { + setIsEnabled(false); + setValue(''); + }, [setIsEnabled, setValue]); + + useEffect(() => { + if (!isEnabled) return; + + requestAnimationFrame(() => { + codeMirrorViewRef.current?.focus(); + }); + }, [codeMirrorViewRef, isEnabled]); return (
- - + {isEnabled ? (
+
+ Task creation params + +
+

+ Optional JSON object sent only when this GUI starts a new task. +

-

- Optional JSON object sent as task/create params only when this GUI - starts a new task. Leave empty to use the prompt as the default - params. -

-
+ ) : ( + + )}
); }; From 573578b0ddb66515e2a2a543d150d8da06a873b3 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 5 May 2026 17:52:43 +0000 Subject: [PATCH 3/4] Format task params UI changes Co-authored-by: Michael Chou --- agentex-ui/components/primary-content/prompt-input.tsx | 3 ++- agentex-ui/lib/json-utils.test.ts | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/agentex-ui/components/primary-content/prompt-input.tsx b/agentex-ui/components/primary-content/prompt-input.tsx index 2679fe7e..6a7cd19c 100644 --- a/agentex-ui/components/primary-content/prompt-input.tsx +++ b/agentex-ui/components/primary-content/prompt-input.tsx @@ -128,7 +128,8 @@ export function PromptInput({ prompt, setPrompt }: PromptInputProps) { } catch (error) { toast.error({ title: 'Invalid task creation params', - message: error instanceof Error ? error.message : 'Invalid JSON object', + message: + error instanceof Error ? error.message : 'Invalid JSON object', }); return; } diff --git a/agentex-ui/lib/json-utils.test.ts b/agentex-ui/lib/json-utils.test.ts index 3f5fd541..0676b127 100644 --- a/agentex-ui/lib/json-utils.test.ts +++ b/agentex-ui/lib/json-utils.test.ts @@ -97,7 +97,9 @@ describe('parseOptionalJsonObject', () => { }); it('rejects arrays', () => { - expect(() => parseOptionalJsonObject('[]')).toThrow('Expected a JSON object'); + expect(() => parseOptionalJsonObject('[]')).toThrow( + 'Expected a JSON object' + ); }); it('rejects null', () => { From 278b661b947625ec51b989edb19178db560a004e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 5 May 2026 18:02:50 +0000 Subject: [PATCH 4/4] Reset task params after task creation Co-authored-by: Michael Chou --- agentex-ui/components/primary-content/prompt-input.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/agentex-ui/components/primary-content/prompt-input.tsx b/agentex-ui/components/primary-content/prompt-input.tsx index 6a7cd19c..aab272d9 100644 --- a/agentex-ui/components/primary-content/prompt-input.tsx +++ b/agentex-ui/components/primary-content/prompt-input.tsx @@ -147,6 +147,8 @@ export function PromptInput({ prompt, setPrompt }: PromptInputProps) { }); currentTaskId = task.id; updateParams({ [SearchParamKey.TASK_ID]: currentTaskId }); + setIsTaskParamsEnabled(false); + setTaskParamsPrompt(''); } const content: TextContent | DataContent = isSendingJSON