@minded-ai/mindedjs 1.0.0-ec2-beta-20 → 1.0.0-ec2-beta-170
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -148,6 +148,119 @@ class ScreenshotCapture:
|
|
|
148
148
|
# Don't raise - continue execution
|
|
149
149
|
|
|
150
150
|
|
|
151
|
+
class LogsCapture:
|
|
152
|
+
"""Logs capture class that saves browser-use history logs to S3"""
|
|
153
|
+
|
|
154
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
|
155
|
+
self.config = config or {}
|
|
156
|
+
|
|
157
|
+
# Import boto3 only when logs capture is needed
|
|
158
|
+
try:
|
|
159
|
+
import boto3
|
|
160
|
+
from botocore.exceptions import ClientError
|
|
161
|
+
self.boto3 = boto3
|
|
162
|
+
self.ClientError = ClientError
|
|
163
|
+
except ImportError:
|
|
164
|
+
raise ImportError(
|
|
165
|
+
"boto3 is required for logs capture. "
|
|
166
|
+
"Please install it with: uv pip install boto3 "
|
|
167
|
+
"or run: npx minded setup-local-operator"
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# S3 configuration with defaults
|
|
171
|
+
self.s3_bucket = self.config.get('s3_bucket', os.getenv('SCREENSHOT_S3_BUCKET', 'global-development-agentsforce'))
|
|
172
|
+
self.s3_prefix = self.config.get('s3_prefix', os.getenv('SCREENSHOT_S3_PREFIX', 'browser-use-runs-logs/'))
|
|
173
|
+
|
|
174
|
+
# AWS region configuration
|
|
175
|
+
self.aws_region = self.config.get('aws_region', os.getenv('AWS_REGION', 'us-east-1'))
|
|
176
|
+
|
|
177
|
+
# Ensure prefix ends with slash
|
|
178
|
+
if not self.s3_prefix.endswith('/'):
|
|
179
|
+
self.s3_prefix += '/'
|
|
180
|
+
|
|
181
|
+
# Get session ID from config or generate one
|
|
182
|
+
self.session_id = self.config.get('session_id', datetime.now().strftime("%Y%m%d_%H%M%S_%f")[:-3])
|
|
183
|
+
|
|
184
|
+
# Initialize S3 client with region
|
|
185
|
+
self.s3_client = self.boto3.client('s3', region_name=self.aws_region)
|
|
186
|
+
|
|
187
|
+
# Track accumulated logs
|
|
188
|
+
self.log_entries: List[Dict[str, Any]] = []
|
|
189
|
+
self.step_counter = 0
|
|
190
|
+
|
|
191
|
+
logger.info(f"📝 Logs capture ENABLED - will save history logs periodically")
|
|
192
|
+
logger.info(f" S3 destination: s3://{self.s3_bucket}/{self.s3_prefix}")
|
|
193
|
+
logger.info(f" Session ID: {self.session_id}")
|
|
194
|
+
logger.info(f" AWS Region: {self.aws_region}")
|
|
195
|
+
|
|
196
|
+
async def capture_logs(self, agent: Any) -> None:
|
|
197
|
+
"""Capture logs at step end and upload to S3"""
|
|
198
|
+
try:
|
|
199
|
+
step_number = self.step_counter
|
|
200
|
+
self.step_counter += 1
|
|
201
|
+
|
|
202
|
+
# Get history object from agent
|
|
203
|
+
history = agent.history
|
|
204
|
+
|
|
205
|
+
# Extract relevant information from history
|
|
206
|
+
log_entry = {
|
|
207
|
+
'step': step_number,
|
|
208
|
+
'timestamp': datetime.now().isoformat(),
|
|
209
|
+
'urls': history.urls() if hasattr(history, 'urls') else [],
|
|
210
|
+
'action_names': history.action_names() if hasattr(history, 'action_names') else [],
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
# Extract model outputs if available
|
|
214
|
+
try:
|
|
215
|
+
model_outputs = history.model_outputs() if hasattr(history, 'model_outputs') else []
|
|
216
|
+
log_entry['model_outputs'] = [
|
|
217
|
+
{
|
|
218
|
+
'memory': h.current_state.memory if hasattr(h, 'current_state') else None,
|
|
219
|
+
'next_goal': h.current_state.next_goal if hasattr(h, 'current_state') else None,
|
|
220
|
+
'thinking': h.current_state.thinking if hasattr(h, 'current_state') else None,
|
|
221
|
+
}
|
|
222
|
+
for h in model_outputs
|
|
223
|
+
]
|
|
224
|
+
except Exception as e:
|
|
225
|
+
logger.debug(f"Could not extract model outputs: {e}")
|
|
226
|
+
log_entry['model_outputs'] = []
|
|
227
|
+
|
|
228
|
+
# Add to accumulated logs
|
|
229
|
+
self.log_entries.append(log_entry)
|
|
230
|
+
|
|
231
|
+
# Upload to S3
|
|
232
|
+
s3_key = f"{self.s3_prefix}{self.session_id}/operator.log"
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
# Convert logs to JSON string
|
|
236
|
+
logs_json = json.dumps(self.log_entries, indent=2, default=str)
|
|
237
|
+
logs_bytes = logs_json.encode('utf-8')
|
|
238
|
+
|
|
239
|
+
# Upload to S3 with KMS encryption
|
|
240
|
+
self.s3_client.put_object(
|
|
241
|
+
Bucket=self.s3_bucket,
|
|
242
|
+
Key=s3_key,
|
|
243
|
+
Body=logs_bytes,
|
|
244
|
+
ContentType='application/json',
|
|
245
|
+
ServerSideEncryption='aws:kms',
|
|
246
|
+
Tagging='retention=30d'
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
s3_url = f"s3://{self.s3_bucket}/{s3_key}"
|
|
250
|
+
logger.info(f"✅ Logs uploaded: {s3_url} (step {step_number}, size: {len(logs_bytes)} bytes, total entries: {len(self.log_entries)})")
|
|
251
|
+
|
|
252
|
+
except self.ClientError as e:
|
|
253
|
+
logger.error(f"❌ Failed to upload logs for step #{step_number} to S3")
|
|
254
|
+
logger.error(f" Error: {str(e)}")
|
|
255
|
+
logger.error(f" Bucket: {self.s3_bucket}, Key: {s3_key}")
|
|
256
|
+
# Continue execution even if logs upload fails
|
|
257
|
+
|
|
258
|
+
except Exception as e:
|
|
259
|
+
step_num = getattr(self, 'step_counter', 'unknown')
|
|
260
|
+
logger.error(f"❌ Error capturing logs #{step_num}: {str(e)}")
|
|
261
|
+
# Don't raise - continue execution
|
|
262
|
+
|
|
263
|
+
|
|
151
264
|
def create_pydantic_model_from_schema(output_schema: Optional[List[Dict[str, Any]]]) -> Optional[type[BaseModel]]:
|
|
152
265
|
if not output_schema:
|
|
153
266
|
return None
|
|
@@ -172,7 +285,12 @@ def create_pydantic_model_from_schema(output_schema: Optional[List[Dict[str, Any
|
|
|
172
285
|
|
|
173
286
|
|
|
174
287
|
async def main(session_id: str, cdp_url: str, task: str, output_schema_json: Optional[str] = None,
|
|
288
|
+
<<<<<<< HEAD
|
|
289
|
+
otp_secret: Optional[str] = None, screenshot_config: Optional[Dict[str, Any]] = None,
|
|
290
|
+
logs_config: Optional[Dict[str, Any]] = None):
|
|
291
|
+
=======
|
|
175
292
|
otp_secret: Optional[str] = None, screenshot_config: Optional[Dict[str, Any]] = None, folder_path: str = None):
|
|
293
|
+
>>>>>>> ec2-support
|
|
176
294
|
llm = ChatOpenAI(
|
|
177
295
|
model="gpt-4.1",
|
|
178
296
|
api_key=os.getenv("OPENAI_API_KEY"),
|
|
@@ -191,6 +309,7 @@ async def main(session_id: str, cdp_url: str, task: str, output_schema_json: Opt
|
|
|
191
309
|
|
|
192
310
|
# Initialize screenshot capture if enabled
|
|
193
311
|
screenshot_capture = None
|
|
312
|
+
logs_capture = None
|
|
194
313
|
on_step_end_hook = None
|
|
195
314
|
|
|
196
315
|
if screenshot_config and screenshot_config.get('enabled', False):
|
|
@@ -202,14 +321,31 @@ async def main(session_id: str, cdp_url: str, task: str, output_schema_json: Opt
|
|
|
202
321
|
# Add session_id to config
|
|
203
322
|
screenshot_config['session_id'] = session_id
|
|
204
323
|
screenshot_capture = ScreenshotCapture(screenshot_config)
|
|
324
|
+
else:
|
|
325
|
+
logger.info("📷 Screenshot capture is DISABLED for this browser task")
|
|
326
|
+
|
|
327
|
+
# Initialize logs capture if enabled
|
|
328
|
+
if screenshot_config and screenshot_config.get('enabled', False):
|
|
329
|
+
logger.info("-" * 50)
|
|
330
|
+
logger.info("🎯 Initializing logs capture for browser task")
|
|
331
|
+
logger.info(f" Session: {session_id}")
|
|
332
|
+
logger.info("-" * 50)
|
|
205
333
|
|
|
206
|
-
#
|
|
334
|
+
# Add session_id to config
|
|
335
|
+
screenshot_config['session_id'] = session_id
|
|
336
|
+
logs_capture = LogsCapture(screenshot_config)
|
|
337
|
+
else:
|
|
338
|
+
logger.info("📝 Logs capture is DISABLED for this browser task")
|
|
339
|
+
|
|
340
|
+
# Create composite hook function for step end
|
|
341
|
+
if screenshot_capture or logs_capture:
|
|
207
342
|
async def on_step_end(agent: Any) -> None:
|
|
208
|
-
|
|
343
|
+
if screenshot_capture:
|
|
344
|
+
await screenshot_capture.capture_screenshot(agent)
|
|
345
|
+
if logs_capture:
|
|
346
|
+
await logs_capture.capture_logs(agent)
|
|
209
347
|
|
|
210
348
|
on_step_end_hook = on_step_end
|
|
211
|
-
else:
|
|
212
|
-
logger.info("📷 Screenshot capture is DISABLED for this browser task")
|
|
213
349
|
|
|
214
350
|
output_schema = None
|
|
215
351
|
if output_schema_json:
|
|
@@ -298,12 +434,20 @@ if __name__ == '__main__':
|
|
|
298
434
|
output_schema = payload.get('outputSchema')
|
|
299
435
|
otp_secret = payload.get('otpSecret')
|
|
300
436
|
screenshot_config = payload.get('screenshotConfig')
|
|
437
|
+
<<<<<<< HEAD
|
|
438
|
+
logs_config = payload.get('logsConfig')
|
|
439
|
+
=======
|
|
301
440
|
folder_path = payload.get('folderPath')
|
|
441
|
+
>>>>>>> ec2-support
|
|
302
442
|
|
|
303
443
|
if not session_id or not cdp_url or not task or not folder_path:
|
|
304
444
|
raise SystemExit("Missing required fields in JSON payload: sessionId, cdpUrl, task, folderPath")
|
|
305
445
|
|
|
306
446
|
output_schema_json = json.dumps(output_schema) if output_schema is not None else None
|
|
447
|
+
<<<<<<< HEAD
|
|
448
|
+
asyncio.run(main(session_id, cdp_url, task, output_schema_json, otp_secret, screenshot_config, logs_config))
|
|
449
|
+
=======
|
|
307
450
|
asyncio.run(main(session_id, cdp_url, task, output_schema_json, otp_secret, screenshot_config, folder_path))
|
|
451
|
+
>>>>>>> ec2-support
|
|
308
452
|
else:
|
|
309
453
|
raise SystemExit("Usage: uv run executeBrowserTask.py; send a JSON payload via stdin")
|
package/package.json
CHANGED
|
@@ -148,6 +148,119 @@ class ScreenshotCapture:
|
|
|
148
148
|
# Don't raise - continue execution
|
|
149
149
|
|
|
150
150
|
|
|
151
|
+
class LogsCapture:
|
|
152
|
+
"""Logs capture class that saves browser-use history logs to S3"""
|
|
153
|
+
|
|
154
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
|
155
|
+
self.config = config or {}
|
|
156
|
+
|
|
157
|
+
# Import boto3 only when logs capture is needed
|
|
158
|
+
try:
|
|
159
|
+
import boto3
|
|
160
|
+
from botocore.exceptions import ClientError
|
|
161
|
+
self.boto3 = boto3
|
|
162
|
+
self.ClientError = ClientError
|
|
163
|
+
except ImportError:
|
|
164
|
+
raise ImportError(
|
|
165
|
+
"boto3 is required for logs capture. "
|
|
166
|
+
"Please install it with: uv pip install boto3 "
|
|
167
|
+
"or run: npx minded setup-local-operator"
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# S3 configuration with defaults
|
|
171
|
+
self.s3_bucket = self.config.get('s3_bucket', os.getenv('SCREENSHOT_S3_BUCKET', 'global-development-agentsforce'))
|
|
172
|
+
self.s3_prefix = self.config.get('s3_prefix', os.getenv('SCREENSHOT_S3_PREFIX', 'browser-use-runs-logs/'))
|
|
173
|
+
|
|
174
|
+
# AWS region configuration
|
|
175
|
+
self.aws_region = self.config.get('aws_region', os.getenv('AWS_REGION', 'us-east-1'))
|
|
176
|
+
|
|
177
|
+
# Ensure prefix ends with slash
|
|
178
|
+
if not self.s3_prefix.endswith('/'):
|
|
179
|
+
self.s3_prefix += '/'
|
|
180
|
+
|
|
181
|
+
# Get session ID from config or generate one
|
|
182
|
+
self.session_id = self.config.get('session_id', datetime.now().strftime("%Y%m%d_%H%M%S_%f")[:-3])
|
|
183
|
+
|
|
184
|
+
# Initialize S3 client with region
|
|
185
|
+
self.s3_client = self.boto3.client('s3', region_name=self.aws_region)
|
|
186
|
+
|
|
187
|
+
# Track accumulated logs
|
|
188
|
+
self.log_entries: List[Dict[str, Any]] = []
|
|
189
|
+
self.step_counter = 0
|
|
190
|
+
|
|
191
|
+
logger.info(f"📝 Logs capture ENABLED - will save history logs periodically")
|
|
192
|
+
logger.info(f" S3 destination: s3://{self.s3_bucket}/{self.s3_prefix}")
|
|
193
|
+
logger.info(f" Session ID: {self.session_id}")
|
|
194
|
+
logger.info(f" AWS Region: {self.aws_region}")
|
|
195
|
+
|
|
196
|
+
async def capture_logs(self, agent: Any) -> None:
|
|
197
|
+
"""Capture logs at step end and upload to S3"""
|
|
198
|
+
try:
|
|
199
|
+
step_number = self.step_counter
|
|
200
|
+
self.step_counter += 1
|
|
201
|
+
|
|
202
|
+
# Get history object from agent
|
|
203
|
+
history = agent.history
|
|
204
|
+
|
|
205
|
+
# Extract relevant information from history
|
|
206
|
+
log_entry = {
|
|
207
|
+
'step': step_number,
|
|
208
|
+
'timestamp': datetime.now().isoformat(),
|
|
209
|
+
'urls': history.urls() if hasattr(history, 'urls') else [],
|
|
210
|
+
'action_names': history.action_names() if hasattr(history, 'action_names') else [],
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
# Extract model outputs if available
|
|
214
|
+
try:
|
|
215
|
+
model_outputs = history.model_outputs() if hasattr(history, 'model_outputs') else []
|
|
216
|
+
log_entry['model_outputs'] = [
|
|
217
|
+
{
|
|
218
|
+
'memory': h.current_state.memory if hasattr(h, 'current_state') else None,
|
|
219
|
+
'next_goal': h.current_state.next_goal if hasattr(h, 'current_state') else None,
|
|
220
|
+
'thinking': h.current_state.thinking if hasattr(h, 'current_state') else None,
|
|
221
|
+
}
|
|
222
|
+
for h in model_outputs
|
|
223
|
+
]
|
|
224
|
+
except Exception as e:
|
|
225
|
+
logger.debug(f"Could not extract model outputs: {e}")
|
|
226
|
+
log_entry['model_outputs'] = []
|
|
227
|
+
|
|
228
|
+
# Add to accumulated logs
|
|
229
|
+
self.log_entries.append(log_entry)
|
|
230
|
+
|
|
231
|
+
# Upload to S3
|
|
232
|
+
s3_key = f"{self.s3_prefix}{self.session_id}/operator.log"
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
# Convert logs to JSON string
|
|
236
|
+
logs_json = json.dumps(self.log_entries, indent=2, default=str)
|
|
237
|
+
logs_bytes = logs_json.encode('utf-8')
|
|
238
|
+
|
|
239
|
+
# Upload to S3 with KMS encryption
|
|
240
|
+
self.s3_client.put_object(
|
|
241
|
+
Bucket=self.s3_bucket,
|
|
242
|
+
Key=s3_key,
|
|
243
|
+
Body=logs_bytes,
|
|
244
|
+
ContentType='application/json',
|
|
245
|
+
ServerSideEncryption='aws:kms',
|
|
246
|
+
Tagging='retention=30d'
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
s3_url = f"s3://{self.s3_bucket}/{s3_key}"
|
|
250
|
+
logger.info(f"✅ Logs uploaded: {s3_url} (step {step_number}, size: {len(logs_bytes)} bytes, total entries: {len(self.log_entries)})")
|
|
251
|
+
|
|
252
|
+
except self.ClientError as e:
|
|
253
|
+
logger.error(f"❌ Failed to upload logs for step #{step_number} to S3")
|
|
254
|
+
logger.error(f" Error: {str(e)}")
|
|
255
|
+
logger.error(f" Bucket: {self.s3_bucket}, Key: {s3_key}")
|
|
256
|
+
# Continue execution even if logs upload fails
|
|
257
|
+
|
|
258
|
+
except Exception as e:
|
|
259
|
+
step_num = getattr(self, 'step_counter', 'unknown')
|
|
260
|
+
logger.error(f"❌ Error capturing logs #{step_num}: {str(e)}")
|
|
261
|
+
# Don't raise - continue execution
|
|
262
|
+
|
|
263
|
+
|
|
151
264
|
def create_pydantic_model_from_schema(output_schema: Optional[List[Dict[str, Any]]]) -> Optional[type[BaseModel]]:
|
|
152
265
|
if not output_schema:
|
|
153
266
|
return None
|
|
@@ -172,7 +285,12 @@ def create_pydantic_model_from_schema(output_schema: Optional[List[Dict[str, Any
|
|
|
172
285
|
|
|
173
286
|
|
|
174
287
|
async def main(session_id: str, cdp_url: str, task: str, output_schema_json: Optional[str] = None,
|
|
288
|
+
<<<<<<< HEAD
|
|
289
|
+
otp_secret: Optional[str] = None, screenshot_config: Optional[Dict[str, Any]] = None,
|
|
290
|
+
logs_config: Optional[Dict[str, Any]] = None):
|
|
291
|
+
=======
|
|
175
292
|
otp_secret: Optional[str] = None, screenshot_config: Optional[Dict[str, Any]] = None, folder_path: str = None):
|
|
293
|
+
>>>>>>> ec2-support
|
|
176
294
|
llm = ChatOpenAI(
|
|
177
295
|
model="gpt-4.1",
|
|
178
296
|
api_key=os.getenv("OPENAI_API_KEY"),
|
|
@@ -191,6 +309,7 @@ async def main(session_id: str, cdp_url: str, task: str, output_schema_json: Opt
|
|
|
191
309
|
|
|
192
310
|
# Initialize screenshot capture if enabled
|
|
193
311
|
screenshot_capture = None
|
|
312
|
+
logs_capture = None
|
|
194
313
|
on_step_end_hook = None
|
|
195
314
|
|
|
196
315
|
if screenshot_config and screenshot_config.get('enabled', False):
|
|
@@ -202,14 +321,31 @@ async def main(session_id: str, cdp_url: str, task: str, output_schema_json: Opt
|
|
|
202
321
|
# Add session_id to config
|
|
203
322
|
screenshot_config['session_id'] = session_id
|
|
204
323
|
screenshot_capture = ScreenshotCapture(screenshot_config)
|
|
324
|
+
else:
|
|
325
|
+
logger.info("📷 Screenshot capture is DISABLED for this browser task")
|
|
326
|
+
|
|
327
|
+
# Initialize logs capture if enabled
|
|
328
|
+
if screenshot_config and screenshot_config.get('enabled', False):
|
|
329
|
+
logger.info("-" * 50)
|
|
330
|
+
logger.info("🎯 Initializing logs capture for browser task")
|
|
331
|
+
logger.info(f" Session: {session_id}")
|
|
332
|
+
logger.info("-" * 50)
|
|
205
333
|
|
|
206
|
-
#
|
|
334
|
+
# Add session_id to config
|
|
335
|
+
screenshot_config['session_id'] = session_id
|
|
336
|
+
logs_capture = LogsCapture(screenshot_config)
|
|
337
|
+
else:
|
|
338
|
+
logger.info("📝 Logs capture is DISABLED for this browser task")
|
|
339
|
+
|
|
340
|
+
# Create composite hook function for step end
|
|
341
|
+
if screenshot_capture or logs_capture:
|
|
207
342
|
async def on_step_end(agent: Any) -> None:
|
|
208
|
-
|
|
343
|
+
if screenshot_capture:
|
|
344
|
+
await screenshot_capture.capture_screenshot(agent)
|
|
345
|
+
if logs_capture:
|
|
346
|
+
await logs_capture.capture_logs(agent)
|
|
209
347
|
|
|
210
348
|
on_step_end_hook = on_step_end
|
|
211
|
-
else:
|
|
212
|
-
logger.info("📷 Screenshot capture is DISABLED for this browser task")
|
|
213
349
|
|
|
214
350
|
output_schema = None
|
|
215
351
|
if output_schema_json:
|
|
@@ -298,12 +434,20 @@ if __name__ == '__main__':
|
|
|
298
434
|
output_schema = payload.get('outputSchema')
|
|
299
435
|
otp_secret = payload.get('otpSecret')
|
|
300
436
|
screenshot_config = payload.get('screenshotConfig')
|
|
437
|
+
<<<<<<< HEAD
|
|
438
|
+
logs_config = payload.get('logsConfig')
|
|
439
|
+
=======
|
|
301
440
|
folder_path = payload.get('folderPath')
|
|
441
|
+
>>>>>>> ec2-support
|
|
302
442
|
|
|
303
443
|
if not session_id or not cdp_url or not task or not folder_path:
|
|
304
444
|
raise SystemExit("Missing required fields in JSON payload: sessionId, cdpUrl, task, folderPath")
|
|
305
445
|
|
|
306
446
|
output_schema_json = json.dumps(output_schema) if output_schema is not None else None
|
|
447
|
+
<<<<<<< HEAD
|
|
448
|
+
asyncio.run(main(session_id, cdp_url, task, output_schema_json, otp_secret, screenshot_config, logs_config))
|
|
449
|
+
=======
|
|
307
450
|
asyncio.run(main(session_id, cdp_url, task, output_schema_json, otp_secret, screenshot_config, folder_path))
|
|
451
|
+
>>>>>>> ec2-support
|
|
308
452
|
else:
|
|
309
453
|
raise SystemExit("Usage: uv run executeBrowserTask.py; send a JSON payload via stdin")
|