feat(core): 会话「📁 项目」统一入口——agent_project_popup.dspy(menu/new/switch/create四op);新建=输项目名→create_project能力函数(alloc目录+审计)→start激活+初始迭代→会话进入;切换=本产线+本人创建列表点选→归属校验→双写session+全局指针→会话进入;agent_menus硬编码项目按钮(所有专业产线通用);无项目提示升级为'选择或新建'带直达按钮(agent_menu_open/workspace_popup同口径);menu_open透传session_id/pipeline_id给内嵌页修多tab串项目;模型配置三件套升级会话级解析get_session_project_id

This commit is contained in:
yumoqing 2026-09-11 18:29:17 +08:00
parent f5cf90eb17
commit 166e282d26
6 changed files with 425 additions and 34 deletions

View File

@ -32,29 +32,63 @@ if require_project:
from pipeline_service.workspace import get_session_project_id
dbname = get_module_dbname('pipeline_core')
async with DBPools().sqlorContext(dbname) as sor:
_pid = await get_session_project_id(sor, uid, session_id) or ''
if _pid:
# 产线隔离:跨产线项目不算本产线的项目
precs = await sor.sqlExe(
"SELECT pipeline_id FROM sd_projects WHERE id=${p}$", {"p": _pid})
proj_pl = getattr(precs[0], 'pipeline_id', '') or '' if precs else ''
_has_project = (not pipeline_id) or (proj_pl == pipeline_id)
_pid = await get_session_project_id(sor, uid, session_id, pipeline_id) or ''
_has_project = bool(_pid)
except Exception:
_has_project = True # 解析异常保守放行(不因校验故障误伤功能)
if not _has_project:
# 2026-09-11 用户要求:没当前项目 → 提示选择或新建项目(给直达按钮,不给死胡同)
_proj_q = []
if session_id:
_proj_q.append("session_id=" + session_id)
if pipeline_id:
_proj_q.append("pipeline_id=" + pipeline_id)
_proj_base = entire_url("/pipeline_core/api/agent_project_popup.dspy")
def _pbtn(label, css, op):
_js = ("var r=await fetch(" + json.dumps(_proj_base + "?op=" + op +
(("&" + "&".join(_proj_q)) if _proj_q else "")) + ");"
"var d=await r.json();if(d){bricks.widgetBuild(d,bricks.app);}")
return {"widgettype": "Button", "options": {"label": label, "css": css},
"binds": [{"wid": "self", "event": "click", "actiontype": "script",
"target": "self", "script": _js}]}
return json.dumps({
"widgettype": "PopupWindow",
"options": {"title": "提示", "cwidth": 30, "cheight": 8, "auto_open": True},
"id": "agent_menu_no_project_pw",
"options": {"title": "提示", "cwidth": 38, "cheight": 10, "auto_open": True},
"subwidgets": [{
"widgettype": "Text",
"options": {"text": "请先在会话中切换项目", "cfontsize": 1, "color": "#64748b", "padding": "20px"}
"widgettype": "VBox",
"options": {"width": "100%", "gap": "12px", "padding": "18px 22px"},
"subwidgets": [
{"widgettype": "Text",
"options": {"text": "当前会话还没有项目,请先选择或新建项目",
"cfontsize": 1, "color": "#64748b"}},
{"widgettype": "HBox",
"options": {"width": "100%", "gap": "10px"},
"subwidgets": [_pbtn("✨ 新建项目", "primary", "new"),
_pbtn("🔀 切换项目", "small", "switch")]}
]
}]
}, ensure_ascii=False)
# 把 session_id/pipeline_id 透传给内嵌页面(多 tab 会话级项目隔离):
# 内嵌 index.ui 的 data_params 会把请求参数转发给 get_*.dspy注入的项目过滤
# 代码据此按会话解析当前项目;不透传会退回全局指针(串 tab
# ⚠️ 查询串拼在 entire_url() 之后workspace_popup 同款写法),不把 query 喂给 entire_url。
_inner_url = entire_url(url)
_fwd = []
if session_id:
_fwd.append("session_id=" + session_id)
if pipeline_id:
_fwd.append("pipeline_id=" + pipeline_id)
if _fwd:
_inner_url += ("&" if "?" in _inner_url else "?") + "&".join(_fwd)
return json.dumps({
"widgettype": "PopupWindow",
"options": {"title": title, "width": width, "height": height, "auto_open": True},
"subwidgets": [
{"widgettype": "urlwidget", "options": {"url": entire_url(url)}}
{"widgettype": "urlwidget", "options": {"url": _inner_url}}
]
}, ensure_ascii=False)

View File

@ -80,6 +80,18 @@ if pipeline_id == '_generic':
+ "?pipeline_id=_generic",
})
else:
# 固定「📁 项目」入口2026-09-11 用户要求,所有专业产线通用):
# 点击弹出「新建项目 / 切换项目」;新建输项目名后关闭、会话进入新项目;
# 切换列出本产线全部项目、选择后关闭、会话进入所选项目。
_proj_q = []
if session_id:
_proj_q.append("session_id=" + session_id)
if pipeline_id:
_proj_q.append("pipeline_id=" + pipeline_id)
_proj_url = (entire_url("/pipeline_core/api/agent_project_popup.dspy") + "?op=menu"
+ (("&" + "&".join(_proj_q)) if _proj_q else ""))
actions.append({"label": "📁 项目", "url": _proj_url})
# 固定「工作空间」入口(通用能力,所有 agent 都有;依赖当前项目)
# 带产线参数:入口产线与会话项目跨产线时视为无项目(产线隔离,弹窗端点自身在点击时判断)
_ws_url = entire_url("/pipeline-sdlc/api/workspace_popup.dspy")

View File

@ -0,0 +1,333 @@
# agent_project_popup.dspy - 会话「📁 项目」统一入口弹窗2026-09-11 用户要求)
# GET op=menu → 小弹窗:「新建项目」「切换项目」两个按钮
# GET op=new → 新建项目弹窗:输入项目名 → 创建成功后关闭,会话进入新项目
# GET op=switch → 项目切换弹窗:列出本产线全部项目 → 选择后关闭,会话进入该项目
# POST op=create → 创建项目(走 project_capability.create_project带 alloc_project_dir+审计,
# 禁止裸 INSERT+ start_project + 初始迭代 + 写会话项目指针
# POST op=switch → 校验项目归属(产线/机构)后写会话项目指针
# 参数session_id会话级隔离多 tab 各自项目) pipeline_id产线隔离
# 会话「进入项目」= 写 pipeline_session_settings + pipeline_agent_settings
# AgentExecutor._persist_project 同口径gateway/菜单/工作空间每次请求实时解析,写完即生效。
import json
import re as _re
_op = (params_kw or {}).get('op', 'menu') or 'menu'
session_id = (params_kw or {}).get('session_id', '') or ''
pipeline_id = (params_kw or {}).get('pipeline_id', '') or ''
uid = await get_user()
dbname = get_module_dbname('pipeline_core')
_STATUS_TXT = {
'draft': ('草稿', '#94a3b8'),
'active': ('进行中', '#16a34a'),
'in_progress': ('进行中', '#16a34a'),
'paused': ('已暂停', '#d97706'),
'completed': ('已完成', '#2563eb'),
'archived': ('已归档', '#64748b'),
}
# ══════════════════ POST创建 / 切换 ══════════════════
if _op in ('create', 'switch'):
if not uid:
return json.dumps({"success": False, "error": "请先登录"}, ensure_ascii=False)
if not pipeline_id or pipeline_id == '_generic':
return json.dumps({"success": False, "error": "当前会话不支持项目管理"}, ensure_ascii=False)
userorg = (await get_userorgid()) or '0'
from appPublic.uniqueID import getID as _getID
async def _enter_project(sor, pid):
"""会话进入项目:会话级 + 全局兜底双写迭代上下文清空_persist_project 同口径)。"""
await sor.sqlExe(
"UPDATE pipeline_agent_settings SET current_project_id=${pid}$, current_iteration_id='' "
"WHERE user_id=${uid}$", {"pid": pid, "uid": uid})
_ex = await sor.sqlExe(
"SELECT 1 FROM pipeline_agent_settings WHERE user_id=${uid}$", {"uid": uid})
if not _ex:
await sor.C('pipeline_agent_settings', {
'id': _getID(), 'user_id': uid,
'current_project_id': pid, 'current_iteration_id': ''})
if session_id:
await sor.sqlExe(
"UPDATE pipeline_session_settings SET current_project_id=${pid}$, current_iteration_id='' "
"WHERE user_id=${uid}$ AND session_id=${sid}$",
{"pid": pid, "uid": uid, "sid": session_id})
_sex = await sor.sqlExe(
"SELECT 1 FROM pipeline_session_settings WHERE user_id=${uid}$ AND session_id=${sid}$",
{"uid": uid, "sid": session_id})
if not _sex:
await sor.C('pipeline_session_settings', {
'id': _getID(), 'user_id': uid, 'session_id': session_id,
'current_project_id': pid, 'current_iteration_id': ''})
await sor.sqlExe("COMMIT", {})
if _op == 'create':
name = ((params_kw or {}).get('name', '') or '').strip()
if not name:
return json.dumps({"success": False, "error": "请输入项目名"}, ensure_ascii=False)
if len(name) > 64:
return json.dumps({"success": False, "error": "项目名过长(最多 64 字符)"}, ensure_ascii=False)
# 项目名会作为工作空间目录名alloc_project_dir拒绝路径穿越与非法字符
if ('/' in name) or ('\\' in name) or ('..' in name) or name.startswith('.') or \
(not _re.match(r'^[\w\u4e00-\u9fff .\-()+]+$', name)):
return json.dumps({"success": False,
"error": "项目名含非法字符(仅允许中文、字母、数字、空格 . - _ ( ) +"},
ensure_ascii=False)
# 同产线重名预检:提示走切换,不静默建重名项目
async with DBPools().sqlorContext(dbname) as sor:
_dup = await sor.sqlExe(
"SELECT id FROM sd_projects WHERE name=${n}$ AND pipeline_id=${pl}$ LIMIT 1",
{"n": name, "pl": pipeline_id})
await sor.sqlExe("COMMIT", {})
if _dup:
return json.dumps({"success": False,
"error": "该产线已存在同名项目,请用「切换项目」直接进入"},
ensure_ascii=False)
# 铁律创建项目走能力函数alloc_project_dir 定死目录 + record_audit 审计)
from pipeline_service.project_capability import create_project, start_project
ok, pid = await create_project(
name, project_type='web_app', description='',
org_id=userorg, created_by=uid, who=uid,
pipeline_id=pipeline_id)
if not ok:
return json.dumps({"success": False, "error": str(pid)}, ensure_ascii=False)
# draft → activeCAS+审计),与会话 agent 创建的项目状态一致
await start_project(pid, who=uid)
# 初始迭代(与 agent_loop_v2._t_create_project 行为一致;
# sd_iterations 无 goal 列,范围写 scopeiteration_type 用 'default'
async with DBPools().sqlorContext(dbname) as sor:
await sor.C('sd_iterations', {
'id': _getID(), 'project_id': pid,
'iteration_name': name + '-初始迭代',
'iteration_type': 'default', 'status': 'in_progress',
'priority': 1, 'seq_no': 1})
await _enter_project(sor, pid)
return json.dumps({"success": True, "project_id": pid, "name": name}, ensure_ascii=False)
else: # switch
pid = (params_kw or {}).get('project_id', '') or ''
if not pid:
return json.dumps({"success": False, "error": "缺少 project_id"}, ensure_ascii=False)
async with DBPools().sqlorContext(dbname) as sor:
recs = await sor.sqlExe(
"SELECT id, name, org_id, pipeline_id, created_by FROM sd_projects "
"WHERE id=${p}$ LIMIT 1", {"p": pid})
await sor.sqlExe("COMMIT", {})
if not recs:
return json.dumps({"success": False, "error": "项目不存在"}, ensure_ascii=False)
r0 = recs[0]
p_pl = getattr(r0, 'pipeline_id', '') or ''
p_org = getattr(r0, 'org_id', '') or ''
p_by = getattr(r0, 'created_by', '') or ''
# 产线隔离:跨产线项目不得切入本会话(与 get_session_project_id 语义一致)
if p_pl and p_pl != pipeline_id:
return json.dumps({"success": False,
"error": "该项目属于其他产线,不能切入当前会话"},
ensure_ascii=False)
# 可见范围2026-09-11 用户拍板):本产线 + 本人创建
if p_by != uid:
return json.dumps({"success": False,
"error": "无权切换到该项目(仅限本人创建的项目)"},
ensure_ascii=False)
await _enter_project(sor, pid)
return json.dumps({"success": True, "project_id": pid,
"name": getattr(r0, 'name', '') or pid}, ensure_ascii=False)
# ══════════════════ GET弹窗 widget ══════════════════
if not uid:
return json.dumps({
"widgettype": "PopupWindow",
"id": "agent_project_menu_pw",
"options": {"title": "📁 项目", "cwidth": 30, "cheight": 8, "auto_open": True},
"subwidgets": [{"widgettype": "Text",
"options": {"text": "请先登录", "cfontsize": 1, "color": "#64748b",
"padding": "20px"}}]
}, ensure_ascii=False)
_self_url = entire_url("/pipeline_core/api/agent_project_popup.dspy")
_qb = []
if session_id:
_qb.append("session_id=" + session_id)
if pipeline_id:
_qb.append("pipeline_id=" + pipeline_id)
_qs_base = ("&" + "&".join(_qb)) if _qb else ""
def _destroy_js(pw_id):
return ("var _pw=bricks.getWidgetById(" + json.dumps(pw_id) + ",bricks.app);"
"if(_pw){_pw.destroy();}")
def _open_get_js(op):
"""fetch 本端点 GET op=xxx → widgetBuild 弹出PopupWindow 挂 body从 bricks.app 搜)。"""
return ("var r=await fetch(" + json.dumps(_self_url + "?op=" + op + _qs_base) + ");"
"var d=await r.json();if(d){bricks.widgetBuild(d,bricks.app);}")
def _post_js(fields):
js = "var body=new URLSearchParams();"
for k, v in fields:
js += "body.append(" + json.dumps(k) + "," + v + ");"
js += ("var rp=await fetch(" + json.dumps(_self_url) + ",{method:'POST',"
"headers:{'Content-Type':'application/x-www-form-urlencoded'},body:body});"
"var d=await rp.json();")
return js
def _btn(label, css, script):
return {"widgettype": "Button", "options": {"label": label, "css": css},
"binds": [{"wid": "self", "event": "click", "actiontype": "script",
"target": "self", "script": script}]}
# ── op=menu新建 / 切换 两按钮 ──
if _op == 'menu':
return json.dumps({
"widgettype": "PopupWindow",
"id": "agent_project_menu_pw",
"options": {"title": "📁 项目", "cwidth": 36, "cheight": 11, "auto_open": True},
"subwidgets": [{
"widgettype": "VBox",
"options": {"width": "100%", "gap": "12px", "padding": "18px 22px"},
"subwidgets": [
{"widgettype": "Text",
"options": {"text": "新建一个项目,或把当前会话切换到已有项目",
"cfontsize": 0.85, "color": "#64748b"}},
{"widgettype": "HBox",
"options": {"width": "100%", "gap": "10px"},
"subwidgets": [
_btn("✨ 新建项目", "primary",
_destroy_js("agent_project_menu_pw") + _open_get_js("new")),
_btn("🔀 切换项目", "small",
_destroy_js("agent_project_menu_pw") + _open_get_js("switch")),
]}
]
}]
}, ensure_ascii=False)
# ── op=new输入项目名 ──
if _op == 'new':
_create_js = (
"var cw=bricks.getWidgetById('proj_new_name',bricks.app);var cv='';"
"if(cw){cv=(typeof cw.resultValue==='function')?cw.resultValue():"
"((cw.dom_element&&cw.dom_element.value)||'');}"
"cv=(cv===null||cv===undefined)?'':String(cv).trim();"
"if(!cv){var mn=new bricks.Message({title:'提示',message:'请输入项目名'});mn.open();return;}"
+ _post_js([('op', "'create'"), ('name', 'cv'),
('session_id', json.dumps(session_id)),
('pipeline_id', json.dumps(pipeline_id))])
+ "if(d.success){"
+ _destroy_js("agent_project_new_pw")
+ "var mo=new bricks.Message({title:'已创建',message:'项目「'+d.name+'」已创建,会话已进入该项目'});mo.open();"
"}else{var mf=new bricks.Message({title:'创建失败',message:d.error||'未知错误'});mf.open();}")
return json.dumps({
"widgettype": "PopupWindow",
"id": "agent_project_new_pw",
"options": {"title": "✨ 新建项目", "cwidth": 40, "cheight": 13, "auto_open": True},
"subwidgets": [{
"widgettype": "VBox",
"options": {"width": "100%", "gap": "10px", "padding": "16px 20px"},
"subwidgets": [
{"widgettype": "Text",
"options": {"text": "项目名(将同时作为项目工作空间目录名)",
"cfontsize": 0.8, "color": "#64748b"}},
{"widgettype": "UiStr", "id": "proj_new_name",
"options": {"name": "proj_new_name", "placeholder": "输入项目名",
"width": "100%", "cfontsize": 0.9}},
{"widgettype": "HBox",
"options": {"width": "100%", "gap": "10px", "halign": "right"},
"subwidgets": [
_btn("创建", "primary", _create_js),
_btn("取消", "small", _destroy_js("agent_project_new_pw")),
]}
]
}]
}, ensure_ascii=False)
# ── op=switch本产线项目列表 ──
projects = []
cur_pid = ''
try:
from pipeline_service.workspace import get_session_project_id
async with DBPools().sqlorContext(dbname) as sor:
cur_pid = await get_session_project_id(sor, uid, session_id, pipeline_id) or ''
recs = await sor.sqlExe(
"SELECT id, name, status, created_at FROM sd_projects "
"WHERE pipeline_id=${pl}$ AND created_by=${uid}$ "
"ORDER BY created_at DESC LIMIT 100",
{"pl": pipeline_id, "uid": uid})
await sor.sqlExe("COMMIT", {})
projects = [dict(r) for r in (recs or [])]
except Exception as e:
debug('agent_project_popup switch list error: ' + str(e))
projects = []
rows = []
if not projects:
rows.append({"widgettype": "Text",
"options": {"text": "本产线暂无项目,请先「新建项目」",
"cfontsize": 0.9, "color": "#64748b", "padding": "16px"}})
for p in projects:
_pid_v = str(p.get('id') or '')
_pname = str(p.get('name') or _pid_v)
_st = str(p.get('status') or '')
_st_txt, _st_color = _STATUS_TXT.get(_st, (_st or '—', '#64748b'))
_is_cur = (_pid_v == cur_pid)
_switch_js = (
_post_js([('op', "'switch'"), ('project_id', json.dumps(_pid_v)),
('session_id', json.dumps(session_id)),
('pipeline_id', json.dumps(pipeline_id))])
+ "if(d.success){"
+ _destroy_js("agent_project_switch_pw")
+ "var mo=new bricks.Message({title:'已切换',message:'会话已进入项目「'+d.name+'」'});mo.open();"
"}else{var mf=new bricks.Message({title:'切换失败',message:d.error||'未知错误'});mf.open();}")
rows.append({
"widgettype": "HBox",
"options": {"width": "100%", "gap": "10px", "alignItems": "center",
"padding": "10px 12px", "borderRadius": "6px",
"bgcolor": "#f0fdf4" if _is_cur else "#ffffff",
"border": "1px solid #e2e8f0",
"style": {"cursor": "pointer"}},
"subwidgets": [
{"widgettype": "Text",
"options": {"text": ("✓ " if _is_cur else "") + _pname,
"cfontsize": 0.95, "color": "#0f172a", "css": "filler",
"halign": "left"}},
{"widgettype": "Text",
"options": {"text": _st_txt, "cfontsize": 0.75, "color": "#ffffff",
"bgcolor": _st_color, "padding": "2px 8px",
"borderRadius": "8px", "whiteSpace": "nowrap"}},
{"widgettype": "Text",
"options": {"text": str(p.get('created_at') or '')[:16],
"cfontsize": 0.75, "color": "#94a3b8"}},
],
"binds": [{"wid": "self", "event": "click", "actiontype": "script",
"target": "self", "script": _switch_js}],
})
return json.dumps({
"widgettype": "PopupWindow",
"id": "agent_project_switch_pw",
"options": {"title": "🔀 切换项目", "width": "60%", "height": "70%", "auto_open": True},
"subwidgets": [{
"widgettype": "VBox",
"options": {"css": "filler", "width": "100%", "height": "100%", "gap": "0px"},
"subwidgets": [
{"widgettype": "Text",
"options": {"text": "点击项目行即切换(仅显示本产线、本人创建的项目)",
"cfontsize": 0.8, "color": "#94a3b8", "padding": "10px 14px 4px 14px"}},
{"widgettype": "VScrollPanel",
"options": {"css": "filler", "width": "100%", "padding": "8px 14px", "gap": "6px"},
"subwidgets": rows}
]
}]
}, ensure_ascii=False)

