Commit Graph

21 Commits

Author SHA1 Message Date
652c160640 feat(runtime): 抽出 RuntimeService 公共任务入口,任务线程拆除 Flask 隐式上下文
Gateway 化阶段一/二实施(公共任务入口),计划与验收记录见 cache_research/gateway/phase12_implementation_plan.md;契约文档 docs/runtime_contract.md 为本地文档按仓库惯例不入库

- 新增 server/runtime/:RuntimeContext 三层模型(TrustedPrincipal/TaskParams/InternalDirectives)+ RuntimeService(create_task/cancel/guidance/queue/get_task_events)
- create_chat_task 强制显式 session_data(缺失 ValueError,i18n key tasks.missing_session_data);6 处调用点迁移至 runtime_service.create_task()(tasks/api、api_v1、workflow_runtime_api x2、chat_flow_task_main x2),门闸 token 移交与 notice 互斥豁免语义保留
- _run_chat_task 拆除 test_request_context 桥,任务线程全程 RuntimeIdentity 驱动
- 审批超时经 terminal._approval_timeout_seconds 透传至工具循环 4 个 _wait_* 调用点(默认 3600s 语义不变)
- 附带:config/_load_dotenv 容忍沙箱禁读 .env;AGENTS.md 同步结构

测试:改造相关 24/24 全绿(冒烟 6 + runtime_service 10 + model_persistence 4 + identity 路由 4);真实环境验证已由用户人工完成

Co-authored-by: Astrion powered by Kimi-K3 <astrion-agent@users.noreply.github.com>
Co-authored-by: Codex powered by ChatGPT-6-Astra <codex@example.com>
2026-09-07 16:16:12 +08:00
7d290d1790 refactor(context): server/context.py 拆分为子包,get_user_resources 参数化
- 989 行单文件拆为 server/context/ 9 模块(identity/broadcast/personalization/usage/upload/conversation/resources/decorators/reaper)+ __init__.py 兼容 re-export,外部 import 路径不变
- 新增 RuntimeIdentity 显式身份快照(identity.py),get_user_resources 支持参数化身份驱动,为任务线程拆除 Flask 隐式上下文做准备
- 测试 patch 目标随迁至 server.context.personalization;新增 test_runtime_identity_resources 覆盖 web/host/api 身份路由矩阵

Co-authored-by: Astrion powered by Kimi-K3 <astrion-agent@users.noreply.github.com>
Co-authored-by: Codex powered by ChatGPT-6-Astra <codex@example.com>
2026-09-07 16:14:53 +08:00
08f7adb0fd fix(containers): 配额只统计 docker 句柄,修复 host 模式工作区列表 503
- ensure_container 的全局/每用户配额闸门仅在创建 docker 句柄时执行,
  host 句柄(会话标识,不占容器池)不再计入任何配额,修复 host 模式
  下第 4 个工作区起 GET /api/conversations 返回 503 的问题
- _has_capacity / has_capacity 全局上限计数同步只统计 docker 句柄
- MAX_ACTIVE_CONTAINERS_PER_USER 默认值 3 -> 8
- admin 面板 available_slots 展示对齐为 docker 句柄计数
2026-09-02 23:39:42 +08:00
0522414a6c fix(security): 第二轮全量 API 审计修复(角色穿越/宿主 GUI/限流收口)
背景:对全部 206 个 HTTP 路由 + SocketIO 事件逐个排查(主审 + 2 个子
智能体初筛后复核),另按用户要求专项审计前端渲染面(无新发现)。
完整报告见 _experiments/security_audit_2026-09-02/REPORT.md 第八章。

新漏洞修复:
- 多智能体角色 role_id 路径穿越(高):save_custom_role 直接拼接
  "{role_id}.md",POST body 的 role_id 无任何校验,可 ../ 穿越覆盖
  其他用户角色文件(跨用户提示词注入),本地已复现写入成功。
  修复:新增 validate_role_id 白名单(^[a-z0-9][a-z0-9_-]{0,63}$),
  role_store 存储层 + multi_agent API 层(POST/PUT/DELETE)双保险
- open-in-file-manager 漏 _is_host_mode_request 检查(同文件其余三个
  端点都有):docker 模式下任意登录用户可触发宿主 GUI 弹窗+探测路径。
  修复:补 host 检查,docker 下 403
- /api/admin/secondary/verify 无限流且 CSRF 豁免:admin 会话前提下
  可在线爆破二级密码(纵深防御缺失)。修复:5 次/300s 用户维度限流
- WS client_chunk_log / client_stream_debug_log 无连接认证检查且
  无限流(日志写盘放大面)。修复:必须已认证连接 + 30 次/60s 滑窗
  + 桶表万级上限回收
- _get_conversation_file_path 直接拼 "{id}.json"(conv_ 前缀恰好阻碍
  直接穿越,属防御深度缺失)。修复:^conv_[A-Za-z0-9_-]+$ 白名单,
  已核验全部 5 个调用点与 temp_ 前缀排除路径不受影响
- /api/conversations/media/<id> 采信 entry mime_type 并 inline 返回。
  修复:text/html、image/svg+xml、xhtml 强制 octet-stream + attachment
  (与 file/content 的 SVG 策略对齐,防存储型 XSS 一致性收口)
- /api/status 每次心跳返回宿主绝对 project_path(泄露系统用户名与
  目录布局)。修复:docker 模式脱敏为容器视角 /workspace,host 不变

LLM 成本攻击止血(第一轮发现 5 的端点级落地):
- POST /api/tasks(GUI 发消息主入口):30 次/60s/user
- POST /api/conversations/<id>/compress(调 api_client.chat 做摘要):
  5 次/300s/user
- 注:对话回顾 review 端点实为纯本地 Markdown 生成(不调 LLM),
  其「发送给模型」模式走 /api/tasks,已被上述限流覆盖;
  按 token 计费的完整配额方案仍遗留待产品决策

验证:全部 py_compile 通过;冒烟测试 6/6;validate_role_id 8 个恶意
样本全拒 + 3 个合法样本放行 + 穿越写入拦截回归通过;conv_id 白名单
4 组样本符合预期。端点级行为待服务重启后实测。

Co-authored-by: Astrion powered by Kimi-K3 <astrion-agent@users.noreply.github.com>
2026-09-02 19:37:34 +08:00
1a4e5baa23 fix(security): Docker 多用户模式全面安全加固(安全审计修复)
威胁模型:拥有普通账号的已登录用户攻击服务器。完整审计报告见
_experiments/security_audit_2026-09-02/(不入库),含实测复现记录。

严重/高危修复:
- 彻底移除文件夹打包下载(/api/download/folder 端点删除,gui/api_v1
  目录下载分支改 410):文件型符号链接会被宿主进程解析,实测可读宿主
  任意文件(含 settings.json 全部 LLM 密钥),删除比补校验更彻底
- XFF 伪造防护:get_client_ip 仅信任 ASTRION_TRUSTED_PROXIES
  (默认空=不信 XFF);登录新增账号维度锁定(5 次失败锁 300s);
  注册邀请码加爆破锁定;限流桶/失败表加 2 万键上限+GC 回收
- auth_debug.log 接入大小轮转;/api/client_debug_log 加限流+长度截断
- /host-login 增加 LINUX_SAFETY 检查且仅回环地址可用

容器加固:
- 默认 cpus=1 / memory=1g,新增 pids-limit=512、memory-swap、
  no-new-privileges
- 新增每用户容器配额 MAX_ACTIVE_CONTAINERS_PER_USER(默认 3),
  防止单用户占满全局容器池

api_v1:
- prompts/personalizations 的 name 加白名单校验(^[A-Za-z0-9_-]{1,64}$),
  修复路径穿越写入;对话元数据中的引用名同步加白名单
- workspaces/conversations/messages/upload 四端点加用户维度限流
- 消息体加 MAX_MESSAGE_CHARS 上限(默认 200000,/api/tasks 同步)
- _path_within 加分隔符边界(修复 startswith 前缀碰撞)

其他:
- SVG 预览强制 application/octet-stream(修存储型 XSS 漏网)
- monitor_snapshot 缓存键加 username 维度(修跨用户快照读取)
- ensure/delete_workspace 加 workspace_id 白名单+父目录二次核验
- delete_folder 拒绝删除工作区根(crud_mixin 与容器代理同步修)
- GuiFileManager 死代码加名称校验防复活
- admin_dashboard 静态壳非 admin 访问一律 404
- /api/app/apk/latest 加登录校验+限流(原未认证可拉 133MB)

有意未修(见报告 7.3 遗留清单):容器 egress 过滤(部署层)、
--cap-drop ALL(怕破坏容器内工作流,已先上 no-new-privileges)、
API 用户/子智能体 LLM 按 token 计费(待产品决策)、str(exc) 收口、
WebSocket 限流、network_permission=restricted 容器语义。

验证:全部文件 py_compile 通过;冒烟测试 6/6 通过;关键新逻辑
(名称校验/路径边界/容器代理删除防护/限流回收)已单测级验证;
端点级行为待服务重启后实测复核(清单见报告 7.5)。

Co-authored-by: Astrion powered by Kimi-K3 <astrion-agent@users.noreply.github.com>
2026-09-02 18:31:00 +08:00
92e4931408 fix(server): 修正宿主机免登录模式文件服务路由的工作区解析
/user_upload/ 与 /workspace/ 路由此前误用 user_manager 的
users/<user>/projects/<id> 结构,宿主机免登录模式下解析到空工作区,
导致已上传文件 404(show_image 引用上传目录图片加载失败)。
改为与 get_user_resources 同源的 host_workspaces.json 解析。

Co-authored-by: Astrion powered by Kimi-K3 <astrion-agent@users.noreply.github.com>
2026-09-02 01:08:31 +08:00
e6d8f194d2 feat(chat): 行内引用胶囊(inline citations)
- 模型输出 【cite:src_xxx】/【file:相对路径】 标记,渲染为可交互来源胶囊(悬停延迟开/点击固定/滚动收起)
- 后端:modules/citations.py 注册表与落库校验(剥离幻觉 id 与不存在的文件);web_search/extract_webpage 注册来源并在工具结果带 citation 信息;assistant 消息落库时 finalize 并挂载 message.metadata.citations
- 前端:marker 输出瞬间即渲染(工具结果查表 + 文件 token 自解析),task_complete 后权威裁决与富化;弹层支持网页摘要/文件内容片段/图片预览,文件头部点击打开右侧预览面板,宿主机模式可在文件管理器中打开
- prompt:三份系统提示加入引用格式与规则(提到文件名不等于引用;图片等二进制文件同样可引用)
2026-09-01 22:31:37 +08:00
deaa1560a5 feat(personalization): 个人空间支持配置对话标题生成模型
新增 title_model 配置,复用子智能体模型库条目生成对话标题:
- 个人空间「常规」页签新增「标题生成模型」下拉菜单(复用子智能体
  模型列表,general 页签挂载时加载)
- 标题生成改为使用所选子智能体模型 profile;未配置时回落模型库
  default_model,移除 AGENT_TITLE_* 环境变量覆盖与主智能体默认
  模型回退(个人空间为唯一配置来源)
- personalization 白名单注册 title_model 键(默认值 + sanitize)
2026-09-01 09:46:05 +08:00
55b3ee44b7 feat(sandbox): Windows 沙箱环境检测弹窗与一键安装向导 2026-09-01 01:01:09 +08:00
6c0a32330c feat(stream): 流式断流「清除重来」自动重试机制
后端↔API 流式连接在输出中途断开时,旧行为直接终止任务。现改为:
- 网络类断流(status_code 为空)有接收也重试,与零接收共用 5 次
  额度、间隔 10s;4xx 业务错误保持原逻辑
