diff --git a/pipeline_service/agent_loop_v2.py b/pipeline_service/agent_loop_v2.py index ad865c8..5f9dde7 100644 --- a/pipeline_service/agent_loop_v2.py +++ b/pipeline_service/agent_loop_v2.py @@ -985,10 +985,16 @@ class AgentExecutor: xml = z.read('word/document.xml').decode('utf-8', errors='ignore') texts = _re.findall(r']*>(.*?)', xml) return ('\n'.join(texts)[:30000]) or '(docx 无文本内容)' - # 纯文本类:直接读 + # 纯文本类:直接读(offset 分段 + 截断显式告知——禁止静默截断) if ext in ('.txt', '.md', '.json', '.csv', '.py', '.log', '.yaml', '.yml', '.xml', '.html', '.ini', ''): + offset = int(p.get("offset") or 0) with open(full, encoding="utf-8", errors="ignore") as f: - return f.read()[:30000] + full_txt = f.read() + chunk = full_txt[offset:offset + 30000] + if offset + 30000 < len(full_txt): + chunk += (f"\n\n[⚠️ 截断提示:以上为第 {offset}~{offset + len(chunk.rstrip())} 字符," + f"全文共 {len(full_txt)} 字符。读后文请再次 read_file 并传 offset={offset + 30000}。]") + return chunk # 其他二进制 return f"该文件是二进制格式({ext or '无扩展名'}),无法直接读取文本。可改用 run_command 处理,或让用户上传文本版本。" except Exception as e: @@ -1285,13 +1291,18 @@ class AgentExecutor: async with db.sqlorContext("pipeline") as sor: # 会话级隔离(web 多 tab 独立会话) if self.session_id: - recs = await sor.sqlExe( - "SELECT role, content FROM pipeline_conversations " - "WHERE session_id=${sid}$ AND created_by=${uid}$ " - "ORDER BY created_at ASC LIMIT ${lim}$", - {"sid": self.session_id, "uid": self.user_id or "", - "lim": self.config.history_limit}, - ) + # 产线隔离(2026-08-31 修复跨产线串扰):各产线页面默认 tab 共用 + # session_id='default',历史若不按产线过滤,投标页会加载开发页的 + # 对话(如"元景项目/新建demo项目"),LLM 跟着最后上下文答错产线。 + sql = ("SELECT role, content FROM pipeline_conversations " + "WHERE session_id=${sid}$ AND created_by=${uid}$ ") + params = {"sid": self.session_id, "uid": self.user_id or "", + "lim": self.config.history_limit} + if self.pipeline_id and not self.generic: + sql += "AND pipeline_id=${pl}$ " + params["pl"] = self.pipeline_id + sql += "ORDER BY created_at ASC LIMIT ${lim}$" + recs = await sor.sqlExe(sql, params) else: # 项目隔离 pid = self.project_id @@ -1349,6 +1360,7 @@ class AgentExecutor: "created_by": self.user_id or "", "iteration_id": self.project_id or "", "session_id": self.session_id or "", + "pipeline_id": self.pipeline_id or "", }) # 助手回复(不含 tool_call JSON!) reply_clean = reply @@ -1361,6 +1373,7 @@ class AgentExecutor: "created_by": self.user_id or "", "iteration_id": self.project_id or "", "session_id": self.session_id or "", + "pipeline_id": self.pipeline_id or "", }) except Exception as e: logger.warning(f"Session save failed: {e}") diff --git a/pipeline_service/workspace.py b/pipeline_service/workspace.py index a9edf73..acfe6f2 100644 --- a/pipeline_service/workspace.py +++ b/pipeline_service/workspace.py @@ -100,27 +100,42 @@ async def get_max_task_retry(sor): return 3 -async def get_session_project_id(sor, uid, session_id=''): +async def get_session_project_id(sor, uid, session_id='', pipeline_id=''): """按会话解析当前项目 id。 session_id 非空时优先读 pipeline_session_settings(web 多 tab 各自项目上下文), 无记录或表不存在时回退全局 pipeline_agent_settings.current_project_id。 + pipeline_id 产线隔离(2026-08-31 修复跨产线串扰):各产线页面默认 tab 共用 + session_id='default',而 session/全局记录不区分产线——投标页会读到开发页切换 + 的项目。传入 pipeline_id 时,解析出的项目必须属于该产线(sd_projects.pipeline_id + 匹配)才生效,否则视为无记录继续回退;不匹配的记录**不删除**(它属于另一产线 + 的合法上下文)。 + 存在性校验(悬空引用防护):记录指向的项目若已从 sd_projects 删除, 视为无效——顺手清掉死记录(自愈,避免每次请求重复命中),继续回退全局; 全局也无效则返回 ''。若不清理,删除项目后该会话的所有项目相关入口 (工作空间/任务/菜单)都会被死引用遮蔽,报「请先在会话中切换项目」。 """ - async def _valid(pid): - """pid 对应项目存在则原样返回;不存在返回 '';查询异常保守放行(不因校验误伤功能)。""" - if not pid: - return '' + async def _project_pipeline(pid): + """项目所属 pipeline_id;不存在返回 None;查询异常返回 ''(保守放行)。""" try: recs = await sor.sqlExe( - "SELECT 1 FROM sd_projects WHERE id=${p}$ LIMIT 1", {"p": pid}) - return pid if recs else '' + "SELECT pipeline_id FROM sd_projects WHERE id=${p}$ LIMIT 1", {"p": pid}) + if not recs: + return None + return getattr(recs[0], 'pipeline_id', '') or '' except Exception: - return pid + return '' + + async def _valid(pid): + """pid 存在(且产线匹配,若指定)则返回 pid;否则 ''。""" + pl = await _project_pipeline(pid) + if pl is None: + return '' + if pipeline_id and pl and pl != pipeline_id: + return '' + return pid if session_id: try: