feat: multi-agent framework support (Jules, Gemini, Antigravity, Custom)#1964
feat: multi-agent framework support (Jules, Gemini, Antigravity, Custom)#1964siddh7007 wants to merge 3 commits intoAndyMik90:developfrom
Conversation
…Custom) Extend the Agent Framework dropdown in Settings > Agent > Other Agent Settings to support multiple coding agent backends beyond the built-in Aperant pipeline: - Jules (Google): Async coding agent running in cloud VMs, auto-creates PRs - Claude Code: Local Claude Code CLI in non-interactive (-p) mode - Gemini CLI: Google Gemini CLI for task execution - Antigravity: Gemini-powered MCP server with multi-model support - Custom: User-defined shell command for any agent When Jules is selected, conditional settings appear for: - API key configuration - Default repository (owner/repo) - Auto-create PR toggle When Custom is selected, a command input appears. Includes jules-dispatcher.ts with functions for: - dispatchToJules(): Send task to Jules via CLI - listJulesSessions(): Poll session status - pullJulesSession(): Pull and apply results locally i18n: English and French translations added for all new options. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Sid b. Patel seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account. You have signed the CLA already but the status is still pending? Let us recheck it. |
📝 WalkthroughWalkthroughAdds a framework router and Jules integration: new Jules dispatcher module, routing from agent manager to external frameworks (including Jules), type and UI updates for multiple agent frameworks (Jules/custom), and new i18n entries for English and French. Changes
Sequence Diagram(s)sequenceDiagram
participant Manager as AgentManager
participant Router as FrameworkRouter
participant Jules as JulesDispatcher
participant CLI as Jules CLI
participant Emitter as EventEmitter
Manager->>Router: routePhase(phase)
Router->>Manager: FrameworkDecision(useBuiltinWorker=false, framework='jules')
Manager->>Router: executeExternal(decision, prompt, cwd, emitter, taskId, projectId)
Router->>Emitter: emit(progress:start)
Router->>Jules: dispatchToJules(prompt, settings, cwd)
Jules->>CLI: spawn "jules ..." (env JULES_API_KEY?)
CLI-->>Jules: stdout/stderr (streamed)
Jules->>Router: return { success, sessionId?, output }
Router->>Emitter: emit(progress:log, success/failure)
Router->>Manager: { success, output }
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Suggested labels
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
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 |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the application's multi-agent framework by integrating several new AI coding agents, including Google Jules, Claude Code, Gemini CLI, and Antigravity. It also introduces a flexible "Custom Command" option for users to define their own agent execution. The changes include dedicated UI for configuring these new agents, particularly Jules, and robust backend dispatching logic, alongside comprehensive internationalization updates. Highlights
Changelog
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
🎉 Thanks for your first PR!
A maintainer will review it soon. Please make sure:
- Your branch is synced with
develop - CI checks pass
- You've followed our contribution guide
Welcome to the Auto Claude community!
| /** | ||
| * Jules Dispatcher | ||
| * ================ | ||
| * | ||
| * Dispatches tasks to Google Jules (async coding agent) when | ||
| * agentFramework is set to 'jules' in app settings. | ||
| * | ||
| * Jules runs tasks in isolated cloud VMs and creates PRs automatically. | ||
| * Uses the Jules CLI (`jules new`) for task creation and | ||
| * `jules remote list --session` for status polling. |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
| // Pass API key if configured (used by Jules MCP server, CLI uses OAuth) | ||
| ...(settings.julesApiKey ? { JULES_API_KEY: settings.julesApiKey } : {}), | ||
| }, | ||
| shell: true, |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
There was a problem hiding this comment.
Code Review
This pull request adds support for multiple agent backends, with a primary focus on integrating the 'Jules' agent. This includes a new dispatcher for the Jules CLI, UI components for configuration, and updates to application types and internationalization files. My review focuses on improving security, robustness, and maintainability. I've identified a critical security vulnerability related to shell command execution and suggest refactoring for better code structure and error handling.
| const child = spawn('jules', args, { | ||
| cwd, | ||
| env: { | ||
| ...process.env, | ||
| // Pass API key if configured (used by Jules MCP server, CLI uses OAuth) | ||
| ...(settings.julesApiKey ? { JULES_API_KEY: settings.julesApiKey } : {}), | ||
| }, | ||
| shell: true, | ||
| }); |
There was a problem hiding this comment.
Using shell: true with spawn is a security risk that can lead to command injection, especially when arguments are constructed from user input like the prompt. While Node.js may offer some protection by quoting arguments, it's best to avoid this pattern unless shell-specific features (like pipes or redirection) are necessary. For security and robustness, it's recommended to remove shell: true as spawn can find executables in the system's PATH without it.
This advice applies to all spawn calls in this file.
const child = spawn('jules', args, {
cwd,
env: {
...process.env,
// Pass API key if configured (used by Jules MCP server, CLI uses OAuth)
...(settings.julesApiKey ? { JULES_API_KEY: settings.julesApiKey } : {}),
},
});| child.on('error', () => { | ||
| resolve([]); | ||
| }); |
There was a problem hiding this comment.
The error event handler currently swallows errors and returns an empty array without logging. This can make debugging difficult if, for example, the jules CLI is not installed or fails to start. It's better to log the error to aid in troubleshooting.
child.on('error', (err: Error) => {
console.error(`[jules-dispatcher] Failed to list sessions: ${err.message}`);
resolve([]);
});| {settings.agentFramework === 'jules' && ( | ||
| <div className="space-y-3 pl-4 border-l-2 border-muted"> | ||
| <p className="text-sm font-medium text-foreground">{t('general.julesSettings')}</p> | ||
| <div className="space-y-2"> | ||
| <Label htmlFor="julesApiKey" className="text-sm font-medium text-foreground">{t('general.julesApiKey')}</Label> | ||
| <p className="text-sm text-muted-foreground">{t('general.julesApiKeyDescription')}</p> | ||
| <Input | ||
| id="julesApiKey" | ||
| type="password" | ||
| placeholder={t('general.julesApiKeyPlaceholder')} | ||
| className="w-full max-w-md" | ||
| value={settings.julesApiKey || ''} | ||
| onChange={(e) => onSettingsChange({ ...settings, julesApiKey: e.target.value })} | ||
| /> | ||
| </div> | ||
| <div className="space-y-2"> | ||
| <Label htmlFor="julesDefaultRepo" className="text-sm font-medium text-foreground">{t('general.julesDefaultRepo')}</Label> | ||
| <p className="text-sm text-muted-foreground">{t('general.julesDefaultRepoDescription')}</p> | ||
| <Input | ||
| id="julesDefaultRepo" | ||
| placeholder={t('general.julesDefaultRepoPlaceholder')} | ||
| className="w-full max-w-md" | ||
| value={settings.julesDefaultRepo || ''} | ||
| onChange={(e) => onSettingsChange({ ...settings, julesDefaultRepo: e.target.value })} | ||
| /> | ||
| </div> | ||
| <div className="flex items-center justify-between max-w-md"> | ||
| <div className="space-y-1"> | ||
| <Label htmlFor="julesAutoCreatePr" className="text-sm font-medium text-foreground"> | ||
| {t('general.julesAutoCreatePr')} | ||
| </Label> | ||
| <p className="text-sm text-muted-foreground"> | ||
| {t('general.julesAutoCreatePrDescription')} | ||
| </p> | ||
| </div> | ||
| <Switch | ||
| id="julesAutoCreatePr" | ||
| checked={settings.julesAutoCreatePr ?? true} | ||
| onCheckedChange={(checked) => onSettingsChange({ ...settings, julesAutoCreatePr: checked })} | ||
| /> | ||
| </div> | ||
| </div> | ||
| )} | ||
|
|
||
| {/* Custom command settings */} | ||
| {settings.agentFramework === 'custom' && ( | ||
| <div className="space-y-3 pl-4 border-l-2 border-muted"> | ||
| <div className="space-y-2"> | ||
| <Label htmlFor="customAgentCommand" className="text-sm font-medium text-foreground">{t('general.customAgentCommand')}</Label> | ||
| <p className="text-sm text-muted-foreground">{t('general.customAgentCommandDescription')}</p> | ||
| <Input | ||
| id="customAgentCommand" | ||
| placeholder={t('general.customAgentCommandPlaceholder')} | ||
| className="w-full max-w-md" | ||
| value={settings.customAgentCommand || ''} | ||
| onChange={(e) => onSettingsChange({ ...settings, customAgentCommand: e.target.value })} | ||
| /> | ||
| </div> | ||
| </div> | ||
| )} |
There was a problem hiding this comment.
The current implementation adds conditional blocks directly in GeneralSettings.tsx for each agent framework's specific settings. As more frameworks are added, this component will become large and hard to maintain. Consider refactoring this by creating separate components for each framework's settings (e.g., JulesSettings.tsx, CustomAgentSettings.tsx) and rendering them dynamically. This improves modularity and scalability.
For example:
// In GeneralSettings.tsx
...
{settings.agentFramework === 'jules' && <JulesSettings settings={settings} onSettingsChange={onSettingsChange} />}
{settings.agentFramework === 'custom' && <CustomAgentSettings settings={settings} onSettingsChange={onSettingsChange} />}
...
// In a new file, e.g., JulesSettings.tsx
export function JulesSettings({ settings, onSettingsChange }) {
const { t } = useTranslation('settings');
return (
<div className="space-y-3 pl-4 border-l-2 border-muted">
{/* ... Jules settings JSX ... */}
</div>
);
}There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/desktop/src/main/agent/jules-dispatcher.ts`:
- Around line 71-73: Remove the unconditional injection of the secret
JULES_API_KEY into the child process env: only include settings.julesApiKey when
the child process requires API-key auth (e.g., the Jules MCP server), not for
the CLI/OAuth flow. Update the env spread that currently uses
...(settings.julesApiKey ? { JULES_API_KEY: settings.julesApiKey } : {}) to be
gated by a flag or explicit check for the non-CLI execution path (use the same
launcher/context check you have for MCP vs CLI), so that JULES_API_KEY is
omitted when launching the CLI flow.
- Around line 43-187: The Jules dispatcher functions (dispatchToJules,
listJulesSessions, pullJulesSession) are never used at runtime; update the main
task routing to honor settings.agentFramework by adding a branch in
agentManager.startTaskExecution (or the central task handler) that calls
dispatchToJules when settings.agentFramework === 'jules' (pass cwd, settings and
task prompt), and expose IPC handlers (e.g., 'jules:dispatch', 'jules:list',
'jules:pull') that proxy to dispatchToJules, listJulesSessions and
pullJulesSession so the renderer UI works; ensure you pass settings.julesApiKey
and settings.julesDefaultRepo, propagate errors/backpressure from spawn, and
update any docs/UI, or if you prefer removal, delete these functions and any
renderer UI/IPCs referencing Jules to avoid dead config options.
- Around line 52-75: The Jules spawn usage currently passes user-controlled
values (prompt, repo, sessionId) with shell: true and forwards JULES_API_KEY,
creating a command-injection and secrets-leak surface and lacks timeouts;
replace the shell invocation by resolving the Jules binary via
findExecutable('jules') and call spawn with that executable and the args array
(no shell:true), remove the JULES_API_KEY from the env object (do not pass
settings.julesApiKey), and add a configurable process timeout/cancellation
(e.g., setTimeout + child.kill or use child.kill on an AbortSignal) around the
child created in the jules-dispatcher spawn sites that use
args/fullPrompt/sessionId so the subprocess cannot hang indefinitely.
In `@apps/desktop/src/renderer/components/settings/GeneralSettings.tsx`:
- Around line 192-203: When settings.agentFramework === 'custom', add a UI-side
required validation for the customAgentCommand input so an empty string cannot
be saved: validate the Input with id "customAgentCommand" (used inside
GeneralSettings.tsx) before calling onSettingsChange/save, show a user-facing
error message near the Label/Input and prevent the save action (disable the Save
button or block the submit handler) until settings.customAgentCommand is
non-empty; keep existing server-side validation as well.
In `@apps/desktop/src/shared/types/settings.ts`:
- Around line 267-268: The AppSettings field julesApiKey should not be persisted
in plaintext; remove julesApiKey from the AppSettings type and instead store the
secret in the OS keychain/credential store via your existing secret manager APIs
(e.g., use functions like setCredential/getCredential or add a new secrets
service). Replace direct usages of AppSettings.julesApiKey with calls to a new
helper (e.g., getJulesApiKey() / setJulesApiKey()) that read/write the keychain,
and keep only a non-secret flag in AppSettings if needed (e.g.,
julesApiKeyPresent: boolean) to indicate presence without storing the secret
itself.
- Line 266: The union for agentFramework currently ends with "| string", which
collapses the literal types and defeats narrowing; remove the "| string" and
instead declare a named literal union type (e.g., AgentFramework = 'auto-claude'
| 'jules' | 'claude-code' | 'gemini' | 'antigravity' | 'custom') and use that
type for the agentFramework property (replace the existing inline union with
AgentFramework) so TypeScript strict narrowing (e.g., settings.agentFramework
=== 'jules') works correctly.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: a8fe3101-0b94-4ccc-8311-11ce65e2dc60
📒 Files selected for processing (5)
apps/desktop/src/main/agent/jules-dispatcher.tsapps/desktop/src/renderer/components/settings/GeneralSettings.tsxapps/desktop/src/shared/i18n/locales/en/settings.jsonapps/desktop/src/shared/i18n/locales/fr/settings.jsonapps/desktop/src/shared/types/settings.ts
| export async function dispatchToJules( | ||
| prompt: string, | ||
| settings: AppSettings, | ||
| cwd: string | ||
| ): Promise<JulesDispatchResult> { | ||
| const repo = settings.julesDefaultRepo; | ||
| const autoCreatePr = settings.julesAutoCreatePr ?? true; | ||
|
|
||
| const args = ['new']; | ||
| if (repo) { | ||
| args.push('--repo', repo); | ||
| } | ||
|
|
||
| // Append project context to the prompt | ||
| const fullPrompt = `${prompt} | ||
|
|
||
| ## Instructions | ||
| - Read CLAUDE.md / README.md for project conventions before starting | ||
| - Run the project's lint/check/build commands before committing | ||
| - ${autoCreatePr ? 'Create a PR when done' : 'Push to a branch (do not create PR)'}`; | ||
|
|
||
| args.push(fullPrompt); | ||
|
|
||
| return new Promise((resolve) => { | ||
| const child = spawn('jules', args, { | ||
| cwd, | ||
| env: { | ||
| ...process.env, | ||
| // Pass API key if configured (used by Jules MCP server, CLI uses OAuth) | ||
| ...(settings.julesApiKey ? { JULES_API_KEY: settings.julesApiKey } : {}), | ||
| }, | ||
| shell: true, | ||
| }); | ||
|
|
||
| let output = ''; | ||
| let errorOutput = ''; | ||
|
|
||
| child.stdout?.on('data', (data: Buffer) => { | ||
| output += data.toString(); | ||
| }); | ||
|
|
||
| child.stderr?.on('data', (data: Buffer) => { | ||
| errorOutput += data.toString(); | ||
| }); | ||
|
|
||
| child.on('error', (err: Error) => { | ||
| resolve({ | ||
| success: false, | ||
| error: `Failed to launch jules CLI: ${err.message}. Is jules installed? (npm i -g @google/jules)`, | ||
| output: errorOutput, | ||
| }); | ||
| }); | ||
|
|
||
| child.on('close', (code: number | null) => { | ||
| if (code === 0) { | ||
| // Try to extract session ID from output | ||
| const sessionMatch = output.match(/session[:\s]+(\d+)/i) || | ||
| output.match(/ID[:\s]+(\d+)/i); | ||
| resolve({ | ||
| success: true, | ||
| sessionId: sessionMatch?.[1], | ||
| output, | ||
| }); | ||
| } else { | ||
| resolve({ | ||
| success: false, | ||
| error: `jules exited with code ${code}`, | ||
| output: output + errorOutput, | ||
| }); | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * List recent Jules sessions. | ||
| */ | ||
| export async function listJulesSessions(): Promise<JulesSessionStatus[]> { | ||
| return new Promise((resolve) => { | ||
| const child = spawn('jules', ['remote', 'list', '--session'], { | ||
| shell: true, | ||
| }); | ||
|
|
||
| let output = ''; | ||
|
|
||
| child.stdout?.on('data', (data: Buffer) => { | ||
| output += data.toString(); | ||
| }); | ||
|
|
||
| child.on('error', () => { | ||
| resolve([]); | ||
| }); | ||
|
|
||
| child.on('close', () => { | ||
| // Parse the table output from jules remote list | ||
| const lines = output.split('\n').filter(l => l.trim() && !l.includes('ID')); | ||
| const sessions: JulesSessionStatus[] = lines.map(line => { | ||
| const parts = line.trim().split(/\s{2,}/); | ||
| return { | ||
| id: parts[0] || '', | ||
| description: parts[1] || '', | ||
| repo: parts[2] || '', | ||
| lastActive: parts[3] || '', | ||
| status: parts[4] || '', | ||
| }; | ||
| }).filter(s => s.id); | ||
|
|
||
| resolve(sessions); | ||
| }); | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Pull and apply a Jules session result to the local repo. | ||
| */ | ||
| export async function pullJulesSession( | ||
| sessionId: string, | ||
| apply: boolean = false | ||
| ): Promise<{ success: boolean; output: string }> { | ||
| const args = ['remote', 'pull', '--session', sessionId]; | ||
| if (apply) { | ||
| args.push('--apply'); | ||
| } | ||
|
|
||
| return new Promise((resolve) => { | ||
| const child = spawn('jules', args, { shell: true }); | ||
| let output = ''; | ||
|
|
||
| child.stdout?.on('data', (data: Buffer) => { | ||
| output += data.toString(); | ||
| }); | ||
|
|
||
| child.stderr?.on('data', (data: Buffer) => { | ||
| output += data.toString(); | ||
| }); | ||
|
|
||
| child.on('error', (err: Error) => { | ||
| resolve({ success: false, output: err.message }); | ||
| }); | ||
|
|
||
| child.on('close', (code: number | null) => { | ||
| resolve({ success: code === 0, output }); | ||
| }); | ||
| }); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Find all references/imports to the dispatcher and exported symbols
rg -nP --type=ts -C2 '\bjules-dispatcher\b|\bdispatchToJules\b|\blistJulesSessions\b|\bpullJulesSession\b'
# 2) Check IPC channel/constants and handler registration for Jules flows
rg -nP --type=ts -C2 '\bIPC_CHANNELS\b.*JULES|register.*Jules|jules.*handler|jules.*ipc'
# 3) Check main task routing paths for framework-specific dispatch
rg -nP --type=ts -C2 '\bagentFramework\b|dispatch.*jules|framework.*jules|jules.*dispatch'Repository: AndyMik90/Aperant
Length of output: 1335
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Search for any imports from jules-dispatcher file
echo "=== Searching for imports from jules-dispatcher ==="
rg -nP --type=ts "from.*jules-dispatcher|import.*from.*jules-dispatcher"
# 2) List all files in ipc-handlers directory
echo -e "\n=== IPC handlers directory structure ==="
find apps/desktop/src/main/ipc-handlers -type f -name "*.ts" 2>/dev/null | head -20
# 3) Search broadly for "jules" in ipc-handlers
echo -e "\n=== References to 'jules' in ipc-handlers ==="
rg -nP --type=ts "jules" apps/desktop/src/main/ipc-handlers/ 2>/dev/null || echo "No matches found"
# 4) Check main process entry point for any jules initialization
echo -e "\n=== References to 'jules' in main process files ==="
rg -nP --type=ts "jules" apps/desktop/src/main/ --include-dir=ipc-handlers --invert-match 2>/dev/null | head -20 || echo "No matches found"
# 5) Search for any dynamic require or lazy-loading patterns
echo -e "\n=== Searching for dynamic imports or lazy loading ==="
rg -nP --type=ts "require.*jules|import.*\(.*jules|lazy.*jules|framework.*dispatch" apps/desktop/src/main/ || echo "No matches found"Repository: AndyMik90/Aperant
Length of output: 112
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Verify the dispatcher file exists
echo "=== Check if dispatcher file exists ==="
test -f apps/desktop/src/main/agent/jules-dispatcher.ts && echo "✓ File exists" || echo "✗ File not found"
# 2) List main directory structure
echo -e "\n=== Main directory structure ==="
ls -la apps/desktop/src/main/ | head -20
# 3) Check ipc-handlers directory
echo -e "\n=== IPC handlers directory ==="
ls -la apps/desktop/src/main/ipc-handlers/ 2>/dev/null || echo "Directory not found"
# 4) Simple grep for "jules" anywhere in the codebase (no restrictions)
echo -e "\n=== All 'jules' references in codebase ==="
grep -r "jules" apps/desktop/src/ 2>/dev/null | grep -v "node_modules" | head -30 || echo "No matches found"
# 5) Check if dispatcher is referenced anywhere, even as a string
echo -e "\n=== All 'dispatcher' references ==="
grep -r "dispatcher" apps/desktop/src/main --include="*.ts" 2>/dev/null | head -30 || echo "No matches found"Repository: AndyMik90/Aperant
Length of output: 8413
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Check agent-manager.ts for dispatcher usage
echo "=== Checking agent-manager.ts ==="
cat -n apps/desktop/src/main/agent-manager.ts
# 2) Search for where agentFramework setting is used
echo -e "\n=== Where agentFramework is checked ==="
grep -rn "agentFramework" apps/desktop/src/main --include="*.ts" | head -20
# 3) Look at queue-routing-handlers for task routing
echo -e "\n=== Checking queue-routing-handlers.ts (first 100 lines) ==="
head -100 apps/desktop/src/main/ipc-handlers/queue-routing-handlers.ts
# 4) Check claude-code-handlers for agent framework routing
echo -e "\n=== Search for agent framework dispatch in claude-code-handlers ==="
grep -n "agentFramework\|dispatchToJules\|jules-dispatcher" apps/desktop/src/main/ipc-handlers/claude-code-handlers.ts || echo "Not found"Repository: AndyMik90/Aperant
Length of output: 4986
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) List agent directory
echo "=== Agent directory structure ==="
find apps/desktop/src/main/agent -type f -name "*.ts" | sort
# 2) Check agent/index.ts for exports and dispatcher usage
echo -e "\n=== agent/index.ts (first 80 lines) ==="
head -80 apps/desktop/src/main/agent/index.ts
# 3) Search for where task execution or dispatch happens
echo -e "\n=== Search for executeTask or task execution ==="
grep -rn "executeTask\|execute.*task\|dispatch.*task" apps/desktop/src/main/ipc-handlers --include="*.ts" | head -20
# 4) Check claude-code-handlers for how tasks are executed
echo -e "\n=== claude-code-handlers.ts search for task execution (lines 1-150) ==="
head -150 apps/desktop/src/main/ipc-handlers/claude-code-handlers.ts | tail -80Repository: AndyMik90/Aperant
Length of output: 2205
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Search for agentFramework in entire apps/desktop (both main and renderer)
echo "=== All references to agentFramework setting ==="
grep -rn "agentFramework" apps/desktop/src --include="*.ts" --include="*.tsx" | grep -v "node_modules"
# 2) Check claude-code-handlers for the main task handling
echo -e "\n=== claude-code-handlers.ts - search for task creation/execution ==="
grep -n "new.*task\|create.*task\|CLAUDE_CODE\|executeAgent\|agentManager" apps/desktop/src/main/ipc-handlers/claude-code-handlers.ts | head -30
# 3) Look at the agent-process.ts to see actual task spawning
echo -e "\n=== agent-process.ts (first 100 lines) ==="
head -100 apps/desktop/src/main/agent/agent-process.tsRepository: AndyMik90/Aperant
Length of output: 6707
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Search for any actual usage of agentFramework in main process code
echo "=== Search for agentFramework usage patterns in main process ==="
grep -rn "settings\.agentFramework\|\.agentFramework\|'auto-claude'\|'claude-code'\|'gemini'" apps/desktop/src/main --include="*.ts" | grep -v "^Binary"
# 2) Check agent-process.ts for task execution (look for spawn calls)
echo -e "\n=== agent-process.ts - spawn and execution logic ==="
grep -n "spawn\|\.exec\|child_process\|framework\|agentFramework" apps/desktop/src/main/agent/agent-process.ts | head -20
# 3) Check claude-code-handlers.ts for where tasks are created
echo -e "\n=== claude-code-handlers.ts - task routing/execution ==="
grep -n "agentManager\|executeTask\|processTask\|dispatch" apps/desktop/src/main/ipc-handlers/claude-code-handlers.ts | head -20
# 4) Verify the setting is defined but unused
echo -e "\n=== Check AppSettings type includes agentFramework ==="
grep -A5 -B5 "agentFramework" apps/desktop/src/shared/types/settings.ts | head -20Repository: AndyMik90/Aperant
Length of output: 6655
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check where tasks are actually created and executed in handlers
echo "=== agent-events-handlers.ts (first 150 lines) ==="
head -150 apps/desktop/src/main/ipc-handlers/agent-events-handlers.ts
# Search for actual task spawning/execution
echo -e "\n=== Where agentManager.startTask is called ==="
grep -rn "startTask\|\.spawn\|executeTask" apps/desktop/src/main/ipc-handlers --include="*.ts" | head -20Repository: AndyMik90/Aperant
Length of output: 8411
Wire the Jules dispatcher into task routing or remove the incomplete feature.
The dispatcher exports (dispatchToJules, listJulesSessions, pullJulesSession) are unreferenced in the codebase. Task execution routes through agentManager.startTaskExecution() regardless of the agentFramework setting, which is only used in the renderer UI. The agentFramework setting is defined in AppSettings but never read or checked in the main process—users can configure Jules but it will have no effect at runtime.
Either integrate the dispatcher by:
- Adding conditional routing in task execution handlers based on
settings.agentFramework - Registering Jules-specific IPC channels and handlers
- Wiring dispatcher calls into the main task execution flow
Or remove the incomplete feature (dispatcher, IPC channel definitions, and renderer UI) to avoid user confusion.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/desktop/src/main/agent/jules-dispatcher.ts` around lines 43 - 187, The
Jules dispatcher functions (dispatchToJules, listJulesSessions,
pullJulesSession) are never used at runtime; update the main task routing to
honor settings.agentFramework by adding a branch in
agentManager.startTaskExecution (or the central task handler) that calls
dispatchToJules when settings.agentFramework === 'jules' (pass cwd, settings and
task prompt), and expose IPC handlers (e.g., 'jules:dispatch', 'jules:list',
'jules:pull') that proxy to dispatchToJules, listJulesSessions and
pullJulesSession so the renderer UI works; ensure you pass settings.julesApiKey
and settings.julesDefaultRepo, propagate errors/backpressure from spawn, and
update any docs/UI, or if you prefer removal, delete these functions and any
renderer UI/IPCs referencing Jules to avoid dead config options.
| if (repo) { | ||
| args.push('--repo', repo); | ||
| } | ||
|
|
||
| // Append project context to the prompt | ||
| const fullPrompt = `${prompt} | ||
|
|
||
| ## Instructions | ||
| - Read CLAUDE.md / README.md for project conventions before starting | ||
| - Run the project's lint/check/build commands before committing | ||
| - ${autoCreatePr ? 'Create a PR when done' : 'Push to a branch (do not create PR)'}`; | ||
|
|
||
| args.push(fullPrompt); | ||
|
|
||
| return new Promise((resolve) => { | ||
| const child = spawn('jules', args, { | ||
| cwd, | ||
| env: { | ||
| ...process.env, | ||
| // Pass API key if configured (used by Jules MCP server, CLI uses OAuth) | ||
| ...(settings.julesApiKey ? { JULES_API_KEY: settings.julesApiKey } : {}), | ||
| }, | ||
| shell: true, | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find apps/desktop/src/main -name "*.ts" -type f | head -20Repository: AndyMik90/Aperant
Length of output: 1199
🏁 Script executed:
cd apps/desktop && git ls-files src/main/agent/ 2>/dev/null || find src/main/agent -type f 2>/dev/nullRepository: AndyMik90/Aperant
Length of output: 797
🏁 Script executed:
if [ -f "apps/desktop/src/main/agent/jules-dispatcher.ts" ]; then
wc -l apps/desktop/src/main/agent/jules-dispatcher.ts
else
echo "File not found"
fiRepository: AndyMik90/Aperant
Length of output: 112
🏁 Script executed:
cat -n apps/desktop/src/main/agent/jules-dispatcher.ts 2>/dev/null | head -80Repository: AndyMik90/Aperant
Length of output: 2845
🏁 Script executed:
# Check platform abstraction functions availability
ls -la apps/desktop/src/main/platform/ 2>/dev/null || echo "Platform directory not found"Repository: AndyMik90/Aperant
Length of output: 417
🏁 Script executed:
# Search for references to jules-dispatcher functions
rg "jules-dispatcher|julesDispatcher" apps/desktop/src/main --type ts -A 2 -B 2Repository: AndyMik90/Aperant
Length of output: 43
🏁 Script executed:
cat -n apps/desktop/src/main/agent/jules-dispatcher.ts | sed -n '70,130p'Repository: AndyMik90/Aperant
Length of output: 2089
🏁 Script executed:
cat -n apps/desktop/src/main/agent/jules-dispatcher.ts | sed -n '150,187p'Repository: AndyMik90/Aperant
Length of output: 1193
🏁 Script executed:
# Check what platform abstraction functions are available
cat apps/desktop/src/main/platform/index.tsRepository: AndyMik90/Aperant
Length of output: 13503
🏁 Script executed:
# Search more broadly for any imports or references to jules-dispatcher
rg "dispatchToJules|pollJulesSession|cancelJulesSession" apps/desktop/src --type ts -A 1 -B 1Repository: AndyMik90/Aperant
Length of output: 265
🏁 Script executed:
# Check if this file is imported anywhere
rg "from.*jules-dispatcher|import.*jules-dispatcher" apps/desktop/src --type tsRepository: AndyMik90/Aperant
Length of output: 43
🏁 Script executed:
# Check the IPC handlers to see if jules functions are wired
grep -n "dispatcher\|jules" apps/desktop/src/main/ipc-handlers.ts 2>/dev/null | head -20Repository: AndyMik90/Aperant
Length of output: 43
🏁 Script executed:
# Check if file is exported from agent/index.ts
grep -n "jules-dispatcher\|dispatchToJules\|pullJulesSession" apps/desktop/src/main/agent/index.tsRepository: AndyMik90/Aperant
Length of output: 43
🏁 Script executed:
# Check main entry point for any references
rg "dispatchToJules|pullJulesSession|listJulesSessions" apps/desktop/src/main --type ts | head -20Repository: AndyMik90/Aperant
Length of output: 358
🏁 Script executed:
# Search more broadly with escaped patterns
rg "jules" apps/desktop/src/main/ipc-handlers.ts -A 3 -B 3 2>/dev/nullRepository: AndyMik90/Aperant
Length of output: 43
🏁 Script executed:
# Check if agentFramework === 'julius' setting is used anywhere
rg "julesDefaultRepo|agentFramework.*julius|JULES_API_KEY" apps/desktop/src --type ts | head -20Repository: AndyMik90/Aperant
Length of output: 1528
Remove shell: true for Jules command execution (command injection risk) and add process timeouts.
Lines 67–75, 122–124, and 168 execute the Jules CLI while passing user-influenced values (prompt, repo, sessionId). shell: true combined with external command spawning in the main process creates a command injection surface. Additionally, none of these spawned processes have timeout or cancellation mechanisms and can hang indefinitely.
Safer execution pattern
- const child = spawn('jules', args, {
- cwd,
- env: { ... },
- shell: true,
- });
+ const child = spawn('jules', args, {
+ cwd,
+ env: { ... },
+ timeout: 30000,
+ });- const child = spawn('jules', ['remote', 'list', '--session'], {
- shell: true,
- });
+ const child = spawn('jules', ['remote', 'list', '--session'], {
+ timeout: 30000,
+ });- const child = spawn('julius', args, { shell: true });
+ const child = spawn('jules', args, { timeout: 30000 });Additionally, use findExecutable('jules') from apps/desktop/src/main/platform/ to locate the command instead of relying on PATH, and remove the JULES_API_KEY environment variable passthrough since the CLI uses OAuth, not API keys.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/desktop/src/main/agent/jules-dispatcher.ts` around lines 52 - 75, The
Jules spawn usage currently passes user-controlled values (prompt, repo,
sessionId) with shell: true and forwards JULES_API_KEY, creating a
command-injection and secrets-leak surface and lacks timeouts; replace the shell
invocation by resolving the Jules binary via findExecutable('jules') and call
spawn with that executable and the args array (no shell:true), remove the
JULES_API_KEY from the env object (do not pass settings.julesApiKey), and add a
configurable process timeout/cancellation (e.g., setTimeout + child.kill or use
child.kill on an AbortSignal) around the child created in the jules-dispatcher
spawn sites that use args/fullPrompt/sessionId so the subprocess cannot hang
indefinitely.
| return new Promise((resolve) => { | ||
| const child = spawn('jules', args, { | ||
| cwd, | ||
| env: { | ||
| ...process.env, | ||
| // Pass API key if configured (used by Jules MCP server, CLI uses OAuth) | ||
| ...(settings.julesApiKey ? { JULES_API_KEY: settings.julesApiKey } : {}), | ||
| }, | ||
| shell: true, | ||
| }); | ||
|
|
||
| let output = ''; | ||
| let errorOutput = ''; | ||
|
|
||
| child.stdout?.on('data', (data: Buffer) => { | ||
| output += data.toString(); | ||
| }); | ||
|
|
||
| child.stderr?.on('data', (data: Buffer) => { | ||
| errorOutput += data.toString(); | ||
| }); | ||
|
|
||
| child.on('error', (err: Error) => { | ||
| resolve({ | ||
| success: false, | ||
| error: `Failed to launch jules CLI: ${err.message}. Is jules installed? (npm i -g @google/jules)`, | ||
| output: errorOutput, | ||
| }); | ||
| }); | ||
|
|
||
| child.on('close', (code: number | null) => { | ||
| if (code === 0) { | ||
| // Try to extract session ID from output | ||
| const sessionMatch = output.match(/session[:\s]+(\d+)/i) || | ||
| output.match(/ID[:\s]+(\d+)/i); | ||
| resolve({ | ||
| success: true, | ||
| sessionId: sessionMatch?.[1], | ||
| output, | ||
| }); | ||
| } else { | ||
| resolve({ | ||
| success: false, | ||
| error: `jules exited with code ${code}`, | ||
| output: output + errorOutput, | ||
| }); | ||
| } | ||
| }); | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, verify the file exists and check its basic structure
if [ -f "apps/desktop/src/main/agent/jules-dispatcher.ts" ]; then
echo "File exists. Checking line count:"
wc -l "apps/desktop/src/main/agent/jules-dispatcher.ts"
echo -e "\n--- File content ---"
cat -n "apps/desktop/src/main/agent/jules-dispatcher.ts"
else
echo "File not found"
fiRepository: AndyMik90/Aperant
Length of output: 6423
Add timeout/abort handling for all spawned Jules processes.
All three spawn('jules', ...) calls in this file (lines 66–114, 121–152, 167–186) lack timeout guards. If the CLI stalls or hangs, the Promise never resolves, which can wedge UI flows and degrade reliability.
Implement a timeout constant and kill the process if it exceeds the limit:
Timeout guard pattern
+const JULES_TIMEOUT_MS = 60_000;
return new Promise((resolve) => {
const child = spawn('jules', args, { cwd, env });
+ const timer = setTimeout(() => {
+ child.kill('SIGTERM');
+ resolve({ success: false, error: 'Jules command timed out', output });
+ }, JULES_TIMEOUT_MS);
child.on('close', (code) => {
+ clearTimeout(timer);
...
});
child.on('error', (err) => {
+ clearTimeout(timer);
...
});
});Apply to all three functions: dispatchToJules, listJulesSessions, and pullJulesSession.
| // Pass API key if configured (used by Jules MCP server, CLI uses OAuth) | ||
| ...(settings.julesApiKey ? { JULES_API_KEY: settings.julesApiKey } : {}), | ||
| }, |
There was a problem hiding this comment.
Do not inject JULES_API_KEY into CLI env unless strictly required.
Line 71–Line 73 exports a secret into child process environment even though the module header says the CLI flow uses OAuth. This broadens secret exposure unnecessarily.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/desktop/src/main/agent/jules-dispatcher.ts` around lines 71 - 73, Remove
the unconditional injection of the secret JULES_API_KEY into the child process
env: only include settings.julesApiKey when the child process requires API-key
auth (e.g., the Jules MCP server), not for the CLI/OAuth flow. Update the env
spread that currently uses ...(settings.julesApiKey ? { JULES_API_KEY:
settings.julesApiKey } : {}) to be gated by a flag or explicit check for the
non-CLI execution path (use the same launcher/context check you have for MCP vs
CLI), so that JULES_API_KEY is omitted when launching the CLI flow.
| {settings.agentFramework === 'custom' && ( | ||
| <div className="space-y-3 pl-4 border-l-2 border-muted"> | ||
| <div className="space-y-2"> | ||
| <Label htmlFor="customAgentCommand" className="text-sm font-medium text-foreground">{t('general.customAgentCommand')}</Label> | ||
| <p className="text-sm text-muted-foreground">{t('general.customAgentCommandDescription')}</p> | ||
| <Input | ||
| id="customAgentCommand" | ||
| placeholder={t('general.customAgentCommandPlaceholder')} | ||
| className="w-full max-w-md" | ||
| value={settings.customAgentCommand || ''} | ||
| onChange={(e) => onSettingsChange({ ...settings, customAgentCommand: e.target.value })} | ||
| /> |
There was a problem hiding this comment.
Block empty custom command when agentFramework === 'custom'.
Line 201 and Line 202 allow saving an empty command, which will fail at execution time for the custom backend path. Add a required validation guard in this UI (and keep server-side validation too).
Suggested UI-side guard
+const isCustomCommandValid =
+ settings.agentFramework !== 'custom' || Boolean(settings.customAgentCommand?.trim());
...
{settings.agentFramework === 'custom' && (
<div className="space-y-3 pl-4 border-l-2 border-muted">
<div className="space-y-2">
<Label htmlFor="customAgentCommand" className="text-sm font-medium text-foreground">{t('general.customAgentCommand')}</Label>
<p className="text-sm text-muted-foreground">{t('general.customAgentCommandDescription')}</p>
<Input
id="customAgentCommand"
placeholder={t('general.customAgentCommandPlaceholder')}
className="w-full max-w-md"
value={settings.customAgentCommand || ''}
+ aria-invalid={!isCustomCommandValid}
onChange={(e) => onSettingsChange({ ...settings, customAgentCommand: e.target.value })}
/>
+ {!isCustomCommandValid && (
+ <p className="text-sm text-destructive">{t('general.customAgentCommandRequired')}</p>
+ )}
</div>
</div>
)}📝 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.
| {settings.agentFramework === 'custom' && ( | |
| <div className="space-y-3 pl-4 border-l-2 border-muted"> | |
| <div className="space-y-2"> | |
| <Label htmlFor="customAgentCommand" className="text-sm font-medium text-foreground">{t('general.customAgentCommand')}</Label> | |
| <p className="text-sm text-muted-foreground">{t('general.customAgentCommandDescription')}</p> | |
| <Input | |
| id="customAgentCommand" | |
| placeholder={t('general.customAgentCommandPlaceholder')} | |
| className="w-full max-w-md" | |
| value={settings.customAgentCommand || ''} | |
| onChange={(e) => onSettingsChange({ ...settings, customAgentCommand: e.target.value })} | |
| /> | |
| const isCustomCommandValid = | |
| settings.agentFramework !== 'custom' || Boolean(settings.customAgentCommand?.trim()); | |
| {settings.agentFramework === 'custom' && ( | |
| <div className="space-y-3 pl-4 border-l-2 border-muted"> | |
| <div className="space-y-2"> | |
| <Label htmlFor="customAgentCommand" className="text-sm font-medium text-foreground">{t('general.customAgentCommand')}</Label> | |
| <p className="text-sm text-muted-foreground">{t('general.customAgentCommandDescription')}</p> | |
| <Input | |
| id="customAgentCommand" | |
| placeholder={t('general.customAgentCommandPlaceholder')} | |
| className="w-full max-w-md" | |
| value={settings.customAgentCommand || ''} | |
| aria-invalid={!isCustomCommandValid} | |
| onChange={(e) => onSettingsChange({ ...settings, customAgentCommand: e.target.value })} | |
| /> | |
| {!isCustomCommandValid && ( | |
| <p className="text-sm text-destructive">{t('general.customAgentCommandRequired')}</p> | |
| )} | |
| </div> | |
| </div> | |
| )} |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/desktop/src/renderer/components/settings/GeneralSettings.tsx` around
lines 192 - 203, When settings.agentFramework === 'custom', add a UI-side
required validation for the customAgentCommand input so an empty string cannot
be saved: validate the Input with id "customAgentCommand" (used inside
GeneralSettings.tsx) before calling onSettingsChange/save, show a user-facing
error message near the Label/Input and prevent the save action (disable the Save
button or block the submit handler) until settings.customAgentCommand is
non-empty; keep existing server-side validation as well.
| colorTheme?: ColorTheme; | ||
| defaultModel: string; | ||
| agentFramework: string; | ||
| agentFramework: 'auto-claude' | 'jules' | 'claude-code' | 'gemini' | 'antigravity' | 'custom' | string; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n apps/desktop/src/shared/types/settings.ts | sed -n '260,280p'Repository: AndyMik90/Aperant
Length of output: 1048
🏁 Script executed:
# Check the overall file size and structure
wc -l apps/desktop/src/shared/types/settings.ts
head -20 apps/desktop/src/shared/types/settings.tsRepository: AndyMik90/Aperant
Length of output: 1065
🏁 Script executed:
# Search for agentFramework usage across the codebase
rg 'agentFramework' --type ts --type tsx -A 2 -B 2Repository: AndyMik90/Aperant
Length of output: 88
🏁 Script executed:
# Check TypeScript configuration
cat tsconfig.json | head -30Repository: AndyMik90/Aperant
Length of output: 106
🏁 Script executed:
# Search for agentFramework usage with correct syntax
rg 'agentFramework' -A 2 -B 2Repository: AndyMik90/Aperant
Length of output: 8284
🏁 Script executed:
# Find tsconfig.json
fd tsconfig.jsonRepository: AndyMik90/Aperant
Length of output: 87
🏁 Script executed:
# Check if there are type narrowing patterns based on agentFramework
rg "case 'jules'|case 'auto-claude'|case 'custom'" -A 2 -B 2Repository: AndyMik90/Aperant
Length of output: 824
🏁 Script executed:
# Look for switch statements or if conditions checking agentFramework
rg 'if.*agentFramework|switch.*agentFramework' -A 3Repository: AndyMik90/Aperant
Length of output: 43
🏁 Script executed:
cat apps/desktop/tsconfig.json | grep -A 5 '"strict"'Repository: AndyMik90/Aperant
Length of output: 241
🏁 Script executed:
# Look at the full compilerOptions
cat apps/desktop/tsconfig.json | sed -n '/"compilerOptions"/,/^ }/p' | head -30Repository: AndyMik90/Aperant
Length of output: 800
🏁 Script executed:
# Check if type narrowing with agentFramework could be improved with stricter types
rg "settings\.agentFramework ===" -B 2 -A 5Repository: AndyMik90/Aperant
Length of output: 2164
Remove | string from the agentFramework union type to preserve type safety.
The | string suffix collapses the entire literal union to string, eliminating TypeScript's ability to perform type narrowing. Although the code uses settings.agentFramework === 'jules' and similar checks, the type is weakened at the declaration level, conflicting with the strict mode requirement.
Extract the literal values into a named AgentFramework type without the | string suffix:
Suggested fix
+export type AgentFramework =
+ | 'auto-claude'
+ | 'jules'
+ | 'claude-code'
+ | 'gemini'
+ | 'antigravity'
+ | 'custom';
+
export interface AppSettings {
theme: 'light' | 'dark' | 'system';
colorTheme?: ColorTheme;
defaultModel: string;
- agentFramework: 'auto-claude' | 'jules' | 'claude-code' | 'gemini' | 'antigravity' | 'custom' | string;
+ agentFramework: AgentFramework;Per coding guidelines: "Use TypeScript strict mode. Perform type checking with npm run typecheck."
📝 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.
| agentFramework: 'auto-claude' | 'jules' | 'claude-code' | 'gemini' | 'antigravity' | 'custom' | string; | |
| export type AgentFramework = | |
| | 'auto-claude' | |
| | 'jules' | |
| | 'claude-code' | |
| | 'gemini' | |
| | 'antigravity' | |
| | 'custom'; | |
| export interface AppSettings { | |
| theme: 'light' | 'dark' | 'system'; | |
| colorTheme?: ColorTheme; | |
| defaultModel: string; | |
| agentFramework: AgentFramework; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/desktop/src/shared/types/settings.ts` at line 266, The union for
agentFramework currently ends with "| string", which collapses the literal types
and defeats narrowing; remove the "| string" and instead declare a named literal
union type (e.g., AgentFramework = 'auto-claude' | 'jules' | 'claude-code' |
'gemini' | 'antigravity' | 'custom') and use that type for the agentFramework
property (replace the existing inline union with AgentFramework) so TypeScript
strict narrowing (e.g., settings.agentFramework === 'jules') works correctly.
| /** Jules API key (from https://jules.google/settings) — used when agentFramework is 'jules' */ | ||
| julesApiKey?: string; |
There was a problem hiding this comment.
Avoid storing Jules API key as a regular app-settings field.
Line 268 introduces julesApiKey on AppSettings; given the current save pipeline forwards settings directly, this is likely persisted in plaintext. Move secrets to OS keychain/credential store and keep only a reference/flag in settings.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/desktop/src/shared/types/settings.ts` around lines 267 - 268, The
AppSettings field julesApiKey should not be persisted in plaintext; remove
julesApiKey from the AppSettings type and instead store the secret in the OS
keychain/credential store via your existing secret manager APIs (e.g., use
functions like setCredential/getCredential or add a new secrets service).
Replace direct usages of AppSettings.julesApiKey with calls to a new helper
(e.g., getJulesApiKey() / setJulesApiKey()) that read/write the keychain, and
keep only a non-secret flag in AppSettings if needed (e.g., julesApiKeyPresent:
boolean) to indicate presence without storing the secret itself.
Route coding/QA phases to external frameworks (Jules, Claude Code, Gemini CLI, Antigravity, Custom) based on agentFramework setting. - Add framework-router.ts: routePhase(), executeExternal(), requiresBuiltinAuth() - Wire routing into startTaskExecution() and startQAProcess() - Make auth pre-flight conditional (skip for external frameworks) - Jules uses dispatchToJules() with structured API; others spawn CLI - Antigravity handles spec/planning phases; falls back to Aperant for coding Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| externalArgs: ['E:/Work/antigravity-mcp-server/dist/index.js'], | ||
| extraEnv: {}, |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
| if (settings) { | ||
| emitter.emit('log', taskId, `[FrameworkRouter] Dispatching to Jules: ${prompt.substring(0, 100)}...`, projectId); | ||
| emitter.emit('execution-progress', taskId, { |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/desktop/src/main/agent/agent-manager.ts`:
- Around line 554-570: When routing the coding phase to an external framework
(when codingDecision.useBuiltinWorker is false) the method returns after
executeExternal() without emitting the 'exit' event, which prevents
cleanup/unregister behavior; after calling executeExternal(codingDecision,
prompt, effectiveCwd, this, taskId, projectId) ensure you emit the exit event
(e.g., this.emit('exit', taskId, projectId)) in both success and error paths
before returning, and apply the same change to the other external-route branch
that uses executeExternal elsewhere in this file (referencing executeExternal,
codingDecision.useBuiltinWorker, and this.emit('exit')).
In `@apps/desktop/src/main/agent/framework-router.ts`:
- Around line 155-162: The progress events inside executeExternal currently
hardcode the "coding" phase, causing QA-delegated work to emit incorrect
telemetry; change the emitter.emit('progress', ...) calls to derive the phase
from the passed-in decision (e.g., const phase = decision.phase || decision.type
|| 'coding') and use that variable for the phase field, updating every
occurrence in executeExternal (including the other emitter.emit locations
referenced) so QA decisions emit "qa" (or the appropriate decision phase)
instead of always "coding".
- Around line 70-77: Replace the hardcoded Windows path in the framework-router
branch for 'antigravity' with platform-aware resolution: add and call a helper
like resolveAntigravityEntry(settings) from this module that returns the entry
path using the app packaging/runtime helpers (app.isPackaged,
process.resourcesPath and the platform utilities in
apps/desktop/src/main/platform/) or falls back to
settings.antigravityServerPath; then set externalArgs to use that resolved path
and handle null (return useBuiltinWorker:true or error) so the code works on dev
and packaged builds; apply the same replacement for the other antigravity
occurrence around the 84–90 block.
- Around line 131-136: The current naive customAgentCommand tokenization using
split(' ') breaks quoted paths/args (e.g., "C:\Program Files\tool.exe" or "hello
world"); change the parsing of settings.customAgentCommand in
framework-router.ts to use a shell-style tokenizer (e.g., string-argv or
shell-quote) instead of split(' '), then set customCmd = parsedArgs (or fallback
to ['echo', 'No custom command configured']) and keep externalCommand =
customCmd[0] and externalArgs = customCmd.slice(1); also trim the input and
handle empty/null settings.customAgentCommand consistently so quoted paths and
spaced arguments remain intact.
- Around line 201-204: The code currently appends the raw prompt into args for
decision.framework 'claude-code' and 'gemini' and calls spawn() with shell:
true, which allows shell metacharacters in prompt to be executed; update the
spawn invocation so that shell: true is used only for the 'custom' framework and
ensure for frameworks 'claude-code' and 'gemini' spawn() is called with shell:
false (or omitted) so args (including prompt pushed by the block referencing
decision.framework and args) are passed safely without shell interpretation;
locate the logic around decision.framework, args.push(prompt), and the
spawn(...) call to conditionally set the shell option accordingly.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 18d88dd7-5f3a-4cee-9733-8467995625b2
📒 Files selected for processing (2)
apps/desktop/src/main/agent/agent-manager.tsapps/desktop/src/main/agent/framework-router.ts
| // Route to external framework if configured (Jules, Claude Code, Gemini, Custom) | ||
| if (!codingDecision.useBuiltinWorker) { | ||
| const specPath = path.join(effectiveCwd, getSpecsDir(project?.autoBuildPath), specId, 'spec.md'); | ||
| let prompt = `Implement spec ${specId} in ${effectiveCwd}`; | ||
| try { | ||
| if (existsSync(specPath)) { | ||
| prompt = readFileSync(specPath, 'utf-8'); | ||
| } | ||
| } catch { /* agent will read spec itself */ } | ||
|
|
||
| // Note (Python fallback preserved for reference): | ||
| // const combinedEnv = this.processManager.getCombinedEnv(projectPath); | ||
| // const args = [runPath, '--spec', specId, '--project-dir', projectPath, '--auto-continue', '--force']; | ||
| // await this.processManager.spawnProcess(taskId, projectPath, args, combinedEnv, 'task-execution', projectId); | ||
| this.emit('log', taskId, `[AgentManager] Routing coding phase to ${codingDecision.framework}`, projectId); | ||
| const result = await executeExternal(codingDecision, prompt, effectiveCwd, this, taskId, projectId); | ||
| if (!result.success) { | ||
| this.emit('error', taskId, `${codingDecision.framework} failed: ${result.output}`, projectId); | ||
| } | ||
| return; | ||
| } |
There was a problem hiding this comment.
Emit exit for external routes to preserve lifecycle cleanup/registry behavior.
These branches return immediately after executeExternal(), but this class’s cleanup and unregister flow is driven by the exit event listener. Externally routed task execution can leave stale context/registry state.
🧩 Proposed fix
-const result = await executeExternal(codingDecision, prompt, effectiveCwd, this, taskId, projectId);
+const result = await executeExternal(codingDecision, prompt, effectiveCwd, this, taskId, 'coding', projectId);
if (!result.success) {
this.emit('error', taskId, `${codingDecision.framework} failed: ${result.output}`, projectId);
}
+this.emit('exit', taskId, result.success ? 0 : 1, 'task-execution', projectId);
return;-const result = await executeExternal(qaDecision, prompt, effectiveCwd, this, taskId, projectId);
+const result = await executeExternal(qaDecision, prompt, effectiveCwd, this, taskId, 'qa', projectId);
if (!result.success) {
this.emit('error', taskId, `${qaDecision.framework} QA failed: ${result.output}`, projectId);
}
+this.emit('exit', taskId, result.success ? 0 : 1, 'qa-process', projectId);
return;Also applies to: 616-633
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/desktop/src/main/agent/agent-manager.ts` around lines 554 - 570, When
routing the coding phase to an external framework (when
codingDecision.useBuiltinWorker is false) the method returns after
executeExternal() without emitting the 'exit' event, which prevents
cleanup/unregister behavior; after calling executeExternal(codingDecision,
prompt, effectiveCwd, this, taskId, projectId) ensure you emit the exit event
(e.g., this.emit('exit', taskId, projectId)) in both success and error paths
before returning, and apply the same change to the other external-route branch
that uses executeExternal elsewhere in this file (referencing executeExternal,
codingDecision.useBuiltinWorker, and this.emit('exit')).
| if (framework === 'antigravity') { | ||
| return { | ||
| framework: 'antigravity', | ||
| useBuiltinWorker: false, | ||
| externalCommand: 'node', | ||
| externalArgs: ['E:/Work/antigravity-mcp-server/dist/index.js'], | ||
| extraEnv: {}, | ||
| }; |
There was a problem hiding this comment.
Replace the machine-specific Antigravity path with platform-aware resolution.
The hardcoded E:/Work/antigravity-mcp-server/dist/index.js will fail on non-Windows systems and typical packaged Electron installs.
🔧 Proposed fix
+const antigravityEntry = resolveAntigravityEntry(settings);
+if (!antigravityEntry) {
+ return { framework: 'auto-claude', useBuiltinWorker: true };
+}
+
return {
framework: 'antigravity',
useBuiltinWorker: false,
externalCommand: 'node',
- externalArgs: ['E:/Work/antigravity-mcp-server/dist/index.js'],
+ externalArgs: [antigravityEntry],
};// Add helper near this module (implementation should use app.isPackaged/process.resourcesPath + platform helpers)
function resolveAntigravityEntry(settings: AppSettings | null): string | null {
return settings?.antigravityServerPath?.trim() || null;
}As per coding guidelines: "Cross-platform development must use platform abstraction functions from apps/desktop/src/main/platform/..." and "For bug fixes in the Electron app, check path resolution differences between dev and production builds (app.isPackaged, process.resourcesPath)."
Also applies to: 84-90
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/desktop/src/main/agent/framework-router.ts` around lines 70 - 77,
Replace the hardcoded Windows path in the framework-router branch for
'antigravity' with platform-aware resolution: add and call a helper like
resolveAntigravityEntry(settings) from this module that returns the entry path
using the app packaging/runtime helpers (app.isPackaged, process.resourcesPath
and the platform utilities in apps/desktop/src/main/platform/) or falls back to
settings.antigravityServerPath; then set externalArgs to use that resolved path
and handle null (return useBuiltinWorker:true or error) so the code works on dev
and packaged builds; apply the same replacement for the other antigravity
occurrence around the 84–90 block.
| const customCmd = settings?.customAgentCommand?.split(' ') ?? ['echo', 'No custom command configured']; | ||
| return { | ||
| framework, | ||
| useBuiltinWorker: false, | ||
| externalCommand: customCmd[0], | ||
| externalArgs: customCmd.slice(1), |
There was a problem hiding this comment.
split(' ') breaks valid custom commands with quotes/spaces.
Quoted args and paths like "C:\Program Files\tool.exe" --flag "hello world" are tokenized incorrectly.
🛠️ Proposed fix
-const customCmd = settings?.customAgentCommand?.split(' ') ?? ['echo', 'No custom command configured'];
+const rawCustom = settings?.customAgentCommand?.trim();
+if (!rawCustom) {
+ return {
+ framework,
+ useBuiltinWorker: false,
+ externalCommand: 'echo',
+ externalArgs: ['No custom command configured'],
+ };
+}
+const customCmd = parseCommand(rawCustom);
return {
framework,
useBuiltinWorker: false,
externalCommand: customCmd[0],
externalArgs: customCmd.slice(1),
};🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/desktop/src/main/agent/framework-router.ts` around lines 131 - 136, The
current naive customAgentCommand tokenization using split(' ') breaks quoted
paths/args (e.g., "C:\Program Files\tool.exe" or "hello world"); change the
parsing of settings.customAgentCommand in framework-router.ts to use a
shell-style tokenizer (e.g., string-argv or shell-quote) instead of split(' '),
then set customCmd = parsedArgs (or fallback to ['echo', 'No custom command
configured']) and keep externalCommand = customCmd[0] and externalArgs =
customCmd.slice(1); also trim the input and handle empty/null
settings.customAgentCommand consistently so quoted paths and spaced arguments
remain intact.
| export async function executeExternal( | ||
| decision: FrameworkDecision, | ||
| prompt: string, | ||
| cwd: string, | ||
| emitter: EventEmitter, | ||
| taskId: string, | ||
| projectId?: string, | ||
| ): Promise<{ success: boolean; output: string }> { |
There was a problem hiding this comment.
External progress events hardcode coding, so QA routing emits wrong phase telemetry.
When QA is delegated, this function still reports coding-phase progress.
📡 Proposed fix
export async function executeExternal(
decision: FrameworkDecision,
prompt: string,
cwd: string,
emitter: EventEmitter,
taskId: string,
+ phase: 'coding' | 'qa',
projectId?: string,
): Promise<{ success: boolean; output: string }> {
@@
- emitter.emit('execution-progress', taskId, {
- phase: 'coding',
+ emitter.emit('execution-progress', taskId, {
+ phase,
phaseProgress: 10,
overallProgress: 30,
message: 'Dispatching task to Jules (Google Cloud)...',
}, projectId);
@@
- emitter.emit('execution-progress', taskId, {
- phase: 'coding',
+ emitter.emit('execution-progress', taskId, {
+ phase,
phaseProgress: 100,
overallProgress: 50,
message: `Jules session ${result.sessionId} created. Task running in cloud VM.`,
}, projectId);
@@
- emitter.emit('execution-progress', taskId, {
- phase: 'coding',
+ emitter.emit('execution-progress', taskId, {
+ phase,
phaseProgress: 10,
overallProgress: 30,
message: `Running via ${decision.framework}...`,
}, projectId);
@@
- emitter.emit('execution-progress', taskId, {
- phase: success ? 'completed' : 'failed',
+ emitter.emit('execution-progress', taskId, {
+ phase,
phaseProgress: success ? 100 : 0,
overallProgress: success ? 100 : 0,
message: success ? `${decision.framework} completed successfully` : `${decision.framework} exited with code ${code}`,
}, projectId);Also applies to: 172-177, 183-188, 207-212, 247-252
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/desktop/src/main/agent/framework-router.ts` around lines 155 - 162, The
progress events inside executeExternal currently hardcode the "coding" phase,
causing QA-delegated work to emit incorrect telemetry; change the
emitter.emit('progress', ...) calls to derive the phase from the passed-in
decision (e.g., const phase = decision.phase || decision.type || 'coding') and
use that variable for the phase field, updating every occurrence in
executeExternal (including the other emitter.emit locations referenced) so QA
decisions emit "qa" (or the appropriate decision phase) instead of always
"coding".
| // For CLI tools that accept prompt as argument (claude -p, gemini -p) | ||
| if (decision.framework === 'claude-code' || decision.framework === 'gemini') { | ||
| args.push(prompt); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -nP --type=ts -C3 'spawn\([^)]*\{[^}]*shell:\s*true' apps/desktop/src/main
rg -nP --type=ts -C3 'args\.push\(prompt\)|stdin\?\.write\(prompt\)' apps/desktop/src/mainRepository: AndyMik90/Aperant
Length of output: 1822
🏁 Script executed:
cat -n apps/desktop/src/main/agent/framework-router.ts | sed -n '195,230p'Repository: AndyMik90/Aperant
Length of output: 1440
🏁 Script executed:
cat -n apps/desktop/src/main/agent/framework-router.ts | sed -n '230,250p'Repository: AndyMik90/Aperant
Length of output: 939
🏁 Script executed:
rg -nP --type=ts 'decision\.framework\s*===\s*['\''"]' apps/desktop/src/main/agent/framework-router.tsRepository: AndyMik90/Aperant
Length of output: 238
🏁 Script executed:
rg -nP --type=ts 'decision\.framework' apps/desktop/src/main/agent/framework-router.ts | head -20Repository: AndyMik90/Aperant
Length of output: 527
Disable shell: true for prompt-bearing commands to prevent shell injection.
When prompt is passed as an argument to spawn() (lines 203: claude-code, gemini frameworks), and shell: true is enabled (line 217), shell metacharacters in the prompt are interpreted by the shell, enabling arbitrary command injection. The custom framework safely pipes prompt to stdin and is the only case where shell: true is appropriate.
Fix
const child = spawn(decision.externalCommand!, args, {
cwd,
env: { ...process.env, ...(decision.extraEnv ?? {}) },
- shell: true,
+ shell: decision.framework === 'custom',
});📝 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.
| // For CLI tools that accept prompt as argument (claude -p, gemini -p) | |
| if (decision.framework === 'claude-code' || decision.framework === 'gemini') { | |
| args.push(prompt); | |
| } | |
| const child = spawn(decision.externalCommand!, args, { | |
| cwd, | |
| env: { ...process.env, ...(decision.extraEnv ?? {}) }, | |
| shell: decision.framework === 'custom', | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/desktop/src/main/agent/framework-router.ts` around lines 201 - 204, The
code currently appends the raw prompt into args for decision.framework
'claude-code' and 'gemini' and calls spawn() with shell: true, which allows
shell metacharacters in prompt to be executed; update the spawn invocation so
that shell: true is used only for the 'custom' framework and ensure for
frameworks 'claude-code' and 'gemini' spawn() is called with shell: false (or
omitted) so args (including prompt pushed by the block referencing
decision.framework and args) are passed safely without shell interpretation;
locate the logic around decision.framework, args.push(prompt), and the
spawn(...) call to conditionally set the shell option accordingly.
Jules CLI supports piped input (cat prompt.txt | jules new). Using stdin instead of passing the full spec as a CLI argument avoids the Windows CreateProcess ~8K character limit that silently truncates long prompts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| } catch { /* agent will read spec itself */ } | ||
|
|
||
| // Note (Python fallback preserved for reference): | ||
| // const combinedEnv = this.processManager.getCombinedEnv(projectPath); | ||
| // const args = [runPath, '--spec', specId, '--project-dir', projectPath, '--auto-continue', '--force']; | ||
| // await this.processManager.spawnProcess(taskId, projectPath, args, combinedEnv, 'task-execution', projectId); | ||
| this.emit('log', taskId, `[AgentManager] Routing coding phase to ${codingDecision.framework}`, projectId); | ||
| const result = await executeExternal(codingDecision, prompt, effectiveCwd, this, taskId, projectId); | ||
| if (!result.success) { | ||
| this.emit('error', taskId, `${codingDecision.framework} failed: ${result.output}`, projectId); | ||
| } | ||
| return; | ||
| } | ||
|
|
||
| await this.processManager.spawnWorkerProcess(taskId, executorConfig, {}, 'task-execution', projectId); |
There was a problem hiding this comment.
Bug: The startTaskExecution method creates a git worktree before checking if an external framework is used, leading to orphaned worktrees and wasted resources when routing externally.
Severity: MEDIUM
Suggested Fix
Move the external routing check to before the worktree creation logic. The check if (!codingDecision.useBuiltinWorker) should be performed early in the function. If an external worker is to be used, handle that routing and return immediately, avoiding the unnecessary setup and worktree creation intended only for the built-in worker.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location: apps/desktop/src/main/agent/agent-manager.ts#L551-L572
Potential issue: In the `startTaskExecution` method, a git worktree is unconditionally
created before the routing decision is acted upon. If the task is routed to an external
framework, the function returns, but the worktree is never used or cleaned up. This
leads to an accumulation of orphaned git worktrees and branches in the user's
repository, consuming disk space and cluttering the git history. The expensive setup
operations, including worktree and branch creation, are wasted resources when an
external worker is selected.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/desktop/src/main/agent/jules-dispatcher.ts`:
- Line 18: Replace the relative import of AppSettings with the project path
alias: change the import that currently references '../../shared/types' to use
'@shared/types' so the symbol AppSettings is imported via the `@shared` path
(update the import statement where AppSettings is referenced in
jules-dispatcher.ts).
- Around line 161-171: Modify pullJulesSession to accept a cwd parameter (e.g.,
cwd?: string) and pass it into spawn so the Jules CLI runs in the intended
repository; specifically update the function signature
pullJulesSession(sessionId: string, apply: boolean = false, cwd?: string) and
add the cwd option to the spawn call (spawn('jules', args, { shell: true, cwd
})). Ensure any callers are updated or default to undefined so behavior remains
unchanged when cwd is not provided.
- Around line 102-103: The session parsing is brittle: replace the numeric-only
match that sets sessionMatch (currently using
output.match(/session[:\s]+(\d+)/i) || output.match(/ID[:\s]+(\d+)/i)) with a
more permissive capture for non-space session IDs (e.g. capture \S+), then
validate the captured value against the expected sessionId type (sessionId?:
string) and reject if empty or contains unexpected characters; when validation
fails, log the full raw CLI output to surface upstream format changes. Also make
the human-table parsing that splits on multiple spaces more robust by splitting
rows on /\r?\n/ then splitting columns with a regex for two-or-more spaces
(/\s{2,}/) and trimming cells, taking the first cell as the session ID; if any
step fails, emit a clear error log including the raw output and return no
session rather than silently continuing.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: beb316eb-34c1-4b4c-a78d-756dbb9dd096
📒 Files selected for processing (1)
apps/desktop/src/main/agent/jules-dispatcher.ts
|
|
||
| import { spawn } from 'child_process'; | ||
| import { EventEmitter } from 'events'; | ||
| import type { AppSettings } from '../../shared/types'; |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Use the shared path alias for AppSettings import.
Line 18 uses a relative import in a TS file where aliases are required; switch this to @shared/*.
As per coding guidelines, “Use path aliases defined in tsconfig.json for imports: @shared/*”.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/desktop/src/main/agent/jules-dispatcher.ts` at line 18, Replace the
relative import of AppSettings with the project path alias: change the import
that currently references '../../shared/types' to use '@shared/types' so the
symbol AppSettings is imported via the `@shared` path (update the import statement
where AppSettings is referenced in jules-dispatcher.ts).
| const sessionMatch = output.match(/session[:\s]+(\d+)/i) || | ||
| output.match(/ID[:\s]+(\d+)/i); |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
What is the current output format of jules newandjules remote list --sessionin the latest Jules CLI, and doesjules remote list --session support JSON output?
💡 Result:
jules new (latest Jules Tools / @google/jules)
- What it prints: the CLI docs don’t specify any stable, machine-readable stdout format for
jules new; it’s primarily a “create a new remote session” command (often used with prompts via stdin), and you typically obtain the resulting session id by subsequently listing sessions. [2] - Note: In the official CLI reference, the documented command for creating a session is
jules remote new(notjules new). [1]
jules remote list --session
- Current output format: human-readable text table (header row + one row per session). Scripts in the wild rely on:
- session id in the first column (e.g.,
awk 'NR==2 {print $1}'to grab the newest-listed session id) [4] - columns separated by multiple spaces, with status in the last column (e.g., splitting on
\s{2,}and taking the last field). [3]
- session id in the first column (e.g.,
Does jules remote list --session support JSON output?
- No JSON output option is documented for
jules remote list --session(no--json/--output-formatflag shown in the official Jules Tools CLI reference), and the command is treated as a plain-text/table output in scripting examples. [1] [3] [4]
Sources: [1] Jules Tools CLI reference (jules.google) (jules.google), [2] Mad Devs article showing jules new --repo ... usage (maddevs.io), [3] parsing example relying on multi-space-separated columns & last-column status (playbooks.com), [4] parsing example relying on first-column session id (playbooks.com)
Citations:
- 1: https://jules.google/docs/cli/reference/
- 2: https://maddevs.io/writeups/feeding-docker-logs-to-google-jules-and-getting-features/
- 3: https://playbooks.com/skills/openclaw/skills/jules-cli?utm_source=openai
- 4: https://playbooks.com/skills/sanjay3290/postgres-skill/jules?utm_source=openai
🏁 Script executed:
# Check file size first
wc -l apps/desktop/src/main/agent/jules-dispatcher.tsRepository: AndyMik90/Aperant
Length of output: 112
🏁 Script executed:
# Read the relevant sections of the file with context
sed -n '95,160p' apps/desktop/src/main/agent/jules-dispatcher.ts | cat -nRepository: AndyMik90/Aperant
Length of output: 2161
🏁 Script executed:
# Search for session ID usage patterns
rg -i "session.*id|session.*match" apps/desktop/src/main/agent/ -A 2 -B 2Repository: AndyMik90/Aperant
Length of output: 12651
Session parsing is brittle against CLI output changes.
Lines 102-103 use a numeric-only regex, but session IDs are stored as strings (line 12, sessionId?: string), suggesting support for non-numeric formats. Similarly, lines 141-151 parse a human table by splitting on multiple spaces, which is fragile to CLI formatting changes.
The web search confirms jules remote list --session outputs a human-readable text table (no JSON output option exists), with session IDs in the first column and columns separated by multiple spaces. However, this format is not guaranteed to remain stable.
Consider adding validation for the extracted session ID and logging the raw output when parsing fails, to surface upstream CLI changes early.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/desktop/src/main/agent/jules-dispatcher.ts` around lines 102 - 103, The
session parsing is brittle: replace the numeric-only match that sets
sessionMatch (currently using output.match(/session[:\s]+(\d+)/i) ||
output.match(/ID[:\s]+(\d+)/i)) with a more permissive capture for non-space
session IDs (e.g. capture \S+), then validate the captured value against the
expected sessionId type (sessionId?: string) and reject if empty or contains
unexpected characters; when validation fails, log the full raw CLI output to
surface upstream format changes. Also make the human-table parsing that splits
on multiple spaces more robust by splitting rows on /\r?\n/ then splitting
columns with a regex for two-or-more spaces (/\s{2,}/) and trimming cells,
taking the first cell as the session ID; if any step fails, emit a clear error
log including the raw output and return no session rather than silently
continuing.
| export async function pullJulesSession( | ||
| sessionId: string, | ||
| apply: boolean = false | ||
| ): Promise<{ success: boolean; output: string }> { | ||
| const args = ['remote', 'pull', '--session', sessionId]; | ||
| if (apply) { | ||
| args.push('--apply'); | ||
| } | ||
|
|
||
| return new Promise((resolve) => { | ||
| const child = spawn('jules', args, { shell: true }); |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
For the latest Jules CLI, does jules remote pull --session --apply apply changes relative to the current working directory, and is an explicit repo path/cwd recommended?
💡 Result:
Yes—when you use jules remote pull --session <id> --apply, the CLI applies the patch to the repository it infers from your current working directory (i.e., whichever local Git repo you’re currently “in”), rather than to some path embedded in the session ID. Jules’ CLI docs explicitly note this “infer repo from CWD” behavior (documented for remote new), and the changelog describes “apply patches locally” as a local-working-copy operation. [1], [2]
Recommendation: in scripts/CI, set an explicit cwd (e.g., cd /path/to/repo first, ideally the repo root) before running --apply, so you don’t accidentally apply to the wrong checkout or a non-repo directory. The official CLI reference doesn’t document a separate --repo <path>/--cwd flag for remote pull, so controlling the working directory is the reliable way to disambiguate. [1], [3]
Sources: [1] [2] [3]
🏁 Script executed:
# Search for all calls to pullJulesSession
rg 'pullJulesSession' --type ts --type tsx -B 2 -A 2Repository: AndyMik90/Aperant
Length of output: 88
🏁 Script executed:
# Search for all calls to pullJulesSession
rg 'pullJulesSession' --type ts -B 2 -A 2Repository: AndyMik90/Aperant
Length of output: 450
🏁 Script executed:
# Search for calls to pullJulesSession (excluding the definition itself)
rg 'pullJulesSession\s*\(' --type ts -B 3 -A 3 | grep -v '^apps/desktop/src/main/agent/jules-dispatcher.ts-export'Repository: AndyMik90/Aperant
Length of output: 601
🏁 Script executed:
# Search for actual invocations of pullJulesSession (where it's being called)
rg 'pullJulesSession\(' --type ts | grep -v 'export async function pullJulesSession'Repository: AndyMik90/Aperant
Length of output: 43
🏁 Script executed:
# Check if pullJulesSession is imported anywhere
rg 'pullJulesSession' --type tsRepository: AndyMik90/Aperant
Length of output: 148
🏁 Script executed:
# Check the full context of the pullJulesSession function and its usage
head -n 200 apps/desktop/src/main/agent/jules-dispatcher.ts | tail -n 50Repository: AndyMik90/Aperant
Length of output: 1356
🏁 Script executed:
# Search for any file that imports from jules-dispatcher to see what's being used
rg 'from.*jules-dispatcher|import.*jules-dispatcher' --type tsRepository: AndyMik90/Aperant
Length of output: 188
🏁 Script executed:
# Check if pullJulesSession is used within the same file (jules-dispatcher.ts)
rg 'pullJulesSession' apps/desktop/src/main/agent/jules-dispatcher.tsRepository: AndyMik90/Aperant
Length of output: 100
🏁 Script executed:
# Check the complete jules-dispatcher.ts file to understand the full context
wc -l apps/desktop/src/main/agent/jules-dispatcher.tsRepository: AndyMik90/Aperant
Length of output: 112
🏁 Script executed:
# Read the entire file to see pullJulesSession in full context and check for its usage
cat -n apps/desktop/src/main/agent/jules-dispatcher.tsRepository: AndyMik90/Aperant
Length of output: 6516
Add cwd parameter to pullJulesSession to ensure patches are applied to the correct repository.
When --apply is used, the Jules CLI applies patches relative to the current working directory. Without an explicit cwd parameter, patch application depends on the Electron main process's working directory, which is unreliable. The dispatchToJules function in the same file already follows this pattern correctly by accepting cwd and passing it to spawn(); pullJulesSession should do the same.
export async function pullJulesSession(
sessionId: string,
+ cwd: string,
apply: boolean = false
): Promise<{ success: boolean; output: string }> {
const args = ['remote', 'pull', '--session', sessionId];
if (apply) {
args.push('--apply');
}
return new Promise((resolve) => {
- const child = spawn('julius', args, { shell: true });
+ const child = spawn('julius', args, { cwd, shell: true });📝 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.
| export async function pullJulesSession( | |
| sessionId: string, | |
| apply: boolean = false | |
| ): Promise<{ success: boolean; output: string }> { | |
| const args = ['remote', 'pull', '--session', sessionId]; | |
| if (apply) { | |
| args.push('--apply'); | |
| } | |
| return new Promise((resolve) => { | |
| const child = spawn('jules', args, { shell: true }); | |
| export async function pullJulesSession( | |
| sessionId: string, | |
| cwd: string, | |
| apply: boolean = false | |
| ): Promise<{ success: boolean; output: string }> { | |
| const args = ['remote', 'pull', '--session', sessionId]; | |
| if (apply) { | |
| args.push('--apply'); | |
| } | |
| return new Promise((resolve) => { | |
| const child = spawn('julius', args, { cwd, shell: true }); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/desktop/src/main/agent/jules-dispatcher.ts` around lines 161 - 171,
Modify pullJulesSession to accept a cwd parameter (e.g., cwd?: string) and pass
it into spawn so the Jules CLI runs in the intended repository; specifically
update the function signature pullJulesSession(sessionId: string, apply: boolean
= false, cwd?: string) and add the cwd option to the spawn call (spawn('jules',
args, { shell: true, cwd })). Ensure any callers are updated or default to
undefined so behavior remains unchanged when cwd is not provided.
Summary
Summary by CodeRabbit