fix: improve droid provider with context isolation and message parsing

Changes:
- Add context isolation using --cwd flag to prevent project context leakage
- Fix message content parsing to support both string and array formats
- Add debug logging for troubleshooting
- Create isolated temporary working directory for each service instance

This prevents Droid from seeing the current project directory and mentioning
it in responses, providing a cleaner API experience.

Tested:
 Context isolation verified - Droid reports empty temp directory
 Message parsing works with OpenAI format
 API responses no longer mention project context
This commit is contained in:
bee4come 2025-10-15 14:02:21 +08:00
parent 66b6363ad4
commit e91aaa02e5

View file

@ -1,4 +1,7 @@
import { spawn } from 'child_process'; import { spawn } from 'child_process';
import { mkdtempSync } from 'fs';
import { tmpdir } from 'os';
import { join } from 'path';
/** /**
* Droid (Factory.ai) API Service * Droid (Factory.ai) API Service
@ -13,6 +16,10 @@ export class DroidApiService {
this.config = config; this.config = config;
this.isInitialized = false; this.isInitialized = false;
this.droidCommand = config.DROID_COMMAND || 'droid'; this.droidCommand = config.DROID_COMMAND || 'droid';
// Create isolated working directory to prevent context leakage
this.isolatedWorkDir = mkdtempSync(join(tmpdir(), 'droid-isolated-'));
console.log('[Droid] Created isolated working directory:', this.isolatedWorkDir);
} }
/** /**
@ -75,14 +82,21 @@ export class DroidApiService {
*/ */
messagesToPrompt(messages) { messagesToPrompt(messages) {
return messages.map(msg => { return messages.map(msg => {
if (msg.role === 'user') { // Handle both string and array content formats
return msg.content; let content = msg.content;
} else if (msg.role === 'assistant') { if (Array.isArray(content)) {
return `Assistant: ${msg.content}`; // Extract text from content blocks
} else if (msg.role === 'system') { content = content.map(block => block.text || '').join('');
return `System: ${msg.content}`;
} }
return msg.content;
if (msg.role === 'user') {
return content;
} else if (msg.role === 'assistant') {
return `Assistant: ${content}`;
} else if (msg.role === 'system') {
return `System: ${content}`;
}
return content;
}).join('\n\n'); }).join('\n\n');
} }
@ -99,7 +113,14 @@ export class DroidApiService {
try { try {
const prompt = this.messagesToPrompt(requestBody.messages); const prompt = this.messagesToPrompt(requestBody.messages);
const output = await this.executeDroidCommand(['exec', '--skip-permissions-unsafe', prompt]); console.log('[Droid DEBUG] Sending prompt to droid exec:', prompt);
console.log('[Droid DEBUG] Isolated working directory:', this.isolatedWorkDir);
const output = await this.executeDroidCommand([
'exec',
'--skip-permissions-unsafe',
'--cwd', this.isolatedWorkDir,
prompt
]);
return { return {
id: `msg_${Date.now()}`, id: `msg_${Date.now()}`,
@ -151,7 +172,12 @@ export class DroidApiService {
content_block: { type: 'text', text: '' } content_block: { type: 'text', text: '' }
}; };
const droid = spawn(this.droidCommand, ['exec', '--skip-permissions-unsafe', prompt]); const droid = spawn(this.droidCommand, [
'exec',
'--skip-permissions-unsafe',
'--cwd', this.isolatedWorkDir,
prompt
]);
let buffer = ''; let buffer = '';
for await (const chunk of droid.stdout) { for await (const chunk of droid.stdout) {