vibesurf 0.1.36__py3-none-any.whl → 0.1.38__py3-none-any.whl
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.
Potentially problematic release.
This version of vibesurf might be problematic. Click here for more details.
- vibe_surf/_version.py +2 -2
- vibe_surf/agents/browser_use_agent.py +14 -276
- vibe_surf/agents/prompts/vibe_surf_prompt.py +1 -1
- vibe_surf/agents/report_writer_agent.py +22 -2
- vibe_surf/agents/vibe_surf_agent.py +62 -3
- vibe_surf/backend/llm_config.py +41 -0
- vibe_surf/backend/shared_state.py +26 -26
- vibe_surf/backend/utils/encryption.py +40 -4
- vibe_surf/backend/utils/llm_factory.py +24 -0
- vibe_surf/browser/agen_browser_profile.py +5 -0
- vibe_surf/browser/agent_browser_session.py +116 -25
- vibe_surf/browser/watchdogs/action_watchdog.py +1 -83
- vibe_surf/browser/watchdogs/dom_watchdog.py +9 -6
- vibe_surf/cli.py +2 -0
- vibe_surf/llm/openai_compatible.py +2 -9
- vibe_surf/telemetry/views.py +32 -0
- vibe_surf/tools/browser_use_tools.py +39 -42
- vibe_surf/tools/file_system.py +5 -2
- vibe_surf/tools/utils.py +118 -0
- vibe_surf/tools/vibesurf_tools.py +44 -236
- vibe_surf/tools/views.py +1 -1
- {vibesurf-0.1.36.dist-info → vibesurf-0.1.38.dist-info}/METADATA +12 -2
- {vibesurf-0.1.36.dist-info → vibesurf-0.1.38.dist-info}/RECORD +27 -26
- {vibesurf-0.1.36.dist-info → vibesurf-0.1.38.dist-info}/WHEEL +0 -0
- {vibesurf-0.1.36.dist-info → vibesurf-0.1.38.dist-info}/entry_points.txt +0 -0
- {vibesurf-0.1.36.dist-info → vibesurf-0.1.38.dist-info}/licenses/LICENSE +0 -0
- {vibesurf-0.1.36.dist-info → vibesurf-0.1.38.dist-info}/top_level.txt +0 -0
vibe_surf/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.1.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 1,
|
|
31
|
+
__version__ = version = '0.1.38'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 1, 38)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -72,7 +72,7 @@ from browser_use.utils import (
|
|
|
72
72
|
time_execution_async,
|
|
73
73
|
time_execution_sync,
|
|
74
74
|
)
|
|
75
|
-
|
|
75
|
+
from browser_use.llm.messages import BaseMessage, ContentPartImageParam, ContentPartTextParam, UserMessage
|
|
76
76
|
from browser_use.agent.service import Agent, AgentHookFunc
|
|
77
77
|
from vibe_surf.tools.file_system import CustomFileSystem
|
|
78
78
|
from vibe_surf.telemetry.service import ProductTelemetry
|
|
@@ -107,6 +107,7 @@ class BrowserUseAgent(Agent):
|
|
|
107
107
|
| None
|
|
108
108
|
) = None,
|
|
109
109
|
register_external_agent_status_raise_error_callback: Callable[[], Awaitable[bool]] | None = None,
|
|
110
|
+
register_should_stop_callback: Callable[[], Awaitable[bool]] | None = None,
|
|
110
111
|
# Agent settings
|
|
111
112
|
output_model_schema: type[AgentStructuredOutput] | None = None,
|
|
112
113
|
use_vision: bool = True,
|
|
@@ -127,7 +128,6 @@ class BrowserUseAgent(Agent):
|
|
|
127
128
|
source: str | None = None,
|
|
128
129
|
file_system_path: str | None = None,
|
|
129
130
|
task_id: str | None = None,
|
|
130
|
-
cloud_sync: CloudSync | None = None,
|
|
131
131
|
calculate_cost: bool = False,
|
|
132
132
|
display_files_in_done_text: bool = True,
|
|
133
133
|
include_tool_call_examples: bool = False,
|
|
@@ -136,7 +136,9 @@ class BrowserUseAgent(Agent):
|
|
|
136
136
|
step_timeout: int = 120,
|
|
137
137
|
directly_open_url: bool = False,
|
|
138
138
|
include_recent_events: bool = False,
|
|
139
|
-
|
|
139
|
+
sample_images: list[ContentPartTextParam | ContentPartImageParam] | None = None,
|
|
140
|
+
final_response_after_failure: bool = True,
|
|
141
|
+
allow_parallel_action_types: list[str] = ["extract", "extract_content_from_file"],
|
|
140
142
|
_url_shortening_limit: int = 25,
|
|
141
143
|
token_cost_service: Optional[TokenCost] = None,
|
|
142
144
|
**kwargs,
|
|
@@ -151,7 +153,7 @@ class BrowserUseAgent(Agent):
|
|
|
151
153
|
self.session_id: str = uuid7str()
|
|
152
154
|
self.allow_parallel_action_types = allow_parallel_action_types
|
|
153
155
|
self._url_shortening_limit = _url_shortening_limit
|
|
154
|
-
|
|
156
|
+
self.sample_images = sample_images
|
|
155
157
|
browser_profile = browser_profile or DEFAULT_BROWSER_PROFILE
|
|
156
158
|
|
|
157
159
|
# Handle browser vs browser_session parameter (browser takes precedence)
|
|
@@ -206,6 +208,7 @@ class BrowserUseAgent(Agent):
|
|
|
206
208
|
include_tool_call_examples=include_tool_call_examples,
|
|
207
209
|
llm_timeout=llm_timeout,
|
|
208
210
|
step_timeout=step_timeout,
|
|
211
|
+
final_response_after_failure=final_response_after_failure,
|
|
209
212
|
)
|
|
210
213
|
|
|
211
214
|
# Token cost service
|
|
@@ -289,7 +292,6 @@ class BrowserUseAgent(Agent):
|
|
|
289
292
|
self._message_manager = MessageManager(
|
|
290
293
|
task=task,
|
|
291
294
|
system_message=SystemPrompt(
|
|
292
|
-
action_description=self.unfiltered_actions,
|
|
293
295
|
max_actions_per_step=self.settings.max_actions_per_step,
|
|
294
296
|
override_system_message=override_system_message,
|
|
295
297
|
extend_system_message=extend_system_message,
|
|
@@ -306,6 +308,7 @@ class BrowserUseAgent(Agent):
|
|
|
306
308
|
vision_detail_level=self.settings.vision_detail_level,
|
|
307
309
|
include_tool_call_examples=self.settings.include_tool_call_examples,
|
|
308
310
|
include_recent_events=self.include_recent_events,
|
|
311
|
+
sample_images=self.sample_images,
|
|
309
312
|
)
|
|
310
313
|
|
|
311
314
|
if self.sensitive_data:
|
|
@@ -375,6 +378,7 @@ class BrowserUseAgent(Agent):
|
|
|
375
378
|
# Callbacks
|
|
376
379
|
self.register_new_step_callback = register_new_step_callback
|
|
377
380
|
self.register_done_callback = register_done_callback
|
|
381
|
+
self.register_should_stop_callback = register_should_stop_callback
|
|
378
382
|
self.register_external_agent_status_raise_error_callback = register_external_agent_status_raise_error_callback
|
|
379
383
|
|
|
380
384
|
# Telemetry
|
|
@@ -463,7 +467,7 @@ class BrowserUseAgent(Agent):
|
|
|
463
467
|
|
|
464
468
|
# Use _make_history_item like main branch
|
|
465
469
|
await self._make_history_item(self.state.last_model_output, browser_state_summary, self.state.last_result,
|
|
466
|
-
metadata)
|
|
470
|
+
metadata, state_message=self._message_manager.last_state_message_text,)
|
|
467
471
|
|
|
468
472
|
# Log step completion summary
|
|
469
473
|
self._log_step_completion_summary(self.step_start_time, self.state.last_result)
|
|
@@ -490,6 +494,9 @@ class BrowserUseAgent(Agent):
|
|
|
490
494
|
self.task = new_task
|
|
491
495
|
self._message_manager.add_new_task(new_task)
|
|
492
496
|
|
|
497
|
+
# Mark as follow-up task and recreate eventbus (gets shut down after each run)
|
|
498
|
+
self.state.follow_up_task = True
|
|
499
|
+
|
|
493
500
|
@observe(name='agent.run', metadata={'task': '{{task}}', 'debug': '{{debug}}'})
|
|
494
501
|
@time_execution_async('--run')
|
|
495
502
|
async def run(
|
|
@@ -749,273 +756,4 @@ class BrowserUseAgent(Agent):
|
|
|
749
756
|
# Add the last group
|
|
750
757
|
groups.append(current_group)
|
|
751
758
|
|
|
752
|
-
return groups
|
|
753
|
-
|
|
754
|
-
@observe_debug(ignore_input=True, ignore_output=True)
|
|
755
|
-
@time_execution_async('--multi_act')
|
|
756
|
-
async def multi_act(
|
|
757
|
-
self,
|
|
758
|
-
actions: list[ActionModel],
|
|
759
|
-
check_for_new_elements: bool = True,
|
|
760
|
-
) -> list[ActionResult]:
|
|
761
|
-
"""Execute multiple actions, with parallel execution for allowed action types"""
|
|
762
|
-
results: list[ActionResult] = []
|
|
763
|
-
time_elapsed = 0
|
|
764
|
-
total_actions = len(actions)
|
|
765
|
-
|
|
766
|
-
assert self.browser_session is not None, 'BrowserSession is not set up'
|
|
767
|
-
try:
|
|
768
|
-
if (
|
|
769
|
-
self.browser_session._cached_browser_state_summary is not None
|
|
770
|
-
and self.browser_session._cached_browser_state_summary.dom_state is not None
|
|
771
|
-
):
|
|
772
|
-
cached_selector_map = dict(self.browser_session._cached_browser_state_summary.dom_state.selector_map)
|
|
773
|
-
cached_element_hashes = {e.parent_branch_hash() for e in cached_selector_map.values()}
|
|
774
|
-
else:
|
|
775
|
-
cached_selector_map = {}
|
|
776
|
-
cached_element_hashes = set()
|
|
777
|
-
except Exception as e:
|
|
778
|
-
self.logger.error(f'Error getting cached selector map: {e}')
|
|
779
|
-
cached_selector_map = {}
|
|
780
|
-
cached_element_hashes = set()
|
|
781
|
-
|
|
782
|
-
# Group actions for potential parallel execution
|
|
783
|
-
action_groups = self._group_actions_for_parallel_execution(actions)
|
|
784
|
-
|
|
785
|
-
# Track global action index for logging and DOM checks
|
|
786
|
-
global_action_index = 0
|
|
787
|
-
|
|
788
|
-
for group_index, action_group in enumerate(action_groups):
|
|
789
|
-
group_size = len(action_group)
|
|
790
|
-
|
|
791
|
-
# Check if this group can be executed in parallel
|
|
792
|
-
can_execute_in_parallel = (
|
|
793
|
-
group_size > 1 and
|
|
794
|
-
all(self._is_action_parallel_allowed(action) for action in action_group)
|
|
795
|
-
)
|
|
796
|
-
|
|
797
|
-
if can_execute_in_parallel:
|
|
798
|
-
self.logger.info(
|
|
799
|
-
f'🚀 Executing {group_size} actions in parallel: group {group_index + 1}/{len(action_groups)}')
|
|
800
|
-
# Execute actions in parallel using asyncio.gather
|
|
801
|
-
parallel_results = await self._execute_actions_in_parallel(
|
|
802
|
-
action_group, global_action_index, total_actions,
|
|
803
|
-
cached_selector_map, cached_element_hashes, check_for_new_elements
|
|
804
|
-
)
|
|
805
|
-
results.extend(parallel_results)
|
|
806
|
-
global_action_index += group_size
|
|
807
|
-
|
|
808
|
-
# Check if any result indicates completion or error
|
|
809
|
-
if any(result.is_done or result.error for result in parallel_results):
|
|
810
|
-
break
|
|
811
|
-
else:
|
|
812
|
-
# Execute actions sequentially
|
|
813
|
-
for local_index, action in enumerate(action_group):
|
|
814
|
-
i = global_action_index + local_index
|
|
815
|
-
|
|
816
|
-
# Original sequential execution logic continues here...
|
|
817
|
-
# if i > 0:
|
|
818
|
-
# # ONLY ALLOW TO CALL `done` IF IT IS A SINGLE ACTION
|
|
819
|
-
# if action.model_dump(exclude_unset=True).get('done') is not None:
|
|
820
|
-
# msg = f'Done action is allowed only as a single action - stopped after action {i} / {total_actions}.'
|
|
821
|
-
# self.logger.debug(msg)
|
|
822
|
-
# break
|
|
823
|
-
|
|
824
|
-
# DOM synchronization check - verify element indexes are still valid AFTER first action
|
|
825
|
-
if action.get_index() is not None and i != 0:
|
|
826
|
-
result = await self._check_dom_synchronization(
|
|
827
|
-
action, i, total_actions, cached_selector_map, cached_element_hashes,
|
|
828
|
-
check_for_new_elements, actions
|
|
829
|
-
)
|
|
830
|
-
if result:
|
|
831
|
-
results.append(result)
|
|
832
|
-
break
|
|
833
|
-
|
|
834
|
-
# wait between actions (only after first action)
|
|
835
|
-
if i > 0:
|
|
836
|
-
await asyncio.sleep(self.browser_profile.wait_between_actions)
|
|
837
|
-
|
|
838
|
-
# Execute single action
|
|
839
|
-
try:
|
|
840
|
-
action_result = await self._execute_single_action(action, i, total_actions)
|
|
841
|
-
results.append(action_result)
|
|
842
|
-
|
|
843
|
-
if action_result.is_done or action_result.error or i == total_actions - 1:
|
|
844
|
-
break
|
|
845
|
-
|
|
846
|
-
except Exception as e:
|
|
847
|
-
self.logger.error(f'❌ Executing action {i + 1} failed: {type(e).__name__}: {e}')
|
|
848
|
-
raise e
|
|
849
|
-
|
|
850
|
-
global_action_index += len(action_group)
|
|
851
|
-
|
|
852
|
-
return results
|
|
853
|
-
|
|
854
|
-
async def _execute_actions_in_parallel(
|
|
855
|
-
self,
|
|
856
|
-
actions: list[ActionModel],
|
|
857
|
-
start_index: int,
|
|
858
|
-
total_actions: int,
|
|
859
|
-
cached_selector_map: dict,
|
|
860
|
-
cached_element_hashes: set,
|
|
861
|
-
check_for_new_elements: bool
|
|
862
|
-
) -> list[ActionResult]:
|
|
863
|
-
"""Execute a group of actions in parallel using asyncio.gather"""
|
|
864
|
-
|
|
865
|
-
async def execute_single_parallel_action(action: ActionModel, action_index: int) -> ActionResult:
|
|
866
|
-
"""Execute a single action for parallel execution"""
|
|
867
|
-
await self._raise_if_stopped_or_paused()
|
|
868
|
-
|
|
869
|
-
# Get action info for logging
|
|
870
|
-
action_data = action.model_dump(exclude_unset=True)
|
|
871
|
-
action_name = next(iter(action_data.keys())) if action_data else 'unknown'
|
|
872
|
-
action_params = getattr(action, action_name, '') or str(action.model_dump(mode='json'))[:140].replace(
|
|
873
|
-
'"', ''
|
|
874
|
-
).replace('{', '').replace('}', '').replace("'", '').strip().strip(',')
|
|
875
|
-
action_params = str(action_params)
|
|
876
|
-
action_params = f'{action_params[:122]}...' if len(action_params) > 128 else action_params
|
|
877
|
-
|
|
878
|
-
time_start = time.time()
|
|
879
|
-
blue = '\033[34m'
|
|
880
|
-
reset = '\033[0m'
|
|
881
|
-
self.logger.info(f' 🦾 {blue}[PARALLEL ACTION {action_index + 1}/{total_actions}]{reset} {action_params}')
|
|
882
|
-
|
|
883
|
-
# Execute the action
|
|
884
|
-
result = await self.tools.act(
|
|
885
|
-
action=action,
|
|
886
|
-
browser_session=self.browser_session,
|
|
887
|
-
file_system=self.file_system,
|
|
888
|
-
page_extraction_llm=self.settings.page_extraction_llm,
|
|
889
|
-
sensitive_data=self.sensitive_data,
|
|
890
|
-
available_file_paths=self.available_file_paths,
|
|
891
|
-
)
|
|
892
|
-
|
|
893
|
-
time_end = time.time()
|
|
894
|
-
time_elapsed = time_end - time_start
|
|
895
|
-
|
|
896
|
-
green = '\033[92m'
|
|
897
|
-
self.logger.debug(
|
|
898
|
-
f'☑️ Parallel action {action_index + 1}/{total_actions}: {green}{action_params}{reset} in {time_elapsed:.2f}s'
|
|
899
|
-
)
|
|
900
|
-
|
|
901
|
-
return result
|
|
902
|
-
|
|
903
|
-
# Create tasks for parallel execution
|
|
904
|
-
tasks = [
|
|
905
|
-
execute_single_parallel_action(action, start_index + i)
|
|
906
|
-
for i, action in enumerate(actions)
|
|
907
|
-
]
|
|
908
|
-
|
|
909
|
-
# Execute all tasks in parallel
|
|
910
|
-
parallel_results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
911
|
-
|
|
912
|
-
# Process results and handle any exceptions
|
|
913
|
-
processed_results = []
|
|
914
|
-
for i, result in enumerate(parallel_results):
|
|
915
|
-
if isinstance(result, Exception):
|
|
916
|
-
action_index = start_index + i
|
|
917
|
-
self.logger.error(f'❌ Parallel action {action_index + 1} failed: {type(result).__name__}: {result}')
|
|
918
|
-
raise result
|
|
919
|
-
else:
|
|
920
|
-
processed_results.append(result)
|
|
921
|
-
|
|
922
|
-
return processed_results
|
|
923
|
-
|
|
924
|
-
async def _check_dom_synchronization(
|
|
925
|
-
self,
|
|
926
|
-
action: ActionModel,
|
|
927
|
-
action_index: int,
|
|
928
|
-
total_actions: int,
|
|
929
|
-
cached_selector_map: dict,
|
|
930
|
-
cached_element_hashes: set,
|
|
931
|
-
check_for_new_elements: bool,
|
|
932
|
-
all_actions: list[ActionModel]
|
|
933
|
-
) -> ActionResult | None:
|
|
934
|
-
"""Check DOM synchronization and return result if page changed"""
|
|
935
|
-
new_browser_state_summary = await self.browser_session.get_browser_state_summary(
|
|
936
|
-
cache_clickable_elements_hashes=False,
|
|
937
|
-
include_screenshot=False,
|
|
938
|
-
)
|
|
939
|
-
new_selector_map = new_browser_state_summary.dom_state.selector_map
|
|
940
|
-
|
|
941
|
-
# Detect index change after previous action
|
|
942
|
-
orig_target = cached_selector_map.get(action.get_index())
|
|
943
|
-
orig_target_hash = orig_target.parent_branch_hash() if orig_target else None
|
|
944
|
-
|
|
945
|
-
new_target = new_selector_map.get(action.get_index()) # type: ignore
|
|
946
|
-
new_target_hash = new_target.parent_branch_hash() if new_target else None
|
|
947
|
-
|
|
948
|
-
def get_remaining_actions_str(actions: list[ActionModel], index: int) -> str:
|
|
949
|
-
remaining_actions = []
|
|
950
|
-
for remaining_action in actions[index:]:
|
|
951
|
-
action_data = remaining_action.model_dump(exclude_unset=True)
|
|
952
|
-
action_name = next(iter(action_data.keys())) if action_data else 'unknown'
|
|
953
|
-
remaining_actions.append(action_name)
|
|
954
|
-
return ', '.join(remaining_actions)
|
|
955
|
-
|
|
956
|
-
if orig_target_hash != new_target_hash:
|
|
957
|
-
# Get names of remaining actions that won't be executed
|
|
958
|
-
remaining_actions_str = get_remaining_actions_str(all_actions, action_index)
|
|
959
|
-
msg = f'Page changed after action {action_index} / {total_actions}: actions {remaining_actions_str} were not executed'
|
|
960
|
-
self.logger.info(msg)
|
|
961
|
-
return ActionResult(
|
|
962
|
-
extracted_content=msg,
|
|
963
|
-
include_in_memory=True,
|
|
964
|
-
long_term_memory=msg,
|
|
965
|
-
)
|
|
966
|
-
|
|
967
|
-
# Check for new elements that appeared
|
|
968
|
-
new_element_hashes = {e.parent_branch_hash() for e in new_selector_map.values()}
|
|
969
|
-
if check_for_new_elements and not new_element_hashes.issubset(cached_element_hashes):
|
|
970
|
-
# next action requires index but there are new elements on the page
|
|
971
|
-
remaining_actions_str = get_remaining_actions_str(all_actions, action_index)
|
|
972
|
-
msg = f'Something new appeared after action {action_index} / {total_actions}: actions {remaining_actions_str} were not executed'
|
|
973
|
-
self.logger.info(msg)
|
|
974
|
-
return ActionResult(
|
|
975
|
-
extracted_content=msg,
|
|
976
|
-
include_in_memory=True,
|
|
977
|
-
long_term_memory=msg,
|
|
978
|
-
)
|
|
979
|
-
|
|
980
|
-
return None
|
|
981
|
-
|
|
982
|
-
async def _execute_single_action(self, action: ActionModel, action_index: int, total_actions: int) -> ActionResult:
|
|
983
|
-
"""Execute a single action in sequential mode"""
|
|
984
|
-
await self._raise_if_stopped_or_paused()
|
|
985
|
-
|
|
986
|
-
# Get action name from the action model
|
|
987
|
-
action_data = action.model_dump(exclude_unset=True)
|
|
988
|
-
action_name = next(iter(action_data.keys())) if action_data else 'unknown'
|
|
989
|
-
action_params = getattr(action, action_name, '') or str(action.model_dump(mode='json'))[:140].replace(
|
|
990
|
-
'"', ''
|
|
991
|
-
).replace('{', '').replace('}', '').replace("'", '').strip().strip(',')
|
|
992
|
-
# Ensure action_params is always a string before checking length
|
|
993
|
-
action_params = str(action_params)
|
|
994
|
-
action_params = f'{action_params[:122]}...' if len(action_params) > 128 else action_params
|
|
995
|
-
|
|
996
|
-
time_start = time.time()
|
|
997
|
-
|
|
998
|
-
red = '\033[91m'
|
|
999
|
-
green = '\033[92m'
|
|
1000
|
-
blue = '\033[34m'
|
|
1001
|
-
reset = '\033[0m'
|
|
1002
|
-
|
|
1003
|
-
self.logger.info(f' 🦾 {blue}[ACTION {action_index + 1}/{total_actions}]{reset} {action_params}')
|
|
1004
|
-
|
|
1005
|
-
result = await self.tools.act(
|
|
1006
|
-
action=action,
|
|
1007
|
-
browser_session=self.browser_session,
|
|
1008
|
-
file_system=self.file_system,
|
|
1009
|
-
page_extraction_llm=self.settings.page_extraction_llm,
|
|
1010
|
-
sensitive_data=self.sensitive_data,
|
|
1011
|
-
available_file_paths=self.available_file_paths,
|
|
1012
|
-
)
|
|
1013
|
-
|
|
1014
|
-
time_end = time.time()
|
|
1015
|
-
time_elapsed = time_end - time_start
|
|
1016
|
-
|
|
1017
|
-
self.logger.debug(
|
|
1018
|
-
f'☑️ Executed action {action_index + 1}/{total_actions}: {green}{action_params}{reset} in {time_elapsed:.2f}s'
|
|
1019
|
-
)
|
|
1020
|
-
|
|
1021
|
-
return result
|
|
759
|
+
return groups
|
|
@@ -78,7 +78,7 @@ When using Composio tools (those with `cpo.{toolkit_name}.{tool_name}` prefix):
|
|
|
78
78
|
- **Special Cases**: `skill_deep_research` only returns guidelines only, then follow guidelines to conduct actual research
|
|
79
79
|
- **Execution Policy**: Skill actions execute only once (no need to retry if errors occur), and all results - whether successful or failed - should be presented to users in structured markdown format.
|
|
80
80
|
- **Follow-up Operations**: When users input skill operations without specifying additional tasks, do not automatically perform subsequent operations. Only perform additional tool operations when users specifically request actions like saving results to files or writing reports.
|
|
81
|
-
-
|
|
81
|
+
- **Search Skill Usage**: `/search` should ONLY be used when users want to quickly obtain specific information or news. Please analyze user intent carefully - if the request contains other browser tasks or requires more complex web operations, you should generally execute browser tasks instead of using skill search.
|
|
82
82
|
|
|
83
83
|
## Language Adaptability
|
|
84
84
|
|
|
@@ -157,7 +157,27 @@ class ReportWriterAgent:
|
|
|
157
157
|
|
|
158
158
|
# Add system message with unified prompt only if message history is empty
|
|
159
159
|
if not self.message_history:
|
|
160
|
-
|
|
160
|
+
report_system_prompt = REPORT_WRITER_PROMPT
|
|
161
|
+
if self.use_thinking:
|
|
162
|
+
report_system_prompt += """
|
|
163
|
+
You must ALWAYS respond with a valid JSON in this exact format:
|
|
164
|
+
{{
|
|
165
|
+
"thinking": "A structured <think>-style reasoning.",
|
|
166
|
+
"action":[{{"<action_name>": {{<action_params>}}]
|
|
167
|
+
}}
|
|
168
|
+
|
|
169
|
+
Action list should NEVER be empty and Each step can only output one action. If multiple actions are output, only the first one will be executed.
|
|
170
|
+
"""
|
|
171
|
+
else:
|
|
172
|
+
report_system_prompt += """
|
|
173
|
+
You must ALWAYS respond with a valid JSON in this exact format:
|
|
174
|
+
{{
|
|
175
|
+
"action":[{{"<action_name>": {{<action_params>}}]
|
|
176
|
+
}}
|
|
177
|
+
|
|
178
|
+
Action list should NEVER be empty and Each step can only output one action. If multiple actions are output, only the first one will be executed.
|
|
179
|
+
"""
|
|
180
|
+
self.message_history.append(SystemMessage(content=report_system_prompt))
|
|
161
181
|
|
|
162
182
|
# Add initial user message with task details
|
|
163
183
|
user_message = f"""Please generate a report within MAX {max_iterations} steps based on the following:
|
|
@@ -214,7 +234,7 @@ Please analyze the task, determine if you need to read any additional files, the
|
|
|
214
234
|
results = []
|
|
215
235
|
time_start = time.time()
|
|
216
236
|
|
|
217
|
-
for i, action in enumerate(actions):
|
|
237
|
+
for i, action in enumerate(actions[:1]):
|
|
218
238
|
action_data = action.model_dump(exclude_unset=True)
|
|
219
239
|
action_name = next(iter(action_data.keys())) if action_data else 'unknown'
|
|
220
240
|
logger.info(f"🛠️ Executing action {i + 1}/{len(actions)}: {action_name}")
|
|
@@ -41,7 +41,11 @@ from vibe_surf.tools.file_system import CustomFileSystem
|
|
|
41
41
|
from vibe_surf.agents.views import VibeSurfAgentSettings
|
|
42
42
|
|
|
43
43
|
from vibe_surf.telemetry.service import ProductTelemetry
|
|
44
|
-
from vibe_surf.telemetry.views import
|
|
44
|
+
from vibe_surf.telemetry.views import (
|
|
45
|
+
VibeSurfAgentTelemetryEvent,
|
|
46
|
+
VibeSurfAgentParsedOutputEvent,
|
|
47
|
+
VibeSurfAgentExceptionEvent
|
|
48
|
+
)
|
|
45
49
|
|
|
46
50
|
from vibe_surf.logger import get_logger
|
|
47
51
|
|
|
@@ -400,11 +404,30 @@ async def _vibesurf_agent_node_impl(state: VibeSurfState) -> VibeSurfState:
|
|
|
400
404
|
AssistantMessage(content=json.dumps(response.completion.model_dump(exclude_none=True, exclude_unset=True),
|
|
401
405
|
ensure_ascii=False)))
|
|
402
406
|
|
|
407
|
+
# Capture telemetry for parsed output
|
|
408
|
+
import vibe_surf
|
|
409
|
+
action_types = []
|
|
410
|
+
for action in actions:
|
|
411
|
+
action_data = action.model_dump(exclude_unset=True)
|
|
412
|
+
action_name = next(iter(action_data.keys())) if action_data else 'unknown'
|
|
413
|
+
action_types.append(action_name)
|
|
414
|
+
|
|
415
|
+
parsed_output_event = VibeSurfAgentParsedOutputEvent(
|
|
416
|
+
version=vibe_surf.__version__,
|
|
417
|
+
parsed_output=json.dumps(parsed.model_dump(exclude_none=True, exclude_unset=True), ensure_ascii=False), # Limit size
|
|
418
|
+
action_count=len(actions),
|
|
419
|
+
action_types=action_types,
|
|
420
|
+
model=getattr(vibesurf_agent.llm, 'model_name', None),
|
|
421
|
+
session_id=state.session_id,
|
|
422
|
+
)
|
|
423
|
+
vibesurf_agent.telemetry.capture(parsed_output_event)
|
|
424
|
+
vibesurf_agent.telemetry.flush()
|
|
425
|
+
|
|
403
426
|
# Log thinking if present
|
|
404
427
|
if hasattr(parsed, 'thinking') and parsed.thinking:
|
|
405
428
|
await log_agent_activity(state, agent_name, "thinking", parsed.thinking)
|
|
406
429
|
|
|
407
|
-
for i, action in enumerate(actions):
|
|
430
|
+
for i, action in enumerate(actions[:1]):
|
|
408
431
|
action_data = action.model_dump(exclude_unset=True)
|
|
409
432
|
action_name = next(iter(action_data.keys())) if action_data else 'unknown'
|
|
410
433
|
logger.info(f"🛠️ Processing VibeSurf action {i + 1}/{len(actions)}: {action_name}")
|
|
@@ -505,8 +528,24 @@ async def _vibesurf_agent_node_impl(state: VibeSurfState) -> VibeSurfState:
|
|
|
505
528
|
|
|
506
529
|
except Exception as e:
|
|
507
530
|
import traceback
|
|
531
|
+
traceback_str = traceback.format_exc()
|
|
508
532
|
traceback.print_exc()
|
|
509
533
|
logger.error(f"❌ VibeSurf agent failed: {e}")
|
|
534
|
+
|
|
535
|
+
# Capture telemetry for exception
|
|
536
|
+
import vibe_surf
|
|
537
|
+
exception_event = VibeSurfAgentExceptionEvent(
|
|
538
|
+
version=vibe_surf.__version__,
|
|
539
|
+
error_message=str(e)[:500], # Limit error message length
|
|
540
|
+
error_type=type(e).__name__,
|
|
541
|
+
traceback=traceback_str[:1000], # Limit traceback length
|
|
542
|
+
model=getattr(vibesurf_agent.llm, 'model_name', None),
|
|
543
|
+
session_id=state.session_id,
|
|
544
|
+
function_name='_vibesurf_agent_node_impl'
|
|
545
|
+
)
|
|
546
|
+
vibesurf_agent.telemetry.capture(exception_event)
|
|
547
|
+
vibesurf_agent.telemetry.flush()
|
|
548
|
+
|
|
510
549
|
state.final_response = f"Task execution failed: {str(e)}"
|
|
511
550
|
state.is_complete = True
|
|
512
551
|
await log_agent_activity(state, agent_name, "error", f"Agent failed: {str(e)}")
|
|
@@ -1606,7 +1645,27 @@ Please continue with your assigned work, incorporating this guidance only if it'
|
|
|
1606
1645
|
upload_files = await self.process_upload_files(upload_files)
|
|
1607
1646
|
|
|
1608
1647
|
if not self.message_history:
|
|
1609
|
-
|
|
1648
|
+
vibesurf_system_prompt = VIBESURF_SYSTEM_PROMPT
|
|
1649
|
+
if self.settings.agent_mode == "thinking":
|
|
1650
|
+
vibesurf_system_prompt += """
|
|
1651
|
+
You must ALWAYS respond with a valid JSON in this exact format:
|
|
1652
|
+
{{
|
|
1653
|
+
"thinking": "A structured <think>-style reasoning.",
|
|
1654
|
+
"action":[{{"<action_name>": {{<action_params>}}]
|
|
1655
|
+
}}
|
|
1656
|
+
|
|
1657
|
+
Action list should NEVER be empty and Each step can only output one action. If multiple actions are output, only the first one will be executed.
|
|
1658
|
+
"""
|
|
1659
|
+
else:
|
|
1660
|
+
vibesurf_system_prompt += """
|
|
1661
|
+
You must ALWAYS respond with a valid JSON in this exact format:
|
|
1662
|
+
{{
|
|
1663
|
+
"action":[{{"<action_name>": {{<action_params>}}]
|
|
1664
|
+
}}
|
|
1665
|
+
|
|
1666
|
+
Action list should NEVER be empty and Each step can only output one action. If multiple actions are output, only the first one will be executed.
|
|
1667
|
+
"""
|
|
1668
|
+
self.message_history.append(SystemMessage(content=vibesurf_system_prompt))
|
|
1610
1669
|
|
|
1611
1670
|
# Format processed upload files for prompt
|
|
1612
1671
|
user_request = f"* User's New Request:\n{task}\n"
|
vibe_surf/backend/llm_config.py
CHANGED
|
@@ -24,6 +24,17 @@ LLM_PROVIDERS = {
|
|
|
24
24
|
"gemini-2.5-pro",
|
|
25
25
|
"gemini-2.5-flash",
|
|
26
26
|
],
|
|
27
|
+
"kimi": [
|
|
28
|
+
"kimi-k2-0905-preview",
|
|
29
|
+
"kimi-k2-0711-preview",
|
|
30
|
+
"kimi-k2-turbo-preview"
|
|
31
|
+
],
|
|
32
|
+
"qwen": [
|
|
33
|
+
"qwen-flash",
|
|
34
|
+
"qwen-plus",
|
|
35
|
+
"qwen3-vl-plus",
|
|
36
|
+
"qwen3-vl-flash"
|
|
37
|
+
],
|
|
27
38
|
"azure_openai": [
|
|
28
39
|
"gpt-4o",
|
|
29
40
|
"gpt-4o-mini",
|
|
@@ -58,6 +69,12 @@ LLM_PROVIDERS = {
|
|
|
58
69
|
"anthropic_bedrock": [
|
|
59
70
|
],
|
|
60
71
|
"openai_compatible": [
|
|
72
|
+
],
|
|
73
|
+
"lm_studio":[
|
|
74
|
+
"qwen/qwen3-vl-8b",
|
|
75
|
+
"qwen/qwen3-vl-30b",
|
|
76
|
+
"qwen/qwen3-14b",
|
|
77
|
+
"openai/gpt-oss-20b"
|
|
61
78
|
]
|
|
62
79
|
}
|
|
63
80
|
|
|
@@ -152,6 +169,30 @@ PROVIDER_METADATA = {
|
|
|
152
169
|
"supports_tools": True,
|
|
153
170
|
"supports_vision": True,
|
|
154
171
|
"default_model": ""
|
|
172
|
+
},
|
|
173
|
+
"qwen": {
|
|
174
|
+
"display_name": "Qwen",
|
|
175
|
+
"requires_api_key": True,
|
|
176
|
+
"requires_base_url": False,
|
|
177
|
+
"supports_tools": True,
|
|
178
|
+
"supports_vision": False,
|
|
179
|
+
"default_model": ""
|
|
180
|
+
},
|
|
181
|
+
"kimi": {
|
|
182
|
+
"display_name": "Kimi",
|
|
183
|
+
"requires_api_key": True,
|
|
184
|
+
"requires_base_url": False,
|
|
185
|
+
"supports_tools": True,
|
|
186
|
+
"supports_vision": False,
|
|
187
|
+
"default_model": ""
|
|
188
|
+
},
|
|
189
|
+
"lm_studio": {
|
|
190
|
+
"display_name": "LM Studio",
|
|
191
|
+
"requires_api_key": False,
|
|
192
|
+
"requires_base_url": False,
|
|
193
|
+
"supports_tools": True,
|
|
194
|
+
"supports_vision": True,
|
|
195
|
+
"default_model": ""
|
|
155
196
|
}
|
|
156
197
|
}
|
|
157
198
|
|
|
@@ -505,32 +505,6 @@ async def initialize_vibesurf_components():
|
|
|
505
505
|
# Initialize LLM from default profile (if available) or fallback to environment variables
|
|
506
506
|
llm = await _initialize_default_llm()
|
|
507
507
|
|
|
508
|
-
# Initialize browser manager
|
|
509
|
-
if browser_manager:
|
|
510
|
-
main_browser_session = browser_manager.main_browser_session
|
|
511
|
-
else:
|
|
512
|
-
from screeninfo import get_monitors
|
|
513
|
-
primary_monitor = get_monitors()[0]
|
|
514
|
-
_update_extension_backend_url(envs["VIBESURF_EXTENSION"], backend_url)
|
|
515
|
-
|
|
516
|
-
browser_profile = AgentBrowserProfile(
|
|
517
|
-
executable_path=browser_execution_path,
|
|
518
|
-
user_data_dir=browser_user_data,
|
|
519
|
-
headless=False,
|
|
520
|
-
keep_alive=True,
|
|
521
|
-
auto_download_pdfs=False,
|
|
522
|
-
highlight_elements=True,
|
|
523
|
-
custom_extensions=[envs["VIBESURF_EXTENSION"]],
|
|
524
|
-
window_size={"width": primary_monitor.width, "height": primary_monitor.height}
|
|
525
|
-
)
|
|
526
|
-
|
|
527
|
-
# Initialize components
|
|
528
|
-
main_browser_session = AgentBrowserSession(browser_profile=browser_profile)
|
|
529
|
-
await main_browser_session.start()
|
|
530
|
-
browser_manager = BrowserManager(
|
|
531
|
-
main_browser_session=main_browser_session
|
|
532
|
-
)
|
|
533
|
-
|
|
534
508
|
# Load active MCP servers from database
|
|
535
509
|
mcp_server_config = await _load_active_mcp_servers()
|
|
536
510
|
|
|
@@ -564,6 +538,32 @@ async def initialize_vibesurf_components():
|
|
|
564
538
|
)
|
|
565
539
|
logger.info(f"✅ Registered Composio tools from {len(toolkit_tools_dict)} enabled toolkits")
|
|
566
540
|
|
|
541
|
+
# Initialize browser manager
|
|
542
|
+
if browser_manager:
|
|
543
|
+
main_browser_session = browser_manager.main_browser_session
|
|
544
|
+
else:
|
|
545
|
+
from screeninfo import get_monitors
|
|
546
|
+
primary_monitor = get_monitors()[0]
|
|
547
|
+
_update_extension_backend_url(envs["VIBESURF_EXTENSION"], backend_url)
|
|
548
|
+
|
|
549
|
+
browser_profile = AgentBrowserProfile(
|
|
550
|
+
executable_path=browser_execution_path,
|
|
551
|
+
user_data_dir=browser_user_data,
|
|
552
|
+
headless=False,
|
|
553
|
+
keep_alive=True,
|
|
554
|
+
auto_download_pdfs=False,
|
|
555
|
+
highlight_elements=True,
|
|
556
|
+
custom_extensions=[envs["VIBESURF_EXTENSION"]],
|
|
557
|
+
window_size={"width": primary_monitor.width, "height": primary_monitor.height}
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
# Initialize components
|
|
561
|
+
main_browser_session = AgentBrowserSession(browser_profile=browser_profile)
|
|
562
|
+
await main_browser_session.start()
|
|
563
|
+
browser_manager = BrowserManager(
|
|
564
|
+
main_browser_session=main_browser_session
|
|
565
|
+
)
|
|
566
|
+
|
|
567
567
|
# Initialize VibeSurfAgent
|
|
568
568
|
vibesurf_agent = VibeSurfAgent(
|
|
569
569
|
llm=llm,
|