mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-02-24 03:00:28 -05:00
Compare commits
23 Commits
fix/messed
...
fix/agent-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e489ba5b7e | ||
|
|
f069aa3ab3 | ||
|
|
e2f32eb99e | ||
|
|
eead01f919 | ||
|
|
2bc6481522 | ||
|
|
b4c3bbe4c4 | ||
|
|
2447c30eff | ||
|
|
04ef290273 | ||
|
|
6a7cd84b26 | ||
|
|
12d0a1f13b | ||
|
|
35a7f98ba7 | ||
|
|
34b70d0673 | ||
|
|
deb2bc4344 | ||
|
|
89785c88f9 | ||
|
|
c08ba6a818 | ||
|
|
73b6ec3371 | ||
|
|
95afa8c2f5 | ||
|
|
1de260c425 | ||
|
|
bfdc1edac1 | ||
|
|
eef394683a | ||
|
|
66c241644f | ||
|
|
f25c2d1e6a | ||
|
|
fc64f83331 |
572
autogpt_platform/backend/.application.logs
Normal file
572
autogpt_platform/backend/.application.logs
Normal file
@@ -0,0 +1,572 @@
|
||||
2026-02-21 20:31:19,811 [34mINFO[0m Initializing LaunchDarkly Client 9.15.0
|
||||
2026-02-21 20:31:19,812 [34mINFO[0m Starting event processor
|
||||
2026-02-21 20:31:19,812 [34mINFO[0m Starting StreamingUpdateProcessor connecting to uri: https://stream.launchdarkly.com/all
|
||||
2026-02-21 20:31:19,812 [34mINFO[0m Waiting up to 5 seconds for LaunchDarkly client to initialize...
|
||||
2026-02-21 20:31:19,812 [34mINFO[0m Connecting to stream at https://stream.launchdarkly.com/all
|
||||
2026-02-21 20:31:20,051 [34mINFO[0m StreamingUpdateProcessor initialized ok.
|
||||
2026-02-21 20:31:20,051 [34mINFO[0m Started LaunchDarkly Client: OK
|
||||
2026-02-21 20:31:20,051 [34mINFO[0m LaunchDarkly client initialized successfully
|
||||
2026-02-21 20:31:21,578 [33mWARNING[0m [33mProvider LINEAR implements OAuth but the required env vars LINEAR_CLIENT_ID and LINEAR_CLIENT_SECRET are not both set[0m
|
||||
2026-02-21 20:31:21,623 [33mWARNING[0m [33mAuthentication error: Langfuse client initialized without public_key. Client will be disabled. Provide a public_key parameter or set LANGFUSE_PUBLIC_KEY environment variable. [0m
|
||||
2026-02-21 20:31:21,796 [34mINFO[0m Metrics endpoint exposed at /metrics for external-api
|
||||
2026-02-21 20:31:21,800 [34mINFO[0m Metrics endpoint exposed at /metrics for rest-api
|
||||
2026-02-21 20:31:21,881 [34mINFO[0m Metrics endpoint exposed at /metrics for websocket-server
|
||||
2026-02-21 20:31:21,913 [33mWARNING[0m [33mPostmark server API token not found, email sending disabled[0m
|
||||
2026-02-21 20:31:21,956 [34mINFO[0m [DatabaseManager] started with PID 6089
|
||||
2026-02-21 20:31:21,958 [34mINFO[0m [Scheduler] started with PID 6090
|
||||
2026-02-21 20:31:21,959 [34mINFO[0m [NotificationManager] started with PID 6091
|
||||
2026-02-21 20:31:21,960 [34mINFO[0m [WebsocketServer] started with PID 6092
|
||||
2026-02-21 20:31:21,961 [34mINFO[0m [AgentServer] started with PID 6093
|
||||
2026-02-21 20:31:21,962 [34mINFO[0m [ExecutionManager] started with PID 6094
|
||||
2026-02-21 20:31:21,963 [34mINFO[0m [CoPilotExecutor] Starting...
|
||||
2026-02-21 20:31:21,963 [34mINFO[0m [CoPilotExecutor] Pod assigned executor_id: fb7d76b3-8dc3-40a4-947e-a93bfad207da
|
||||
2026-02-21 20:31:21,963 [34mINFO[0m [CoPilotExecutor] Spawn max-5 workers...
|
||||
2026-02-21 20:31:21,970 [34mINFO[0m [PID-6048|THREAD-77685505|CoPilotExecutor|RabbitMQ-124e33d7-4877-4745-9778-6b6b06de92d2] Acquiring connection started...
|
||||
2026-02-21 20:31:21,971 [34mINFO[0m [PID-6048|THREAD-77685506|CoPilotExecutor|RabbitMQ-124e33d7-4877-4745-9778-6b6b06de92d2] Acquiring connection started...
|
||||
2026-02-21 20:31:21,973 [34mINFO[0m Pika version 1.3.2 connecting to ('::1', 5672, 0, 0)
|
||||
2026-02-21 20:31:21,973 [34mINFO[0m Pika version 1.3.2 connecting to ('::1', 5672, 0, 0)
|
||||
2026-02-21 20:31:21,974 [34mINFO[0m Socket connected: <socket.socket fd=30, family=30, type=1, proto=6, laddr=('::1', 55999, 0, 0), raddr=('::1', 5672, 0, 0)>
|
||||
2026-02-21 20:31:21,975 [34mINFO[0m Socket connected: <socket.socket fd=29, family=30, type=1, proto=6, laddr=('::1', 55998, 0, 0), raddr=('::1', 5672, 0, 0)>
|
||||
2026-02-21 20:31:21,975 [34mINFO[0m Streaming transport linked up: (<pika.adapters.utils.io_services_utils._AsyncPlaintextTransport object at 0x120f5eba0>, _StreamingProtocolShim: <SelectConnection PROTOCOL transport=<pika.adapters.utils.io_services_utils._AsyncPlaintextTransport object at 0x120f5eba0> params=<ConnectionParameters host=localhost port=5672 virtual_host=/ ssl=False>>).
|
||||
2026-02-21 20:31:21,976 [34mINFO[0m Streaming transport linked up: (<pika.adapters.utils.io_services_utils._AsyncPlaintextTransport object at 0x120fa0410>, _StreamingProtocolShim: <SelectConnection PROTOCOL transport=<pika.adapters.utils.io_services_utils._AsyncPlaintextTransport object at 0x120fa0410> params=<ConnectionParameters host=localhost port=5672 virtual_host=/ ssl=False>>).
|
||||
2026-02-21 20:31:21,990 [34mINFO[0m AMQPConnector - reporting success: <SelectConnection OPEN transport=<pika.adapters.utils.io_services_utils._AsyncPlaintextTransport object at 0x120fa0410> params=<ConnectionParameters host=localhost port=5672 virtual_host=/ ssl=False>>
|
||||
2026-02-21 20:31:21,991 [34mINFO[0m AMQPConnectionWorkflow - reporting success: <SelectConnection OPEN transport=<pika.adapters.utils.io_services_utils._AsyncPlaintextTransport object at 0x120fa0410> params=<ConnectionParameters host=localhost port=5672 virtual_host=/ ssl=False>>
|
||||
2026-02-21 20:31:21,991 [34mINFO[0m AMQPConnector - reporting success: <SelectConnection OPEN transport=<pika.adapters.utils.io_services_utils._AsyncPlaintextTransport object at 0x120f5eba0> params=<ConnectionParameters host=localhost port=5672 virtual_host=/ ssl=False>>
|
||||
2026-02-21 20:31:21,991 [34mINFO[0m Connection workflow succeeded: <SelectConnection OPEN transport=<pika.adapters.utils.io_services_utils._AsyncPlaintextTransport object at 0x120fa0410> params=<ConnectionParameters host=localhost port=5672 virtual_host=/ ssl=False>>
|
||||
2026-02-21 20:31:21,991 [34mINFO[0m AMQPConnectionWorkflow - reporting success: <SelectConnection OPEN transport=<pika.adapters.utils.io_services_utils._AsyncPlaintextTransport object at 0x120f5eba0> params=<ConnectionParameters host=localhost port=5672 virtual_host=/ ssl=False>>
|
||||
2026-02-21 20:31:21,991 [34mINFO[0m Created channel=1
|
||||
2026-02-21 20:31:21,992 [34mINFO[0m Connection workflow succeeded: <SelectConnection OPEN transport=<pika.adapters.utils.io_services_utils._AsyncPlaintextTransport object at 0x120f5eba0> params=<ConnectionParameters host=localhost port=5672 virtual_host=/ ssl=False>>
|
||||
2026-02-21 20:31:21,992 [34mINFO[0m Created channel=1
|
||||
2026-02-21 20:31:22,005 [34mINFO[0m [PID-6048|THREAD-77685505|CoPilotExecutor|RabbitMQ-124e33d7-4877-4745-9778-6b6b06de92d2] Acquiring connection completed successfully.
|
||||
2026-02-21 20:31:22,005 [34mINFO[0m [PID-6048|THREAD-77685506|CoPilotExecutor|RabbitMQ-124e33d7-4877-4745-9778-6b6b06de92d2] Acquiring connection completed successfully.
|
||||
2026-02-21 20:31:22,007 [34mINFO[0m [CoPilotExecutor] Starting to consume cancel messages...
|
||||
2026-02-21 20:31:22,008 [34mINFO[0m [CoPilotExecutor] Starting to consume run messages...
|
||||
2026-02-21 20:31:23,199 [34mINFO[0m Initializing LaunchDarkly Client 9.15.0
|
||||
2026-02-21 20:31:23,201 [34mINFO[0m Starting event processor
|
||||
2026-02-21 20:31:23,202 [34mINFO[0m Starting StreamingUpdateProcessor connecting to uri: https://stream.launchdarkly.com/all
|
||||
2026-02-21 20:31:23,202 [34mINFO[0m Waiting up to 5 seconds for LaunchDarkly client to initialize...
|
||||
2026-02-21 20:31:23,202 [34mINFO[0m Connecting to stream at https://stream.launchdarkly.com/all
|
||||
2026-02-21 20:31:23,331 [34mINFO[0m StreamingUpdateProcessor initialized ok.
|
||||
2026-02-21 20:31:23,331 [34mINFO[0m Started LaunchDarkly Client: OK
|
||||
2026-02-21 20:31:23,332 [34mINFO[0m LaunchDarkly client initialized successfully
|
||||
2026-02-21 20:31:23,891 [34mINFO[0m Initializing LaunchDarkly Client 9.15.0
|
||||
2026-02-21 20:31:23,892 [34mINFO[0m Starting event processor
|
||||
2026-02-21 20:31:23,893 [34mINFO[0m Starting StreamingUpdateProcessor connecting to uri: https://stream.launchdarkly.com/all
|
||||
2026-02-21 20:31:23,893 [34mINFO[0m Waiting up to 5 seconds for LaunchDarkly client to initialize...
|
||||
2026-02-21 20:31:23,893 [34mINFO[0m Connecting to stream at https://stream.launchdarkly.com/all
|
||||
2026-02-21 20:31:23,946 [34mINFO[0m Initializing LaunchDarkly Client 9.15.0
|
||||
2026-02-21 20:31:23,947 [34mINFO[0m Starting event processor
|
||||
2026-02-21 20:31:23,947 [34mINFO[0m Starting StreamingUpdateProcessor connecting to uri: https://stream.launchdarkly.com/all
|
||||
2026-02-21 20:31:23,947 [34mINFO[0m Waiting up to 5 seconds for LaunchDarkly client to initialize...
|
||||
2026-02-21 20:31:23,948 [34mINFO[0m Connecting to stream at https://stream.launchdarkly.com/all
|
||||
2026-02-21 20:31:24,017 [34mINFO[0m StreamingUpdateProcessor initialized ok.
|
||||
2026-02-21 20:31:24,017 [34mINFO[0m Started LaunchDarkly Client: OK
|
||||
2026-02-21 20:31:24,017 [34mINFO[0m LaunchDarkly client initialized successfully
|
||||
2026-02-21 20:31:24,065 [34mINFO[0m StreamingUpdateProcessor initialized ok.
|
||||
2026-02-21 20:31:24,065 [34mINFO[0m Started LaunchDarkly Client: OK
|
||||
2026-02-21 20:31:24,065 [34mINFO[0m LaunchDarkly client initialized successfully
|
||||
2026-02-21 20:31:24,707 [34mINFO[0m [NotificationManager] Starting...
|
||||
2026-02-21 20:31:24,750 [34mINFO[0m Metrics endpoint exposed at /metrics for NotificationManager
|
||||
2026-02-21 20:31:24,754 [34mINFO[0m [PID-6091|THREAD-77685702|NotificationManager|FastAPI server-d17271ed-e3a2-4e93-900b-a0d3bd2b8100] Running FastAPI server started...
|
||||
2026-02-21 20:31:24,755 [34mINFO[0m [NotificationManager] Starting RPC server at http://localhost:8007
|
||||
2026-02-21 20:31:24,756 [34mINFO[0m [NotificationManager] [NotificationManager] ⏳ Configuring RabbitMQ...
|
||||
2026-02-21 20:31:24,757 [34mINFO[0m [PID-6091|THREAD-77685703|NotificationManager|AsyncRabbitMQ-7963c91c-c443-4479-a55e-5e9a8d7d942d] Acquiring async connection started...
|
||||
2026-02-21 20:31:24,775 [34mINFO[0m Started server process [6091]
|
||||
2026-02-21 20:31:24,775 [34mINFO[0m Waiting for application startup.
|
||||
2026-02-21 20:31:24,776 [34mINFO[0m Application startup complete.
|
||||
2026-02-21 20:31:24,777 [31mERROR[0m [31m[Errno 48] error while attempting to bind on address ('::1', 8007, 0, 0): [errno 48] address already in use[0m
|
||||
2026-02-21 20:31:24,781 [34mINFO[0m Waiting for application shutdown.
|
||||
2026-02-21 20:31:24,781 [34mINFO[0m [NotificationManager] ✅ FastAPI has finished
|
||||
2026-02-21 20:31:24,782 [34mINFO[0m Application shutdown complete.
|
||||
2026-02-21 20:31:24,783 [34mINFO[0m [NotificationManager] 🛑 Shared event loop stopped
|
||||
2026-02-21 20:31:24,783 [34mINFO[0m [NotificationManager] 🧹 Running cleanup
|
||||
2026-02-21 20:31:24,783 [34mINFO[0m [NotificationManager] ⏳ Disconnecting RabbitMQ...
|
||||
Process NotificationManager:
|
||||
Traceback (most recent call last):
|
||||
File "/opt/homebrew/Cellar/python@3.13/3.13.1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/multiprocessing/process.py", line 313, in _bootstrap
|
||||
self.run()
|
||||
~~~~~~~~^^
|
||||
File "/opt/homebrew/Cellar/python@3.13/3.13.1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/multiprocessing/process.py", line 108, in run
|
||||
self._target(*self._args, **self._kwargs)
|
||||
~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Users/majdyz/Code/AutoGPT/autogpt_platform/backend/backend/util/process.py", line 83, in execute_run_command
|
||||
self.cleanup()
|
||||
~~~~~~~~~~~~^^
|
||||
File "/Users/majdyz/Code/AutoGPT/autogpt_platform/backend/backend/notifications/notifications.py", line 1094, in cleanup
|
||||
self.run_and_wait(self.rabbitmq_service.disconnect())
|
||||
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Users/majdyz/Code/AutoGPT/autogpt_platform/backend/backend/util/service.py", line 136, in run_and_wait
|
||||
return asyncio.run_coroutine_threadsafe(coro, self.shared_event_loop).result()
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/opt/homebrew/Cellar/python@3.13/3.13.1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/tasks.py", line 1003, in run_coroutine_threadsafe
|
||||
loop.call_soon_threadsafe(callback)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^
|
||||
File "/opt/homebrew/Cellar/python@3.13/3.13.1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/base_events.py", line 873, in call_soon_threadsafe
|
||||
self._check_closed()
|
||||
~~~~~~~~~~~~~~~~~~^^
|
||||
File "/opt/homebrew/Cellar/python@3.13/3.13.1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/base_events.py", line 551, in _check_closed
|
||||
raise RuntimeError('Event loop is closed')
|
||||
RuntimeError: Event loop is closed
|
||||
/opt/homebrew/Cellar/python@3.13/3.13.1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/multiprocessing/process.py:327: RuntimeWarning: coroutine 'AsyncRabbitMQ.disconnect' was never awaited
|
||||
traceback.print_exc()
|
||||
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
|
||||
2026-02-21 20:31:24,846 [34mINFO[0m Initializing LaunchDarkly Client 9.15.0
|
||||
2026-02-21 20:31:24,848 [34mINFO[0m Starting event processor
|
||||
2026-02-21 20:31:24,848 [34mINFO[0m Starting StreamingUpdateProcessor connecting to uri: https://stream.launchdarkly.com/all
|
||||
2026-02-21 20:31:24,849 [34mINFO[0m Waiting up to 5 seconds for LaunchDarkly client to initialize...
|
||||
2026-02-21 20:31:24,849 [34mINFO[0m Connecting to stream at https://stream.launchdarkly.com/all
|
||||
2026-02-21 20:31:24,857 [34mINFO[0m Initializing LaunchDarkly Client 9.15.0
|
||||
2026-02-21 20:31:24,858 [34mINFO[0m Starting event processor
|
||||
2026-02-21 20:31:24,858 [34mINFO[0m Starting StreamingUpdateProcessor connecting to uri: https://stream.launchdarkly.com/all
|
||||
2026-02-21 20:31:24,858 [34mINFO[0m Waiting up to 5 seconds for LaunchDarkly client to initialize...
|
||||
2026-02-21 20:31:24,858 [34mINFO[0m Connecting to stream at https://stream.launchdarkly.com/all
|
||||
2026-02-21 20:31:24,862 [34mINFO[0m Initializing LaunchDarkly Client 9.15.0
|
||||
2026-02-21 20:31:24,863 [34mINFO[0m Starting event processor
|
||||
2026-02-21 20:31:24,864 [34mINFO[0m Starting StreamingUpdateProcessor connecting to uri: https://stream.launchdarkly.com/all
|
||||
2026-02-21 20:31:24,864 [34mINFO[0m Waiting up to 5 seconds for LaunchDarkly client to initialize...
|
||||
2026-02-21 20:31:24,864 [34mINFO[0m Connecting to stream at https://stream.launchdarkly.com/all
|
||||
2026-02-21 20:31:24,966 [34mINFO[0m StreamingUpdateProcessor initialized ok.
|
||||
2026-02-21 20:31:24,967 [34mINFO[0m Started LaunchDarkly Client: OK
|
||||
2026-02-21 20:31:24,967 [34mINFO[0m LaunchDarkly client initialized successfully
|
||||
2026-02-21 20:31:24,976 [34mINFO[0m StreamingUpdateProcessor initialized ok.
|
||||
2026-02-21 20:31:24,976 [34mINFO[0m Started LaunchDarkly Client: OK
|
||||
2026-02-21 20:31:24,976 [34mINFO[0m LaunchDarkly client initialized successfully
|
||||
2026-02-21 20:31:24,989 [34mINFO[0m StreamingUpdateProcessor initialized ok.
|
||||
2026-02-21 20:31:24,989 [34mINFO[0m Started LaunchDarkly Client: OK
|
||||
2026-02-21 20:31:24,989 [34mINFO[0m LaunchDarkly client initialized successfully
|
||||
2026-02-21 20:31:25,035 [34mINFO[0m Metrics endpoint exposed at /metrics for websocket-server
|
||||
2026-02-21 20:31:25,036 [34mINFO[0m [WebsocketServer] Starting...
|
||||
2026-02-21 20:31:25,036 [34mINFO[0m CORS allow origins: ['http://localhost:3000', 'http://127.0.0.1:3000']
|
||||
2026-02-21 20:31:25,076 [34mINFO[0m Started server process [6092]
|
||||
2026-02-21 20:31:25,076 [34mINFO[0m Waiting for application startup.
|
||||
2026-02-21 20:31:25,077 [34mINFO[0m Application startup complete.
|
||||
2026-02-21 20:31:25,077 [34mINFO[0m [PID-6092|THREAD-77685501|WebsocketServer|AsyncRedis-b6fb3c5c-0070-4c5c-90eb-922d4f2152c2] Acquiring connection started...
|
||||
2026-02-21 20:31:25,077 [34mINFO[0m [PID-6092|THREAD-77685501|WebsocketServer|AsyncRedis-b6fb3c5c-0070-4c5c-90eb-922d4f2152c2] Acquiring connection started...
|
||||
2026-02-21 20:31:25,078 [31mERROR[0m [31m[Errno 48] error while attempting to bind on address ('0.0.0.0', 8001): address already in use[0m
|
||||
2026-02-21 20:31:25,080 [34mINFO[0m Waiting for application shutdown.
|
||||
2026-02-21 20:31:25,080 [34mINFO[0m Application shutdown complete.
|
||||
2026-02-21 20:31:25,080 [34mINFO[0m Event broadcaster stopped
|
||||
2026-02-21 20:31:25,081 [33mWARNING[0m [33m[WebsocketServer] 🛑 Terminating because of SystemExit: 1[0m
|
||||
2026-02-21 20:31:25,081 [34mINFO[0m [WebsocketServer] 🧹 Running cleanup
|
||||
2026-02-21 20:31:25,081 [34mINFO[0m [WebsocketServer] ✅ Cleanup done
|
||||
2026-02-21 20:31:25,081 [34mINFO[0m [WebsocketServer] 🛑 Terminated
|
||||
2026-02-21 20:31:25,915 [34mINFO[0m [DatabaseManager] Starting...
|
||||
2026-02-21 20:31:25,947 [34mINFO[0m Metrics endpoint exposed at /metrics for DatabaseManager
|
||||
2026-02-21 20:31:25,970 [34mINFO[0m [ExecutionManager] Starting...
|
||||
2026-02-21 20:31:25,970 [34mINFO[0m [GraphExecutor] [ExecutionManager] 🆔 Pod assigned executor_id: 90ff5962-bdc8-456d-a864-01c5f4f199bd
|
||||
2026-02-21 20:31:25,971 [34mINFO[0m [GraphExecutor] [ExecutionManager] ⏳ Spawn max-10 workers...
|
||||
2026-02-21 20:31:25,973 [34mINFO[0m [Scheduler] Starting...
|
||||
2026-02-21 20:31:25,971 [33mWARNING[0m [33m[ExecutionManager] 🛑 Terminating because of OSError: [Errno 48] Address already in use[0m
|
||||
Traceback (most recent call last):
|
||||
File "/Users/majdyz/Code/AutoGPT/autogpt_platform/backend/backend/util/process.py", line 65, in execute_run_command
|
||||
self.run()
|
||||
~~~~~~~~^^
|
||||
File "/Users/majdyz/Code/AutoGPT/autogpt_platform/backend/backend/executor/manager.py", line 1554, in run
|
||||
start_http_server(settings.config.execution_manager_port)
|
||||
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Users/majdyz/Code/AutoGPT/autogpt_platform/backend/.venv/lib/python3.13/site-packages/prometheus_client/exposition.py", line 251, in start_wsgi_server
|
||||
httpd = make_server(addr, port, app, TmpServer, handler_class=_SilentHandler)
|
||||
File "/opt/homebrew/Cellar/python@3.13/3.13.1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/wsgiref/simple_server.py", line 150, in make_server
|
||||
server = server_class((host, port), handler_class)
|
||||
File "/opt/homebrew/Cellar/python@3.13/3.13.1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/socketserver.py", line 457, in __init__
|
||||
self.server_bind()
|
||||
~~~~~~~~~~~~~~~~^^
|
||||
File "/opt/homebrew/Cellar/python@3.13/3.13.1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/wsgiref/simple_server.py", line 50, in server_bind
|
||||
HTTPServer.server_bind(self)
|
||||
~~~~~~~~~~~~~~~~~~~~~~^^^^^^
|
||||
File "/opt/homebrew/Cellar/python@3.13/3.13.1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/http/server.py", line 136, in server_bind
|
||||
socketserver.TCPServer.server_bind(self)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
|
||||
File "/opt/homebrew/Cellar/python@3.13/3.13.1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/socketserver.py", line 473, in server_bind
|
||||
self.socket.bind(self.server_address)
|
||||
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
|
||||
OSError: [Errno 48] Address already in use
|
||||
2026-02-21 20:31:25,978 [34mINFO[0m [ExecutionManager] 🧹 Running cleanup
|
||||
2026-02-21 20:31:25,978 [34mINFO[0m [GraphExecutor] [ExecutionManager][on_graph_executor_stop 6094] 🧹 Starting graceful shutdown...
|
||||
2026-02-21 20:31:25,978 [34mINFO[0m [PID-6094|THREAD-77685503|ExecutionManager|RabbitMQ-5b203f2b-8b80-46b1-8e47-481497e68a82] Acquiring connection started...
|
||||
2026-02-21 20:31:25,980 [34mINFO[0m Pika version 1.3.2 connecting to ('::1', 5672, 0, 0)
|
||||
2026-02-21 20:31:25,981 [34mINFO[0m Socket connected: <socket.socket fd=14, family=30, type=1, proto=6, laddr=('::1', 56040, 0, 0), raddr=('::1', 5672, 0, 0)>
|
||||
2026-02-21 20:31:25,982 [34mINFO[0m Streaming transport linked up: (<pika.adapters.utils.io_services_utils._AsyncPlaintextTransport object at 0x1316cd550>, _StreamingProtocolShim: <SelectConnection PROTOCOL transport=<pika.adapters.utils.io_services_utils._AsyncPlaintextTransport object at 0x1316cd550> params=<ConnectionParameters host=localhost port=5672 virtual_host=/ ssl=False>>).
|
||||
2026-02-21 20:31:25,991 [34mINFO[0m AMQPConnector - reporting success: <SelectConnection OPEN transport=<pika.adapters.utils.io_services_utils._AsyncPlaintextTransport object at 0x1316cd550> params=<ConnectionParameters host=localhost port=5672 virtual_host=/ ssl=False>>
|
||||
2026-02-21 20:31:25,991 [34mINFO[0m AMQPConnectionWorkflow - reporting success: <SelectConnection OPEN transport=<pika.adapters.utils.io_services_utils._AsyncPlaintextTransport object at 0x1316cd550> params=<ConnectionParameters host=localhost port=5672 virtual_host=/ ssl=False>>
|
||||
2026-02-21 20:31:25,991 [34mINFO[0m Connection workflow succeeded: <SelectConnection OPEN transport=<pika.adapters.utils.io_services_utils._AsyncPlaintextTransport object at 0x1316cd550> params=<ConnectionParameters host=localhost port=5672 virtual_host=/ ssl=False>>
|
||||
2026-02-21 20:31:25,991 [34mINFO[0m Created channel=1
|
||||
2026-02-21 20:31:26,001 [34mINFO[0m [PID-6094|THREAD-77685503|ExecutionManager|RabbitMQ-5b203f2b-8b80-46b1-8e47-481497e68a82] Acquiring connection completed successfully.
|
||||
2026-02-21 20:31:26,001 [34mINFO[0m [GraphExecutor] [ExecutionManager][on_graph_executor_stop 6094] ✅ Exec consumer has been signaled to stop
|
||||
2026-02-21 20:31:26,001 [34mINFO[0m [GraphExecutor] [ExecutionManager][on_graph_executor_stop 6094] ✅ Executor shutdown completed
|
||||
2026-02-21 20:31:26,001 [34mINFO[0m [GraphExecutor] [ExecutionManager][on_graph_executor_stop 6094] ✅ Released execution locks
|
||||
2026-02-21 20:31:26,001 [31mERROR[0m [31m[GraphExecutor] [ExecutionManager][on_graph_executor_stop 6094] [run-consumer] ⚠️ Error disconnecting run client: <class 'RuntimeError'> cannot join thread before it is started [0m
|
||||
2026-02-21 20:31:26,003 [34mINFO[0m [PID-6094|THREAD-77685503|ExecutionManager|RabbitMQ-5b203f2b-8b80-46b1-8e47-481497e68a82] Acquiring connection started...
|
||||
2026-02-21 20:31:26,005 [34mINFO[0m Pika version 1.3.2 connecting to ('::1', 5672, 0, 0)
|
||||
2026-02-21 20:31:26,005 [34mINFO[0m Socket connected: <socket.socket fd=20, family=30, type=1, proto=6, laddr=('::1', 56043, 0, 0), raddr=('::1', 5672, 0, 0)>
|
||||
2026-02-21 20:31:26,006 [34mINFO[0m Streaming transport linked up: (<pika.adapters.utils.io_services_utils._AsyncPlaintextTransport object at 0x1318e4cd0>, _StreamingProtocolShim: <SelectConnection PROTOCOL transport=<pika.adapters.utils.io_services_utils._AsyncPlaintextTransport object at 0x1318e4cd0> params=<ConnectionParameters host=localhost port=5672 virtual_host=/ ssl=False>>).
|
||||
2026-02-21 20:31:26,009 [34mINFO[0m Metrics endpoint exposed at /metrics for Scheduler
|
||||
2026-02-21 20:31:26,010 [34mINFO[0m AMQPConnector - reporting success: <SelectConnection OPEN transport=<pika.adapters.utils.io_services_utils._AsyncPlaintextTransport object at 0x1318e4cd0> params=<ConnectionParameters host=localhost port=5672 virtual_host=/ ssl=False>>
|
||||
2026-02-21 20:31:26,010 [34mINFO[0m AMQPConnectionWorkflow - reporting success: <SelectConnection OPEN transport=<pika.adapters.utils.io_services_utils._AsyncPlaintextTransport object at 0x1318e4cd0> params=<ConnectionParameters host=localhost port=5672 virtual_host=/ ssl=False>>
|
||||
2026-02-21 20:31:26,010 [34mINFO[0m Connection workflow succeeded: <SelectConnection OPEN transport=<pika.adapters.utils.io_services_utils._AsyncPlaintextTransport object at 0x1318e4cd0> params=<ConnectionParameters host=localhost port=5672 virtual_host=/ ssl=False>>
|
||||
2026-02-21 20:31:26,011 [34mINFO[0m Created channel=1
|
||||
2026-02-21 20:31:26,015 [34mINFO[0m [PID-6090|THREAD-77685897|Scheduler|FastAPI server-6caca9cc-c4c1-417f-8b83-d96f02472df9] Running FastAPI server started...
|
||||
2026-02-21 20:31:26,016 [34mINFO[0m [Scheduler] Starting RPC server at http://localhost:8003
|
||||
2026-02-21 20:31:26,016 [34mINFO[0m [PID-6094|THREAD-77685503|ExecutionManager|RabbitMQ-5b203f2b-8b80-46b1-8e47-481497e68a82] Acquiring connection completed successfully.
|
||||
2026-02-21 20:31:26,016 [31mERROR[0m [31m[GraphExecutor] [ExecutionManager][on_graph_executor_stop 6094] [cancel-consumer] ⚠️ Error disconnecting run client: <class 'RuntimeError'> cannot join thread before it is started [0m
|
||||
2026-02-21 20:31:26,019 [34mINFO[0m [GraphExecutor] [ExecutionManager][on_graph_executor_stop 6094] ✅ Finished GraphExec cleanup
|
||||
2026-02-21 20:31:26,019 [34mINFO[0m [ExecutionManager] ✅ Cleanup done
|
||||
2026-02-21 20:31:26,019 [34mINFO[0m [ExecutionManager] 🛑 Terminated
|
||||
2026-02-21 20:31:26,188 [34mINFO[0m [PID-6089|THREAD-77685901|DatabaseManager|FastAPI server-7019e67b-30c1-4d08-a0ec-4f0175629d0e] Running FastAPI server started...
|
||||
2026-02-21 20:31:26,189 [34mINFO[0m [DatabaseManager] Starting RPC server at http://localhost:8005
|
||||
2026-02-21 20:31:26,197 [34mINFO[0m [DatabaseManager] ⏳ Connecting to Database...
|
||||
2026-02-21 20:31:26,197 [34mINFO[0m [PID-6089|THREAD-77685902|DatabaseManager|Prisma-64fcde85-3de3-4783-b2c6-789775451cd0] Acquiring connection started...
|
||||
2026-02-21 20:31:26,254 [34mINFO[0m [Scheduler] [APScheduler] Adding job tentatively -- it will be properly scheduled when the scheduler starts
|
||||
2026-02-21 20:31:26,255 [34mINFO[0m [Scheduler] [APScheduler] Adding job tentatively -- it will be properly scheduled when the scheduler starts
|
||||
2026-02-21 20:31:26,255 [34mINFO[0m [Scheduler] [APScheduler] Adding job tentatively -- it will be properly scheduled when the scheduler starts
|
||||
2026-02-21 20:31:26,255 [34mINFO[0m [Scheduler] [APScheduler] Adding job tentatively -- it will be properly scheduled when the scheduler starts
|
||||
2026-02-21 20:31:26,255 [34mINFO[0m [Scheduler] [APScheduler] Adding job tentatively -- it will be properly scheduled when the scheduler starts
|
||||
2026-02-21 20:31:26,255 [34mINFO[0m [Scheduler] [APScheduler] Adding job tentatively -- it will be properly scheduled when the scheduler starts
|
||||
2026-02-21 20:31:26,256 [34mINFO[0m [Scheduler] [APScheduler] Adding job tentatively -- it will be properly scheduled when the scheduler starts
|
||||
2026-02-21 20:31:26,346 [34mINFO[0m [PID-6089|THREAD-77685902|DatabaseManager|Prisma-64fcde85-3de3-4783-b2c6-789775451cd0] Acquiring connection completed successfully.
|
||||
2026-02-21 20:31:26,346 [34mINFO[0m [DatabaseManager] ✅ Ready
|
||||
2026-02-21 20:31:26,347 [31mERROR[0m [31m[Errno 48] error while attempting to bind on address ('::1', 8005, 0, 0): [errno 48] address already in use[0m
|
||||
2026-02-21 20:31:26,349 [34mINFO[0m [DatabaseManager] ⏳ Disconnecting Database...
|
||||
2026-02-21 20:31:26,349 [34mINFO[0m [PID-6089|THREAD-77685902|DatabaseManager|Prisma-2397ec31-7da6-4598-a012-6c48f17ea97f] Releasing connection started...
|
||||
2026-02-21 20:31:26,350 [34mINFO[0m [PID-6089|THREAD-77685902|DatabaseManager|Prisma-2397ec31-7da6-4598-a012-6c48f17ea97f] Releasing connection completed successfully.
|
||||
2026-02-21 20:31:26,351 [34mINFO[0m [DatabaseManager] ✅ FastAPI has finished
|
||||
2026-02-21 20:31:26,351 [34mINFO[0m [DatabaseManager] 🛑 Shared event loop stopped
|
||||
2026-02-21 20:31:26,351 [34mINFO[0m [DatabaseManager] 🧹 Running cleanup
|
||||
Process DatabaseManager:
|
||||
Traceback (most recent call last):
|
||||
File "/opt/homebrew/Cellar/python@3.13/3.13.1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/multiprocessing/process.py", line 313, in _bootstrap
|
||||
self.run()
|
||||
~~~~~~~~^^
|
||||
File "/opt/homebrew/Cellar/python@3.13/3.13.1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/multiprocessing/process.py", line 108, in run
|
||||
self._target(*self._args, **self._kwargs)
|
||||
~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Users/majdyz/Code/AutoGPT/autogpt_platform/backend/backend/util/process.py", line 83, in execute_run_command
|
||||
self.cleanup()
|
||||
~~~~~~~~~~~~^^
|
||||
File "/Users/majdyz/Code/AutoGPT/autogpt_platform/backend/backend/util/service.py", line 153, in cleanup
|
||||
self.shared_event_loop.call_soon_threadsafe(self.shared_event_loop.stop)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/opt/homebrew/Cellar/python@3.13/3.13.1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/base_events.py", line 873, in call_soon_threadsafe
|
||||
self._check_closed()
|
||||
~~~~~~~~~~~~~~~~~~^^
|
||||
File "/opt/homebrew/Cellar/python@3.13/3.13.1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/base_events.py", line 551, in _check_closed
|
||||
raise RuntimeError('Event loop is closed')
|
||||
RuntimeError: Event loop is closed
|
||||
2026-02-21 20:31:26,382 [34mINFO[0m [Scheduler] [APScheduler] Added job "process_weekly_summary" to job store "weekly_notifications"
|
||||
2026-02-21 20:31:26,390 [34mINFO[0m [Scheduler] [APScheduler] Added job "report_late_executions" to job store "execution"
|
||||
2026-02-21 20:31:26,392 [34mINFO[0m [Scheduler] [APScheduler] Added job "report_block_error_rates" to job store "execution"
|
||||
2026-02-21 20:31:26,395 [34mINFO[0m [Scheduler] [APScheduler] Added job "cleanup_expired_files" to job store "execution"
|
||||
2026-02-21 20:31:26,397 [34mINFO[0m [Scheduler] [APScheduler] Added job "cleanup_oauth_tokens" to job store "execution"
|
||||
2026-02-21 20:31:26,399 [34mINFO[0m [Scheduler] [APScheduler] Added job "execution_accuracy_alerts" to job store "execution"
|
||||
2026-02-21 20:31:26,401 [34mINFO[0m [Scheduler] [APScheduler] Added job "ensure_embeddings_coverage" to job store "execution"
|
||||
2026-02-21 20:31:26,401 [34mINFO[0m [Scheduler] [APScheduler] Scheduler started
|
||||
2026-02-21 20:31:26,402 [34mINFO[0m [Scheduler] Running embedding backfill on startup...
|
||||
2026-02-21 20:31:26,440 [33mWARNING[0m [33mProvider LINEAR implements OAuth but the required env vars LINEAR_CLIENT_ID and LINEAR_CLIENT_SECRET are not both set[0m
|
||||
2026-02-21 20:31:26,468 [34mINFO[0m [PID-6090|THREAD-77685499|Scheduler|AppService client-24942e64-d380-4d36-a245-5c41172e5293] Creating service client started...
|
||||
2026-02-21 20:31:26,468 [34mINFO[0m [PID-6090|THREAD-77685499|Scheduler|AppService client-24942e64-d380-4d36-a245-5c41172e5293] Creating service client completed successfully.
|
||||
2026-02-21 20:31:26,485 [33mWARNING[0m [33mAuthentication error: Langfuse client initialized without public_key. Client will be disabled. Provide a public_key parameter or set LANGFUSE_PUBLIC_KEY environment variable. [0m
|
||||
2026-02-21 20:31:26,652 [34mINFO[0m Metrics endpoint exposed at /metrics for external-api
|
||||
2026-02-21 20:31:26,655 [34mINFO[0m Metrics endpoint exposed at /metrics for rest-api
|
||||
2026-02-21 20:31:26,735 [34mINFO[0m [AgentServer] Starting...
|
||||
2026-02-21 20:31:26,745 [34mINFO[0m Started server process [6093]
|
||||
2026-02-21 20:31:26,745 [34mINFO[0m Waiting for application startup.
|
||||
2026-02-21 20:31:26,746 [33mWARNING[0m [33m⚠️ JWT_SIGN_ALGORITHM is set to 'HS256', a symmetric shared-key signature algorithm. We highly recommend using an asymmetric algorithm such as ES256, because when leaked, a shared secret would allow anyone to forge valid tokens and impersonate users. More info: https://supabase.com/docs/guides/auth/signing-keys#choosing-the-right-signing-algorithm[0m
|
||||
2026-02-21 20:31:26,747 [34mINFO[0m [PID-6093|THREAD-77685502|AgentServer|Prisma-9d930243-0262-4697-b4af-e0bcbec281c4] Acquiring connection started...
|
||||
2026-02-21 20:31:26,812 [34mINFO[0m [PID-6093|THREAD-77685502|AgentServer|Prisma-9d930243-0262-4697-b4af-e0bcbec281c4] Acquiring connection completed successfully.
|
||||
2026-02-21 20:31:26,825 [34mINFO[0m Thread pool size set to 60 for sync endpoint/dependency performance
|
||||
2026-02-21 20:31:26,825 [34mINFO[0m Successfully patched IntegrationCredentialsStore.get_all_creds
|
||||
2026-02-21 20:31:26,825 [34mINFO[0m Syncing provider costs to blocks...
|
||||
2026-02-21 20:31:27,576 [33mWARNING[0m [33mProvider WORDPRESS implements OAuth but the required env vars WORDPRESS_CLIENT_ID and WORDPRESS_CLIENT_SECRET are not both set[0m
|
||||
2026-02-21 20:31:27,631 [34mINFO[0m Registered 1 custom costs for block FirecrawlExtractBlock
|
||||
/Users/majdyz/Code/AutoGPT/autogpt_platform/backend/backend/blocks/exa/helpers.py:56: UserWarning: Field name "schema" in "SummarySettings" shadows an attribute in parent "BaseModel"
|
||||
class SummarySettings(BaseModel):
|
||||
2026-02-21 20:31:27,954 [33mWARNING[0m [33mProvider AIRTABLE implements OAuth but the required env vars AIRTABLE_CLIENT_ID and AIRTABLE_CLIENT_SECRET are not both set[0m
|
||||
2026-02-21 20:31:29,238 [34mINFO[0m Successfully patched IntegrationCredentialsStore.get_all_creds
|
||||
2026-02-21 20:31:29,397 [33mWARNING[0m [33mBlock WordPressCreatePostBlock credential input 'credentials' provider 'wordpress' has no authentication methods configured - Disabling[0m
|
||||
2026-02-21 20:31:29,397 [33mWARNING[0m [33mBlock WordPressGetAllPostsBlock credential input 'credentials' provider 'wordpress' has no authentication methods configured - Disabling[0m
|
||||
2026-02-21 20:31:29,465 [34mINFO[0m Synced 82 costs to 82 blocks
|
||||
2026-02-21 20:31:29,466 [33mWARNING[0m [33mExecuting <Task pending name='Task-2' coro=<LifespanOn.main() running at /Users/majdyz/Code/AutoGPT/autogpt_platform/backend/.venv/lib/python3.13/site-packages/uvicorn/lifespan/on.py:86> created at /Users/majdyz/Code/AutoGPT/autogpt_platform/backend/.venv/lib/python3.13/site-packages/uvicorn/lifespan/on.py:51> took 2.654 seconds[0m
|
||||
2026-02-21 20:31:29,511 [34mINFO[0m [Scheduler] All content has embeddings, skipping backfill
|
||||
2026-02-21 20:31:29,512 [34mINFO[0m [Scheduler] Running cleanup for orphaned embeddings (blocks/docs)...
|
||||
2026-02-21 20:31:29,542 [34mINFO[0m [Scheduler] Cleanup completed: no orphaned embeddings found
|
||||
2026-02-21 20:31:29,542 [34mINFO[0m [Scheduler] Startup embedding backfill complete: {'backfill': {'processed': 0, 'success': 0, 'failed': 0}, 'cleanup': {'deleted': 0}}
|
||||
2026-02-21 20:31:29,553 [34mINFO[0m Started server process [6090]
|
||||
2026-02-21 20:31:29,553 [34mINFO[0m Waiting for application startup.
|
||||
2026-02-21 20:31:29,554 [34mINFO[0m Application startup complete.
|
||||
2026-02-21 20:31:29,555 [34mINFO[0m Uvicorn running on http://localhost:8003 (Press CTRL+C to quit)
|
||||
2026-02-21 20:31:31,074 [34mINFO[0m Migrating integration credentials for 0 users
|
||||
2026-02-21 20:31:31,087 [34mINFO[0m Fixing LLM credential inputs on 0 nodes
|
||||
2026-02-21 20:31:31,087 [34mINFO[0m Migrating LLM models
|
||||
2026-02-21 20:31:31,107 [34mINFO[0m Migrated 0 node triggers to triggered presets
|
||||
2026-02-21 20:31:31,107 [34mINFO[0m [PID-6093|THREAD-77685502|AgentServer|AsyncRedis-f8b888fc-8b03-4807-adfd-c93710c11c85] Acquiring connection started...
|
||||
2026-02-21 20:31:31,114 [34mINFO[0m [PID-6093|THREAD-77685502|AgentServer|AsyncRedis-f8b888fc-8b03-4807-adfd-c93710c11c85] Acquiring connection completed successfully.
|
||||
2026-02-21 20:31:31,115 [34mINFO[0m Created consumer group 'chat_consumers' on stream 'chat:completions'
|
||||
2026-02-21 20:31:31,115 [34mINFO[0m Chat completion consumer started (consumer: consumer-2f92959a)
|
||||
2026-02-21 20:31:31,116 [34mINFO[0m Application startup complete.
|
||||
2026-02-21 20:31:31,117 [34mINFO[0m Uvicorn running on http://0.0.0.0:8006 (Press CTRL+C to quit)
|
||||
2026-02-21 20:31:45,616 [34mINFO[0m 127.0.0.1:56174 - "GET /api/health HTTP/1.1" 404
|
||||
2026-02-21 20:32:07,632 [34mINFO[0m 127.0.0.1:56317 - "GET /openapi.json HTTP/1.1" 200
|
||||
2026-02-21 20:32:07,635 [33mWARNING[0m [33mExecuting <Task finished name='Task-7' coro=<RequestResponseCycle.run_asgi() done, defined at /Users/majdyz/Code/AutoGPT/autogpt_platform/backend/.venv/lib/python3.13/site-packages/uvicorn/protocols/http/httptools_impl.py:414> result=None created at /Users/majdyz/Code/AutoGPT/autogpt_platform/backend/.venv/lib/python3.13/site-packages/uvicorn/protocols/http/httptools_impl.py:295> took 0.346 seconds[0m
|
||||
2026-02-21 20:32:41,502 [34mINFO[0m 127.0.0.1:56681 - "POST /api/v2/chat/sessions HTTP/1.1" 404
|
||||
2026-02-21 20:32:50,005 [34mINFO[0m 127.0.0.1:56736 - "GET /api/docs HTTP/1.1" 404
|
||||
2026-02-21 20:33:10,267 [34mINFO[0m 127.0.0.1:56898 - "GET /openapi.json HTTP/1.1" 200
|
||||
2026-02-21 20:33:28,399 [34mINFO[0m 127.0.0.1:56993 - "POST /api/chat/sessions HTTP/1.1" 401
|
||||
2026-02-21 20:34:20,913 [34mINFO[0m 127.0.0.1:57313 - "GET /openapi.json HTTP/1.1" 200
|
||||
2026-02-21 20:36:26,326 [34mINFO[0m Running job "report_late_executions (trigger: interval[0:05:00], next run at: 2026-02-21 13:36:26 UTC)" (scheduled at 2026-02-21 13:36:26.255260+00:00)
|
||||
2026-02-21 20:36:26,333 [34mINFO[0m [PID-6090|THREAD-77695300|Scheduler|AppService client-24942e64-d380-4d36-a245-5c41172e5293] Creating service client started...
|
||||
2026-02-21 20:36:26,336 [34mINFO[0m [PID-6090|THREAD-77695300|Scheduler|AppService client-24942e64-d380-4d36-a245-5c41172e5293] Creating service client completed successfully.
|
||||
2026-02-21 20:36:26,336 [34mINFO[0m [PID-6090|THREAD-77695300|Scheduler|AppService client-24942e64-d380-4d36-a245-5c41172e5293] Creating service client started...
|
||||
2026-02-21 20:36:26,340 [34mINFO[0m [PID-6090|THREAD-77695300|Scheduler|AppService client-24942e64-d380-4d36-a245-5c41172e5293] Creating service client completed successfully.
|
||||
2026-02-21 20:36:26,439 [33mWARNING[0m [33mService communication: Retry attempt 1 for '_call_method_sync': HTTPServerError: HTTP 500: Server error '500 Internal Server Error' for url 'http://localhost:8005/get_graph_executions'
|
||||
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500[0m
|
||||
2026-02-21 20:36:27,802 [33mWARNING[0m [33mService communication: Retry attempt 2 for '_call_method_sync': HTTPServerError: HTTP 500: Server error '500 Internal Server Error' for url 'http://localhost:8005/get_graph_executions'
|
||||
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500[0m
|
||||
2026-02-21 20:36:30,362 [33mWARNING[0m [33mService communication: Retry attempt 3 for '_call_method_sync': HTTPServerError: HTTP 500: Server error '500 Internal Server Error' for url 'http://localhost:8005/get_graph_executions'
|
||||
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500[0m
|
||||
2026-02-21 20:36:34,885 [33mWARNING[0m [33mService communication: Retry attempt 4 for '_call_method_sync': HTTPServerError: HTTP 500: Server error '500 Internal Server Error' for url 'http://localhost:8005/get_graph_executions'
|
||||
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500[0m
|
||||
2026-02-21 20:36:43,438 [33mWARNING[0m [33mService communication: Retry attempt 5 for '_call_method_sync': HTTPServerError: HTTP 500: Server error '500 Internal Server Error' for url 'http://localhost:8005/get_graph_executions'
|
||||
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500[0m
|
||||
2026-02-21 20:36:59,905 [33mWARNING[0m [33mService communication: Retry attempt 6 for '_call_method_sync': HTTPServerError: HTTP 500: Server error '500 Internal Server Error' for url 'http://localhost:8005/get_graph_executions'
|
||||
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500[0m
|
||||
2026-02-21 20:37:12,581 [33mWARNING[0m [33mExecuting <Task pending name='Task-13' coro=<RequestResponseCycle.run_asgi() running at /Users/majdyz/Code/AutoGPT/autogpt_platform/backend/.venv/lib/python3.13/site-packages/uvicorn/protocols/http/httptools_impl.py:416> cb=[set.discard()] created at /Users/majdyz/Code/AutoGPT/autogpt_platform/backend/.venv/lib/python3.13/site-packages/uvicorn/protocols/http/httptools_impl.py:295> took 0.109 seconds[0m
|
||||
2026-02-21 20:37:12,767 [34mINFO[0m 127.0.0.1:58472 - "GET /api/store/profile HTTP/1.1" 404
|
||||
2026-02-21 20:37:12,886 [34mINFO[0m 127.0.0.1:58469 - "GET /api/chat/sessions?limit=50 HTTP/1.1" 200
|
||||
/Users/majdyz/Code/AutoGPT/autogpt_platform/backend/.venv/lib/python3.13/site-packages/pyiceberg/expressions/parser.py:72: PyparsingDeprecationWarning: 'enablePackrat' deprecated - use 'enable_packrat'
|
||||
ParserElement.enablePackrat()
|
||||
/Users/majdyz/Code/AutoGPT/autogpt_platform/backend/.venv/lib/python3.13/site-packages/pyiceberg/expressions/parser.py:85: PyparsingDeprecationWarning: 'escChar' argument is deprecated, use 'esc_char'
|
||||
quoted_identifier = QuotedString('"', escChar="\\", unquoteResults=True)
|
||||
/Users/majdyz/Code/AutoGPT/autogpt_platform/backend/.venv/lib/python3.13/site-packages/pyiceberg/expressions/parser.py:85: PyparsingDeprecationWarning: 'unquoteResults' argument is deprecated, use 'unquote_results'
|
||||
quoted_identifier = QuotedString('"', escChar="\\", unquoteResults=True)
|
||||
/Users/majdyz/Code/AutoGPT/autogpt_platform/backend/.venv/lib/python3.13/site-packages/pyiceberg/table/metadata.py:365: PydanticDeprecatedSince212: Using `@model_validator` with mode='after' on a classmethod is deprecated. Instead, use an instance method. See the documentation at https://docs.pydantic.dev/2.12/concepts/validators/#model-after-validator. Deprecated in Pydantic V2.12 to be removed in V3.0.
|
||||
@model_validator(mode="after")
|
||||
/Users/majdyz/Code/AutoGPT/autogpt_platform/backend/.venv/lib/python3.13/site-packages/pyiceberg/table/metadata.py:494: PydanticDeprecatedSince212: Using `@model_validator` with mode='after' on a classmethod is deprecated. Instead, use an instance method. See the documentation at https://docs.pydantic.dev/2.12/concepts/validators/#model-after-validator. Deprecated in Pydantic V2.12 to be removed in V3.0.
|
||||
@model_validator(mode="after")
|
||||
/Users/majdyz/Code/AutoGPT/autogpt_platform/backend/.venv/lib/python3.13/site-packages/pyiceberg/table/metadata.py:498: PydanticDeprecatedSince212: Using `@model_validator` with mode='after' on a classmethod is deprecated. Instead, use an instance method. See the documentation at https://docs.pydantic.dev/2.12/concepts/validators/#model-after-validator. Deprecated in Pydantic V2.12 to be removed in V3.0.
|
||||
@model_validator(mode="after")
|
||||
/Users/majdyz/Code/AutoGPT/autogpt_platform/backend/.venv/lib/python3.13/site-packages/pyiceberg/table/metadata.py:502: PydanticDeprecatedSince212: Using `@model_validator` with mode='after' on a classmethod is deprecated. Instead, use an instance method. See the documentation at https://docs.pydantic.dev/2.12/concepts/validators/#model-after-validator. Deprecated in Pydantic V2.12 to be removed in V3.0.
|
||||
@model_validator(mode="after")
|
||||
/Users/majdyz/Code/AutoGPT/autogpt_platform/backend/.venv/lib/python3.13/site-packages/pyiceberg/table/metadata.py:506: PydanticDeprecatedSince212: Using `@model_validator` with mode='after' on a classmethod is deprecated. Instead, use an instance method. See the documentation at https://docs.pydantic.dev/2.12/concepts/validators/#model-after-validator. Deprecated in Pydantic V2.12 to be removed in V3.0.
|
||||
@model_validator(mode="after")
|
||||
/Users/majdyz/Code/AutoGPT/autogpt_platform/backend/.venv/lib/python3.13/site-packages/pyiceberg/table/metadata.py:538: PydanticDeprecatedSince212: Using `@model_validator` with mode='after' on a classmethod is deprecated. Instead, use an instance method. See the documentation at https://docs.pydantic.dev/2.12/concepts/validators/#model-after-validator. Deprecated in Pydantic V2.12 to be removed in V3.0.
|
||||
@model_validator(mode="after")
|
||||
/Users/majdyz/Code/AutoGPT/autogpt_platform/backend/.venv/lib/python3.13/site-packages/pyiceberg/table/metadata.py:542: PydanticDeprecatedSince212: Using `@model_validator` with mode='after' on a classmethod is deprecated. Instead, use an instance method. See the documentation at https://docs.pydantic.dev/2.12/concepts/validators/#model-after-validator. Deprecated in Pydantic V2.12 to be removed in V3.0.
|
||||
@model_validator(mode="after")
|
||||
/Users/majdyz/Code/AutoGPT/autogpt_platform/backend/.venv/lib/python3.13/site-packages/pyiceberg/table/metadata.py:546: PydanticDeprecatedSince212: Using `@model_validator` with mode='after' on a classmethod is deprecated. Instead, use an instance method. See the documentation at https://docs.pydantic.dev/2.12/concepts/validators/#model-after-validator. Deprecated in Pydantic V2.12 to be removed in V3.0.
|
||||
@model_validator(mode="after")
|
||||
/Users/majdyz/Code/AutoGPT/autogpt_platform/backend/.venv/lib/python3.13/site-packages/pyiceberg/table/metadata.py:550: PydanticDeprecatedSince212: Using `@model_validator` with mode='after' on a classmethod is deprecated. Instead, use an instance method. See the documentation at https://docs.pydantic.dev/2.12/concepts/validators/#model-after-validator. Deprecated in Pydantic V2.12 to be removed in V3.0.
|
||||
@model_validator(mode="after")
|
||||
2026-02-21 20:37:14,074 [34mINFO[0m 127.0.0.1:58470 - "GET /api/executions HTTP/1.1" 200
|
||||
2026-02-21 20:37:14,081 [33mWARNING[0m [33mExecuting <Task finished name='Task-14' coro=<RequestResponseCycle.run_asgi() done, defined at /Users/majdyz/Code/AutoGPT/autogpt_platform/backend/.venv/lib/python3.13/site-packages/uvicorn/protocols/http/httptools_impl.py:414> result=None created at /Users/majdyz/Code/AutoGPT/autogpt_platform/backend/.venv/lib/python3.13/site-packages/uvicorn/protocols/http/httptools_impl.py:295> took 1.169 seconds[0m
|
||||
2026-02-21 20:37:15,102 [33mWARNING[0m [33mExecuting <Task pending name='Task-1' coro=<Server.serve() running at /Users/majdyz/Code/AutoGPT/autogpt_platform/backend/.venv/lib/python3.13/site-packages/uvicorn/server.py:71> wait_for=<Future pending cb=[Task.task_wakeup()] created at /opt/homebrew/Cellar/python@3.13/3.13.1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/tasks.py:713> cb=[run_until_complete.<locals>.done_cb()] created at /opt/homebrew/Cellar/python@3.13/3.13.1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/runners.py:100> took 0.224 seconds[0m
|
||||
2026-02-21 20:37:17,085 [34mINFO[0m 127.0.0.1:58530 - "GET /api/store/profile HTTP/1.1" 404
|
||||
2026-02-21 20:37:20,772 [33mWARNING[0m [33mExecuting <Task pending name='Task-1' coro=<Server.serve() running at /Users/majdyz/Code/AutoGPT/autogpt_platform/backend/.venv/lib/python3.13/site-packages/uvicorn/server.py:71> wait_for=<Future pending cb=[Task.task_wakeup()] created at /opt/homebrew/Cellar/python@3.13/3.13.1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/tasks.py:713> cb=[run_until_complete.<locals>.done_cb()] created at /opt/homebrew/Cellar/python@3.13/3.13.1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/runners.py:100> took 0.261 seconds[0m
|
||||
2026-02-21 20:37:21,276 [34mINFO[0m 127.0.0.1:58568 - "GET /api/integrations/providers/system HTTP/1.1" 200
|
||||
2026-02-21 20:37:21,309 [33mWARNING[0m [33mExecuting <Task finished name='Task-23' coro=<RequestResponseCycle.run_asgi() done, defined at /Users/majdyz/Code/AutoGPT/autogpt_platform/backend/.venv/lib/python3.13/site-packages/uvicorn/protocols/http/httptools_impl.py:414> result=None created at /Users/majdyz/Code/AutoGPT/autogpt_platform/backend/.venv/lib/python3.13/site-packages/uvicorn/protocols/http/httptools_impl.py:295> took 0.158 seconds[0m
|
||||
2026-02-21 20:37:21,329 [34mINFO[0m 127.0.0.1:58570 - "GET /api/integrations/providers HTTP/1.1" 200
|
||||
2026-02-21 20:37:21,421 [33mWARNING[0m [33mExecuting <Task finished name='Task-24' coro=<RequestResponseCycle.run_asgi() done, defined at /Users/majdyz/Code/AutoGPT/autogpt_platform/backend/.venv/lib/python3.13/site-packages/uvicorn/protocols/http/httptools_impl.py:414> result=None created at /Users/majdyz/Code/AutoGPT/autogpt_platform/backend/.venv/lib/python3.13/site-packages/uvicorn/protocols/http/httptools_impl.py:295> took 0.110 seconds[0m
|
||||
2026-02-21 20:37:22,406 [34mINFO[0m 127.0.0.1:58590 - "GET /api/store/profile HTTP/1.1" 404
|
||||
2026-02-21 20:37:22,430 [34mINFO[0m 127.0.0.1:58588 - "GET /api/onboarding HTTP/1.1" 200
|
||||
2026-02-21 20:37:22,453 [34mINFO[0m 127.0.0.1:58570 - "GET /api/executions HTTP/1.1" 200
|
||||
2026-02-21 20:37:22,476 [34mINFO[0m Loaded session 322af5c3-70fc-4a06-9443-8c5df0aa0c9f from DB: has_messages=True, message_count=11, roles=['user', 'assistant', 'tool', 'assistant', 'tool', 'assistant', 'tool', 'tool', 'assistant', 'tool', 'tool']
|
||||
2026-02-21 20:37:22,485 [34mINFO[0m Cached session 322af5c3-70fc-4a06-9443-8c5df0aa0c9f from database
|
||||
2026-02-21 20:37:22,510 [34mINFO[0m 127.0.0.1:58568 - "GET /api/library/agents?page=1&page_size=100 HTTP/1.1" 200
|
||||
2026-02-21 20:37:22,515 [34mINFO[0m [GET_SESSION] session=322af5c3-70fc-4a06-9443-8c5df0aa0c9f, active_task=False, msg_count=11, last_role=tool
|
||||
2026-02-21 20:37:22,524 [34mINFO[0m 127.0.0.1:58599 - "GET /api/chat/sessions/322af5c3-70fc-4a06-9443-8c5df0aa0c9f HTTP/1.1" 200
|
||||
2026-02-21 20:37:22,535 [34mINFO[0m 127.0.0.1:58607 - "GET /api/chat/sessions?limit=50 HTTP/1.1" 200
|
||||
2026-02-21 20:37:22,608 [34mINFO[0m 127.0.0.1:58568 - "GET /api/integrations/credentials HTTP/1.1" 200
|
||||
2026-02-21 20:37:23,531 [34mINFO[0m 127.0.0.1:58568 - "GET /api/store/profile HTTP/1.1" 404
|
||||
2026-02-21 20:37:25,612 [34mINFO[0m 127.0.0.1:58568 - "GET /api/store/profile HTTP/1.1" 404
|
||||
2026-02-21 20:37:29,708 [34mINFO[0m 127.0.0.1:58671 - "GET /api/store/profile HTTP/1.1" 404
|
||||
2026-02-21 20:37:29,975 [33mWARNING[0m [33mService communication: Retry attempt 7 for '_call_method_sync': HTTPServerError: HTTP 500: Server error '500 Internal Server Error' for url 'http://localhost:8005/get_graph_executions'
|
||||
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500[0m
|
||||
2026-02-21 20:37:34,125 [34mINFO[0m [TIMING] stream_chat_post STARTED, session=322af5c3-70fc-4a06-9443-8c5df0aa0c9f, user=68383665-d3d9-41f3-b10c-fca0dc6080ed, message_len=36
|
||||
2026-02-21 20:37:34,134 [34mINFO[0m Loading session 322af5c3-70fc-4a06-9443-8c5df0aa0c9f from cache: message_count=11, roles=['user', 'assistant', 'tool', 'assistant', 'tool', 'assistant', 'tool', 'tool', 'assistant', 'tool', 'tool']
|
||||
2026-02-21 20:37:34,135 [34mINFO[0m [TIMING] session validated in 10.6ms
|
||||
2026-02-21 20:37:34,136 [34mINFO[0m [STREAM] Saving user message to session 322af5c3-70fc-4a06-9443-8c5df0aa0c9f
|
||||
2026-02-21 20:37:34,138 [34mINFO[0m Loading session 322af5c3-70fc-4a06-9443-8c5df0aa0c9f from cache: message_count=11, roles=['user', 'assistant', 'tool', 'assistant', 'tool', 'assistant', 'tool', 'tool', 'assistant', 'tool', 'tool']
|
||||
2026-02-21 20:37:34,168 [34mINFO[0m Saving 1 new messages to DB for session 322af5c3-70fc-4a06-9443-8c5df0aa0c9f: roles=['user'], start_sequence=11
|
||||
2026-02-21 20:37:34,201 [34mINFO[0m [STREAM] User message saved for session 322af5c3-70fc-4a06-9443-8c5df0aa0c9f
|
||||
2026-02-21 20:37:34,202 [34mINFO[0m [TIMING] create_task STARTED, task=bba63941-8048-4f39-9329-8568e5ebe9cd, session=322af5c3-70fc-4a06-9443-8c5df0aa0c9f, user=68383665-d3d9-41f3-b10c-fca0dc6080ed
|
||||
2026-02-21 20:37:34,202 [34mINFO[0m [TIMING] get_redis_async took 0.0ms
|
||||
2026-02-21 20:37:34,205 [34mINFO[0m [TIMING] redis.hset took 2.9ms
|
||||
2026-02-21 20:37:34,208 [34mINFO[0m [TIMING] create_task COMPLETED in 6.1ms; task=bba63941-8048-4f39-9329-8568e5ebe9cd, session=322af5c3-70fc-4a06-9443-8c5df0aa0c9f
|
||||
2026-02-21 20:37:34,208 [34mINFO[0m [TIMING] create_task completed in 6.8ms
|
||||
2026-02-21 20:37:34,210 [34mINFO[0m [PID-6093|THREAD-77685502|AgentServer|AsyncRabbitMQ-bbe1cabd-35fe-4944-89d1-fddd09c93923] Acquiring async connection started...
|
||||
2026-02-21 20:37:34,296 [34mINFO[0m [PID-6093|THREAD-77685502|AgentServer|AsyncRabbitMQ-bbe1cabd-35fe-4944-89d1-fddd09c93923] Acquiring async connection completed successfully.
|
||||
2026-02-21 20:37:34,305 [34mINFO[0m [TIMING] Task enqueued to RabbitMQ, setup=180.6ms
|
||||
2026-02-21 20:37:34,307 [34mINFO[0m [TIMING] event_generator STARTED, task=bba63941-8048-4f39-9329-8568e5ebe9cd, session=322af5c3-70fc-4a06-9443-8c5df0aa0c9f, user=68383665-d3d9-41f3-b10c-fca0dc6080ed
|
||||
2026-02-21 20:37:34,307 [34mINFO[0m [TIMING] subscribe_to_task STARTED, task=bba63941-8048-4f39-9329-8568e5ebe9cd, user=68383665-d3d9-41f3-b10c-fca0dc6080ed, last_msg=0-0
|
||||
2026-02-21 20:37:34,309 [34mINFO[0m [TIMING] Redis hgetall took 2.1ms
|
||||
2026-02-21 20:37:34,353 [34mINFO[0m [PID-6048|THREAD-77685506|CoPilotExecutor|Redis-943506d1-86e7-48a7-871b-9977fb0ace47] Acquiring connection started...
|
||||
2026-02-21 20:37:34,435 [34mINFO[0m [PID-6048|THREAD-77685506|CoPilotExecutor|Redis-943506d1-86e7-48a7-871b-9977fb0ace47] Acquiring connection completed successfully.
|
||||
2026-02-21 20:37:34,442 [34mINFO[0m [CoPilotExecutor] Acquired cluster lock for bba63941-8048-4f39-9329-8568e5ebe9cd, executor_id=fb7d76b3-8dc3-40a4-947e-a93bfad207da
|
||||
2026-02-21 20:37:34,535 [34mINFO[0m [CoPilotExecutor] [CoPilotExecutor] Worker 13455405056 started
|
||||
2026-02-21 20:37:34,536 [34mINFO[0m [CoPilotExecutor|task_id:bba63941-8048-4f39-9329-8568e5ebe9cd|session_id:322af5c3-70fc-4a06-9443-8c5df0aa0c9f|user_id:68383665-d3d9-41f3-b10c-fca0dc6080ed] Starting execution
|
||||
2026-02-21 20:37:35,596 [34mINFO[0m [CoPilotExecutor|task_id:bba63941-8048-4f39-9329-8568e5ebe9cd|session_id:322af5c3-70fc-4a06-9443-8c5df0aa0c9f|user_id:68383665-d3d9-41f3-b10c-fca0dc6080ed] Using SDK service
|
||||
2026-02-21 20:37:35,596 [34mINFO[0m [PID-6048|THREAD-77697399|CoPilotExecutor|AsyncRedis-2e10c980-0364-4c4b-9b2d-8186f23b1735] Acquiring connection started...
|
||||
2026-02-21 20:37:35,600 [34mINFO[0m [PID-6048|THREAD-77697399|CoPilotExecutor|AsyncRedis-2e10c980-0364-4c4b-9b2d-8186f23b1735] Acquiring connection completed successfully.
|
||||
2026-02-21 20:37:35,601 [34mINFO[0m Loading session 322af5c3-70fc-4a06-9443-8c5df0aa0c9f from cache: message_count=12, roles=['user', 'assistant', 'tool', 'assistant', 'tool', 'assistant', 'tool', 'tool', 'assistant', 'tool', 'tool', 'user']
|
||||
2026-02-21 20:37:35,601 [34mINFO[0m [PID-6048|THREAD-77697399|CoPilotExecutor|AppService client-34797c8f-0201-4f99-bf73-3f3fb4697e6d] Creating service client started...
|
||||
2026-02-21 20:37:35,601 [34mINFO[0m [PID-6048|THREAD-77697399|CoPilotExecutor|AppService client-34797c8f-0201-4f99-bf73-3f3fb4697e6d] Creating service client completed successfully.
|
||||
2026-02-21 20:37:35,657 [33mWARNING[0m [33mService communication: Retry attempt 1 for '_call_method_async': HTTPServerError: HTTP 500: Server error '500 Internal Server Error' for url 'http://localhost:8005/get_chat_session_message_count'
|
||||
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500[0m
|
||||
2026-02-21 20:37:36,713 [33mWARNING[0m [33mService communication: Retry attempt 2 for '_call_method_async': HTTPServerError: HTTP 500: Server error '500 Internal Server Error' for url 'http://localhost:8005/get_chat_session_message_count'
|
||||
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500[0m
|
||||
2026-02-21 20:37:39,646 [33mWARNING[0m [33mService communication: Retry attempt 3 for '_call_method_async': HTTPServerError: HTTP 500: Server error '500 Internal Server Error' for url 'http://localhost:8005/get_chat_session_message_count'
|
||||
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500[0m
|
||||
2026-02-21 20:37:43,415 [34mINFO[0m 127.0.0.1:58782 - "GET /api/store/profile HTTP/1.1" 404
|
||||
2026-02-21 20:37:44,423 [33mWARNING[0m [33mService communication: Retry attempt 4 for '_call_method_async': HTTPServerError: HTTP 500: Server error '500 Internal Server Error' for url 'http://localhost:8005/get_chat_session_message_count'
|
||||
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500[0m
|
||||
2026-02-21 20:37:44,486 [34mINFO[0m 127.0.0.1:58782 - "GET /api/store/profile HTTP/1.1" 404
|
||||
2026-02-21 20:37:45,048 [34mINFO[0m Loading session 322af5c3-70fc-4a06-9443-8c5df0aa0c9f from cache: message_count=12, roles=['user', 'assistant', 'tool', 'assistant', 'tool', 'assistant', 'tool', 'tool', 'assistant', 'tool', 'tool', 'user']
|
||||
2026-02-21 20:37:45,053 [34mINFO[0m [TASK_LOOKUP] Found running task bba63941... for session 322af5c3...
|
||||
2026-02-21 20:37:45,063 [34mINFO[0m [CoPilotExecutor] Received cancel for bba63941-8048-4f39-9329-8568e5ebe9cd
|
||||
2026-02-21 20:37:45,064 [34mINFO[0m [CANCEL] Published cancel for task ...e5ebe9cd session ...f0aa0c9f
|
||||
2026-02-21 20:37:45,113 [34mINFO[0m Loading session 322af5c3-70fc-4a06-9443-8c5df0aa0c9f from cache: message_count=12, roles=['user', 'assistant', 'tool', 'assistant', 'tool', 'assistant', 'tool', 'tool', 'assistant', 'tool', 'tool', 'user']
|
||||
2026-02-21 20:37:45,120 [34mINFO[0m [TASK_LOOKUP] Found running task bba63941... for session 322af5c3...
|
||||
2026-02-21 20:37:45,121 [34mINFO[0m [GET_SESSION] session=322af5c3-70fc-4a06-9443-8c5df0aa0c9f, active_task=True, msg_count=12, last_role=user
|
||||
2026-02-21 20:37:45,123 [34mINFO[0m 127.0.0.1:58802 - "GET /api/chat/sessions/322af5c3-70fc-4a06-9443-8c5df0aa0c9f HTTP/1.1" 200
|
||||
2026-02-21 20:37:45,306 [34mINFO[0m [TASK_LOOKUP] Found running task bba63941... for session 322af5c3...
|
||||
2026-02-21 20:37:45,307 [34mINFO[0m [TIMING] subscribe_to_task STARTED, task=bba63941-8048-4f39-9329-8568e5ebe9cd, user=68383665-d3d9-41f3-b10c-fca0dc6080ed, last_msg=0-0
|
||||
2026-02-21 20:37:45,309 [34mINFO[0m [TIMING] Redis hgetall took 1.5ms
|
||||
2026-02-21 20:37:45,604 [34mINFO[0m [CoPilotExecutor|task_id:bba63941-8048-4f39-9329-8568e5ebe9cd|session_id:322af5c3-70fc-4a06-9443-8c5df0aa0c9f|user_id:68383665-d3d9-41f3-b10c-fca0dc6080ed] Cancellation requested
|
||||
2026-02-21 20:37:45,604 [34mINFO[0m [CoPilotExecutor|task_id:bba63941-8048-4f39-9329-8568e5ebe9cd|session_id:322af5c3-70fc-4a06-9443-8c5df0aa0c9f|user_id:68383665-d3d9-41f3-b10c-fca0dc6080ed] Execution completed in 11.07s
|
||||
2026-02-21 20:37:45,604 [34mINFO[0m [CoPilotExecutor] Run completed for bba63941-8048-4f39-9329-8568e5ebe9cd
|
||||
2026-02-21 20:37:45,604 [34mINFO[0m [CoPilotExecutor|task_id:bba63941-8048-4f39-9329-8568e5ebe9cd|session_id:322af5c3-70fc-4a06-9443-8c5df0aa0c9f|user_id:68383665-d3d9-41f3-b10c-fca0dc6080ed] Task cancelled
|
||||
2026-02-21 20:37:45,605 [34mINFO[0m [CoPilotExecutor] Releasing cluster lock for bba63941-8048-4f39-9329-8568e5ebe9cd
|
||||
2026-02-21 20:37:45,609 [34mINFO[0m [CoPilotExecutor] Cleaned up completed task bba63941-8048-4f39-9329-8568e5ebe9cd
|
||||
2026-02-21 20:37:45,610 [34mINFO[0m [TIMING] Redis xread (replay) took 301.1ms, status=running
|
||||
2026-02-21 20:37:45,610 [34mINFO[0m [TIMING] publish_chunk StreamFinish in 1.8ms (xadd=1.3ms)
|
||||
2026-02-21 20:37:45,612 [34mINFO[0m [TIMING] Replayed 1 messages, last_id=1771681065606-0
|
||||
2026-02-21 20:37:45,612 [34mINFO[0m [TIMING] Task still running, starting _stream_listener
|
||||
2026-02-21 20:37:45,613 [34mINFO[0m [TIMING] subscribe_to_task COMPLETED in 305.8ms; task=bba63941-8048-4f39-9329-8568e5ebe9cd, n_messages_replayed=1
|
||||
2026-02-21 20:37:45,614 [34mINFO[0m [TIMING] _stream_listener STARTED, task=bba63941-8048-4f39-9329-8568e5ebe9cd, last_id=1771681065606-0
|
||||
2026-02-21 20:37:45,614 [34mINFO[0m Resume stream chunk
|
||||
2026-02-21 20:37:45,615 [34mINFO[0m 127.0.0.1:58802 - "GET /api/chat/sessions/322af5c3-70fc-4a06-9443-8c5df0aa0c9f/stream HTTP/1.1" 200
|
||||
2026-02-21 20:37:45,615 [34mINFO[0m [TIMING] Redis xread (replay) took 11305.8ms, status=running
|
||||
2026-02-21 20:37:45,616 [34mINFO[0m [TIMING] Replayed 1 messages, last_id=1771681065606-0
|
||||
2026-02-21 20:37:45,616 [34mINFO[0m [TIMING] Task still running, starting _stream_listener
|
||||
2026-02-21 20:37:45,616 [34mINFO[0m [TIMING] subscribe_to_task COMPLETED in 11308.9ms; task=bba63941-8048-4f39-9329-8568e5ebe9cd, n_messages_replayed=1
|
||||
2026-02-21 20:37:45,616 [34mINFO[0m [TIMING] Starting to read from subscriber_queue
|
||||
2026-02-21 20:37:45,616 [34mINFO[0m [TIMING] FIRST CHUNK from queue at 11.31s, type=StreamFinish
|
||||
2026-02-21 20:37:45,616 [34mINFO[0m 127.0.0.1:58710 - "POST /api/chat/sessions/322af5c3-70fc-4a06-9443-8c5df0aa0c9f/stream HTTP/1.1" 200
|
||||
2026-02-21 20:37:45,617 [34mINFO[0m [TIMING] StreamFinish received in 11.31s; n_chunks=1
|
||||
2026-02-21 20:37:45,617 [34mINFO[0m [TIMING] _stream_listener CANCELLED after 3.5ms, delivered=0
|
||||
2026-02-21 20:37:45,617 [34mINFO[0m [TIMING] _stream_listener FINISHED in 0.0s; task=bba63941-8048-4f39-9329-8568e5ebe9cd, delivered=0, xread_count=1
|
||||
2026-02-21 20:37:45,618 [34mINFO[0m Resume stream completed
|
||||
2026-02-21 20:37:45,618 [34mINFO[0m [TIMING] event_generator FINISHED in 11.31s; task=bba63941-8048-4f39-9329-8568e5ebe9cd, session=322af5c3-70fc-4a06-9443-8c5df0aa0c9f, n_chunks=1
|
||||
2026-02-21 20:37:45,691 [34mINFO[0m Loading session 322af5c3-70fc-4a06-9443-8c5df0aa0c9f from cache: message_count=12, roles=['user', 'assistant', 'tool', 'assistant', 'tool', 'assistant', 'tool', 'tool', 'assistant', 'tool', 'tool', 'user']
|
||||
2026-02-21 20:37:45,694 [34mINFO[0m [GET_SESSION] session=322af5c3-70fc-4a06-9443-8c5df0aa0c9f, active_task=False, msg_count=12, last_role=user
|
||||
2026-02-21 20:37:45,695 [34mINFO[0m 127.0.0.1:58710 - "GET /api/chat/sessions/322af5c3-70fc-4a06-9443-8c5df0aa0c9f HTTP/1.1" 200
|
||||
2026-02-21 20:37:45,710 [34mINFO[0m 127.0.0.1:58802 - "GET /api/chat/sessions/322af5c3-70fc-4a06-9443-8c5df0aa0c9f/stream HTTP/1.1" 204
|
||||
2026-02-21 20:37:45,771 [34mINFO[0m Loading session 322af5c3-70fc-4a06-9443-8c5df0aa0c9f from cache: message_count=12, roles=['user', 'assistant', 'tool', 'assistant', 'tool', 'assistant', 'tool', 'tool', 'assistant', 'tool', 'tool', 'user']
|
||||
2026-02-21 20:37:45,775 [34mINFO[0m [GET_SESSION] session=322af5c3-70fc-4a06-9443-8c5df0aa0c9f, active_task=False, msg_count=12, last_role=user
|
||||
2026-02-21 20:37:45,775 [34mINFO[0m 127.0.0.1:58710 - "GET /api/chat/sessions/322af5c3-70fc-4a06-9443-8c5df0aa0c9f HTTP/1.1" 200
|
||||
2026-02-21 20:37:46,075 [34mINFO[0m [CANCEL] Task ...e5ebe9cd confirmed stopped (status=failed) after 1.0s
|
||||
2026-02-21 20:37:46,076 [34mINFO[0m 127.0.0.1:58782 - "POST /api/chat/sessions/322af5c3-70fc-4a06-9443-8c5df0aa0c9f/cancel HTTP/1.1" 200
|
||||
2026-02-21 20:37:46,573 [34mINFO[0m 127.0.0.1:58710 - "GET /api/store/profile HTTP/1.1" 404
|
||||
2026-02-21 20:37:50,090 [34mINFO[0m 127.0.0.1:58710 - "GET /api/integrations/providers/system HTTP/1.1" 200
|
||||
2026-02-21 20:37:50,103 [34mINFO[0m 127.0.0.1:58842 - "GET /api/integrations/providers HTTP/1.1" 200
|
||||
2026-02-21 20:37:50,681 [34mINFO[0m Loading session 322af5c3-70fc-4a06-9443-8c5df0aa0c9f from cache: message_count=12, roles=['user', 'assistant', 'tool', 'assistant', 'tool', 'assistant', 'tool', 'tool', 'assistant', 'tool', 'tool', 'user']
|
||||
2026-02-21 20:37:50,686 [34mINFO[0m 127.0.0.1:58710 - "GET /api/library/agents?page=1&page_size=100 HTTP/1.1" 200
|
||||
2026-02-21 20:37:50,692 [34mINFO[0m 127.0.0.1:58850 - "GET /api/store/profile HTTP/1.1" 404
|
||||
2026-02-21 20:37:50,702 [34mINFO[0m 127.0.0.1:58842 - "GET /api/integrations/credentials HTTP/1.1" 200
|
||||
2026-02-21 20:37:50,710 [34mINFO[0m [GET_SESSION] session=322af5c3-70fc-4a06-9443-8c5df0aa0c9f, active_task=False, msg_count=12, last_role=user
|
||||
2026-02-21 20:37:50,711 [34mINFO[0m 127.0.0.1:58862 - "GET /api/chat/sessions/322af5c3-70fc-4a06-9443-8c5df0aa0c9f HTTP/1.1" 200
|
||||
2026-02-21 20:37:50,714 [34mINFO[0m 127.0.0.1:58852 - "GET /api/onboarding HTTP/1.1" 200
|
||||
2026-02-21 20:37:50,720 [34mINFO[0m 127.0.0.1:58854 - "GET /api/executions HTTP/1.1" 200
|
||||
2026-02-21 20:37:50,795 [34mINFO[0m 127.0.0.1:58710 - "GET /api/chat/sessions?limit=50 HTTP/1.1" 200
|
||||
2026-02-21 20:37:51,955 [34mINFO[0m 127.0.0.1:58710 - "GET /api/store/profile HTTP/1.1" 404
|
||||
2026-02-21 20:37:54,064 [34mINFO[0m 127.0.0.1:58710 - "GET /api/store/profile HTTP/1.1" 404
|
||||
2026-02-21 20:37:54,157 [34mINFO[0m [TIMING] stream_chat_post STARTED, session=322af5c3-70fc-4a06-9443-8c5df0aa0c9f, user=68383665-d3d9-41f3-b10c-fca0dc6080ed, message_len=5
|
||||
2026-02-21 20:37:54,169 [34mINFO[0m Loading session 322af5c3-70fc-4a06-9443-8c5df0aa0c9f from cache: message_count=12, roles=['user', 'assistant', 'tool', 'assistant', 'tool', 'assistant', 'tool', 'tool', 'assistant', 'tool', 'tool', 'user']
|
||||
2026-02-21 20:37:54,170 [34mINFO[0m [TIMING] session validated in 13.0ms
|
||||
2026-02-21 20:37:54,170 [34mINFO[0m [STREAM] Saving user message to session 322af5c3-70fc-4a06-9443-8c5df0aa0c9f
|
||||
2026-02-21 20:37:54,172 [34mINFO[0m Loading session 322af5c3-70fc-4a06-9443-8c5df0aa0c9f from cache: message_count=12, roles=['user', 'assistant', 'tool', 'assistant', 'tool', 'assistant', 'tool', 'tool', 'assistant', 'tool', 'tool', 'user']
|
||||
2026-02-21 20:37:54,212 [34mINFO[0m Saving 1 new messages to DB for session 322af5c3-70fc-4a06-9443-8c5df0aa0c9f: roles=['user'], start_sequence=12
|
||||
2026-02-21 20:37:54,238 [34mINFO[0m [STREAM] User message saved for session 322af5c3-70fc-4a06-9443-8c5df0aa0c9f
|
||||
2026-02-21 20:37:54,238 [34mINFO[0m [TIMING] create_task STARTED, task=6360d249-c803-47d3-8a08-d77275e4b2d8, session=322af5c3-70fc-4a06-9443-8c5df0aa0c9f, user=68383665-d3d9-41f3-b10c-fca0dc6080ed
|
||||
2026-02-21 20:37:54,238 [34mINFO[0m [TIMING] get_redis_async took 0.0ms
|
||||
2026-02-21 20:37:54,242 [34mINFO[0m [TIMING] redis.hset took 3.1ms
|
||||
2026-02-21 20:37:54,250 [34mINFO[0m [TIMING] create_task COMPLETED in 11.6ms; task=6360d249-c803-47d3-8a08-d77275e4b2d8, session=322af5c3-70fc-4a06-9443-8c5df0aa0c9f
|
||||
2026-02-21 20:37:54,251 [34mINFO[0m [TIMING] create_task completed in 12.9ms
|
||||
2026-02-21 20:37:54,261 [34mINFO[0m [TIMING] Task enqueued to RabbitMQ, setup=103.8ms
|
||||
2026-02-21 20:37:54,262 [34mINFO[0m [TIMING] event_generator STARTED, task=6360d249-c803-47d3-8a08-d77275e4b2d8, session=322af5c3-70fc-4a06-9443-8c5df0aa0c9f, user=68383665-d3d9-41f3-b10c-fca0dc6080ed
|
||||
2026-02-21 20:37:54,263 [34mINFO[0m [TIMING] subscribe_to_task STARTED, task=6360d249-c803-47d3-8a08-d77275e4b2d8, user=68383665-d3d9-41f3-b10c-fca0dc6080ed, last_msg=0-0
|
||||
2026-02-21 20:37:54,264 [34mINFO[0m [TIMING] Redis hgetall took 1.7ms
|
||||
2026-02-21 20:37:54,265 [34mINFO[0m [CoPilotExecutor] Acquired cluster lock for 6360d249-c803-47d3-8a08-d77275e4b2d8, executor_id=fb7d76b3-8dc3-40a4-947e-a93bfad207da
|
||||
2026-02-21 20:37:54,267 [34mINFO[0m [CoPilotExecutor|task_id:6360d249-c803-47d3-8a08-d77275e4b2d8|session_id:322af5c3-70fc-4a06-9443-8c5df0aa0c9f|user_id:68383665-d3d9-41f3-b10c-fca0dc6080ed] Starting execution
|
||||
2026-02-21 20:37:54,286 [34mINFO[0m [CoPilotExecutor|task_id:6360d249-c803-47d3-8a08-d77275e4b2d8|session_id:322af5c3-70fc-4a06-9443-8c5df0aa0c9f|user_id:68383665-d3d9-41f3-b10c-fca0dc6080ed] Using SDK service
|
||||
2026-02-21 20:37:54,290 [34mINFO[0m Loading session 322af5c3-70fc-4a06-9443-8c5df0aa0c9f from cache: message_count=13, roles=['user', 'assistant', 'tool', 'assistant', 'tool', 'assistant', 'tool', 'tool', 'assistant', 'tool', 'tool', 'user', 'user']
|
||||
2026-02-21 20:37:54,357 [33mWARNING[0m [33mService communication: Retry attempt 1 for '_call_method_async': HTTPServerError: HTTP 500: Server error '500 Internal Server Error' for url 'http://localhost:8005/get_chat_session_message_count'
|
||||
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500[0m
|
||||
2026-02-21 20:37:56,312 [33mWARNING[0m [33mService communication: Retry attempt 2 for '_call_method_async': HTTPServerError: HTTP 500: Server error '500 Internal Server Error' for url 'http://localhost:8005/get_chat_session_message_count'
|
||||
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500[0m
|
||||
2026-02-21 20:37:58,224 [34mINFO[0m 127.0.0.1:58917 - "GET /api/store/profile HTTP/1.1" 404
|
||||
2026-02-21 20:37:58,928 [33mWARNING[0m [33mService communication: Retry attempt 3 for '_call_method_async': HTTPServerError: HTTP 500: Server error '500 Internal Server Error' for url 'http://localhost:8005/get_chat_session_message_count'
|
||||
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500[0m
|
||||
2026-02-21 20:38:00,041 [33mWARNING[0m [33mService communication: Retry attempt 8 for '_call_method_sync': HTTPServerError: HTTP 500: Server error '500 Internal Server Error' for url 'http://localhost:8005/get_graph_executions'
|
||||
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500[0m
|
||||
2026-02-21 20:38:03,701 [33mWARNING[0m [33mService communication: Retry attempt 4 for '_call_method_async': HTTPServerError: HTTP 500: Server error '500 Internal Server Error' for url 'http://localhost:8005/get_chat_session_message_count'
|
||||
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500[0m
|
||||
2026-02-21 20:38:06,882 [34mINFO[0m Loading session 322af5c3-70fc-4a06-9443-8c5df0aa0c9f from cache: message_count=13, roles=['user', 'assistant', 'tool', 'assistant', 'tool', 'assistant', 'tool', 'tool', 'assistant', 'tool', 'tool', 'user', 'user']
|
||||
2026-02-21 20:38:06,888 [34mINFO[0m [TASK_LOOKUP] Found running task 6360d249... for session 322af5c3...
|
||||
2026-02-21 20:38:06,898 [34mINFO[0m [CoPilotExecutor] Received cancel for 6360d249-c803-47d3-8a08-d77275e4b2d8
|
||||
2026-02-21 20:38:06,898 [34mINFO[0m [CANCEL] Published cancel for task ...75e4b2d8 session ...f0aa0c9f
|
||||
2026-02-21 20:38:06,919 [34mINFO[0m Loading session 322af5c3-70fc-4a06-9443-8c5df0aa0c9f from cache: message_count=13, roles=['user', 'assistant', 'tool', 'assistant', 'tool', 'assistant', 'tool', 'tool', 'assistant', 'tool', 'tool', 'user', 'user']
|
||||
2026-02-21 20:38:06,925 [34mINFO[0m [TASK_LOOKUP] Found running task 6360d249... for session 322af5c3...
|
||||
2026-02-21 20:38:06,926 [34mINFO[0m [GET_SESSION] session=322af5c3-70fc-4a06-9443-8c5df0aa0c9f, active_task=True, msg_count=13, last_role=user
|
||||
2026-02-21 20:38:06,927 [34mINFO[0m 127.0.0.1:58976 - "GET /api/chat/sessions/322af5c3-70fc-4a06-9443-8c5df0aa0c9f HTTP/1.1" 200
|
||||
2026-02-21 20:38:07,136 [34mINFO[0m [TASK_LOOKUP] Found running task 6360d249... for session 322af5c3...
|
||||
2026-02-21 20:38:07,138 [34mINFO[0m [TIMING] subscribe_to_task STARTED, task=6360d249-c803-47d3-8a08-d77275e4b2d8, user=68383665-d3d9-41f3-b10c-fca0dc6080ed, last_msg=0-0
|
||||
2026-02-21 20:38:07,140 [34mINFO[0m [TIMING] Redis hgetall took 1.3ms
|
||||
2026-02-21 20:38:07,359 [34mINFO[0m [CoPilotExecutor|task_id:6360d249-c803-47d3-8a08-d77275e4b2d8|session_id:322af5c3-70fc-4a06-9443-8c5df0aa0c9f|user_id:68383665-d3d9-41f3-b10c-fca0dc6080ed] Cancellation requested
|
||||
2026-02-21 20:38:07,360 [34mINFO[0m [CoPilotExecutor|task_id:6360d249-c803-47d3-8a08-d77275e4b2d8|session_id:322af5c3-70fc-4a06-9443-8c5df0aa0c9f|user_id:68383665-d3d9-41f3-b10c-fca0dc6080ed] Execution completed in 13.09s
|
||||
2026-02-21 20:38:07,360 [34mINFO[0m [CoPilotExecutor] Run completed for 6360d249-c803-47d3-8a08-d77275e4b2d8
|
||||
2026-02-21 20:38:07,360 [34mINFO[0m [CoPilotExecutor|task_id:6360d249-c803-47d3-8a08-d77275e4b2d8|session_id:322af5c3-70fc-4a06-9443-8c5df0aa0c9f|user_id:68383665-d3d9-41f3-b10c-fca0dc6080ed] Task cancelled
|
||||
2026-02-21 20:38:07,360 [34mINFO[0m [CoPilotExecutor] Releasing cluster lock for 6360d249-c803-47d3-8a08-d77275e4b2d8
|
||||
2026-02-21 20:38:07,362 [34mINFO[0m [CoPilotExecutor] Cleaned up completed task 6360d249-c803-47d3-8a08-d77275e4b2d8
|
||||
2026-02-21 20:38:07,364 [34mINFO[0m [TIMING] Redis xread (replay) took 224.1ms, status=running
|
||||
2026-02-21 20:38:07,364 [34mINFO[0m [TIMING] Replayed 1 messages, last_id=1771681087362-0
|
||||
2026-02-21 20:38:07,365 [34mINFO[0m [TIMING] Task still running, starting _stream_listener
|
||||
2026-02-21 20:38:07,365 [34mINFO[0m [TIMING] publish_chunk StreamFinish in 2.1ms (xadd=1.2ms)
|
||||
2026-02-21 20:38:07,365 [34mINFO[0m [TIMING] subscribe_to_task COMPLETED in 226.8ms; task=6360d249-c803-47d3-8a08-d77275e4b2d8, n_messages_replayed=1
|
||||
2026-02-21 20:38:07,366 [34mINFO[0m [TIMING] _stream_listener STARTED, task=6360d249-c803-47d3-8a08-d77275e4b2d8, last_id=1771681087362-0
|
||||
2026-02-21 20:38:07,366 [34mINFO[0m Resume stream chunk
|
||||
2026-02-21 20:38:07,366 [34mINFO[0m 127.0.0.1:58976 - "GET /api/chat/sessions/322af5c3-70fc-4a06-9443-8c5df0aa0c9f/stream HTTP/1.1" 200
|
||||
2026-02-21 20:38:07,367 [34mINFO[0m [TIMING] Redis xread (replay) took 13101.9ms, status=running
|
||||
2026-02-21 20:38:07,367 [34mINFO[0m [TIMING] Replayed 1 messages, last_id=1771681087362-0
|
||||
2026-02-21 20:38:07,367 [34mINFO[0m [TIMING] Task still running, starting _stream_listener
|
||||
2026-02-21 20:38:07,367 [34mINFO[0m [TIMING] subscribe_to_task COMPLETED in 13104.6ms; task=6360d249-c803-47d3-8a08-d77275e4b2d8, n_messages_replayed=1
|
||||
2026-02-21 20:38:07,367 [34mINFO[0m [TIMING] Starting to read from subscriber_queue
|
||||
2026-02-21 20:38:07,368 [34mINFO[0m [TIMING] FIRST CHUNK from queue at 13.11s, type=StreamFinish
|
||||
2026-02-21 20:38:07,368 [34mINFO[0m 127.0.0.1:58710 - "POST /api/chat/sessions/322af5c3-70fc-4a06-9443-8c5df0aa0c9f/stream HTTP/1.1" 200
|
||||
2026-02-21 20:38:07,368 [34mINFO[0m [TIMING] StreamFinish received in 13.11s; n_chunks=1
|
||||
2026-02-21 20:38:07,368 [34mINFO[0m [TIMING] _stream_listener CANCELLED after 2.7ms, delivered=0
|
||||
2026-02-21 20:38:07,368 [34mINFO[0m [TIMING] _stream_listener FINISHED in 0.0s; task=6360d249-c803-47d3-8a08-d77275e4b2d8, delivered=0, xread_count=1
|
||||
2026-02-21 20:38:07,369 [34mINFO[0m Resume stream completed
|
||||
2026-02-21 20:38:07,369 [34mINFO[0m [TIMING] event_generator FINISHED in 13.11s; task=6360d249-c803-47d3-8a08-d77275e4b2d8, session=322af5c3-70fc-4a06-9443-8c5df0aa0c9f, n_chunks=1
|
||||
2026-02-21 20:38:07,408 [34mINFO[0m [CANCEL] Task ...75e4b2d8 confirmed stopped (status=failed) after 0.5s
|
||||
2026-02-21 20:38:07,409 [34mINFO[0m 127.0.0.1:58974 - "POST /api/chat/sessions/322af5c3-70fc-4a06-9443-8c5df0aa0c9f/cancel HTTP/1.1" 200
|
||||
2026-02-21 20:38:07,447 [34mINFO[0m Loading session 322af5c3-70fc-4a06-9443-8c5df0aa0c9f from cache: message_count=13, roles=['user', 'assistant', 'tool', 'assistant', 'tool', 'assistant', 'tool', 'tool', 'assistant', 'tool', 'tool', 'user', 'user']
|
||||
2026-02-21 20:38:07,451 [34mINFO[0m [GET_SESSION] session=322af5c3-70fc-4a06-9443-8c5df0aa0c9f, active_task=False, msg_count=13, last_role=user
|
||||
2026-02-21 20:38:07,451 [34mINFO[0m 127.0.0.1:58710 - "GET /api/chat/sessions/322af5c3-70fc-4a06-9443-8c5df0aa0c9f HTTP/1.1" 200
|
||||
2026-02-21 20:38:07,468 [34mINFO[0m 127.0.0.1:58710 - "GET /api/chat/sessions/322af5c3-70fc-4a06-9443-8c5df0aa0c9f/stream HTTP/1.1" 204
|
||||
2026-02-21 20:38:07,521 [34mINFO[0m Loading session 322af5c3-70fc-4a06-9443-8c5df0aa0c9f from cache: message_count=13, roles=['user', 'assistant', 'tool', 'assistant', 'tool', 'assistant', 'tool', 'tool', 'assistant', 'tool', 'tool', 'user', 'user']
|
||||
2026-02-21 20:38:07,527 [34mINFO[0m [GET_SESSION] session=322af5c3-70fc-4a06-9443-8c5df0aa0c9f, active_task=False, msg_count=13, last_role=user
|
||||
2026-02-21 20:38:07,528 [34mINFO[0m 127.0.0.1:58710 - "GET /api/chat/sessions/322af5c3-70fc-4a06-9443-8c5df0aa0c9f HTTP/1.1" 200
|
||||
2026-02-21 20:38:18,440 [34mINFO[0m 127.0.0.1:59077 - "GET /api/store/profile HTTP/1.1" 404
|
||||
2026-02-21 20:38:19,553 [34mINFO[0m 127.0.0.1:59077 - "GET /api/store/profile HTTP/1.1" 404
|
||||
2026-02-21 20:38:21,643 [34mINFO[0m 127.0.0.1:59077 - "GET /api/store/profile HTTP/1.1" 404
|
||||
2026-02-21 20:38:30,090 [33mWARNING[0m [33mService communication: Retry attempt 9 for '_call_method_sync': HTTPServerError: HTTP 500: Server error '500 Internal Server Error' for url 'http://localhost:8005/get_graph_executions'
|
||||
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500[0m
|
||||
2026-02-21 20:39:00,123 [33mWARNING[0m [33mService communication: Retry attempt 10 for '_call_method_sync': HTTPServerError: HTTP 500: Server error '500 Internal Server Error' for url 'http://localhost:8005/get_graph_executions'
|
||||
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500[0m
|
||||
2026-02-21 20:39:13,881 [34mINFO[0m 127.0.0.1:59398 - "GET /api/chat/sessions?limit=50 HTTP/1.1" 200
|
||||
2026-02-21 20:39:30,173 [33mWARNING[0m [33mService communication: Retry attempt 11 for '_call_method_sync': HTTPServerError: HTTP 500: Server error '500 Internal Server Error' for url 'http://localhost:8005/get_graph_executions'
|
||||
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500[0m
|
||||
2026-02-21 20:39:35,355 [34mINFO[0m 127.0.0.1:59522 - "GET /api/store/profile HTTP/1.1" 404
|
||||
2026-02-21 20:39:35,685 [34mINFO[0m 127.0.0.1:59526 - "GET /api/executions HTTP/1.1" 200
|
||||
2026-02-21 20:39:38,916 [34mINFO[0m 127.0.0.1:59522 - "GET /api/store/profile HTTP/1.1" 404
|
||||
2026-02-21 20:39:40,019 [34mINFO[0m 127.0.0.1:59522 - "GET /api/store/profile HTTP/1.1" 404
|
||||
1
autogpt_platform/backend/.gitignore
vendored
1
autogpt_platform/backend/.gitignore
vendored
@@ -22,3 +22,4 @@ migrations/*/rollback*.sql
|
||||
|
||||
# Workspace files
|
||||
workspaces/
|
||||
sample.logs
|
||||
|
||||
@@ -432,13 +432,23 @@ async def _get_session_from_db(session_id: str) -> ChatSession | None:
|
||||
return session
|
||||
|
||||
|
||||
async def upsert_chat_session(session: ChatSession) -> ChatSession:
|
||||
async def upsert_chat_session(
|
||||
session: ChatSession,
|
||||
*,
|
||||
existing_message_count: int | None = None,
|
||||
) -> ChatSession:
|
||||
"""Update a chat session in both cache and database.
|
||||
|
||||
Uses session-level locking to prevent race conditions when concurrent
|
||||
operations (e.g., background title update and main stream handler)
|
||||
attempt to upsert the same session simultaneously.
|
||||
|
||||
Args:
|
||||
existing_message_count: If provided, skip the DB query to count
|
||||
existing messages. The caller is responsible for tracking this
|
||||
accurately. Useful for incremental saves in a streaming loop
|
||||
where the caller already knows how many messages are persisted.
|
||||
|
||||
Raises:
|
||||
DatabaseError: If the database write fails. The cache is still updated
|
||||
as a best-effort optimization, but the error is propagated to ensure
|
||||
@@ -450,15 +460,20 @@ async def upsert_chat_session(session: ChatSession) -> ChatSession:
|
||||
|
||||
async with lock:
|
||||
# Get existing message count from DB for incremental saves
|
||||
existing_message_count = await chat_db().get_chat_session_message_count(
|
||||
session.session_id
|
||||
)
|
||||
if existing_message_count is None:
|
||||
existing_message_count = await chat_db().get_chat_session_message_count(
|
||||
session.session_id
|
||||
)
|
||||
|
||||
db_error: Exception | None = None
|
||||
|
||||
# Save to database (primary storage)
|
||||
try:
|
||||
await _save_session_to_db(session, existing_message_count)
|
||||
await _save_session_to_db(
|
||||
session,
|
||||
existing_message_count,
|
||||
skip_existence_check=existing_message_count > 0,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Failed to save session {session.session_id} to database: {e}"
|
||||
@@ -489,21 +504,31 @@ async def upsert_chat_session(session: ChatSession) -> ChatSession:
|
||||
|
||||
|
||||
async def _save_session_to_db(
|
||||
session: ChatSession, existing_message_count: int
|
||||
session: ChatSession,
|
||||
existing_message_count: int,
|
||||
*,
|
||||
skip_existence_check: bool = False,
|
||||
) -> None:
|
||||
"""Save or update a chat session in the database."""
|
||||
"""Save or update a chat session in the database.
|
||||
|
||||
Args:
|
||||
skip_existence_check: When True, skip the ``get_chat_session`` query
|
||||
and assume the session row already exists. Saves one DB round trip
|
||||
for incremental saves during streaming.
|
||||
"""
|
||||
db = chat_db()
|
||||
|
||||
# Check if session exists in DB
|
||||
existing = await db.get_chat_session(session.session_id)
|
||||
if not skip_existence_check:
|
||||
# Check if session exists in DB
|
||||
existing = await db.get_chat_session(session.session_id)
|
||||
|
||||
if not existing:
|
||||
# Create new session
|
||||
await db.create_chat_session(
|
||||
session_id=session.session_id,
|
||||
user_id=session.user_id,
|
||||
)
|
||||
existing_message_count = 0
|
||||
if not existing:
|
||||
# Create new session
|
||||
await db.create_chat_session(
|
||||
session_id=session.session_id,
|
||||
user_id=session.user_id,
|
||||
)
|
||||
existing_message_count = 0
|
||||
|
||||
# Calculate total tokens from usage
|
||||
total_prompt = sum(u.prompt_tokens for u in session.usage)
|
||||
|
||||
@@ -34,6 +34,9 @@ class ResponseType(str, Enum):
|
||||
TOOL_INPUT_AVAILABLE = "tool-input-available"
|
||||
TOOL_OUTPUT_AVAILABLE = "tool-output-available"
|
||||
|
||||
# Long-running tool notification (custom extension - uses AI SDK DataUIPart format)
|
||||
LONG_RUNNING_START = "data-long-running-start"
|
||||
|
||||
# Other
|
||||
ERROR = "error"
|
||||
USAGE = "usage"
|
||||
@@ -145,6 +148,10 @@ class StreamToolInputAvailable(StreamBaseResponse):
|
||||
input: dict[str, Any] = Field(
|
||||
default_factory=dict, description="Tool input arguments"
|
||||
)
|
||||
providerMetadata: dict[str, Any] | None = Field(
|
||||
default=None,
|
||||
description="Provider metadata - used to pass isLongRunning flag to frontend",
|
||||
)
|
||||
|
||||
|
||||
class StreamToolOutputAvailable(StreamBaseResponse):
|
||||
@@ -173,6 +180,20 @@ class StreamToolOutputAvailable(StreamBaseResponse):
|
||||
return f"data: {json.dumps(data)}\n\n"
|
||||
|
||||
|
||||
class StreamLongRunningStart(StreamBaseResponse):
|
||||
"""Notification that a long-running tool has started.
|
||||
|
||||
Custom extension using AI SDK DataUIPart format. Signals the frontend to show
|
||||
UI feedback while the tool executes.
|
||||
"""
|
||||
|
||||
type: ResponseType = ResponseType.LONG_RUNNING_START
|
||||
data: dict[str, Any] = Field(
|
||||
default_factory=dict,
|
||||
description="Data for the long-running event containing toolCallId and toolName",
|
||||
)
|
||||
|
||||
|
||||
# ========== Other ==========
|
||||
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ from backend.copilot.response_model import (
|
||||
StreamToolInputStart,
|
||||
StreamToolOutputAvailable,
|
||||
)
|
||||
from backend.copilot.tools import get_tool
|
||||
|
||||
from .tool_adapter import MCP_TOOL_PREFIX, pop_pending_tool_output
|
||||
|
||||
@@ -47,8 +48,9 @@ class SDKResponseAdapter:
|
||||
text blocks, tool calls, and message lifecycle.
|
||||
"""
|
||||
|
||||
def __init__(self, message_id: str | None = None):
|
||||
def __init__(self, message_id: str | None = None, session_id: str | None = None):
|
||||
self.message_id = message_id or str(uuid.uuid4())
|
||||
self.session_id = session_id
|
||||
self.text_block_id = str(uuid.uuid4())
|
||||
self.has_started_text = False
|
||||
self.has_ended_text = False
|
||||
@@ -61,6 +63,11 @@ class SDKResponseAdapter:
|
||||
"""Set the task ID for reconnection support."""
|
||||
self.task_id = task_id
|
||||
|
||||
@property
|
||||
def has_unresolved_tool_calls(self) -> bool:
|
||||
"""True when there are tool calls that haven't received output yet."""
|
||||
return bool(self.current_tool_calls.keys() - self.resolved_tool_calls)
|
||||
|
||||
def convert_message(self, sdk_message: Message) -> list[StreamBaseResponse]:
|
||||
"""Convert a single SDK message to Vercel AI SDK format."""
|
||||
responses: list[StreamBaseResponse] = []
|
||||
@@ -77,7 +84,12 @@ class SDKResponseAdapter:
|
||||
elif isinstance(sdk_message, AssistantMessage):
|
||||
# Flush any SDK built-in tool calls that didn't get a UserMessage
|
||||
# result (e.g. WebSearch, Read handled internally by the CLI).
|
||||
self._flush_unresolved_tool_calls(responses)
|
||||
# BUT skip flush when this AssistantMessage is a parallel tool
|
||||
# continuation (contains only ToolUseBlocks) — the prior tools
|
||||
# are still executing concurrently and haven't finished yet.
|
||||
is_tool_only = all(isinstance(b, ToolUseBlock) for b in sdk_message.content)
|
||||
if not is_tool_only:
|
||||
self._flush_unresolved_tool_calls(responses)
|
||||
|
||||
# After tool results, the SDK sends a new AssistantMessage for the
|
||||
# next LLM turn. Open a new step if the previous one was closed.
|
||||
@@ -100,6 +112,15 @@ class SDKResponseAdapter:
|
||||
# instead of "mcp__copilot__find_block".
|
||||
tool_name = block.name.removeprefix(MCP_TOOL_PREFIX)
|
||||
|
||||
# Check if this is a long-running tool to trigger UI feedback
|
||||
tool = get_tool(tool_name)
|
||||
is_long_running = tool.is_long_running if tool else False
|
||||
|
||||
logger.info(
|
||||
f"[ADAPTER] Tool: {tool_name}, has_tool={tool is not None}, "
|
||||
f"is_long_running={is_long_running}"
|
||||
)
|
||||
|
||||
responses.append(
|
||||
StreamToolInputStart(toolCallId=block.id, toolName=tool_name)
|
||||
)
|
||||
@@ -108,8 +129,15 @@ class SDKResponseAdapter:
|
||||
toolCallId=block.id,
|
||||
toolName=tool_name,
|
||||
input=block.input,
|
||||
providerMetadata=(
|
||||
{"isLongRunning": True} if is_long_running else None
|
||||
),
|
||||
)
|
||||
)
|
||||
logger.info(
|
||||
f"[ADAPTER] Created StreamToolInputAvailable with "
|
||||
f"providerMetadata={{'isLongRunning': {is_long_running}}}"
|
||||
)
|
||||
self.current_tool_calls[block.id] = {"name": tool_name}
|
||||
|
||||
elif isinstance(sdk_message, UserMessage):
|
||||
@@ -118,8 +146,24 @@ class SDKResponseAdapter:
|
||||
blocks = content if isinstance(content, list) else []
|
||||
resolved_in_blocks: set[str] = set()
|
||||
|
||||
sid = (self.session_id or "?")[:12]
|
||||
parent_id_preview = getattr(sdk_message, "parent_tool_use_id", None)
|
||||
logger.info(
|
||||
"[SDK] [%s] UserMessage: %d blocks, content_type=%s, "
|
||||
"parent_tool_use_id=%s",
|
||||
sid,
|
||||
len(blocks),
|
||||
type(content).__name__,
|
||||
parent_id_preview[:12] if parent_id_preview else "None",
|
||||
)
|
||||
|
||||
for block in blocks:
|
||||
if isinstance(block, ToolResultBlock) and block.tool_use_id:
|
||||
# Skip if already resolved (e.g. by flush) — the real
|
||||
# result supersedes the empty flush, but re-emitting
|
||||
# would confuse the frontend's state machine.
|
||||
if block.tool_use_id in self.resolved_tool_calls:
|
||||
continue
|
||||
tool_info = self.current_tool_calls.get(block.tool_use_id, {})
|
||||
tool_name = tool_info.get("name", "unknown")
|
||||
|
||||
@@ -144,7 +188,11 @@ class SDKResponseAdapter:
|
||||
# Handle SDK built-in tool results carried via parent_tool_use_id
|
||||
# instead of (or in addition to) ToolResultBlock content.
|
||||
parent_id = sdk_message.parent_tool_use_id
|
||||
if parent_id and parent_id not in resolved_in_blocks:
|
||||
if (
|
||||
parent_id
|
||||
and parent_id not in resolved_in_blocks
|
||||
and parent_id not in self.resolved_tool_calls
|
||||
):
|
||||
tool_info = self.current_tool_calls.get(parent_id, {})
|
||||
tool_name = tool_info.get("name", "unknown")
|
||||
|
||||
@@ -228,11 +276,28 @@ class SDKResponseAdapter:
|
||||
output, which we pop and emit here before the next ``AssistantMessage``
|
||||
starts.
|
||||
"""
|
||||
unresolved = [
|
||||
(tid, info.get("name", "unknown"))
|
||||
for tid, info in self.current_tool_calls.items()
|
||||
if tid not in self.resolved_tool_calls
|
||||
]
|
||||
sid = (self.session_id or "?")[:12]
|
||||
if not unresolved:
|
||||
logger.info(
|
||||
"[SDK] [%s] Flush called but all %d tool(s) already resolved",
|
||||
sid,
|
||||
len(self.current_tool_calls),
|
||||
)
|
||||
return
|
||||
logger.info(
|
||||
"[SDK] [%s] Flushing %d unresolved tool call(s): %s",
|
||||
sid,
|
||||
len(unresolved),
|
||||
", ".join(f"{name}({tid[:12]})" for tid, name in unresolved),
|
||||
)
|
||||
|
||||
flushed = False
|
||||
for tool_id, tool_info in self.current_tool_calls.items():
|
||||
if tool_id in self.resolved_tool_calls:
|
||||
continue
|
||||
tool_name = tool_info.get("name", "unknown")
|
||||
for tool_id, tool_name in unresolved:
|
||||
output = pop_pending_tool_output(tool_name)
|
||||
if output is not None:
|
||||
responses.append(
|
||||
@@ -245,9 +310,12 @@ class SDKResponseAdapter:
|
||||
)
|
||||
self.resolved_tool_calls.add(tool_id)
|
||||
flushed = True
|
||||
logger.debug(
|
||||
f"Flushed pending output for built-in tool {tool_name} "
|
||||
f"(call {tool_id})"
|
||||
logger.info(
|
||||
"[SDK] [%s] Flushed stashed output for %s " "(call %s, %d chars)",
|
||||
sid,
|
||||
tool_name,
|
||||
tool_id[:12],
|
||||
len(output),
|
||||
)
|
||||
else:
|
||||
# No output available — emit an empty output so the frontend
|
||||
@@ -263,9 +331,14 @@ class SDKResponseAdapter:
|
||||
)
|
||||
self.resolved_tool_calls.add(tool_id)
|
||||
flushed = True
|
||||
logger.debug(
|
||||
f"Flushed empty output for unresolved tool {tool_name} "
|
||||
f"(call {tool_id})"
|
||||
logger.warning(
|
||||
"[SDK] [%s] Flushed EMPTY output for unresolved tool %s "
|
||||
"(call %s) — stash was empty (likely SDK hook race "
|
||||
"condition: PostToolUse hook hadn't completed before "
|
||||
"flush was triggered)",
|
||||
sid,
|
||||
tool_name,
|
||||
tool_id[:12],
|
||||
)
|
||||
|
||||
if flushed and self.step_open:
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
"""Unit tests for the SDK response adapter."""
|
||||
|
||||
import asyncio
|
||||
|
||||
import pytest
|
||||
from claude_agent_sdk import (
|
||||
AssistantMessage,
|
||||
ResultMessage,
|
||||
@@ -27,6 +30,10 @@ from backend.copilot.response_model import (
|
||||
|
||||
from .response_adapter import SDKResponseAdapter
|
||||
from .tool_adapter import MCP_TOOL_PREFIX
|
||||
from .tool_adapter import _pending_tool_outputs as _pto
|
||||
from .tool_adapter import _stash_event
|
||||
from .tool_adapter import stash_pending_tool_output as _stash
|
||||
from .tool_adapter import wait_for_stash
|
||||
|
||||
|
||||
def _adapter() -> SDKResponseAdapter:
|
||||
@@ -364,3 +371,310 @@ def test_full_conversation_flow():
|
||||
"StreamFinishStep", # step 2 closed
|
||||
"StreamFinish",
|
||||
]
|
||||
|
||||
|
||||
# -- Flush unresolved tool calls --------------------------------------------
|
||||
|
||||
|
||||
def test_flush_unresolved_at_result_message():
|
||||
"""Built-in tools (WebSearch) without UserMessage results get flushed at ResultMessage."""
|
||||
adapter = _adapter()
|
||||
all_responses: list[StreamBaseResponse] = []
|
||||
|
||||
# 1. Init
|
||||
all_responses.extend(
|
||||
adapter.convert_message(SystemMessage(subtype="init", data={}))
|
||||
)
|
||||
# 2. Tool use (built-in tool — no MCP prefix)
|
||||
all_responses.extend(
|
||||
adapter.convert_message(
|
||||
AssistantMessage(
|
||||
content=[
|
||||
ToolUseBlock(id="ws-1", name="WebSearch", input={"query": "test"})
|
||||
],
|
||||
model="test",
|
||||
)
|
||||
)
|
||||
)
|
||||
# 3. No UserMessage for this tool — go straight to ResultMessage
|
||||
all_responses.extend(
|
||||
adapter.convert_message(
|
||||
ResultMessage(
|
||||
subtype="success",
|
||||
duration_ms=100,
|
||||
duration_api_ms=50,
|
||||
is_error=False,
|
||||
num_turns=1,
|
||||
session_id="s1",
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
types = [type(r).__name__ for r in all_responses]
|
||||
assert types == [
|
||||
"StreamStart",
|
||||
"StreamStartStep",
|
||||
"StreamToolInputStart",
|
||||
"StreamToolInputAvailable",
|
||||
"StreamToolOutputAvailable", # flushed with empty output
|
||||
"StreamFinishStep", # step closed by flush
|
||||
"StreamFinish",
|
||||
]
|
||||
# The flushed output should be empty (no stash available)
|
||||
output_event = [
|
||||
r for r in all_responses if isinstance(r, StreamToolOutputAvailable)
|
||||
][0]
|
||||
assert output_event.toolCallId == "ws-1"
|
||||
assert output_event.toolName == "WebSearch"
|
||||
assert output_event.output == ""
|
||||
|
||||
|
||||
def test_flush_unresolved_at_next_assistant_message():
|
||||
"""Built-in tools get flushed when the next AssistantMessage arrives."""
|
||||
adapter = _adapter()
|
||||
all_responses: list[StreamBaseResponse] = []
|
||||
|
||||
# 1. Init
|
||||
all_responses.extend(
|
||||
adapter.convert_message(SystemMessage(subtype="init", data={}))
|
||||
)
|
||||
# 2. Tool use (built-in — no UserMessage will come)
|
||||
all_responses.extend(
|
||||
adapter.convert_message(
|
||||
AssistantMessage(
|
||||
content=[
|
||||
ToolUseBlock(id="ws-1", name="WebSearch", input={"query": "test"})
|
||||
],
|
||||
model="test",
|
||||
)
|
||||
)
|
||||
)
|
||||
# 3. Next AssistantMessage triggers flush before processing its blocks
|
||||
all_responses.extend(
|
||||
adapter.convert_message(
|
||||
AssistantMessage(
|
||||
content=[TextBlock(text="Here are the results")], model="test"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
types = [type(r).__name__ for r in all_responses]
|
||||
assert types == [
|
||||
"StreamStart",
|
||||
"StreamStartStep",
|
||||
"StreamToolInputStart",
|
||||
"StreamToolInputAvailable",
|
||||
# Flush at next AssistantMessage:
|
||||
"StreamToolOutputAvailable",
|
||||
"StreamFinishStep", # step closed by flush
|
||||
# New step for continuation text:
|
||||
"StreamStartStep",
|
||||
"StreamTextStart",
|
||||
"StreamTextDelta",
|
||||
]
|
||||
|
||||
|
||||
def test_flush_with_stashed_output():
|
||||
"""Stashed output from PostToolUse hook is used when flushing."""
|
||||
adapter = _adapter()
|
||||
|
||||
# Simulate PostToolUse hook stashing output
|
||||
_pto.set({})
|
||||
_stash("WebSearch", "Search result: 5 items found")
|
||||
|
||||
all_responses: list[StreamBaseResponse] = []
|
||||
|
||||
# Tool use
|
||||
all_responses.extend(
|
||||
adapter.convert_message(
|
||||
AssistantMessage(
|
||||
content=[
|
||||
ToolUseBlock(id="ws-1", name="WebSearch", input={"query": "test"})
|
||||
],
|
||||
model="test",
|
||||
)
|
||||
)
|
||||
)
|
||||
# ResultMessage triggers flush
|
||||
all_responses.extend(
|
||||
adapter.convert_message(
|
||||
ResultMessage(
|
||||
subtype="success",
|
||||
duration_ms=100,
|
||||
duration_api_ms=50,
|
||||
is_error=False,
|
||||
num_turns=1,
|
||||
session_id="s1",
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
output_events = [
|
||||
r for r in all_responses if isinstance(r, StreamToolOutputAvailable)
|
||||
]
|
||||
assert len(output_events) == 1
|
||||
assert output_events[0].output == "Search result: 5 items found"
|
||||
|
||||
# Cleanup
|
||||
_pto.set({}) # type: ignore[arg-type]
|
||||
|
||||
|
||||
# -- wait_for_stash synchronisation tests --
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_wait_for_stash_signaled():
|
||||
"""wait_for_stash returns True when stash_pending_tool_output signals."""
|
||||
_pto.set({})
|
||||
event = asyncio.Event()
|
||||
_stash_event.set(event)
|
||||
|
||||
# Simulate a PostToolUse hook that stashes output after a short delay
|
||||
async def delayed_stash():
|
||||
await asyncio.sleep(0.01)
|
||||
_stash("WebSearch", "result data")
|
||||
|
||||
asyncio.create_task(delayed_stash())
|
||||
result = await wait_for_stash(timeout=1.0)
|
||||
|
||||
assert result is True
|
||||
assert _pto.get({}).get("WebSearch") == ["result data"]
|
||||
|
||||
# Cleanup
|
||||
_pto.set({}) # type: ignore[arg-type]
|
||||
_stash_event.set(None)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_wait_for_stash_timeout():
|
||||
"""wait_for_stash returns False on timeout when no stash occurs."""
|
||||
_pto.set({})
|
||||
event = asyncio.Event()
|
||||
_stash_event.set(event)
|
||||
|
||||
result = await wait_for_stash(timeout=0.05)
|
||||
assert result is False
|
||||
|
||||
# Cleanup
|
||||
_pto.set({}) # type: ignore[arg-type]
|
||||
_stash_event.set(None)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_wait_for_stash_already_stashed():
|
||||
"""wait_for_stash picks up a stash that happened just before the wait."""
|
||||
_pto.set({})
|
||||
event = asyncio.Event()
|
||||
_stash_event.set(event)
|
||||
|
||||
# Stash before waiting — simulates hook completing before message arrives
|
||||
_stash("Read", "file contents")
|
||||
# Event is now set; wait_for_stash detects the fast path and returns
|
||||
# immediately without timing out.
|
||||
result = await wait_for_stash(timeout=0.05)
|
||||
assert result is True
|
||||
|
||||
# But the stash itself is populated
|
||||
assert _pto.get({}).get("Read") == ["file contents"]
|
||||
|
||||
# Cleanup
|
||||
_pto.set({}) # type: ignore[arg-type]
|
||||
_stash_event.set(None)
|
||||
|
||||
|
||||
# -- Parallel tool call tests --
|
||||
|
||||
|
||||
def test_parallel_tool_calls_not_flushed_prematurely():
|
||||
"""Parallel tool calls should NOT be flushed when the next AssistantMessage
|
||||
only contains ToolUseBlocks (parallel continuation)."""
|
||||
adapter = SDKResponseAdapter()
|
||||
|
||||
# Init
|
||||
adapter.convert_message(SystemMessage(subtype="init", data={}))
|
||||
|
||||
# First AssistantMessage: tool call #1
|
||||
msg1 = AssistantMessage(
|
||||
content=[ToolUseBlock(id="t1", name="WebSearch", input={"q": "foo"})],
|
||||
model="test",
|
||||
)
|
||||
r1 = adapter.convert_message(msg1)
|
||||
assert any(isinstance(r, StreamToolInputAvailable) for r in r1)
|
||||
assert adapter.has_unresolved_tool_calls
|
||||
|
||||
# Second AssistantMessage: tool call #2 (parallel continuation)
|
||||
msg2 = AssistantMessage(
|
||||
content=[ToolUseBlock(id="t2", name="WebSearch", input={"q": "bar"})],
|
||||
model="test",
|
||||
)
|
||||
r2 = adapter.convert_message(msg2)
|
||||
|
||||
# No flush should have happened — t1 should NOT have StreamToolOutputAvailable
|
||||
output_events = [r for r in r2 if isinstance(r, StreamToolOutputAvailable)]
|
||||
assert len(output_events) == 0, (
|
||||
f"Tool-only AssistantMessage should not flush prior tools, "
|
||||
f"but got {len(output_events)} output events"
|
||||
)
|
||||
|
||||
# Both t1 and t2 should still be unresolved
|
||||
assert "t1" not in adapter.resolved_tool_calls
|
||||
assert "t2" not in adapter.resolved_tool_calls
|
||||
|
||||
|
||||
def test_text_assistant_message_flushes_prior_tools():
|
||||
"""An AssistantMessage with text (new turn) should flush unresolved tools."""
|
||||
adapter = SDKResponseAdapter()
|
||||
|
||||
# Init
|
||||
adapter.convert_message(SystemMessage(subtype="init", data={}))
|
||||
|
||||
# Tool call
|
||||
msg1 = AssistantMessage(
|
||||
content=[ToolUseBlock(id="t1", name="WebSearch", input={"q": "foo"})],
|
||||
model="test",
|
||||
)
|
||||
adapter.convert_message(msg1)
|
||||
assert adapter.has_unresolved_tool_calls
|
||||
|
||||
# Text AssistantMessage (new turn after tools completed)
|
||||
msg2 = AssistantMessage(
|
||||
content=[TextBlock(text="Here are the results")],
|
||||
model="test",
|
||||
)
|
||||
r2 = adapter.convert_message(msg2)
|
||||
|
||||
# Flush SHOULD have happened — t1 gets empty output
|
||||
output_events = [r for r in r2 if isinstance(r, StreamToolOutputAvailable)]
|
||||
assert len(output_events) == 1
|
||||
assert output_events[0].toolCallId == "t1"
|
||||
assert "t1" in adapter.resolved_tool_calls
|
||||
|
||||
|
||||
def test_already_resolved_tool_skipped_in_user_message():
|
||||
"""A tool result in UserMessage should be skipped if already resolved by flush."""
|
||||
adapter = SDKResponseAdapter()
|
||||
|
||||
adapter.convert_message(SystemMessage(subtype="init", data={}))
|
||||
|
||||
# Tool call + flush via text message
|
||||
adapter.convert_message(
|
||||
AssistantMessage(
|
||||
content=[ToolUseBlock(id="t1", name="WebSearch", input={})],
|
||||
model="test",
|
||||
)
|
||||
)
|
||||
adapter.convert_message(
|
||||
AssistantMessage(
|
||||
content=[TextBlock(text="Done")],
|
||||
model="test",
|
||||
)
|
||||
)
|
||||
assert "t1" in adapter.resolved_tool_calls
|
||||
|
||||
# Now UserMessage arrives with the real result — should be skipped
|
||||
user_msg = UserMessage(content=[ToolResultBlock(tool_use_id="t1", content="real")])
|
||||
r = adapter.convert_message(user_msg)
|
||||
output_events = [r_ for r_ in r if isinstance(r_, StreamToolOutputAvailable)]
|
||||
assert (
|
||||
len(output_events) == 0
|
||||
), "Already-resolved tool should not emit duplicate output"
|
||||
|
||||
194
autogpt_platform/backend/backend/copilot/sdk/sdk_compat_test.py
Normal file
194
autogpt_platform/backend/backend/copilot/sdk/sdk_compat_test.py
Normal file
@@ -0,0 +1,194 @@
|
||||
"""SDK compatibility tests — verify the claude-agent-sdk public API surface we depend on.
|
||||
|
||||
Instead of pinning to a narrow version range, these tests verify that the
|
||||
installed SDK exposes every class, function, attribute, and method the copilot
|
||||
integration relies on. If an SDK upgrade removes or renames something these
|
||||
tests will catch it immediately.
|
||||
"""
|
||||
|
||||
import inspect
|
||||
|
||||
import pytest
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Public types & factories
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_sdk_exports_client_and_options():
|
||||
from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient
|
||||
|
||||
assert inspect.isclass(ClaudeSDKClient)
|
||||
assert inspect.isclass(ClaudeAgentOptions)
|
||||
|
||||
|
||||
def test_sdk_exports_message_types():
|
||||
from claude_agent_sdk import (
|
||||
AssistantMessage,
|
||||
Message,
|
||||
ResultMessage,
|
||||
SystemMessage,
|
||||
UserMessage,
|
||||
)
|
||||
|
||||
for cls in (AssistantMessage, ResultMessage, SystemMessage, UserMessage):
|
||||
assert inspect.isclass(cls), f"{cls.__name__} is not a class"
|
||||
# Message is a Union type alias, just verify it's importable
|
||||
assert Message is not None
|
||||
|
||||
|
||||
def test_sdk_exports_content_block_types():
|
||||
from claude_agent_sdk import TextBlock, ToolResultBlock, ToolUseBlock
|
||||
|
||||
for cls in (TextBlock, ToolResultBlock, ToolUseBlock):
|
||||
assert inspect.isclass(cls), f"{cls.__name__} is not a class"
|
||||
|
||||
|
||||
def test_sdk_exports_mcp_helpers():
|
||||
from claude_agent_sdk import create_sdk_mcp_server, tool
|
||||
|
||||
assert callable(create_sdk_mcp_server)
|
||||
assert callable(tool)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ClaudeSDKClient interface
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_client_has_required_methods():
|
||||
from claude_agent_sdk import ClaudeSDKClient
|
||||
|
||||
required = ["connect", "disconnect", "query", "receive_messages"]
|
||||
for name in required:
|
||||
attr = getattr(ClaudeSDKClient, name, None)
|
||||
assert attr is not None, f"ClaudeSDKClient.{name} missing"
|
||||
assert callable(attr), f"ClaudeSDKClient.{name} is not callable"
|
||||
|
||||
|
||||
def test_client_supports_async_context_manager():
|
||||
from claude_agent_sdk import ClaudeSDKClient
|
||||
|
||||
assert hasattr(ClaudeSDKClient, "__aenter__")
|
||||
assert hasattr(ClaudeSDKClient, "__aexit__")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ClaudeAgentOptions fields
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_agent_options_accepts_required_fields():
|
||||
"""Verify ClaudeAgentOptions accepts all kwargs our code passes."""
|
||||
from claude_agent_sdk import ClaudeAgentOptions
|
||||
|
||||
opts = ClaudeAgentOptions(
|
||||
system_prompt="test",
|
||||
cwd="/tmp",
|
||||
)
|
||||
assert opts.system_prompt == "test"
|
||||
assert opts.cwd == "/tmp"
|
||||
|
||||
|
||||
def test_agent_options_accepts_all_our_fields():
|
||||
"""Comprehensive check of every field we use in service.py."""
|
||||
from claude_agent_sdk import ClaudeAgentOptions
|
||||
|
||||
fields_we_use = [
|
||||
"system_prompt",
|
||||
"mcp_servers",
|
||||
"allowed_tools",
|
||||
"disallowed_tools",
|
||||
"hooks",
|
||||
"cwd",
|
||||
"model",
|
||||
"env",
|
||||
"resume",
|
||||
"max_buffer_size",
|
||||
]
|
||||
sig = inspect.signature(ClaudeAgentOptions)
|
||||
for field in fields_we_use:
|
||||
assert field in sig.parameters, (
|
||||
f"ClaudeAgentOptions no longer accepts '{field}' — "
|
||||
f"available params: {list(sig.parameters.keys())}"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Message attributes
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_assistant_message_has_content_and_model():
|
||||
from claude_agent_sdk import AssistantMessage, TextBlock
|
||||
|
||||
msg = AssistantMessage(content=[TextBlock(text="hi")], model="test")
|
||||
assert hasattr(msg, "content")
|
||||
assert hasattr(msg, "model")
|
||||
|
||||
|
||||
def test_result_message_has_required_attrs():
|
||||
from claude_agent_sdk import ResultMessage
|
||||
|
||||
msg = ResultMessage(
|
||||
subtype="success",
|
||||
duration_ms=100,
|
||||
duration_api_ms=50,
|
||||
is_error=False,
|
||||
num_turns=1,
|
||||
session_id="s1",
|
||||
)
|
||||
assert msg.subtype == "success"
|
||||
assert hasattr(msg, "result")
|
||||
|
||||
|
||||
def test_system_message_has_subtype_and_data():
|
||||
from claude_agent_sdk import SystemMessage
|
||||
|
||||
msg = SystemMessage(subtype="init", data={})
|
||||
assert msg.subtype == "init"
|
||||
assert msg.data == {}
|
||||
|
||||
|
||||
def test_user_message_has_parent_tool_use_id():
|
||||
from claude_agent_sdk import UserMessage
|
||||
|
||||
msg = UserMessage(content="test")
|
||||
assert hasattr(msg, "parent_tool_use_id")
|
||||
assert hasattr(msg, "tool_use_result")
|
||||
|
||||
|
||||
def test_tool_use_block_has_id_name_input():
|
||||
from claude_agent_sdk import ToolUseBlock
|
||||
|
||||
block = ToolUseBlock(id="t1", name="test", input={"key": "val"})
|
||||
assert block.id == "t1"
|
||||
assert block.name == "test"
|
||||
assert block.input == {"key": "val"}
|
||||
|
||||
|
||||
def test_tool_result_block_has_required_attrs():
|
||||
from claude_agent_sdk import ToolResultBlock
|
||||
|
||||
block = ToolResultBlock(tool_use_id="t1", content="result")
|
||||
assert block.tool_use_id == "t1"
|
||||
assert block.content == "result"
|
||||
assert hasattr(block, "is_error")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Hook types
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"hook_event",
|
||||
["PreToolUse", "PostToolUse", "Stop"],
|
||||
)
|
||||
def test_sdk_exports_hook_event_type(hook_event: str):
|
||||
"""Verify HookEvent literal includes the events our security_hooks use."""
|
||||
from claude_agent_sdk.types import HookEvent
|
||||
|
||||
# HookEvent is a Literal type — check that our events are valid values.
|
||||
# We can't easily inspect Literal at runtime, so just verify the type exists.
|
||||
assert HookEvent is not None
|
||||
@@ -246,15 +246,33 @@ def create_security_hooks(
|
||||
"""
|
||||
_ = context
|
||||
tool_name = cast(str, input_data.get("tool_name", ""))
|
||||
logger.debug(f"[SDK] Tool success: {tool_name}, tool_use_id={tool_use_id}")
|
||||
is_builtin = not tool_name.startswith(MCP_TOOL_PREFIX)
|
||||
logger.info(
|
||||
"[SDK] PostToolUse: %s (builtin=%s, tool_use_id=%s)",
|
||||
tool_name,
|
||||
is_builtin,
|
||||
(tool_use_id or "")[:12],
|
||||
)
|
||||
|
||||
# Stash output for SDK built-in tools so the response adapter can
|
||||
# emit StreamToolOutputAvailable even when the CLI doesn't surface
|
||||
# a separate UserMessage with ToolResultBlock content.
|
||||
if not tool_name.startswith(MCP_TOOL_PREFIX):
|
||||
if is_builtin:
|
||||
tool_response = input_data.get("tool_response")
|
||||
if tool_response is not None:
|
||||
resp_preview = str(tool_response)[:100]
|
||||
logger.info(
|
||||
"[SDK] Stashing builtin output for %s (%d chars): %s...",
|
||||
tool_name,
|
||||
len(str(tool_response)),
|
||||
resp_preview,
|
||||
)
|
||||
stash_pending_tool_output(tool_name, tool_response)
|
||||
else:
|
||||
logger.warning(
|
||||
"[SDK] PostToolUse for builtin %s but tool_response is None",
|
||||
tool_name,
|
||||
)
|
||||
|
||||
return cast(SyncHookJSONOutput, {})
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ from typing import Any
|
||||
|
||||
from backend.util.exceptions import NotFoundError
|
||||
|
||||
from .. import stream_registry
|
||||
from ..config import ChatConfig
|
||||
from ..model import (
|
||||
ChatMessage,
|
||||
@@ -24,18 +23,14 @@ from ..response_model import (
|
||||
StreamBaseResponse,
|
||||
StreamError,
|
||||
StreamFinish,
|
||||
StreamFinishStep,
|
||||
StreamHeartbeat,
|
||||
StreamStart,
|
||||
StreamTextDelta,
|
||||
StreamToolInputAvailable,
|
||||
StreamToolOutputAvailable,
|
||||
)
|
||||
from ..service import (
|
||||
_build_system_prompt,
|
||||
_execute_long_running_tool_with_streaming,
|
||||
_generate_session_title,
|
||||
)
|
||||
from ..tools.models import OperationPendingResponse, OperationStartedResponse
|
||||
from ..service import _build_system_prompt, _generate_session_title
|
||||
from ..tools.sandbox import WORKSPACE_PREFIX, make_session_path
|
||||
from ..tracking import track_user_message
|
||||
from .response_adapter import SDKResponseAdapter
|
||||
@@ -43,9 +38,9 @@ from .security_hooks import create_security_hooks
|
||||
from .tool_adapter import (
|
||||
COPILOT_TOOL_NAMES,
|
||||
SDK_DISALLOWED_TOOLS,
|
||||
LongRunningCallback,
|
||||
create_copilot_mcp_server,
|
||||
set_execution_context,
|
||||
wait_for_stash,
|
||||
)
|
||||
from .transcript import (
|
||||
cleanup_cli_project_dir,
|
||||
@@ -121,9 +116,9 @@ When you create or modify important files (code, configs, outputs), you MUST:
|
||||
are available from previous turns
|
||||
|
||||
### Long-running tools
|
||||
Long-running tools (create_agent, edit_agent, etc.) are handled
|
||||
asynchronously. You will receive an immediate response; the actual result
|
||||
is delivered to the user via a background stream.
|
||||
Long-running tools (create_agent, edit_agent, etc.) run synchronously
|
||||
with heartbeats to keep the connection alive. The frontend shows UI feedback
|
||||
during execution based on stream events.
|
||||
|
||||
### Sub-agent tasks
|
||||
- When using the Task tool, NEVER set `run_in_background` to true.
|
||||
@@ -131,121 +126,6 @@ is delivered to the user via a background stream.
|
||||
"""
|
||||
|
||||
|
||||
def _build_long_running_callback(user_id: str | None) -> LongRunningCallback:
|
||||
"""Build a callback that delegates long-running tools to the non-SDK infrastructure.
|
||||
|
||||
Long-running tools (create_agent, edit_agent, etc.) are delegated to the
|
||||
existing background infrastructure: stream_registry (Redis Streams),
|
||||
database persistence, and SSE reconnection. This means results survive
|
||||
page refreshes / pod restarts, and the frontend shows the proper loading
|
||||
widget with progress updates.
|
||||
|
||||
The returned callback matches the ``LongRunningCallback`` signature:
|
||||
``(tool_name, args, session) -> MCP response dict``.
|
||||
"""
|
||||
|
||||
async def _callback(
|
||||
tool_name: str, args: dict[str, Any], session: ChatSession
|
||||
) -> dict[str, Any]:
|
||||
operation_id = str(uuid.uuid4())
|
||||
task_id = str(uuid.uuid4())
|
||||
tool_call_id = f"sdk-{uuid.uuid4().hex[:12]}"
|
||||
session_id = session.session_id
|
||||
|
||||
# --- Build user-friendly messages (matches non-SDK service) ---
|
||||
if tool_name == "create_agent":
|
||||
desc = args.get("description", "")
|
||||
desc_preview = (desc[:100] + "...") if len(desc) > 100 else desc
|
||||
pending_msg = (
|
||||
f"Creating your agent: {desc_preview}"
|
||||
if desc_preview
|
||||
else "Creating agent... This may take a few minutes."
|
||||
)
|
||||
started_msg = (
|
||||
"Agent creation started. You can close this tab - "
|
||||
"check your library in a few minutes."
|
||||
)
|
||||
elif tool_name == "edit_agent":
|
||||
changes = args.get("changes", "")
|
||||
changes_preview = (changes[:100] + "...") if len(changes) > 100 else changes
|
||||
pending_msg = (
|
||||
f"Editing agent: {changes_preview}"
|
||||
if changes_preview
|
||||
else "Editing agent... This may take a few minutes."
|
||||
)
|
||||
started_msg = (
|
||||
"Agent edit started. You can close this tab - "
|
||||
"check your library in a few minutes."
|
||||
)
|
||||
else:
|
||||
pending_msg = f"Running {tool_name}... This may take a few minutes."
|
||||
started_msg = (
|
||||
f"{tool_name} started. You can close this tab - "
|
||||
"check back in a few minutes."
|
||||
)
|
||||
|
||||
# --- Register task in Redis for SSE reconnection ---
|
||||
await stream_registry.create_task(
|
||||
task_id=task_id,
|
||||
session_id=session_id,
|
||||
user_id=user_id,
|
||||
tool_call_id=tool_call_id,
|
||||
tool_name=tool_name,
|
||||
operation_id=operation_id,
|
||||
)
|
||||
|
||||
# --- Save OperationPendingResponse to chat history ---
|
||||
pending_message = ChatMessage(
|
||||
role="tool",
|
||||
content=OperationPendingResponse(
|
||||
message=pending_msg,
|
||||
operation_id=operation_id,
|
||||
tool_name=tool_name,
|
||||
).model_dump_json(),
|
||||
tool_call_id=tool_call_id,
|
||||
)
|
||||
session.messages.append(pending_message)
|
||||
await upsert_chat_session(session)
|
||||
|
||||
# --- Spawn background task (reuses non-SDK infrastructure) ---
|
||||
bg_task = asyncio.create_task(
|
||||
_execute_long_running_tool_with_streaming(
|
||||
tool_name=tool_name,
|
||||
parameters=args,
|
||||
tool_call_id=tool_call_id,
|
||||
operation_id=operation_id,
|
||||
task_id=task_id,
|
||||
session_id=session_id,
|
||||
user_id=user_id,
|
||||
)
|
||||
)
|
||||
_background_tasks.add(bg_task)
|
||||
bg_task.add_done_callback(_background_tasks.discard)
|
||||
await stream_registry.set_task_asyncio_task(task_id, bg_task)
|
||||
|
||||
logger.info(
|
||||
f"[SDK] Long-running tool {tool_name} delegated to background "
|
||||
f"(operation_id={operation_id}, task_id={task_id})"
|
||||
)
|
||||
|
||||
# --- Return OperationStartedResponse as MCP tool result ---
|
||||
# This flows through SDK → response adapter → frontend, triggering
|
||||
# the loading widget with SSE reconnection support.
|
||||
started_json = OperationStartedResponse(
|
||||
message=started_msg,
|
||||
operation_id=operation_id,
|
||||
tool_name=tool_name,
|
||||
task_id=task_id,
|
||||
).model_dump_json()
|
||||
|
||||
return {
|
||||
"content": [{"type": "text", "text": started_json}],
|
||||
"isError": False,
|
||||
}
|
||||
|
||||
return _callback
|
||||
|
||||
|
||||
def _resolve_sdk_model() -> str | None:
|
||||
"""Resolve the model name for the Claude Agent SDK CLI.
|
||||
|
||||
@@ -344,15 +224,15 @@ async def _compress_conversation_history(
|
||||
|
||||
Returns the compressed prior messages (everything except the current message).
|
||||
"""
|
||||
prior = session.messages[:-1]
|
||||
if len(prior) < 2:
|
||||
return prior
|
||||
messages = session.messages[:-1]
|
||||
if len(messages) < 2:
|
||||
return messages
|
||||
|
||||
from backend.util.prompt import compress_context
|
||||
|
||||
# Convert ChatMessages to dicts for compress_context
|
||||
messages_dict = []
|
||||
for msg in prior:
|
||||
for msg in messages:
|
||||
msg_dict: dict[str, Any] = {"role": msg.role}
|
||||
if msg.content:
|
||||
msg_dict["content"] = msg.content
|
||||
@@ -400,7 +280,7 @@ async def _compress_conversation_history(
|
||||
for m in result.messages
|
||||
]
|
||||
|
||||
return prior
|
||||
return messages
|
||||
|
||||
|
||||
def _format_conversation_context(messages: list[ChatMessage]) -> str | None:
|
||||
@@ -442,8 +322,8 @@ def _format_conversation_context(messages: list[ChatMessage]) -> str | None:
|
||||
def _is_tool_error_or_denial(content: str | None) -> bool:
|
||||
"""Check if a tool message content indicates an error or denial.
|
||||
|
||||
We include these in conversation context so the agent doesn't
|
||||
hallucinate success for operations that actually failed.
|
||||
Currently unused — ``_format_conversation_context`` includes all tool
|
||||
results. Kept as a utility for future selective filtering.
|
||||
"""
|
||||
if not content:
|
||||
return False
|
||||
@@ -458,7 +338,7 @@ def _is_tool_error_or_denial(content: str | None) -> bool:
|
||||
"maximum", # subtask-limit denial
|
||||
"denied",
|
||||
"blocked",
|
||||
"failed", # internal tool execution failures
|
||||
"failed to", # internal tool execution failures
|
||||
'"iserror": true', # MCP protocol error flag
|
||||
)
|
||||
)
|
||||
@@ -582,7 +462,7 @@ async def stream_chat_completion_sdk(
|
||||
set_execution_context(
|
||||
user_id,
|
||||
session,
|
||||
long_running_callback=_build_long_running_callback(user_id),
|
||||
long_running_callback=None,
|
||||
)
|
||||
try:
|
||||
from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient
|
||||
@@ -674,7 +554,7 @@ async def stream_chat_completion_sdk(
|
||||
|
||||
options = ClaudeAgentOptions(**sdk_options_kwargs) # type: ignore[arg-type]
|
||||
|
||||
adapter = SDKResponseAdapter(message_id=message_id)
|
||||
adapter = SDKResponseAdapter(message_id=message_id, session_id=session_id)
|
||||
adapter.set_task_id(task_id)
|
||||
|
||||
async with ClaudeSDKClient(options=options) as client:
|
||||
@@ -699,10 +579,13 @@ async def stream_chat_completion_sdk(
|
||||
transcript_msg_count,
|
||||
session_id,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"[SDK] Sending query ({len(session.messages)} msgs, "
|
||||
f"resume={use_resume})"
|
||||
"[SDK] [%s] Sending query — resume=%s, "
|
||||
"total_msgs=%d, query_len=%d",
|
||||
session_id[:12],
|
||||
use_resume,
|
||||
len(session.messages),
|
||||
len(query_message),
|
||||
)
|
||||
await client.query(query_message, session_id=session_id)
|
||||
|
||||
@@ -710,98 +593,296 @@ async def stream_chat_completion_sdk(
|
||||
accumulated_tool_calls: list[dict[str, Any]] = []
|
||||
has_appended_assistant = False
|
||||
has_tool_results = False
|
||||
# Track persisted message count to skip DB count queries
|
||||
# on incremental saves. Initial save happened at line 545.
|
||||
saved_msg_count = len(session.messages)
|
||||
|
||||
# Use an explicit async iterator with timeout to send
|
||||
# heartbeats when the CLI is idle (e.g. executing tools).
|
||||
# This prevents proxies/LBs from closing the SSE connection.
|
||||
# asyncio.timeout() is preferred over asyncio.wait_for()
|
||||
# because wait_for wraps in a separate Task whose cancellation
|
||||
# can leave the async generator in a broken state.
|
||||
# Use an explicit async iterator with non-cancelling heartbeats.
|
||||
# CRITICAL: we must NOT cancel __anext__() mid-flight — doing so
|
||||
# (via asyncio.timeout or wait_for) corrupts the SDK's internal
|
||||
# anyio memory stream, causing StopAsyncIteration on the next
|
||||
# call and silently dropping all in-flight tool results.
|
||||
# Instead, wrap __anext__() in a Task and use asyncio.wait()
|
||||
# with a timeout. On timeout we emit a heartbeat but keep the
|
||||
# Task alive so it can deliver the next message.
|
||||
msg_iter = client.receive_messages().__aiter__()
|
||||
while not stream_completed:
|
||||
try:
|
||||
async with asyncio.timeout(_HEARTBEAT_INTERVAL):
|
||||
sdk_msg = await msg_iter.__anext__()
|
||||
except TimeoutError:
|
||||
yield StreamHeartbeat()
|
||||
continue
|
||||
except StopAsyncIteration:
|
||||
break
|
||||
pending_task: asyncio.Task[Any] | None = None
|
||||
try:
|
||||
while not stream_completed:
|
||||
if pending_task is None:
|
||||
|
||||
logger.debug(
|
||||
f"[SDK] Received: {type(sdk_msg).__name__} "
|
||||
f"{getattr(sdk_msg, 'subtype', '')}"
|
||||
)
|
||||
for response in adapter.convert_message(sdk_msg):
|
||||
if isinstance(response, StreamStart):
|
||||
async def _next_msg() -> Any:
|
||||
return await msg_iter.__anext__()
|
||||
|
||||
pending_task = asyncio.create_task(_next_msg())
|
||||
|
||||
done, _ = await asyncio.wait(
|
||||
{pending_task}, timeout=_HEARTBEAT_INTERVAL
|
||||
)
|
||||
|
||||
if not done:
|
||||
# Timeout — emit heartbeat but keep the task alive
|
||||
yield StreamHeartbeat()
|
||||
continue
|
||||
|
||||
# Log tool events for debugging visibility issues
|
||||
# Task completed — get result
|
||||
pending_task = None
|
||||
try:
|
||||
sdk_msg = done.pop().result()
|
||||
except StopAsyncIteration:
|
||||
logger.info(
|
||||
"[SDK] [%s] Stream ended normally "
|
||||
"(StopAsyncIteration)",
|
||||
session_id[:12],
|
||||
)
|
||||
break
|
||||
except Exception as stream_err:
|
||||
# SDK sends {"type": "error"} which raises
|
||||
# Exception in receive_messages() — capture it
|
||||
# so the session can still be saved and the
|
||||
# frontend gets a clean finish.
|
||||
logger.error(
|
||||
"[SDK] [%s] Stream error from SDK: %s",
|
||||
session_id[:12],
|
||||
stream_err,
|
||||
exc_info=True,
|
||||
)
|
||||
yield StreamError(
|
||||
errorText=f"SDK stream error: {stream_err}",
|
||||
code="sdk_stream_error",
|
||||
)
|
||||
break
|
||||
|
||||
logger.info(
|
||||
"[SDK] [%s] Received: %s %s "
|
||||
"(unresolved=%d, current=%d, resolved=%d)",
|
||||
session_id[:12],
|
||||
type(sdk_msg).__name__,
|
||||
getattr(sdk_msg, "subtype", ""),
|
||||
len(adapter.current_tool_calls)
|
||||
- len(adapter.resolved_tool_calls),
|
||||
len(adapter.current_tool_calls),
|
||||
len(adapter.resolved_tool_calls),
|
||||
)
|
||||
|
||||
# Race-condition fix: SDK hooks (PostToolUse) are
|
||||
# executed asynchronously via start_soon() — the next
|
||||
# message can arrive before the hook stashes output.
|
||||
# wait_for_stash() awaits an asyncio.Event signaled by
|
||||
# stash_pending_tool_output(), completing as soon as
|
||||
# the hook finishes (typically <1ms). The sleep(0)
|
||||
# after lets any remaining concurrent hooks complete.
|
||||
#
|
||||
# Skip for parallel tool continuations: when the SDK
|
||||
# sends parallel tool calls as separate
|
||||
# AssistantMessages (each containing only
|
||||
# ToolUseBlocks), we must NOT wait/flush — the prior
|
||||
# tools are still executing concurrently.
|
||||
from claude_agent_sdk import (
|
||||
AssistantMessage,
|
||||
ResultMessage,
|
||||
ToolUseBlock,
|
||||
)
|
||||
|
||||
is_parallel_continuation = isinstance(
|
||||
sdk_msg, AssistantMessage
|
||||
) and all(isinstance(b, ToolUseBlock) for b in sdk_msg.content)
|
||||
|
||||
if (
|
||||
adapter.has_unresolved_tool_calls
|
||||
and isinstance(sdk_msg, (AssistantMessage, ResultMessage))
|
||||
and not is_parallel_continuation
|
||||
):
|
||||
if await wait_for_stash(timeout=0.5):
|
||||
await asyncio.sleep(0)
|
||||
else:
|
||||
logger.warning(
|
||||
"[SDK] [%s] Timed out waiting for "
|
||||
"PostToolUse hook stash "
|
||||
"(%d unresolved tool calls)",
|
||||
session_id[:12],
|
||||
len(adapter.current_tool_calls)
|
||||
- len(adapter.resolved_tool_calls),
|
||||
)
|
||||
|
||||
for response in adapter.convert_message(sdk_msg):
|
||||
if isinstance(response, StreamStart):
|
||||
continue
|
||||
|
||||
# Log tool events for debugging
|
||||
if isinstance(
|
||||
response,
|
||||
(
|
||||
StreamToolInputAvailable,
|
||||
StreamToolOutputAvailable,
|
||||
),
|
||||
):
|
||||
extra = ""
|
||||
if isinstance(response, StreamToolOutputAvailable):
|
||||
out_len = len(str(response.output))
|
||||
extra = f", output_len={out_len}"
|
||||
logger.info(
|
||||
"[SDK] [%s] Tool event: %s, tool=%s%s",
|
||||
session_id[:12],
|
||||
type(response).__name__,
|
||||
getattr(response, "toolName", "N/A"),
|
||||
extra,
|
||||
)
|
||||
|
||||
yield response
|
||||
|
||||
if isinstance(response, StreamTextDelta):
|
||||
delta = response.delta or ""
|
||||
# After tool results, start a new assistant
|
||||
# message for the post-tool text.
|
||||
if has_tool_results and has_appended_assistant:
|
||||
assistant_response = ChatMessage(
|
||||
role="assistant", content=delta
|
||||
)
|
||||
accumulated_tool_calls = []
|
||||
has_appended_assistant = False
|
||||
has_tool_results = False
|
||||
session.messages.append(assistant_response)
|
||||
has_appended_assistant = True
|
||||
else:
|
||||
assistant_response.content = (
|
||||
assistant_response.content or ""
|
||||
) + delta
|
||||
if not has_appended_assistant:
|
||||
session.messages.append(assistant_response)
|
||||
has_appended_assistant = True
|
||||
|
||||
elif isinstance(response, StreamToolInputAvailable):
|
||||
accumulated_tool_calls.append(
|
||||
{
|
||||
"id": response.toolCallId,
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": response.toolName,
|
||||
"arguments": json.dumps(
|
||||
response.input or {}
|
||||
),
|
||||
},
|
||||
}
|
||||
)
|
||||
assistant_response.tool_calls = accumulated_tool_calls
|
||||
if not has_appended_assistant:
|
||||
session.messages.append(assistant_response)
|
||||
has_appended_assistant = True
|
||||
# Save before tool execution starts so the
|
||||
# pending tool call is visible on refresh /
|
||||
# other devices.
|
||||
try:
|
||||
await upsert_chat_session(
|
||||
session,
|
||||
existing_message_count=saved_msg_count,
|
||||
)
|
||||
saved_msg_count = len(session.messages)
|
||||
except Exception as save_err:
|
||||
logger.warning(
|
||||
"[SDK] [%s] Incremental save " "failed: %s",
|
||||
session_id[:12],
|
||||
save_err,
|
||||
)
|
||||
|
||||
elif isinstance(response, StreamToolOutputAvailable):
|
||||
session.messages.append(
|
||||
ChatMessage(
|
||||
role="tool",
|
||||
content=(
|
||||
response.output
|
||||
if isinstance(response.output, str)
|
||||
else str(response.output)
|
||||
),
|
||||
tool_call_id=response.toolCallId,
|
||||
)
|
||||
)
|
||||
has_tool_results = True
|
||||
# Save after tool completes so the result is
|
||||
# visible on refresh / other devices.
|
||||
try:
|
||||
await upsert_chat_session(
|
||||
session,
|
||||
existing_message_count=saved_msg_count,
|
||||
)
|
||||
saved_msg_count = len(session.messages)
|
||||
except Exception as save_err:
|
||||
logger.warning(
|
||||
"[SDK] [%s] Incremental save " "failed: %s",
|
||||
session_id[:12],
|
||||
save_err,
|
||||
)
|
||||
|
||||
elif isinstance(response, StreamFinish):
|
||||
stream_completed = True
|
||||
|
||||
except asyncio.CancelledError:
|
||||
# Task/generator was cancelled (e.g. client disconnect,
|
||||
# server shutdown). Log and let the safety-net / finally
|
||||
# blocks handle cleanup.
|
||||
logger.warning(
|
||||
"[SDK] [%s] Streaming loop cancelled "
|
||||
"(asyncio.CancelledError)",
|
||||
session_id[:12],
|
||||
)
|
||||
raise
|
||||
finally:
|
||||
# Cancel the pending __anext__ task to avoid a leaked
|
||||
# coroutine. This is safe even if the task already
|
||||
# completed.
|
||||
if pending_task is not None and not pending_task.done():
|
||||
pending_task.cancel()
|
||||
try:
|
||||
await pending_task
|
||||
except (asyncio.CancelledError, StopAsyncIteration):
|
||||
pass
|
||||
|
||||
# Safety net: if tools are still unresolved after the
|
||||
# streaming loop (e.g. StopAsyncIteration before ResultMessage,
|
||||
# or SDK not sending UserMessages for built-in tools), flush
|
||||
# them now so the frontend stops showing spinners.
|
||||
if adapter.has_unresolved_tool_calls:
|
||||
logger.warning(
|
||||
"[SDK] [%s] %d unresolved tool(s) after stream loop — "
|
||||
"flushing as safety net",
|
||||
session_id[:12],
|
||||
len(adapter.current_tool_calls)
|
||||
- len(adapter.resolved_tool_calls),
|
||||
)
|
||||
safety_responses: list[StreamBaseResponse] = []
|
||||
adapter._flush_unresolved_tool_calls(safety_responses)
|
||||
for response in safety_responses:
|
||||
if isinstance(
|
||||
response,
|
||||
(StreamToolInputAvailable, StreamToolOutputAvailable),
|
||||
):
|
||||
logger.info(
|
||||
"[SDK] Tool event: %s, tool=%s",
|
||||
"[SDK] [%s] Safety flush: %s, tool=%s",
|
||||
session_id[:12],
|
||||
type(response).__name__,
|
||||
getattr(response, "toolName", "N/A"),
|
||||
)
|
||||
|
||||
yield response
|
||||
|
||||
if isinstance(response, StreamTextDelta):
|
||||
delta = response.delta or ""
|
||||
# After tool results, start a new assistant
|
||||
# message for the post-tool text.
|
||||
if has_tool_results and has_appended_assistant:
|
||||
assistant_response = ChatMessage(
|
||||
role="assistant", content=delta
|
||||
)
|
||||
accumulated_tool_calls = []
|
||||
has_appended_assistant = False
|
||||
has_tool_results = False
|
||||
session.messages.append(assistant_response)
|
||||
has_appended_assistant = True
|
||||
else:
|
||||
assistant_response.content = (
|
||||
assistant_response.content or ""
|
||||
) + delta
|
||||
if not has_appended_assistant:
|
||||
session.messages.append(assistant_response)
|
||||
has_appended_assistant = True
|
||||
|
||||
elif isinstance(response, StreamToolInputAvailable):
|
||||
accumulated_tool_calls.append(
|
||||
{
|
||||
"id": response.toolCallId,
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": response.toolName,
|
||||
"arguments": json.dumps(response.input or {}),
|
||||
},
|
||||
}
|
||||
)
|
||||
assistant_response.tool_calls = accumulated_tool_calls
|
||||
if not has_appended_assistant:
|
||||
session.messages.append(assistant_response)
|
||||
has_appended_assistant = True
|
||||
|
||||
elif isinstance(response, StreamToolOutputAvailable):
|
||||
session.messages.append(
|
||||
ChatMessage(
|
||||
role="tool",
|
||||
content=(
|
||||
response.output
|
||||
if isinstance(response.output, str)
|
||||
else str(response.output)
|
||||
),
|
||||
tool_call_id=response.toolCallId,
|
||||
)
|
||||
)
|
||||
has_tool_results = True
|
||||
|
||||
elif isinstance(response, StreamFinish):
|
||||
stream_completed = True
|
||||
# If the stream ended without a ResultMessage (no
|
||||
# StreamFinish), the SDK CLI exited unexpectedly. Close
|
||||
# the open step and emit StreamFinish so the frontend
|
||||
# transitions to the "ready" state.
|
||||
if not stream_completed:
|
||||
logger.warning(
|
||||
"[SDK] [%s] Stream ended without ResultMessage "
|
||||
"(StopAsyncIteration) — emitting StreamFinish",
|
||||
session_id[:12],
|
||||
)
|
||||
if adapter.step_open:
|
||||
yield StreamFinishStep()
|
||||
adapter.step_open = False
|
||||
closing_responses: list[StreamBaseResponse] = []
|
||||
adapter._end_text_if_open(closing_responses)
|
||||
for r in closing_responses:
|
||||
yield r
|
||||
yield StreamFinish()
|
||||
stream_completed = True
|
||||
|
||||
if (
|
||||
assistant_response.content or assistant_response.tool_calls
|
||||
@@ -857,12 +938,20 @@ async def stream_chat_completion_sdk(
|
||||
)
|
||||
|
||||
await asyncio.shield(upsert_chat_session(session))
|
||||
logger.debug(
|
||||
f"[SDK] Session {session_id} saved with {len(session.messages)} messages"
|
||||
logger.info(
|
||||
"[SDK] [%s] Session saved with %d messages",
|
||||
session_id[:12],
|
||||
len(session.messages),
|
||||
)
|
||||
if not stream_completed:
|
||||
yield StreamFinish()
|
||||
|
||||
except asyncio.CancelledError:
|
||||
# Client disconnect / server shutdown — log but re-raise so
|
||||
# the framework can clean up. The finally block still runs
|
||||
# for transcript upload.
|
||||
logger.warning("[SDK] [%s] Session cancelled (CancelledError)", session_id[:12])
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"[SDK] Error: {e}", exc_info=True)
|
||||
try:
|
||||
|
||||
@@ -9,6 +9,7 @@ via a callback provided by the service layer. This avoids wasteful SDK polling
|
||||
and makes results survive page refreshes.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import itertools
|
||||
import json
|
||||
import logging
|
||||
@@ -44,6 +45,14 @@ _current_session: ContextVar[ChatSession | None] = ContextVar(
|
||||
_pending_tool_outputs: ContextVar[dict[str, list[str]]] = ContextVar(
|
||||
"pending_tool_outputs", default=None # type: ignore[arg-type]
|
||||
)
|
||||
# Event signaled whenever stash_pending_tool_output() adds a new entry.
|
||||
# Used by the streaming loop to wait for PostToolUse hooks to complete
|
||||
# instead of sleeping an arbitrary duration. The SDK fires hooks via
|
||||
# start_soon (fire-and-forget) so the next message can arrive before
|
||||
# the hook stashes its output — this event bridges that gap.
|
||||
_stash_event: ContextVar[asyncio.Event | None] = ContextVar(
|
||||
"_stash_event", default=None
|
||||
)
|
||||
|
||||
# Callback type for delegating long-running tools to the non-SDK infrastructure.
|
||||
# Args: (tool_name, arguments, session) → MCP-formatted response dict.
|
||||
@@ -76,6 +85,7 @@ def set_execution_context(
|
||||
_current_user_id.set(user_id)
|
||||
_current_session.set(session)
|
||||
_pending_tool_outputs.set({})
|
||||
_stash_event.set(asyncio.Event())
|
||||
_long_running_callback.set(long_running_callback)
|
||||
|
||||
|
||||
@@ -134,6 +144,43 @@ def stash_pending_tool_output(tool_name: str, output: Any) -> None:
|
||||
except (TypeError, ValueError):
|
||||
text = str(output)
|
||||
pending.setdefault(tool_name, []).append(text)
|
||||
# Signal any waiters that new output is available.
|
||||
event = _stash_event.get(None)
|
||||
if event is not None:
|
||||
event.set()
|
||||
|
||||
|
||||
async def wait_for_stash(timeout: float = 0.5) -> bool:
|
||||
"""Wait for a PostToolUse hook to stash tool output.
|
||||
|
||||
The SDK fires PostToolUse hooks asynchronously via ``start_soon()`` —
|
||||
the next message (AssistantMessage/ResultMessage) can arrive before the
|
||||
hook completes and stashes its output. This function bridges that gap
|
||||
by waiting on the ``_stash_event``, which is signaled by
|
||||
:func:`stash_pending_tool_output`.
|
||||
|
||||
After the event fires, callers should ``await asyncio.sleep(0)`` to
|
||||
give any remaining concurrent hooks a chance to complete.
|
||||
|
||||
Returns ``True`` if a stash signal was received, ``False`` on timeout.
|
||||
The timeout is a safety net — normally the stash happens within
|
||||
microseconds of yielding to the event loop.
|
||||
"""
|
||||
event = _stash_event.get(None)
|
||||
if event is None:
|
||||
return False
|
||||
# Fast path: hook already completed before we got here.
|
||||
if event.is_set():
|
||||
event.clear()
|
||||
return True
|
||||
# Slow path: wait for the hook to signal.
|
||||
try:
|
||||
async with asyncio.timeout(timeout):
|
||||
await event.wait()
|
||||
event.clear()
|
||||
return True
|
||||
except TimeoutError:
|
||||
return False
|
||||
|
||||
|
||||
async def _execute_tool_sync(
|
||||
|
||||
@@ -52,6 +52,7 @@ from .response_model import (
|
||||
StreamFinish,
|
||||
StreamFinishStep,
|
||||
StreamHeartbeat,
|
||||
StreamLongRunningStart,
|
||||
StreamStart,
|
||||
StreamStartStep,
|
||||
StreamTextDelta,
|
||||
@@ -63,12 +64,7 @@ from .response_model import (
|
||||
StreamUsage,
|
||||
)
|
||||
from .tools import execute_tool, get_tool, tools
|
||||
from .tools.models import (
|
||||
ErrorResponse,
|
||||
OperationInProgressResponse,
|
||||
OperationPendingResponse,
|
||||
OperationStartedResponse,
|
||||
)
|
||||
from .tools.models import ErrorResponse
|
||||
from .tracking import track_user_message
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -1402,16 +1398,15 @@ async def _yield_tool_call(
|
||||
"""
|
||||
Yield a tool call and its execution result.
|
||||
|
||||
For tools marked with `is_long_running=True` (like agent generation), spawns a
|
||||
background task so the operation survives SSE disconnections. For other tools,
|
||||
yields heartbeat events every 15 seconds to keep the SSE connection alive.
|
||||
Executes tools synchronously and yields heartbeat events every 15 seconds to
|
||||
keep the SSE connection alive during execution. The is_long_running property
|
||||
is only used by the frontend to display UI feedback during long operations.
|
||||
|
||||
Raises:
|
||||
orjson.JSONDecodeError: If tool call arguments cannot be parsed as JSON
|
||||
KeyError: If expected tool call fields are missing
|
||||
TypeError: If tool call structure is invalid
|
||||
"""
|
||||
import uuid as uuid_module
|
||||
|
||||
tool_name = tool_calls[yield_idx]["function"]["name"]
|
||||
tool_call_id = tool_calls[yield_idx]["id"]
|
||||
@@ -1429,163 +1424,17 @@ async def _yield_tool_call(
|
||||
input=arguments,
|
||||
)
|
||||
|
||||
# Check if this tool is long-running (survives SSE disconnection)
|
||||
# Notify frontend if this is a long-running tool (e.g., agent generation)
|
||||
tool = get_tool(tool_name)
|
||||
if tool and tool.is_long_running:
|
||||
# Atomic check-and-set: returns False if operation already running (lost race)
|
||||
if not await _mark_operation_started(tool_call_id):
|
||||
logger.info(
|
||||
f"Tool call {tool_call_id} already in progress, returning status"
|
||||
)
|
||||
# Build dynamic message based on tool name
|
||||
if tool_name == "create_agent":
|
||||
in_progress_msg = "Agent creation already in progress. Please wait..."
|
||||
elif tool_name == "edit_agent":
|
||||
in_progress_msg = "Agent edit already in progress. Please wait..."
|
||||
else:
|
||||
in_progress_msg = f"{tool_name} already in progress. Please wait..."
|
||||
|
||||
yield StreamToolOutputAvailable(
|
||||
toolCallId=tool_call_id,
|
||||
toolName=tool_name,
|
||||
output=OperationInProgressResponse(
|
||||
message=in_progress_msg,
|
||||
tool_call_id=tool_call_id,
|
||||
).model_dump_json(),
|
||||
success=True,
|
||||
)
|
||||
return
|
||||
|
||||
# Generate operation ID and task ID
|
||||
operation_id = str(uuid_module.uuid4())
|
||||
task_id = str(uuid_module.uuid4())
|
||||
|
||||
# Build a user-friendly message based on tool and arguments
|
||||
if tool_name == "create_agent":
|
||||
agent_desc = arguments.get("description", "")
|
||||
# Truncate long descriptions for the message
|
||||
desc_preview = (
|
||||
(agent_desc[:100] + "...") if len(agent_desc) > 100 else agent_desc
|
||||
)
|
||||
pending_msg = (
|
||||
f"Creating your agent: {desc_preview}"
|
||||
if desc_preview
|
||||
else "Creating agent... This may take a few minutes."
|
||||
)
|
||||
started_msg = (
|
||||
"Agent creation started. You can close this tab - "
|
||||
"check your library in a few minutes."
|
||||
)
|
||||
elif tool_name == "edit_agent":
|
||||
changes = arguments.get("changes", "")
|
||||
changes_preview = (changes[:100] + "...") if len(changes) > 100 else changes
|
||||
pending_msg = (
|
||||
f"Editing agent: {changes_preview}"
|
||||
if changes_preview
|
||||
else "Editing agent... This may take a few minutes."
|
||||
)
|
||||
started_msg = (
|
||||
"Agent edit started. You can close this tab - "
|
||||
"check your library in a few minutes."
|
||||
)
|
||||
else:
|
||||
pending_msg = f"Running {tool_name}... This may take a few minutes."
|
||||
started_msg = (
|
||||
f"{tool_name} started. You can close this tab - "
|
||||
"check back in a few minutes."
|
||||
)
|
||||
|
||||
# Track appended message for rollback on failure
|
||||
pending_message: ChatMessage | None = None
|
||||
|
||||
# Wrap session save and task creation in try-except to release lock on failure
|
||||
try:
|
||||
# Create task in stream registry for SSE reconnection support
|
||||
await stream_registry.create_task(
|
||||
task_id=task_id,
|
||||
session_id=session.session_id,
|
||||
user_id=session.user_id,
|
||||
tool_call_id=tool_call_id,
|
||||
tool_name=tool_name,
|
||||
operation_id=operation_id,
|
||||
)
|
||||
|
||||
# Attach tool_call and save pending result — lock serialises
|
||||
# concurrent session mutations during parallel execution.
|
||||
async def _save_pending() -> None:
|
||||
nonlocal pending_message
|
||||
session.add_tool_call_to_current_turn(tool_calls[yield_idx])
|
||||
pending_message = ChatMessage(
|
||||
role="tool",
|
||||
content=OperationPendingResponse(
|
||||
message=pending_msg,
|
||||
operation_id=operation_id,
|
||||
tool_name=tool_name,
|
||||
).model_dump_json(),
|
||||
tool_call_id=tool_call_id,
|
||||
)
|
||||
session.messages.append(pending_message)
|
||||
await upsert_chat_session(session)
|
||||
|
||||
await _with_optional_lock(session_lock, _save_pending)
|
||||
logger.info(
|
||||
f"Saved pending operation {operation_id} (task_id={task_id}) "
|
||||
f"for tool {tool_name} in session {session.session_id}"
|
||||
)
|
||||
|
||||
# Store task reference in module-level set to prevent GC before completion
|
||||
bg_task = asyncio.create_task(
|
||||
_execute_long_running_tool_with_streaming(
|
||||
tool_name=tool_name,
|
||||
parameters=arguments,
|
||||
tool_call_id=tool_call_id,
|
||||
operation_id=operation_id,
|
||||
task_id=task_id,
|
||||
session_id=session.session_id,
|
||||
user_id=session.user_id,
|
||||
)
|
||||
)
|
||||
_background_tasks.add(bg_task)
|
||||
bg_task.add_done_callback(_background_tasks.discard)
|
||||
|
||||
# Associate the asyncio task with the stream registry task
|
||||
await stream_registry.set_task_asyncio_task(task_id, bg_task)
|
||||
except Exception as e:
|
||||
# Roll back appended messages — use identity-based removal so
|
||||
# it works even when other parallel tools have appended after us.
|
||||
async def _rollback() -> None:
|
||||
if pending_message and pending_message in session.messages:
|
||||
session.messages.remove(pending_message)
|
||||
|
||||
await _with_optional_lock(session_lock, _rollback)
|
||||
|
||||
# Release the Redis lock since the background task won't be spawned
|
||||
await _mark_operation_completed(tool_call_id)
|
||||
# Mark stream registry task as failed if it was created
|
||||
try:
|
||||
await stream_registry.mark_task_completed(task_id, status="failed")
|
||||
except Exception as mark_err:
|
||||
logger.warning(f"Failed to mark task {task_id} as failed: {mark_err}")
|
||||
logger.error(
|
||||
f"Failed to setup long-running tool {tool_name}: {e}", exc_info=True
|
||||
)
|
||||
raise
|
||||
|
||||
# Return immediately - don't wait for completion
|
||||
yield StreamToolOutputAvailable(
|
||||
toolCallId=tool_call_id,
|
||||
toolName=tool_name,
|
||||
output=OperationStartedResponse(
|
||||
message=started_msg,
|
||||
operation_id=operation_id,
|
||||
tool_name=tool_name,
|
||||
task_id=task_id, # Include task_id for SSE reconnection
|
||||
).model_dump_json(),
|
||||
success=True,
|
||||
yield StreamLongRunningStart(
|
||||
data={
|
||||
"toolCallId": tool_call_id,
|
||||
"toolName": tool_name,
|
||||
}
|
||||
)
|
||||
return
|
||||
|
||||
# Normal flow: Run tool execution in background task with heartbeats
|
||||
# Run tool execution synchronously with heartbeats
|
||||
tool_task = asyncio.create_task(
|
||||
execute_tool(
|
||||
tool_name=tool_name,
|
||||
|
||||
@@ -540,21 +540,15 @@ async def decompose_goal(
|
||||
async def generate_agent(
|
||||
instructions: DecompositionResult | dict[str, Any],
|
||||
library_agents: list[AgentSummary] | list[dict[str, Any]] | None = None,
|
||||
operation_id: str | None = None,
|
||||
task_id: str | None = None,
|
||||
) -> dict[str, Any] | None:
|
||||
"""Generate agent JSON from instructions.
|
||||
|
||||
Args:
|
||||
instructions: Structured instructions from decompose_goal
|
||||
library_agents: User's library agents available for sub-agent composition
|
||||
operation_id: Operation ID for async processing (enables Redis Streams
|
||||
completion notification)
|
||||
task_id: Task ID for async processing (enables Redis Streams persistence
|
||||
and SSE delivery)
|
||||
|
||||
Returns:
|
||||
Agent JSON dict, {"status": "accepted"} for async, error dict {"type": "error", ...}, or None on error
|
||||
Agent JSON dict, error dict {"type": "error", ...}, or None on error
|
||||
|
||||
Raises:
|
||||
AgentGeneratorNotConfiguredError: If the external service is not configured.
|
||||
@@ -562,13 +556,9 @@ async def generate_agent(
|
||||
_check_service_configured()
|
||||
logger.info("Calling external Agent Generator service for generate_agent")
|
||||
result = await generate_agent_external(
|
||||
dict(instructions), _to_dict_list(library_agents), operation_id, task_id
|
||||
dict(instructions), _to_dict_list(library_agents)
|
||||
)
|
||||
|
||||
# Don't modify async response
|
||||
if result and result.get("status") == "accepted":
|
||||
return result
|
||||
|
||||
if result:
|
||||
if isinstance(result, dict) and result.get("type") == "error":
|
||||
return result
|
||||
@@ -759,8 +749,6 @@ async def generate_agent_patch(
|
||||
update_request: str,
|
||||
current_agent: dict[str, Any],
|
||||
library_agents: list[AgentSummary] | None = None,
|
||||
operation_id: str | None = None,
|
||||
task_id: str | None = None,
|
||||
) -> dict[str, Any] | None:
|
||||
"""Update an existing agent using natural language.
|
||||
|
||||
@@ -773,12 +761,10 @@ async def generate_agent_patch(
|
||||
update_request: Natural language description of changes
|
||||
current_agent: Current agent JSON
|
||||
library_agents: User's library agents available for sub-agent composition
|
||||
operation_id: Operation ID for async processing (enables Redis Streams callback)
|
||||
task_id: Task ID for async processing (enables Redis Streams callback)
|
||||
|
||||
Returns:
|
||||
Updated agent JSON, clarifying questions dict {"type": "clarifying_questions", ...},
|
||||
{"status": "accepted"} for async, error dict {"type": "error", ...}, or None on error
|
||||
error dict {"type": "error", ...}, or None on error
|
||||
|
||||
Raises:
|
||||
AgentGeneratorNotConfiguredError: If the external service is not configured.
|
||||
@@ -789,8 +775,6 @@ async def generate_agent_patch(
|
||||
update_request,
|
||||
current_agent,
|
||||
_to_dict_list(library_agents),
|
||||
operation_id,
|
||||
task_id,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -101,8 +101,6 @@ async def decompose_goal_dummy(
|
||||
async def generate_agent_dummy(
|
||||
instructions: dict[str, Any],
|
||||
library_agents: list[dict[str, Any]] | None = None,
|
||||
operation_id: str | None = None,
|
||||
task_id: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Return dummy agent JSON after a simulated delay."""
|
||||
logger.info("Using dummy agent generator for generate_agent (30s delay)")
|
||||
@@ -114,8 +112,6 @@ async def generate_agent_patch_dummy(
|
||||
update_request: str,
|
||||
current_agent: dict[str, Any],
|
||||
library_agents: list[dict[str, Any]] | None = None,
|
||||
operation_id: str | None = None,
|
||||
task_id: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Return dummy patched agent (returns the current agent with updated description)."""
|
||||
logger.info("Using dummy agent generator for generate_agent_patch")
|
||||
|
||||
@@ -242,24 +242,18 @@ async def decompose_goal_external(
|
||||
async def generate_agent_external(
|
||||
instructions: dict[str, Any],
|
||||
library_agents: list[dict[str, Any]] | None = None,
|
||||
operation_id: str | None = None,
|
||||
task_id: str | None = None,
|
||||
) -> dict[str, Any] | None:
|
||||
"""Call the external service to generate an agent from instructions.
|
||||
|
||||
Args:
|
||||
instructions: Structured instructions from decompose_goal
|
||||
library_agents: User's library agents available for sub-agent composition
|
||||
operation_id: Operation ID for async processing (enables Redis Streams callback)
|
||||
task_id: Task ID for async processing (enables Redis Streams callback)
|
||||
|
||||
Returns:
|
||||
Agent JSON dict, {"status": "accepted"} for async, or error dict {"type": "error", ...} on error
|
||||
Agent JSON dict or error dict {"type": "error", ...} on error
|
||||
"""
|
||||
if _is_dummy_mode():
|
||||
return await generate_agent_dummy(
|
||||
instructions, library_agents, operation_id, task_id
|
||||
)
|
||||
return await generate_agent_dummy(instructions, library_agents)
|
||||
|
||||
client = _get_client()
|
||||
|
||||
@@ -267,25 +261,9 @@ async def generate_agent_external(
|
||||
payload: dict[str, Any] = {"instructions": instructions}
|
||||
if library_agents:
|
||||
payload["library_agents"] = library_agents
|
||||
if operation_id and task_id:
|
||||
payload["operation_id"] = operation_id
|
||||
payload["task_id"] = task_id
|
||||
|
||||
try:
|
||||
response = await client.post("/api/generate-agent", json=payload)
|
||||
|
||||
# Handle 202 Accepted for async processing
|
||||
if response.status_code == 202:
|
||||
logger.info(
|
||||
f"Agent Generator accepted async request "
|
||||
f"(operation_id={operation_id}, task_id={task_id})"
|
||||
)
|
||||
return {
|
||||
"status": "accepted",
|
||||
"operation_id": operation_id,
|
||||
"task_id": task_id,
|
||||
}
|
||||
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
@@ -317,8 +295,6 @@ async def generate_agent_patch_external(
|
||||
update_request: str,
|
||||
current_agent: dict[str, Any],
|
||||
library_agents: list[dict[str, Any]] | None = None,
|
||||
operation_id: str | None = None,
|
||||
task_id: str | None = None,
|
||||
) -> dict[str, Any] | None:
|
||||
"""Call the external service to generate a patch for an existing agent.
|
||||
|
||||
@@ -326,15 +302,13 @@ async def generate_agent_patch_external(
|
||||
update_request: Natural language description of changes
|
||||
current_agent: Current agent JSON
|
||||
library_agents: User's library agents available for sub-agent composition
|
||||
operation_id: Operation ID for async processing (enables Redis Streams callback)
|
||||
task_id: Task ID for async processing (enables Redis Streams callback)
|
||||
|
||||
Returns:
|
||||
Updated agent JSON, clarifying questions dict, {"status": "accepted"} for async, or error dict on error
|
||||
Updated agent JSON, clarifying questions dict, or error dict on error
|
||||
"""
|
||||
if _is_dummy_mode():
|
||||
return await generate_agent_patch_dummy(
|
||||
update_request, current_agent, library_agents, operation_id, task_id
|
||||
update_request, current_agent, library_agents
|
||||
)
|
||||
|
||||
client = _get_client()
|
||||
@@ -346,25 +320,9 @@ async def generate_agent_patch_external(
|
||||
}
|
||||
if library_agents:
|
||||
payload["library_agents"] = library_agents
|
||||
if operation_id and task_id:
|
||||
payload["operation_id"] = operation_id
|
||||
payload["task_id"] = task_id
|
||||
|
||||
try:
|
||||
response = await client.post("/api/update-agent", json=payload)
|
||||
|
||||
# Handle 202 Accepted for async processing
|
||||
if response.status_code == 202:
|
||||
logger.info(
|
||||
f"Agent Generator accepted async update request "
|
||||
f"(operation_id={operation_id}, task_id={task_id})"
|
||||
)
|
||||
return {
|
||||
"status": "accepted",
|
||||
"operation_id": operation_id,
|
||||
"task_id": task_id,
|
||||
}
|
||||
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
|
||||
@@ -38,12 +38,7 @@ class BaseTool:
|
||||
|
||||
@property
|
||||
def is_long_running(self) -> bool:
|
||||
"""Whether this tool is long-running and should execute in background.
|
||||
|
||||
Long-running tools (like agent generation) are executed via background
|
||||
tasks to survive SSE disconnections. The result is persisted to chat
|
||||
history and visible when the user refreshes.
|
||||
"""
|
||||
"""Whether this tool takes a long time to execute (triggers long-running UI)."""
|
||||
return False
|
||||
|
||||
def as_openai_tool(self) -> ChatCompletionToolParam:
|
||||
|
||||
@@ -18,7 +18,6 @@ from .base import BaseTool
|
||||
from .models import (
|
||||
AgentPreviewResponse,
|
||||
AgentSavedResponse,
|
||||
AsyncProcessingResponse,
|
||||
ClarificationNeededResponse,
|
||||
ClarifyingQuestion,
|
||||
ErrorResponse,
|
||||
@@ -49,6 +48,7 @@ class CreateAgentTool(BaseTool):
|
||||
|
||||
@property
|
||||
def is_long_running(self) -> bool:
|
||||
"""Agent generation takes several minutes."""
|
||||
return True
|
||||
|
||||
@property
|
||||
@@ -100,10 +100,6 @@ class CreateAgentTool(BaseTool):
|
||||
save = kwargs.get("save", True)
|
||||
session_id = session.session_id if session else None
|
||||
|
||||
# Extract async processing params (passed by long-running tool handler)
|
||||
operation_id = kwargs.get("_operation_id")
|
||||
task_id = kwargs.get("_task_id")
|
||||
|
||||
if not description:
|
||||
return ErrorResponse(
|
||||
message="Please provide a description of what the agent should do.",
|
||||
@@ -230,8 +226,6 @@ class CreateAgentTool(BaseTool):
|
||||
agent_json = await generate_agent(
|
||||
decomposition_result,
|
||||
library_agents,
|
||||
operation_id=operation_id,
|
||||
task_id=task_id,
|
||||
)
|
||||
except AgentGeneratorNotConfiguredError:
|
||||
return ErrorResponse(
|
||||
@@ -276,19 +270,6 @@ class CreateAgentTool(BaseTool):
|
||||
session_id=session_id,
|
||||
)
|
||||
|
||||
# Check if Agent Generator accepted for async processing
|
||||
if agent_json.get("status") == "accepted":
|
||||
logger.info(
|
||||
f"Agent generation delegated to async processing "
|
||||
f"(operation_id={operation_id}, task_id={task_id})"
|
||||
)
|
||||
return AsyncProcessingResponse(
|
||||
message="Agent generation started. You'll be notified when it's complete.",
|
||||
operation_id=operation_id,
|
||||
task_id=task_id,
|
||||
session_id=session_id,
|
||||
)
|
||||
|
||||
agent_name = agent_json.get("name", "Generated Agent")
|
||||
agent_description = agent_json.get("description", "")
|
||||
node_count = len(agent_json.get("nodes", []))
|
||||
|
||||
@@ -48,6 +48,7 @@ class CustomizeAgentTool(BaseTool):
|
||||
|
||||
@property
|
||||
def is_long_running(self) -> bool:
|
||||
"""Agent customization takes several minutes."""
|
||||
return True
|
||||
|
||||
@property
|
||||
|
||||
@@ -17,7 +17,6 @@ from .base import BaseTool
|
||||
from .models import (
|
||||
AgentPreviewResponse,
|
||||
AgentSavedResponse,
|
||||
AsyncProcessingResponse,
|
||||
ClarificationNeededResponse,
|
||||
ClarifyingQuestion,
|
||||
ErrorResponse,
|
||||
@@ -47,6 +46,7 @@ class EditAgentTool(BaseTool):
|
||||
|
||||
@property
|
||||
def is_long_running(self) -> bool:
|
||||
"""Agent editing takes several minutes."""
|
||||
return True
|
||||
|
||||
@property
|
||||
@@ -105,10 +105,6 @@ class EditAgentTool(BaseTool):
|
||||
save = kwargs.get("save", True)
|
||||
session_id = session.session_id if session else None
|
||||
|
||||
# Extract async processing params (passed by long-running tool handler)
|
||||
operation_id = kwargs.get("_operation_id")
|
||||
task_id = kwargs.get("_task_id")
|
||||
|
||||
if not agent_id:
|
||||
return ErrorResponse(
|
||||
message="Please provide the agent ID to edit.",
|
||||
@@ -157,8 +153,6 @@ class EditAgentTool(BaseTool):
|
||||
update_request,
|
||||
current_agent,
|
||||
library_agents,
|
||||
operation_id=operation_id,
|
||||
task_id=task_id,
|
||||
)
|
||||
except AgentGeneratorNotConfiguredError:
|
||||
return ErrorResponse(
|
||||
@@ -178,19 +172,6 @@ class EditAgentTool(BaseTool):
|
||||
session_id=session_id,
|
||||
)
|
||||
|
||||
# Check if Agent Generator accepted for async processing
|
||||
if result.get("status") == "accepted":
|
||||
logger.info(
|
||||
f"Agent edit delegated to async processing "
|
||||
f"(operation_id={operation_id}, task_id={task_id})"
|
||||
)
|
||||
return AsyncProcessingResponse(
|
||||
message="Agent edit started. You'll be notified when it's complete.",
|
||||
operation_id=operation_id,
|
||||
task_id=task_id,
|
||||
session_id=session_id,
|
||||
)
|
||||
|
||||
# Check if the result is an error from the external service
|
||||
if isinstance(result, dict) and result.get("type") == "error":
|
||||
error_msg = result.get("error", "Unknown error")
|
||||
|
||||
@@ -459,23 +459,6 @@ class OperationInProgressResponse(ToolResponseBase):
|
||||
tool_call_id: str
|
||||
|
||||
|
||||
class AsyncProcessingResponse(ToolResponseBase):
|
||||
"""Response when an operation has been delegated to async processing.
|
||||
|
||||
This is returned by tools when the external service accepts the request
|
||||
for async processing (HTTP 202 Accepted). The Redis Streams completion
|
||||
consumer will handle the result when the external service completes.
|
||||
|
||||
The status field is specifically "accepted" to allow the long-running tool
|
||||
handler to detect this response and skip LLM continuation.
|
||||
"""
|
||||
|
||||
type: ResponseType = ResponseType.OPERATION_STARTED
|
||||
status: str = "accepted" # Must be "accepted" for detection
|
||||
operation_id: str | None = None
|
||||
task_id: str | None = None
|
||||
|
||||
|
||||
class WebFetchResponse(ToolResponseBase):
|
||||
"""Response for web_fetch tool."""
|
||||
|
||||
|
||||
14
autogpt_platform/backend/poetry.lock
generated
14
autogpt_platform/backend/poetry.lock
generated
@@ -899,17 +899,17 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "claude-agent-sdk"
|
||||
version = "0.1.35"
|
||||
version = "0.1.39"
|
||||
description = "Python SDK for Claude Code"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "claude_agent_sdk-0.1.35-py3-none-macosx_11_0_arm64.whl", hash = "sha256:df67f4deade77b16a9678b3a626c176498e40417f33b04beda9628287f375591"},
|
||||
{file = "claude_agent_sdk-0.1.35-py3-none-manylinux_2_17_aarch64.whl", hash = "sha256:14963944f55ded7c8ed518feebfa5b4284aa6dd8d81aeff2e5b21a962ce65097"},
|
||||
{file = "claude_agent_sdk-0.1.35-py3-none-manylinux_2_17_x86_64.whl", hash = "sha256:84344dcc535d179c1fc8a11c6f34c37c3b583447bdf09d869effb26514fd7a65"},
|
||||
{file = "claude_agent_sdk-0.1.35-py3-none-win_amd64.whl", hash = "sha256:1b3d54b47448c93f6f372acd4d1757f047c3c1e8ef5804be7a1e3e53e2c79a5f"},
|
||||
{file = "claude_agent_sdk-0.1.35.tar.gz", hash = "sha256:0f98e2b3c71ca85abfc042e7a35c648df88e87fda41c52e6779ef7b038dcbb52"},
|
||||
{file = "claude_agent_sdk-0.1.39-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ed6a79781f545b761b9fe467bc5ae213a103c9d3f0fe7a9dad3c01790ed58fa"},
|
||||
{file = "claude_agent_sdk-0.1.39-py3-none-manylinux_2_17_aarch64.whl", hash = "sha256:0c03b5a3772eaec42e29ea39240c7d24b760358082f2e36336db9e71dde3dda4"},
|
||||
{file = "claude_agent_sdk-0.1.39-py3-none-manylinux_2_17_x86_64.whl", hash = "sha256:d2665c9e87b6ffece590bcdd6eb9def47cde4809b0d2f66e0a61a719189be7c9"},
|
||||
{file = "claude_agent_sdk-0.1.39-py3-none-win_amd64.whl", hash = "sha256:d03324daf7076be79d2dd05944559aabf4cc11c98d3a574b992a442a7c7a26d6"},
|
||||
{file = "claude_agent_sdk-0.1.39.tar.gz", hash = "sha256:dcf0ebd5a638c9a7d9f3af7640932a9212b2705b7056e4f08bd3968a865b4268"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -8530,4 +8530,4 @@ cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and pyt
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.10,<3.14"
|
||||
content-hash = "55e095de555482f0fe47de7695f390fe93e7bcf739b31c391b2e5e3c3d938ae3"
|
||||
content-hash = "3ef62836d8321b9a3b8e897dade8dc6ca9022fd9468c53f384b0871b521ab343"
|
||||
|
||||
@@ -16,7 +16,7 @@ anthropic = "^0.79.0"
|
||||
apscheduler = "^3.11.1"
|
||||
autogpt-libs = { path = "../autogpt_libs", develop = true }
|
||||
bleach = { extras = ["css"], version = "^6.2.0" }
|
||||
claude-agent-sdk = "^0.1.0"
|
||||
claude-agent-sdk = "^0.1.39" # see copilot/sdk/sdk_compat_test.py for capability checks
|
||||
click = "^8.2.0"
|
||||
cryptography = "^46.0"
|
||||
discord-py = "^2.5.2"
|
||||
|
||||
@@ -109,7 +109,7 @@ class TestGenerateAgent:
|
||||
instructions = {"type": "instructions", "steps": ["Step 1"]}
|
||||
result = await core.generate_agent(instructions)
|
||||
|
||||
mock_external.assert_called_once_with(instructions, None, None, None)
|
||||
mock_external.assert_called_once_with(instructions, None)
|
||||
assert result is not None
|
||||
assert result["name"] == "Test Agent"
|
||||
assert "id" in result
|
||||
@@ -173,9 +173,7 @@ class TestGenerateAgentPatch:
|
||||
current_agent = {"nodes": [], "links": []}
|
||||
result = await core.generate_agent_patch("Add a node", current_agent)
|
||||
|
||||
mock_external.assert_called_once_with(
|
||||
"Add a node", current_agent, None, None, None
|
||||
)
|
||||
mock_external.assert_called_once_with("Add a node", current_agent, None)
|
||||
assert result == expected_result
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -34,7 +34,8 @@ export const ChatContainer = ({
|
||||
onStop,
|
||||
headerSlot,
|
||||
}: ChatContainerProps) => {
|
||||
const isBusy = status === "streaming" || !!isReconnecting;
|
||||
const isBusy =
|
||||
status === "streaming" || status === "submitted" || !!isReconnecting;
|
||||
const inputLayoutId = "copilot-2-chat-input";
|
||||
|
||||
return (
|
||||
|
||||
@@ -13,6 +13,7 @@ import { LoadingSpinner } from "@/components/atoms/LoadingSpinner/LoadingSpinner
|
||||
import { toast } from "@/components/molecules/Toast/use-toast";
|
||||
import { ToolUIPart, UIDataTypes, UIMessage, UITools } from "ai";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { ToolWrapper } from "../ToolWrapper/ToolWrapper";
|
||||
import { CreateAgentTool } from "../../tools/CreateAgent/CreateAgent";
|
||||
import { EditAgentTool } from "../../tools/EditAgent/EditAgent";
|
||||
import {
|
||||
@@ -208,86 +209,110 @@ export const ChatMessagesContainer = ({
|
||||
);
|
||||
case "tool-find_block":
|
||||
return (
|
||||
<FindBlocksTool
|
||||
<ToolWrapper
|
||||
key={`${message.id}-${i}`}
|
||||
part={part as ToolUIPart}
|
||||
/>
|
||||
>
|
||||
<FindBlocksTool part={part as ToolUIPart} />
|
||||
</ToolWrapper>
|
||||
);
|
||||
case "tool-find_agent":
|
||||
case "tool-find_library_agent":
|
||||
return (
|
||||
<FindAgentsTool
|
||||
<ToolWrapper
|
||||
key={`${message.id}-${i}`}
|
||||
part={part as ToolUIPart}
|
||||
/>
|
||||
>
|
||||
<FindAgentsTool part={part as ToolUIPart} />
|
||||
</ToolWrapper>
|
||||
);
|
||||
case "tool-search_docs":
|
||||
case "tool-get_doc_page":
|
||||
return (
|
||||
<SearchDocsTool
|
||||
<ToolWrapper
|
||||
key={`${message.id}-${i}`}
|
||||
part={part as ToolUIPart}
|
||||
/>
|
||||
>
|
||||
<SearchDocsTool part={part as ToolUIPart} />
|
||||
</ToolWrapper>
|
||||
);
|
||||
case "tool-run_block":
|
||||
return (
|
||||
<RunBlockTool
|
||||
<ToolWrapper
|
||||
key={`${message.id}-${i}`}
|
||||
part={part as ToolUIPart}
|
||||
/>
|
||||
>
|
||||
<RunBlockTool part={part as ToolUIPart} />
|
||||
</ToolWrapper>
|
||||
);
|
||||
case "tool-run_agent":
|
||||
case "tool-schedule_agent":
|
||||
return (
|
||||
<RunAgentTool
|
||||
<ToolWrapper
|
||||
key={`${message.id}-${i}`}
|
||||
part={part as ToolUIPart}
|
||||
/>
|
||||
>
|
||||
<RunAgentTool part={part as ToolUIPart} />
|
||||
</ToolWrapper>
|
||||
);
|
||||
case "tool-create_agent":
|
||||
return (
|
||||
<CreateAgentTool
|
||||
<ToolWrapper
|
||||
key={`${message.id}-${i}`}
|
||||
part={part as ToolUIPart}
|
||||
/>
|
||||
>
|
||||
<CreateAgentTool part={part as ToolUIPart} />
|
||||
</ToolWrapper>
|
||||
);
|
||||
case "tool-edit_agent":
|
||||
return (
|
||||
<EditAgentTool
|
||||
<ToolWrapper
|
||||
key={`${message.id}-${i}`}
|
||||
part={part as ToolUIPart}
|
||||
/>
|
||||
>
|
||||
<EditAgentTool part={part as ToolUIPart} />
|
||||
</ToolWrapper>
|
||||
);
|
||||
case "tool-view_agent_output":
|
||||
return (
|
||||
<ViewAgentOutputTool
|
||||
<ToolWrapper
|
||||
key={`${message.id}-${i}`}
|
||||
part={part as ToolUIPart}
|
||||
/>
|
||||
>
|
||||
<ViewAgentOutputTool part={part as ToolUIPart} />
|
||||
</ToolWrapper>
|
||||
);
|
||||
case "tool-search_feature_requests":
|
||||
return (
|
||||
<SearchFeatureRequestsTool
|
||||
<ToolWrapper
|
||||
key={`${message.id}-${i}`}
|
||||
part={part as ToolUIPart}
|
||||
/>
|
||||
>
|
||||
<SearchFeatureRequestsTool
|
||||
part={part as ToolUIPart}
|
||||
/>
|
||||
</ToolWrapper>
|
||||
);
|
||||
case "tool-create_feature_request":
|
||||
return (
|
||||
<CreateFeatureRequestTool
|
||||
<ToolWrapper
|
||||
key={`${message.id}-${i}`}
|
||||
part={part as ToolUIPart}
|
||||
/>
|
||||
>
|
||||
<CreateFeatureRequestTool part={part as ToolUIPart} />
|
||||
</ToolWrapper>
|
||||
);
|
||||
default:
|
||||
// Render a generic tool indicator for SDK built-in
|
||||
// tools (Read, Glob, Grep, etc.) or any unrecognized tool
|
||||
if (part.type.startsWith("tool-")) {
|
||||
return (
|
||||
<GenericTool
|
||||
<ToolWrapper
|
||||
key={`${message.id}-${i}`}
|
||||
part={part as ToolUIPart}
|
||||
/>
|
||||
>
|
||||
<GenericTool part={part as ToolUIPart} />
|
||||
</ToolWrapper>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { PlusCircleIcon } from "@phosphor-icons/react";
|
||||
import { ContentGrid, ContentHint } from "../ToolAccordion/AccordionContent";
|
||||
import { ToolAccordion } from "../ToolAccordion/ToolAccordion";
|
||||
import { MiniGame } from "../../tools/CreateAgent/components/MiniGame/MiniGame";
|
||||
|
||||
interface Props {
|
||||
/** Whether the tool is currently streaming/executing */
|
||||
isStreaming: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays UI feedback while a long-running tool executes.
|
||||
* Automatically shown for tools marked as is_long_running=True in the backend.
|
||||
*/
|
||||
export function LongRunningToolDisplay({ isStreaming }: Props) {
|
||||
if (!isStreaming) return null;
|
||||
|
||||
return (
|
||||
<ToolAccordion
|
||||
icon={<PlusCircleIcon size={32} weight="light" />}
|
||||
title="This may take a few minutes. Play while you wait."
|
||||
defaultExpanded={true}
|
||||
>
|
||||
<ContentGrid>
|
||||
<MiniGame />
|
||||
<ContentHint>
|
||||
This could take a few minutes — play while you wait!
|
||||
</ContentHint>
|
||||
</ContentGrid>
|
||||
</ToolAccordion>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import type { ToolUIPart } from "ai";
|
||||
import { LongRunningToolDisplay } from "../LongRunningToolDisplay/LongRunningToolDisplay";
|
||||
|
||||
interface Props {
|
||||
part: ToolUIPart;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for all tool components. Automatically shows UI feedback
|
||||
* for long-running tools by detecting the isLongRunning flag on the tool part.
|
||||
*/
|
||||
export function ToolWrapper({ part, children }: Props) {
|
||||
const isStreaming =
|
||||
part.state === "input-streaming" || part.state === "input-available";
|
||||
|
||||
// Extract tool name from type (format: "tool-{name}")
|
||||
const toolName = part.type.startsWith("tool-")
|
||||
? part.type.substring(5)
|
||||
: "unknown";
|
||||
|
||||
// Check if this tool is marked as long-running via providerMetadata
|
||||
const isLongRunning =
|
||||
"providerMetadata" in part &&
|
||||
part.providerMetadata &&
|
||||
typeof part.providerMetadata === "object" &&
|
||||
"isLongRunning" in part.providerMetadata &&
|
||||
part.providerMetadata.isLongRunning === true;
|
||||
|
||||
// Debug logging
|
||||
if (part.type.startsWith("tool-")) {
|
||||
console.log("[ToolWrapper]", {
|
||||
toolName,
|
||||
type: part.type,
|
||||
hasProviderMetadata: "providerMetadata" in part,
|
||||
providerMetadata:
|
||||
"providerMetadata" in part ? part.providerMetadata : undefined,
|
||||
isLongRunning,
|
||||
state: part.state,
|
||||
isStreaming,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Show UI feedback if tool is long-running and streaming */}
|
||||
{isLongRunning && <LongRunningToolDisplay isStreaming={isStreaming} />}
|
||||
{/* Render the actual tool component */}
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -58,6 +58,7 @@ function toToolInput(rawArguments: unknown): unknown {
|
||||
export function convertChatSessionMessagesToUiMessages(
|
||||
sessionId: string,
|
||||
rawMessages: unknown[],
|
||||
options?: { isComplete?: boolean },
|
||||
): UIMessage<unknown, UIDataTypes, UITools>[] {
|
||||
const messages = coerceSessionChatMessages(rawMessages);
|
||||
const toolOutputsByCallId = new Map<string, unknown>();
|
||||
@@ -104,6 +105,16 @@ export function convertChatSessionMessagesToUiMessages(
|
||||
input,
|
||||
output: typeof output === "string" ? safeJsonParse(output) : output,
|
||||
});
|
||||
} else if (options?.isComplete) {
|
||||
// Session is complete (no active stream) but this tool call has
|
||||
// no output in the DB — mark as completed to stop stale spinners.
|
||||
parts.push({
|
||||
type: `tool-${toolName}`,
|
||||
toolCallId,
|
||||
state: "output-available",
|
||||
input,
|
||||
output: "",
|
||||
});
|
||||
} else {
|
||||
parts.push({
|
||||
type: `tool-${toolName}`,
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
ContentCardDescription,
|
||||
ContentCodeBlock,
|
||||
ContentGrid,
|
||||
ContentHint,
|
||||
ContentMessage,
|
||||
} from "../../components/ToolAccordion/AccordionContent";
|
||||
import { ToolAccordion } from "../../components/ToolAccordion/ToolAccordion";
|
||||
@@ -25,7 +24,6 @@ import {
|
||||
ClarifyingQuestion,
|
||||
} from "./components/ClarificationQuestionsCard";
|
||||
import sparklesImg from "./components/MiniGame/assets/sparkles.png";
|
||||
import { MiniGame } from "./components/MiniGame/MiniGame";
|
||||
import { SuggestedGoalCard } from "./components/SuggestedGoalCard";
|
||||
import {
|
||||
AccordionIcon,
|
||||
@@ -36,9 +34,6 @@ import {
|
||||
isAgentSavedOutput,
|
||||
isClarificationNeededOutput,
|
||||
isErrorOutput,
|
||||
isOperationInProgressOutput,
|
||||
isOperationPendingOutput,
|
||||
isOperationStartedOutput,
|
||||
isSuggestedGoalOutput,
|
||||
ToolIcon,
|
||||
truncateText,
|
||||
@@ -86,18 +81,6 @@ function getAccordionMeta(output: CreateAgentToolOutput) {
|
||||
expanded: true,
|
||||
};
|
||||
}
|
||||
if (
|
||||
isOperationStartedOutput(output) ||
|
||||
isOperationPendingOutput(output) ||
|
||||
isOperationInProgressOutput(output)
|
||||
) {
|
||||
return {
|
||||
icon,
|
||||
title:
|
||||
"Creating agent, this may take a few minutes. Play while you wait.",
|
||||
expanded: true,
|
||||
};
|
||||
}
|
||||
return {
|
||||
icon: (
|
||||
<WarningDiamondIcon size={32} weight="light" className="text-red-500" />
|
||||
@@ -119,19 +102,10 @@ export function CreateAgentTool({ part }: Props) {
|
||||
const isError =
|
||||
part.state === "output-error" || (!!output && isErrorOutput(output));
|
||||
|
||||
const isOperating =
|
||||
!!output &&
|
||||
(isOperationStartedOutput(output) ||
|
||||
isOperationPendingOutput(output) ||
|
||||
isOperationInProgressOutput(output));
|
||||
|
||||
const hasExpandableContent =
|
||||
part.state === "output-available" &&
|
||||
!!output &&
|
||||
(isOperationStartedOutput(output) ||
|
||||
isOperationPendingOutput(output) ||
|
||||
isOperationInProgressOutput(output) ||
|
||||
isAgentPreviewOutput(output) ||
|
||||
(isAgentPreviewOutput(output) ||
|
||||
isAgentSavedOutput(output) ||
|
||||
isClarificationNeededOutput(output) ||
|
||||
isSuggestedGoalOutput(output) ||
|
||||
@@ -171,15 +145,6 @@ export function CreateAgentTool({ part }: Props) {
|
||||
|
||||
{hasExpandableContent && output && (
|
||||
<ToolAccordion {...getAccordionMeta(output)}>
|
||||
{isOperating && (
|
||||
<ContentGrid>
|
||||
<MiniGame />
|
||||
<ContentHint>
|
||||
This could take a few minutes — play while you wait!
|
||||
</ContentHint>
|
||||
</ContentGrid>
|
||||
)}
|
||||
|
||||
{isAgentSavedOutput(output) && (
|
||||
<div className="rounded-xl border border-border/60 bg-card p-4 shadow-sm">
|
||||
<div className="flex items-baseline gap-2">
|
||||
|
||||
@@ -2,9 +2,6 @@ import type { AgentPreviewResponse } from "@/app/api/__generated__/models/agentP
|
||||
import type { AgentSavedResponse } from "@/app/api/__generated__/models/agentSavedResponse";
|
||||
import type { ClarificationNeededResponse } from "@/app/api/__generated__/models/clarificationNeededResponse";
|
||||
import type { ErrorResponse } from "@/app/api/__generated__/models/errorResponse";
|
||||
import type { OperationInProgressResponse } from "@/app/api/__generated__/models/operationInProgressResponse";
|
||||
import type { OperationPendingResponse } from "@/app/api/__generated__/models/operationPendingResponse";
|
||||
import type { OperationStartedResponse } from "@/app/api/__generated__/models/operationStartedResponse";
|
||||
import { ResponseType } from "@/app/api/__generated__/models/responseType";
|
||||
import type { SuggestedGoalResponse } from "@/app/api/__generated__/models/suggestedGoalResponse";
|
||||
import {
|
||||
@@ -16,9 +13,6 @@ import type { ToolUIPart } from "ai";
|
||||
import { OrbitLoader } from "../../components/OrbitLoader/OrbitLoader";
|
||||
|
||||
export type CreateAgentToolOutput =
|
||||
| OperationStartedResponse
|
||||
| OperationPendingResponse
|
||||
| OperationInProgressResponse
|
||||
| AgentPreviewResponse
|
||||
| AgentSavedResponse
|
||||
| ClarificationNeededResponse
|
||||
@@ -39,9 +33,6 @@ function parseOutput(output: unknown): CreateAgentToolOutput | null {
|
||||
if (typeof output === "object") {
|
||||
const type = (output as { type?: unknown }).type;
|
||||
if (
|
||||
type === ResponseType.operation_started ||
|
||||
type === ResponseType.operation_pending ||
|
||||
type === ResponseType.operation_in_progress ||
|
||||
type === ResponseType.agent_preview ||
|
||||
type === ResponseType.agent_saved ||
|
||||
type === ResponseType.clarification_needed ||
|
||||
@@ -50,9 +41,6 @@ function parseOutput(output: unknown): CreateAgentToolOutput | null {
|
||||
) {
|
||||
return output as CreateAgentToolOutput;
|
||||
}
|
||||
if ("operation_id" in output && "tool_name" in output)
|
||||
return output as OperationStartedResponse | OperationPendingResponse;
|
||||
if ("tool_call_id" in output) return output as OperationInProgressResponse;
|
||||
if ("agent_json" in output && "agent_name" in output)
|
||||
return output as AgentPreviewResponse;
|
||||
if ("agent_id" in output && "library_agent_id" in output)
|
||||
@@ -72,30 +60,6 @@ export function getCreateAgentToolOutput(
|
||||
return parseOutput((part as { output?: unknown }).output);
|
||||
}
|
||||
|
||||
export function isOperationStartedOutput(
|
||||
output: CreateAgentToolOutput,
|
||||
): output is OperationStartedResponse {
|
||||
return (
|
||||
output.type === ResponseType.operation_started ||
|
||||
("operation_id" in output && "tool_name" in output)
|
||||
);
|
||||
}
|
||||
|
||||
export function isOperationPendingOutput(
|
||||
output: CreateAgentToolOutput,
|
||||
): output is OperationPendingResponse {
|
||||
return output.type === ResponseType.operation_pending;
|
||||
}
|
||||
|
||||
export function isOperationInProgressOutput(
|
||||
output: CreateAgentToolOutput,
|
||||
): output is OperationInProgressResponse {
|
||||
return (
|
||||
output.type === ResponseType.operation_in_progress ||
|
||||
"tool_call_id" in output
|
||||
);
|
||||
}
|
||||
|
||||
export function isAgentPreviewOutput(
|
||||
output: CreateAgentToolOutput,
|
||||
): output is AgentPreviewResponse {
|
||||
@@ -144,10 +108,6 @@ export function getAnimationText(part: {
|
||||
case "output-available": {
|
||||
const output = parseOutput(part.output);
|
||||
if (!output) return "Creating a new agent";
|
||||
if (isOperationStartedOutput(output)) return "Agent creation started";
|
||||
if (isOperationPendingOutput(output)) return "Agent creation in progress";
|
||||
if (isOperationInProgressOutput(output))
|
||||
return "Agent creation already in progress";
|
||||
if (isAgentSavedOutput(output)) return `Saved ${output.agent_name}`;
|
||||
if (isAgentPreviewOutput(output)) return `Preview "${output.agent_name}"`;
|
||||
if (isClarificationNeededOutput(output)) return "Needs clarification";
|
||||
|
||||
@@ -4,17 +4,14 @@ import { WarningDiamondIcon } from "@phosphor-icons/react";
|
||||
import type { ToolUIPart } from "ai";
|
||||
import { useCopilotChatActions } from "../../components/CopilotChatActionsProvider/useCopilotChatActions";
|
||||
import { MorphingTextAnimation } from "../../components/MorphingTextAnimation/MorphingTextAnimation";
|
||||
import { OrbitLoader } from "../../components/OrbitLoader/OrbitLoader";
|
||||
import {
|
||||
ContentCardDescription,
|
||||
ContentCodeBlock,
|
||||
ContentGrid,
|
||||
ContentHint,
|
||||
ContentLink,
|
||||
ContentMessage,
|
||||
} from "../../components/ToolAccordion/AccordionContent";
|
||||
import { ToolAccordion } from "../../components/ToolAccordion/ToolAccordion";
|
||||
import { MiniGame } from "../CreateAgent/components/MiniGame/MiniGame";
|
||||
import {
|
||||
ClarificationQuestionsCard,
|
||||
ClarifyingQuestion,
|
||||
@@ -28,9 +25,6 @@ import {
|
||||
isAgentSavedOutput,
|
||||
isClarificationNeededOutput,
|
||||
isErrorOutput,
|
||||
isOperationInProgressOutput,
|
||||
isOperationPendingOutput,
|
||||
isOperationStartedOutput,
|
||||
ToolIcon,
|
||||
truncateText,
|
||||
type EditAgentToolOutput,
|
||||
@@ -75,17 +69,6 @@ function getAccordionMeta(output: EditAgentToolOutput): {
|
||||
description: `${questions.length} question${questions.length === 1 ? "" : "s"}`,
|
||||
};
|
||||
}
|
||||
if (
|
||||
isOperationStartedOutput(output) ||
|
||||
isOperationPendingOutput(output) ||
|
||||
isOperationInProgressOutput(output)
|
||||
) {
|
||||
return {
|
||||
icon: <OrbitLoader size={32} />,
|
||||
title: "Editing agent, this may take a few minutes. Play while you wait.",
|
||||
expanded: true,
|
||||
};
|
||||
}
|
||||
return {
|
||||
icon: (
|
||||
<WarningDiamondIcon size={32} weight="light" className="text-red-500" />
|
||||
@@ -104,18 +87,10 @@ export function EditAgentTool({ part }: Props) {
|
||||
const output = getEditAgentToolOutput(part);
|
||||
const isError =
|
||||
part.state === "output-error" || (!!output && isErrorOutput(output));
|
||||
const isOperating =
|
||||
!!output &&
|
||||
(isOperationStartedOutput(output) ||
|
||||
isOperationPendingOutput(output) ||
|
||||
isOperationInProgressOutput(output));
|
||||
const hasExpandableContent =
|
||||
part.state === "output-available" &&
|
||||
!!output &&
|
||||
(isOperationStartedOutput(output) ||
|
||||
isOperationPendingOutput(output) ||
|
||||
isOperationInProgressOutput(output) ||
|
||||
isAgentPreviewOutput(output) ||
|
||||
(isAgentPreviewOutput(output) ||
|
||||
isAgentSavedOutput(output) ||
|
||||
isClarificationNeededOutput(output) ||
|
||||
isErrorOutput(output));
|
||||
@@ -150,15 +125,6 @@ export function EditAgentTool({ part }: Props) {
|
||||
|
||||
{hasExpandableContent && output && (
|
||||
<ToolAccordion {...getAccordionMeta(output)}>
|
||||
{isOperating && (
|
||||
<ContentGrid>
|
||||
<MiniGame />
|
||||
<ContentHint>
|
||||
This could take a few minutes — play while you wait!
|
||||
</ContentHint>
|
||||
</ContentGrid>
|
||||
)}
|
||||
|
||||
{isAgentSavedOutput(output) && (
|
||||
<ContentGrid>
|
||||
<ContentMessage>{output.message}</ContentMessage>
|
||||
|
||||
@@ -2,9 +2,6 @@ import type { AgentPreviewResponse } from "@/app/api/__generated__/models/agentP
|
||||
import type { AgentSavedResponse } from "@/app/api/__generated__/models/agentSavedResponse";
|
||||
import type { ClarificationNeededResponse } from "@/app/api/__generated__/models/clarificationNeededResponse";
|
||||
import type { ErrorResponse } from "@/app/api/__generated__/models/errorResponse";
|
||||
import type { OperationInProgressResponse } from "@/app/api/__generated__/models/operationInProgressResponse";
|
||||
import type { OperationPendingResponse } from "@/app/api/__generated__/models/operationPendingResponse";
|
||||
import type { OperationStartedResponse } from "@/app/api/__generated__/models/operationStartedResponse";
|
||||
import { ResponseType } from "@/app/api/__generated__/models/responseType";
|
||||
import {
|
||||
NotePencilIcon,
|
||||
@@ -15,9 +12,6 @@ import type { ToolUIPart } from "ai";
|
||||
import { OrbitLoader } from "../../components/OrbitLoader/OrbitLoader";
|
||||
|
||||
export type EditAgentToolOutput =
|
||||
| OperationStartedResponse
|
||||
| OperationPendingResponse
|
||||
| OperationInProgressResponse
|
||||
| AgentPreviewResponse
|
||||
| AgentSavedResponse
|
||||
| ClarificationNeededResponse
|
||||
@@ -38,8 +32,6 @@ function parseOutput(output: unknown): EditAgentToolOutput | null {
|
||||
const type = (output as { type?: unknown }).type;
|
||||
if (
|
||||
type === ResponseType.operation_started ||
|
||||
type === ResponseType.operation_pending ||
|
||||
type === ResponseType.operation_in_progress ||
|
||||
type === ResponseType.agent_preview ||
|
||||
type === ResponseType.agent_saved ||
|
||||
type === ResponseType.clarification_needed ||
|
||||
@@ -47,9 +39,6 @@ function parseOutput(output: unknown): EditAgentToolOutput | null {
|
||||
) {
|
||||
return output as EditAgentToolOutput;
|
||||
}
|
||||
if ("operation_id" in output && "tool_name" in output)
|
||||
return output as OperationStartedResponse | OperationPendingResponse;
|
||||
if ("tool_call_id" in output) return output as OperationInProgressResponse;
|
||||
if ("agent_json" in output && "agent_name" in output)
|
||||
return output as AgentPreviewResponse;
|
||||
if ("agent_id" in output && "library_agent_id" in output)
|
||||
@@ -68,30 +57,6 @@ export function getEditAgentToolOutput(
|
||||
return parseOutput((part as { output?: unknown }).output);
|
||||
}
|
||||
|
||||
export function isOperationStartedOutput(
|
||||
output: EditAgentToolOutput,
|
||||
): output is OperationStartedResponse {
|
||||
return (
|
||||
output.type === ResponseType.operation_started ||
|
||||
("operation_id" in output && "tool_name" in output)
|
||||
);
|
||||
}
|
||||
|
||||
export function isOperationPendingOutput(
|
||||
output: EditAgentToolOutput,
|
||||
): output is OperationPendingResponse {
|
||||
return output.type === ResponseType.operation_pending;
|
||||
}
|
||||
|
||||
export function isOperationInProgressOutput(
|
||||
output: EditAgentToolOutput,
|
||||
): output is OperationInProgressResponse {
|
||||
return (
|
||||
output.type === ResponseType.operation_in_progress ||
|
||||
"tool_call_id" in output
|
||||
);
|
||||
}
|
||||
|
||||
export function isAgentPreviewOutput(
|
||||
output: EditAgentToolOutput,
|
||||
): output is AgentPreviewResponse {
|
||||
@@ -132,10 +97,6 @@ export function getAnimationText(part: {
|
||||
case "output-available": {
|
||||
const output = parseOutput(part.output);
|
||||
if (!output) return "Editing the agent";
|
||||
if (isOperationStartedOutput(output)) return "Agent update started";
|
||||
if (isOperationPendingOutput(output)) return "Agent update in progress";
|
||||
if (isOperationInProgressOutput(output))
|
||||
return "Agent update already in progress";
|
||||
if (isAgentSavedOutput(output)) return `Saved "${output.agent_name}"`;
|
||||
if (isAgentPreviewOutput(output)) return `Preview "${output.agent_name}"`;
|
||||
if (isClarificationNeededOutput(output)) return "Needs clarification";
|
||||
|
||||
@@ -40,16 +40,6 @@ export function useChatSession() {
|
||||
}
|
||||
}, [sessionId, queryClient]);
|
||||
|
||||
// Memoize so the effect in useCopilotPage doesn't infinite-loop on a new
|
||||
// array reference every render. Re-derives only when query data changes.
|
||||
const hydratedMessages = useMemo(() => {
|
||||
if (sessionQuery.data?.status !== 200 || !sessionId) return undefined;
|
||||
return convertChatSessionMessagesToUiMessages(
|
||||
sessionId,
|
||||
sessionQuery.data.data.messages ?? [],
|
||||
);
|
||||
}, [sessionQuery.data, sessionId]);
|
||||
|
||||
// Expose active_stream info so the caller can trigger manual resume
|
||||
// after hydration completes (rather than relying on AI SDK's built-in
|
||||
// resume which fires before hydration).
|
||||
@@ -58,6 +48,19 @@ export function useChatSession() {
|
||||
return !!sessionQuery.data.data.active_stream;
|
||||
}, [sessionQuery.data]);
|
||||
|
||||
// Memoize so the effect in useCopilotPage doesn't infinite-loop on a new
|
||||
// array reference every render. Re-derives only when query data changes.
|
||||
// When the session is complete (no active stream), mark dangling tool
|
||||
// calls as completed so stale spinners don't persist after refresh.
|
||||
const hydratedMessages = useMemo(() => {
|
||||
if (sessionQuery.data?.status !== 200 || !sessionId) return undefined;
|
||||
return convertChatSessionMessagesToUiMessages(
|
||||
sessionId,
|
||||
sessionQuery.data.data.messages ?? [],
|
||||
{ isComplete: !hasActiveStream },
|
||||
);
|
||||
}, [sessionQuery.data, sessionId, hasActiveStream]);
|
||||
|
||||
const { mutateAsync: createSessionMutation, isPending: isCreatingSession } =
|
||||
usePostV2CreateSession({
|
||||
mutation: {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
getGetV2GetSessionQueryKey,
|
||||
getGetV2ListSessionsQueryKey,
|
||||
postV2CancelSessionTask,
|
||||
useDeleteV2DeleteSession,
|
||||
@@ -15,7 +16,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useChatSession } from "./useChatSession";
|
||||
import { useLongRunningToolPolling } from "./hooks/useLongRunningToolPolling";
|
||||
|
||||
const STREAM_START_TIMEOUT_MS = 12_000;
|
||||
const STREAM_START_TIMEOUT_MS = 30_000; // 30s to detect if backend is down/not responding
|
||||
|
||||
/** Mark any in-progress tool parts as completed/errored so spinners stop. */
|
||||
function resolveInProgressTools(
|
||||
@@ -187,11 +188,36 @@ export function useCopilotPage() {
|
||||
});
|
||||
}, [hydratedMessages, setMessages, status]);
|
||||
|
||||
// Ref: tracks whether we've already resumed for a given session.
|
||||
// Reset when the stream ends so re-resume is possible if the backend
|
||||
// task is still running (SSE dropped but executor didn't finish).
|
||||
const hasResumedRef = useRef<string | null>(null);
|
||||
|
||||
// When the stream ends (or drops), invalidate the session cache so the
|
||||
// next hydration fetches fresh messages from the backend. Without this,
|
||||
// staleTime: Infinity means the cache keeps the pre-stream data forever,
|
||||
// and any messages added during streaming are lost on remount/navigation.
|
||||
const prevStatusRef = useRef(status);
|
||||
useEffect(() => {
|
||||
const prev = prevStatusRef.current;
|
||||
prevStatusRef.current = status;
|
||||
|
||||
const wasActive = prev === "streaming" || prev === "submitted";
|
||||
const isReady = status === "ready";
|
||||
// Only invalidate on successful completion, not on error to avoid infinite refetch loop
|
||||
if (wasActive && isReady && sessionId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: getGetV2GetSessionQueryKey(sessionId),
|
||||
});
|
||||
// Allow re-resume if the backend task is still running.
|
||||
hasResumedRef.current = null;
|
||||
}
|
||||
}, [status, sessionId, queryClient]);
|
||||
|
||||
// Resume an active stream AFTER hydration completes.
|
||||
// The backend returns active_stream info when a task is still running.
|
||||
// We wait for hydration so the AI SDK has the conversation history
|
||||
// before the resumed stream appends the in-progress assistant message.
|
||||
const hasResumedRef = useRef<string | null>(null);
|
||||
useEffect(() => {
|
||||
if (!hasActiveStream || !sessionId) return;
|
||||
if (!hydratedMessages || hydratedMessages.length === 0) return;
|
||||
@@ -202,18 +228,6 @@ export function useCopilotPage() {
|
||||
resumeStream();
|
||||
}, [hasActiveStream, sessionId, hydratedMessages, status, resumeStream]);
|
||||
|
||||
// When the stream finishes, resolve any tool parts still showing spinners.
|
||||
// This can happen if the backend didn't emit StreamToolOutputAvailable for
|
||||
// a tool call before sending StreamFinish (e.g. SDK built-in tools).
|
||||
const prevStatusRef = useRef(status);
|
||||
useEffect(() => {
|
||||
const prev = prevStatusRef.current;
|
||||
prevStatusRef.current = status;
|
||||
if (prev === "streaming" && status === "ready") {
|
||||
setMessages((msgs) => resolveInProgressTools(msgs, "completed"));
|
||||
}
|
||||
}, [status, setMessages]);
|
||||
|
||||
// Poll session endpoint when a long-running tool (create_agent, edit_agent)
|
||||
// is in progress. When the backend completes, the session data will contain
|
||||
// the final tool output — this hook detects the change and updates messages.
|
||||
|
||||
Reference in New Issue
Block a user