Procházet zdrojové kódy

fix(runtime): 工作区对话 run 500 — 裸 os NameError(本文件无模块级 import os)

run handler 的 work_dir override 分支用了裸 `os.path.isdir`,但 agents.py 没有
模块级 import os(全文用局部 `import os as _os`),且该分支只在 context.work_dir
有值时触发(普通 run 测不到)→ 工作区对话首条消息必 500
(NameError: name 'os' is not defined, agents.py:742)。

修:两处 override(run_agent + run_agent_stream)改用文件约定的 `import os as _os`
/ `_os.path.isdir`。加回归测试 test_run_with_context_work_dir_override:
TestClient POST /run 带 context.work_dir,断言不 500 且 _execute_agent
收到的 work_dir/source_dir = 所选文件夹。e2e 16 passed。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
kenny67nju před 4 měsíci
rodič
revize
ea36c1cee2

+ 4 - 2
agentpaas/src/agentpaas/api/v1/agents.py

@@ -582,8 +582,9 @@ async def run_agent(
     # 工作区对话模式(Dashboard「工作区对话」):本次 run 经 context.work_dir
     # 工作区对话模式(Dashboard「工作区对话」):本次 run 经 context.work_dir
     # 覆盖工作文件夹 —— Bash CWD + 读写就地在该文件夹(同时作 source_dir 注入
     # 覆盖工作文件夹 —— Bash CWD + 读写就地在该文件夹(同时作 source_dir 注入
     # [数据来源目录])。run_dir 保持 agent 自己的,trace/日志不污染用户项目文件夹。
     # [数据来源目录])。run_dir 保持 agent 自己的,trace/日志不污染用户项目文件夹。
+    import os as _os
     _ctx_wd = (req.context or {}).get("work_dir") if isinstance(req.context, dict) else ""
     _ctx_wd = (req.context or {}).get("work_dir") if isinstance(req.context, dict) else ""
-    if _ctx_wd and os.path.isdir(_ctx_wd):
+    if _ctx_wd and _os.path.isdir(_ctx_wd):
         work_dir = source_dir = _ctx_wd
         work_dir = source_dir = _ctx_wd
 
 
     # 注入顺序(MEMORY_DESIGN §2.2):本会话前情 → 相关历史(L3) → KB → 用户问题
     # 注入顺序(MEMORY_DESIGN §2.2):本会话前情 → 相关历史(L3) → KB → 用户问题
@@ -738,8 +739,9 @@ async def run_agent_stream(
     run_dir = agent.get("run_dir", "") or ""
     run_dir = agent.get("run_dir", "") or ""
 
 
     # 工作区对话模式:本次 run 经 context.work_dir 覆盖工作文件夹(见 run_agent 同段注释)。
     # 工作区对话模式:本次 run 经 context.work_dir 覆盖工作文件夹(见 run_agent 同段注释)。
+    import os as _os
     _ctx_wd = (req.context or {}).get("work_dir") if isinstance(req.context, dict) else ""
     _ctx_wd = (req.context or {}).get("work_dir") if isinstance(req.context, dict) else ""
-    if _ctx_wd and os.path.isdir(_ctx_wd):
+    if _ctx_wd and _os.path.isdir(_ctx_wd):
         work_dir = source_dir = _ctx_wd
         work_dir = source_dir = _ctx_wd
 
 
     # 会话记忆 P1:thread 解析(run 记录在下方流式分支创建,这里先拿 id)
     # 会话记忆 P1:thread 解析(run 记录在下方流式分支创建,这里先拿 id)

+ 40 - 0
tests/test_agentpack_e2e.py

@@ -483,3 +483,43 @@ def test_create_agent_from_pack_auto_provisions_dirs(http_client, tmp_path):
     g = client.get(f"/api/v1/agents/{body['agent_id']}", headers=_auth(key)).json()
     g = client.get(f"/api/v1/agents/{body['agent_id']}", headers=_auth(key)).json()
     assert g["work_dir"] == body["work_dir"]
     assert g["work_dir"] == body["work_dir"]
     assert g["run_dir"] == body["run_dir"]
     assert g["run_dir"] == body["run_dir"]
+
+
+def test_run_with_context_work_dir_override(tmp_path, http_client, monkeypatch):
+    """工作区对话模式回归:run 带 context.work_dir 应覆盖工作目录、不报 500。
+
+    曾因 run handler 用裸 `os`(本文件无模块级 import os,仅 `import os as _os`)
+    在 work_dir override 分支 NameError 崩 500——且该分支只在 context.work_dir
+    有值时触发,普通 run 测试覆盖不到。本例正好走这条分支。
+    """
+    client, key, packs_dir, pack = http_client
+    create_r = client.post(
+        f"/api/v1/agentpacks/{REVIEWER_ID}/create-agent",
+        json={"name": "工作区回归"},
+        headers=_auth(key),
+    )
+    agent_id = create_r.json()["agent_id"]
+
+    captured = {}
+
+    def _stub_exec(config, input_text, **kw):
+        captured["work_dir"] = kw.get("work_dir")
+        captured["source_dir"] = kw.get("source_dir")
+        return ("摘要:ok", {"workspace_path": "", "input_tokens": 1,
+                             "output_tokens": 1, "steps": 1, "cost_usd": 0.0,
+                             "cache_read_tokens": 0, "cache_creation_tokens": 0})
+
+    import agentpaas.api.v1.agents as _agents_mod
+    monkeypatch.setattr(_agents_mod, "_execute_agent", _stub_exec)
+
+    folder = str(tmp_path)
+    r = client.post(
+        f"/api/v1/agents/{agent_id}/run",
+        json={"input": "总结一下", "context": {"work_dir": folder}},
+        headers=_auth(key),
+    )
+    assert r.status_code != 500, f"work_dir override 崩了: {r.text[:300]}"
+    assert r.status_code == 200, r.text[:300]
+    # override 生效:_execute_agent 收到的 work_dir/source_dir 都是所选文件夹
+    assert captured.get("work_dir") == folder
+    assert captured.get("source_dir") == folder