View File

@ -8,17 +8,22 @@ if not id or len(id) > 32:
id = uuid()
ns['id'] = id
# 会话当前项目强制autoproject_id 用当前项目,忽略前端传值
# 会话当前项目强制过滤2026-09-11 升级 v2会话级解析session→全局兜底+产线隔离+悬空自愈),无项目报错
_uid = await get_user()
if not _uid:
_uid = 'user-01'
_dbname = get_module_dbname('pipeline_core')
async with DBPools().sqlorContext(_dbname) as _sor:
_prec = await _sor.sqlExe("SELECT current_project_id FROM pipeline_agent_settings WHERE user_id=${u}$", {"u": _uid})
await _sor.sqlExe("COMMIT", {})
_cur_pid = getattr(_prec[0], 'current_project_id', '') if _prec else ''
_sid = (params_kw or {}).get('session_id', '') or ''
_pl = (params_kw or {}).get('pipeline_id', '') or 'sdlc_general'
_cur_pid = ''
try:
from pipeline_service.workspace import get_session_project_id
_dbname = get_module_dbname('pipeline_core')
async with DBPools().sqlorContext(_dbname) as _sor:
_cur_pid = await get_session_project_id(_sor, _uid, _sid, _pl) or ''
except Exception:
_cur_pid = ''
if not _cur_pid:
return {"widgettype":"Error","options":{"title":"错误","timeout":3,"cwidth":20,"cheight":9,"message":"请先在会话中切换项目"}}
return {"widgettype":"Error","options":{"title":"错误","timeout":3,"cwidth":22,"cheight":9,"message":"当前会话没有项目,请先选择或新建项目"}}
ns['project_id'] = _cur_pid
if not ns.get('role'):

