Skip to content

Commit ad9aa8a

Browse files
fix the initial team swithc issue
1 parent b6cd0d2 commit ad9aa8a

4 files changed

Lines changed: 54 additions & 14 deletions

File tree

src/App/src/api/apiClient.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ function syncBaseUrl(): void {
2020
}
2121

2222
export const apiClient = {
23-
get: <T = any>(url: string, config?: { params?: Record<string, unknown> }): Promise<T> => {
24-
syncBaseUrl();
25-
return httpClient.get<T>(url, { params: config?.params });
26-
},
23+
get: <T = any>(url: string, config?: { params?: Record<string, unknown>; timeout?: number }): Promise<T> => {
24+
syncBaseUrl();
25+
return httpClient.get<T>(url, { params: config?.params, timeout: config?.timeout });
26+
},
2727

2828
post: <T = any>(url: string, body?: unknown): Promise<T> => {
2929
syncBaseUrl();

src/App/src/api/httpClient.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,11 @@ class HttpClient {
6161
}
6262

6363
/** Core request method — applies interceptors, timeout, and error handling */
64-
private async request(
64+
private async request(
6565
path: string,
66-
options: RequestInit & { params?: Record<string, unknown> } = {}
66+
options: RequestInit & { params?: Record<string, unknown>; timeout?: number } = {}
6767
): Promise<Response> {
68-
const { params, ...fetchOptions } = options;
68+
const { params, timeout, ...fetchOptions } = options;
6969
const url = this.buildUrl(path, params);
7070

7171
// Build initial config
@@ -78,9 +78,11 @@ class HttpClient {
7878

7979
const { url: finalUrl, ...rest } = config;
8080

81-
// Timeout via AbortController
81+
82+
// Timeout via AbortController (per-request override falls back to the default)
8283
const controller = new AbortController();
83-
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
84+
const timeoutId = setTimeout(() => controller.abort(), timeout ?? this.timeout);
85+
8486

8587
try {
8688
let response = await fetch(finalUrl, {
@@ -102,12 +104,13 @@ class HttpClient {
102104
/** HTTP GET */
103105
async get<T = unknown>(
104106
path: string,
105-
config?: { params?: Record<string, unknown>; headers?: Record<string, string> }
107+
config?: { params?: Record<string, unknown>; headers?: Record<string, string>; timeout?: number }
106108
): Promise<T> {
107109
const response = await this.request(path, {
108110
method: 'GET',
109111
params: config?.params,
110112
headers: config?.headers,
113+
timeout: config?.timeout,
111114
});
112115

113116
if (!response.ok) {

src/backend/api/router.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,19 +341,31 @@ async def process_request(
341341

342342
# Ensure the workflow is valid (rebuild if terminated or stuck from a prior run)
343343
current_workflow = orchestration_config.get_current_orchestration(user_id)
344+
345+
cached_team_id = getattr(current_workflow, "_team_id", None)
346+
team_mismatch = (
347+
current_workflow is not None and cached_team_id != team_id
348+
)
349+
344350
workflow_unusable = (
345351
current_workflow is None
346352
or getattr(current_workflow, "_terminated", False)
347353
or getattr(current_workflow, "_is_running", False)
354+
or team_mismatch
348355
)
349356
if workflow_unusable:
350357
logger.info(
351-
"Workflow unusable for user '%s' (None=%s, terminated=%s, is_running=%s) — rebuilding",
358+
"Workflow unusable for user '%s' (None=%s, terminated=%s, is_running=%s, "
359+
"team_mismatch=%s cached_team=%s selected_team=%s) — rebuilding",
352360
user_id,
353361
current_workflow is None,
354362
getattr(current_workflow, "_terminated", False),
355363
getattr(current_workflow, "_is_running", False),
364+
team_mismatch,
365+
cached_team_id,
366+
team_id,
356367
)
368+
357369
# Force-clear the running flag so get_current_or_new_orchestration
358370
# sees it as terminated and takes the lightweight reset path.
359371
if current_workflow is not None and getattr(current_workflow, "_is_running", False):

src/backend/orchestration/orchestration_manager.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -202,10 +202,35 @@ async def get_current_or_new_orchestration(
202202
current = orchestration_config.get_current_orchestration(user_id)
203203
workflow_terminated = getattr(current, "_terminated", False)
204204

205-
# Full rebuild: no workflow exists or team explicitly changed
206-
needs_full_rebuild = current is None or team_switched
205+
# Detect a stale cached orchestration: it was built for a different team
206+
# than the one now selected. Without this, /select_team leaves the prior
207+
# team's workflow cached and the next run executes the wrong agents until
208+
# a page refresh rebuilds it. The team_id tag is set on every workflow we
209+
# build/reset below.
210+
current_team_id = getattr(current, "_team_id", None)
211+
team_changed = (
212+
current is not None and current_team_id != team_config.team_id
213+
)
214+
215+
216+
cls.logger.info(
217+
"get_current_or_new_orchestration: user='%s' selected_team='%s' "
218+
"cached_team='%s' team_switched=%s team_changed=%s current_is_none=%s",
219+
user_id, team_config.team_id, current_team_id,
220+
team_switched, team_changed, current is None,
221+
)
222+
223+
224+
# Full rebuild: no workflow exists, team explicitly switched, or the
225+
# cached workflow belongs to a different team than the selected one.
226+
needs_full_rebuild = current is None or team_switched or team_changed
227+
228+
229+
# Lightweight reset: workflow finished but agents are still valid for the
230+
# same team (a team change always routes to full rebuild above so we
231+
# never reuse the previous team's agents here).
232+
207233

208-
# Lightweight reset: workflow finished but agents are still valid
209234
needs_workflow_reset = not needs_full_rebuild and workflow_terminated
210235

211236
if needs_full_rebuild:

0 commit comments

Comments
 (0)