- 重试前发 stream_reset 事件,前端清除本轮已渲染的半截内容
  (思考块含已闭合的/文本/preparing 工具条目),显示「连接中断,
  正在重试(第 n/5 次)…」占位,socket 与 REST 轮询双通道生效
- 修复断流作废轮 usage 重复计数
- 子智能体同构改造:网络断流重试耗尽转 idle 汇报 Team Leader,
  重试前将已推送的「正在调用」工具条目标记 cancelled
2026-08-31 19:58:43 +08:00
8257e4d34d feat(security): 沙箱只读真强制与权限模式边界收敛
- docker:只读/审批档 run_command、后台命令与持久终端改用非特权 uid(10001) 执行角色,内核 DAC 强制只读,取代文本特征识别;Dockerfile 加固(agent 用户 / git safe.directory / 去 setuid)
- macOS:只读与可写沙箱 profile 统一为白名单读模型(deny default + 系统目录/工作区/路径授权),修复 deny 顺序导致的工作区 .env 实际可读漏洞;可写 profile 白名单化后审批不再放大读取,越界读取唯一途径为路径授权
- 权限模式:受限档(readonly/approval/auto_approval)与 direct 执行环境硬互斥——进入受限档压回沙箱并记录,切回 unrestricted 恢复,存量受限+direct 对话加载自愈矫正
- 配置:路径授权来源收敛为 host_sandbox_policy.json + 环境变量两个通道(移除 settings.json 映射)
- 修复:新建对话权限模式被个性化默认值覆盖、/new 切只读后回落无限制的继承 bug
- 原生文件工具读边界与沙箱白名单同源对齐
2026-08-30 22:07:14 +08:00
a30b03fb93 fix(i18n): 未登录 401 返回双语文案「登录状态已过期,请重新登录」
- auth_helpers.py / app_legacy.py 的 api_login_required 401 响应改用 tr('auth.session_expired')
- 新增 i18n key:zh 登录状态已过期,请重新登录 / en Your login session has expired. Please log in again.
2026-08-30 00:33:43 +08:00
2a870c5a35 fix(multi-agent): 多智能体对话创建继承 work_mode,/new 切换后新建对话不再回落默认
Co-authored-by: Astrion powered by DeepSeek-V4-Flash <astrion-agent@users.noreply.github.com>
2026-08-29 15:26:39 +08:00
0b3d98d91b feat(stats): token 统计新增缓存命中追踪与命中率展示
- utils/token_usage.py:usage 归一化补全缓存命中字段提取,覆盖
  prompt_tokens_details/input_tokens_details.cached_tokens(OpenAI 系)、
  顶层 prompt_cache_hit_tokens(DeepSeek)、顶层 cached_tokens(Kimi/Step)、
  cache_read_input_tokens(Anthropic 系)、cachedContentTokenCount(Gemini);
  Anthropic 语义下把缓存读/写加回总输入以统一口径,normalize 保持幂等
- 对话级统计新增 total_cached_input_tokens 与 cache_exempt_input_tokens
  (首轮/深度压缩后首轮未命中缓存的输入视为冷启动成本,豁免出命中率分母;
  压缩通过 cache_cold_start_pending 标记在下一次真实调用时判定)
- token_update 广播与 token-statistics 接口同步携带新字段
- TokenDrawer 面板新增「累积缓存输入」「缓存命中率」(前端按 缓存/(总输入-豁免) 换算)
- 深色模式下「当前上下文」数字由灰色 --accent 改为 --text-primary(白)
- 附 cache_research/ 各厂商缓存字段调研文档(代码注释引用)
2026-08-29 12:43:14 +08:00
a2cf547400 feat(i18n): 后端用户可见消息国际化(zh/en 双语 + ui_locale 偏好持久化)
- 新增 modules/i18n.py:tr() + 进程级语言缓存 + modules/i18n_messages/ 域文案包自动聚合
- 新增 29 个域文案包,共 1153 条双语 key;90+ 源文件 1146 处用户可见消息 tr 化
- ui_locale 存入 personalization.json(用户级共享),前后端双向同步
- 前端匹配点双语兼容(history/shared/ChatArea/taskPolling/upload 等正则)
- 修复语言判等陷阱:审批等待加稳定 code 字段;conversation.py 不存在判等改双语 helper
- 边界:日志/prompt 注入/子智能体工具回填/容器内嵌脚本不迁移
2026-08-29 07:58:29 +08:00
f9f9032ce5 fix(tasks): 用户取消的主任务统一以 stopped 收尾,根治死任务事件重放导致的显示回退
- 收尾不再按 has_bg 分流保持 cancel_requested 等待已废弃的「第二下点击」,
  cancel_requested 退化为 cancel_task 到收尾之间的秒级瞬态