View File

@ -1,20 +1,22 @@
ns = params_kw.copy()
# 会话当前项目强制过滤(auto读 current_project_id,无项目报错
# 会话当前项目强制过滤(2026-09-11 升级 v2会话级解析session→全局兜底+产线隔离+悬空自愈),无项目报错
_uid = await get_user()
if not _uid:
_uid = 'user-01'
_sid = (params_kw or {}).get('session_id', '') or ''
_pl = (params_kw or {}).get('pipeline_id', '') or 'sdlc_general'
_cur_pid = ''
_dbname = get_module_dbname('pipeline_core')
async with DBPools().sqlorContext(_dbname) as _sor:
_prec = await _sor.sqlExe(
"SELECT current_project_id FROM pipeline_agent_settings WHERE user_id=${u}$",
{"u": _uid})
await _sor.sqlExe("COMMIT", {})
_cur_pid = getattr(_prec[0], 'current_project_id', '') if _prec else ''
try:
from pipeline_service.workspace import get_session_project_id
_dbname = get_module_dbname('pipeline_core')
async with DBPools().sqlorContext(_dbname) as _sor:
_cur_pid = await get_session_project_id(_sor, _uid, _sid, _pl) or ''
except Exception:
_cur_pid = ''
if not _cur_pid:
return {"widgettype":"Error","options":{"title":"错误","timeout":3,"cwidth":20,"cheight":9,"message":"请先在会话中切换项目"}}
return {"widgettype":"Error","options":{"title":"错误","timeout":3,"cwidth":22,"cheight":9,"message":"当前会话没有项目,请先选择或新建项目"}}
ns['_cur_pid'] = _cur_pid
userorgid = await get_userorgid()

