diff --git a/tier2/gen-worker/service/bootstrap.py b/tier2/gen-worker/service/bootstrap.py index a125fda2..78760107 100644 --- a/tier2/gen-worker/service/bootstrap.py +++ b/tier2/gen-worker/service/bootstrap.py @@ -105,17 +105,20 @@ async def _post(client: Any, base: str, path: str, json_body: dict, *, user_head return resp.json() -async def _patch(client: Any, base: str, path: str, json_body: dict, *, user_header: str) -> dict: +async def _patch(client: Any, base: str, path: str, json_body: dict, *, + user_header: str, params: dict | None = None) -> dict: """打一个 PATCH 到运行中的 Agent Service(部分更新),返回 JSON;非 2xx 抛(带可追溯日志)。""" headers = {"X-User-Id": user_header} if user_header else {} - resp = await client.patch(f"{base}{path}", json=json_body, headers=headers, timeout=30.0) + resp = await client.patch(f"{base}{path}", json=json_body, headers=headers, + params=params or {}, timeout=30.0) if resp.status_code >= 300: print(f"[tier2-bootstrap] PATCH {path} -> {resp.status_code}: {resp.text[:300]}", flush=True) resp.raise_for_status() return resp.json() -async def _set_permission_bypass(client: Any, base: str, session_id: str, *, user_header: str) -> None: +async def _set_permission_bypass(client: Any, base: str, session_id: str, agent_id: str, + *, user_header: str) -> None: """把会话权限设为 BYPASS(沙箱无人值守档)——服务态等价 CLI studio.py 的 _bypass_state()。 为什么必须设(2026-06-24 实证):tier2 九工具的 FunctionTool 默认 check_permissions 返回 ASK @@ -124,9 +127,13 @@ async def _set_permission_bypass(client: Any, base: str, session_id: str, *, use but received no event」(agent/_agent.py:931)→ 服务态生成零产出。PATCH /sessions/{id} 把 AgentState.permission_context.mode 设成 "bypass"(app/_router/_session.py:334-343 的更新路径; PermissionMode.BYPASS 值="bypass"),跳过 ASK,与 CLI 单写口径一致(均运行在受控生成沙箱)。 + + 注:PATCH /sessions/{session_id} 与 SSE stream 同样【要 query 参数 agent_id】(app 路由约定; + 2026-06-24 smoke 实证不带会 422 missing query agent_id),故必须把 agent_id 作为 query 传。 """ await _patch(client, base, f"/sessions/{session_id}", - {"permission_mode": "bypass"}, user_header=user_header) + {"permission_mode": "bypass"}, user_header=user_header, + params={"agent_id": agent_id}) print(f"[tier2-bootstrap] 会话 {session_id} 权限已设 BYPASS(跳过工具 ASK,无人值守自治)。", flush=True) @@ -195,7 +202,7 @@ async def start_new_game( session = await _post(http, base_url, "/sessions/", session_body, user_header=user_id) session_id = session.get("session_id") or session.get("id") # 设会话权限 = BYPASS:tier2 九工具默认 ASK,服务态无人确认,不设则首个工具调用即卡死(实证)。 - await _set_permission_bypass(http, base_url, session_id, user_header=user_id) + await _set_permission_bypass(http, base_url, session_id, agent_id, user_header=user_id) # ④ 发 kick 触发首轮 chat run(fire-and-forget;事件从 SSE 收)。input 为 UserMsg 形状。 chat_body = { @@ -288,7 +295,7 @@ async def iterate_existing_project( session = await _post(http, base_url, "/sessions/", session_body, user_header=user_id) session_id = session.get("session_id") or session.get("id") # 设会话权限 = BYPASS:tier2 九工具默认 ASK,服务态无人确认,不设则首个工具调用即卡死(实证)。 - await _set_permission_bypass(http, base_url, session_id, user_header=user_id) + await _set_permission_bypass(http, base_url, session_id, agent_id, user_header=user_id) # 迭代指令:先 scaffold_init 起手(已存在文件不覆盖),再按 iterate_instruction 改/加,跑门到绿 finish。 text = (