- 此前有后台任务时按下停止,任务永久卡在活跃集合,被 bootstrap/running-status
  当作最新活跃主任务,前端对账每 2.5s 从事件 0 全量重放死任务事件流,
  表现为回复完成瞬间显示回退到旧回复、刷新空白卡死且无效
- cleanup_old_tasks 清理集修正:"canceled" 是从未被赋值的拼写(实际终态为
  stopped),改为 {succeeded,failed,stopped,canceled,cancel_requested} 兜底
2026-08-28 20:12:20 +08:00
4cf9c38137 feat(avatar): 状态头像新增「等待 API 响应」并修复异常中断状态卡住
- 后端每次发起 API 请求前(含每次重试)发 api_request_start 事件
- 前端新增 apiRequestPending 状态,轮询/socket 双链路维护,
  响应开始(thinking_start/text_start/tool_preparing)或任务终结时清除
- InputComposer 头像旁显示固定文案「等待 API 响应…」(work+apiWaiting 标记)
- chat store 新增 clearStreamingResidualState 统一清理流式残留字段,
  接入全部终结路径(handleTaskError/Stopped/Complete、运行状态对账、
  resetAllStates、socket error),修复断网/停止后形象卡「思考中」
- handleTaskError 补 clearPendingTools 与空占位消息清理
- avatarStatus isThinking 增加运行态防御,双保险防状态卡死
2026-08-26 15:11:18 +08:00
d5a7c94c8e fix(server): /new 页面切换模型被工作区 terminal 残留 has_images 误拦
重启后工作区级 terminal 自动加载最近对话恢复焦点时,会从其 metadata
无条件恢复 has_images/has_videos(attach_history=False 不挂历史但标志仍残留)。
/new 页面切换模型不带 conversation_id,前端守卫已放行,但后端 set_model
守卫读到残留标志误抛「当前对话包含图片」。

/api/model 端点识别 /new 语义(无 conversation_id 且 terminal 未绑定对话)时,
切换前清除残留标志——与 create_new_conversation「新对话视为干净会话」语义一致;
显式打开对话时 load_conversation_by_id 会从 metadata 重新恢复,清除安全。
2026-08-26 10:23:01 +08:00
2d9b4eb5a6 fix(conversation): 修复版本回溯被旧内存merge覆盖,统一presend用户输入口径
- 回溯后同步对话级 terminal 内存为磁盘快照,避免 merge-on-save 把被裁消息当"内存独有"救回(此前只能回溯后立刻重启进程)
- 工作区级服务实例不再挂载对话历史(focus-only 仅恢复焦点+模式),settings/通知判定/工作流游标等保存路径改读磁盘回传,消除旧内存写回污染源
- 左侧输入导航与深度压缩的用户输入口径补 presend(提前输入排队转正),guidance 维持不计入;统一字段缺失老消息视为 user
2026-08-25 22:43:58 +08:00
9d3784bd90 refactor(compression): 压缩注入引导语与存档不再重复附带用户最近一次输入
最近一次输入本身会作为正常消息出现在上下文中,注入消息里再附一份属于重复
2026-08-23 17:13:02 +08:00
10654c407d release: sync from main @0f9fd1b2 (2026-08-22) 2026-08-22 12:27:19 +08:00