View File

@ -7,17 +7,22 @@ id = params_kw.id
if not id:
return {"widgettype":"Error","options":{"title":"Error","timeout":3,"cwidth":16,"cheight":9,"message":"需要 id"}}
# 会话当前项目强制autoproject_id 用当前项目,忽略前端传值
# 会话当前项目强制过滤2026-09-11 升级 v2会话级解析session→全局兜底+产线隔离+悬空自愈),无项目报错
_uid = await get_user()
if not _uid:
_uid = 'user-01'
_dbname = get_module_dbname('pipeline_core')
async with DBPools().sqlorContext(_dbname) as _sor:
_prec = await _sor.sqlExe("SELECT current_project_id FROM pipeline_agent_settings WHERE user_id=${u}$", {"u": _uid})
await _sor.sqlExe("COMMIT", {})
_cur_pid = getattr(_prec[0], 'current_project_id', '') if _prec else ''
_sid = (params_kw or {}).get('session_id', '') or ''
_pl = (params_kw or {}).get('pipeline_id', '') or 'sdlc_general'
_cur_pid = ''
try:
from pipeline_service.workspace import get_session_project_id
_dbname = get_module_dbname('pipeline_core')
async with DBPools().sqlorContext(_dbname) as _sor:
_cur_pid = await get_session_project_id(_sor, _uid, _sid, _pl) or ''
except Exception:
_cur_pid = ''
if not _cur_pid:
return {"widgettype":"Error","options":{"title":"错误","timeout":3,"cwidth":20,"cheight":9,"message":"请先在会话中切换项目"}}
return {"widgettype":"Error","options":{"title":"错误","timeout":3,"cwidth":22,"cheight":9,"message":"当前会话没有项目,请先选择或新建项目"}}
ns['project_id'] = _cur_pid
userorgid = await get_userorgid()