Skip to content

Feat/agent runtime node - #1

Merged
MarisaMagic merged 20 commits into
mainfrom
feat/agent-runtime-node
Sep 19, 2026
Merged

MarisaMagic merged 20 commits into
mainfrom
feat/agent-runtime-node

Conversation

@MarisaMagic

Copy link
Copy Markdown
Owner

No description provided.

MarisaMagic and others added 16 commits September 19, 2026 01:02
新增 docs/agent-protocol.md 作为协议单一真源,覆盖 SSE 字段派生规则、事件生产者边界、请求体契约、18 个工具规格与循环行为。新增 scripts/agent-baseline 工具链:mock LLM 服务(支持分片 tool_call arguments)、golden SSE 抓取、归一化与逐事件 diff。已针对 Python 实现抓取 11 个场景作为冻结基准。

Co-authored-by: Cursor <cursoragent@cursor.com>
新增 src/main/agent:utilityProcess 入口(runtime.ts)、主进程宿主(host.ts)、对称 RPC 桥(bridge.ts)、主进程能力面(ipcSurface.ts)、回环 HTTP 服务(server.ts)与路由表(routes.ts,阶段 1 为 501 骨架)。webpack 增加 agentRuntime 入口。workspace handler 回调体抽为 workspaceOperations.ts,供渲染层 IPC 与运行时 RPC 双端复用,保证授权根校验与 file-system 广播行为一致。serverProcess.ts 增加 LR_AGENT_RUNTIME 开关(默认仍走 Python,Node 侧阶段 2 前只有骨架)。新增 Electron fork 冒烟脚本,已通过真实 utilityProcess 验证 fork/ready/RPC/优雅退出。

Co-authored-by: Cursor <cursoragent@cursor.com>
新增 SSE 契约模块(toSseDict 逐条复刻字段派生规则)、OpenAI 兼容流式客户端与 tool_call 分片累积器、Assist 循环(轮次预算、五道过滤、阶段门禁、tool_choice=any 兜底、取消)、工具注册表(zod 为校验与 schema 单一来源)与只读工具(读取/搜索/列目录/元信息)。新增 workspacePath 移植路径解析与安全校验,fileSearch 用异步 I/O 实现栈式 DFS 遍历。golden diff 结果:11 个场景中 7 个通过(含 reasoning 的已记录差异),剩余 4 个属阶段 3/6 范围。修复过程中发现真实缺陷:工具可执行性必须以模式裁剪后的工具集为准而非全量注册表,否则越界调用会错误执行且 tool_choice=any 兜底会误触发。

Co-authored-by: Cursor <cursoragent@cursor.com>
移植 ProposalStreamInterceptor:在 astream 期间按 tc_index 增量解析半截 JSON(含 uXXXX 转义与代理对合成),实时发出 file_proposal_start / file_proposal_delta / file_edit_delta;按码点而非 UTF-16 码元计数,避免 emoji 内容被切在代理对中间。移植 4 个写入类提案工具与 pendingProposals 基线接力(同轮多次 str_replace 累积为同一份提案)。新增 pythonJsonDumps 对齐 json.dumps 的分隔符空格,修正提案失败时原样回灌完整 JSON 的分支赋值语义。golden diff 11 场景通过 10(仅余 subagent 属阶段 6 范围);主进程测试 269/269 通过。

Co-authored-by: Cursor <cursoragent@cursor.com>
新增 agentImage 模块:文件头解析(PNG/JPEG/GIF/WebP/BMP/ICO 的尺寸与格式)+ 基于 nativeImage 的裁剪/缩放/JPEG 编码,零新增依赖替代 Pillow;显式把 RGBA 合成到白底以对齐 Pillow 的 JPEG 输出。新增 imageService 分层:探测为纯函数(独立模式亦可),编码经 RPC 走主进程。补齐 read_file_annotation(直接 import 主进程 annotationDataStore 复用 sha256 fileKey,无重复实现)、read_document_file(DOCX 用 mammoth 懒加载,PDF 用自研轻量提取器,覆盖 FlateDecode 与 Tj/TJ/引号算子、转义、UTF-16BE)、read_image_for_vision(附图标记与路径存在性校验)。顺带修正 Python 侧 pypdf/python-docx 未声明依赖导致该工具在干净环境不可用的问题。golden diff 保持 10/11;主进程测试 308/308。

Co-authored-by: Cursor <cursoragent@cursor.com>
新增 context/multimodal(路径优先的附图构造,编码失败退化为纯文本)与 vision/autoLoader(VisionAutoLoader 等价实现:触发条件严格合取,仅首轮无 tool call 时兜底一次)。接通 nativeImage 编码能力。修正一处语义:视觉结果显示用 stripInternalMarkers 的完整缩进 JSON,而非 formatToolResultForDisplay 的 summary/message 折叠。golden diff 保持 10/11;主进程测试 336/336。

Co-authored-by: Cursor <cursoragent@cursor.com>
新增 subagent/exploreReadonly:嵌套只读循环(8 个内层工具白名单、禁用名单含记忆与视觉工具、子代理轮上限、收尾摘要轮不带工具),产出完整 subagent_* 事件族;保留三处与主循环的刻意差异(不做父级去重/门禁、allowed 回灌原文而 blocked 用完整 JSON、收尾轮不绑工具)。接入主循环时为 explore 调用单开一路(tool_start 只发一次,与基线一致)。新增 mcp/client:用已依赖的 @modelcontextprotocol/sdk 替换 langchain-mcp-adapters,保留 TTL 缓存 + stale-while-revalidate + 单台失败隔离 + 名字去重;MCP 工具自带 JSON Schema 直接透传(新增 rawParameters)。接通 /agent/mcp/probe。golden diff 达成 11/11 全通过;主进程测试 362/362。

Co-authored-by: Cursor <cursoragent@cursor.com>
新增 annotate 模块:公共基础(extractJsonObject 对齐 Python 的三步解析、invokeJsonModel 刻意不用 response_format、Unicode 孤立代理清洗、LLM 错误分流)、zod 重建的四个请求 schema、mutation-prepare(含 Pydantic model_dump 等价实现:输出全部 28 个字段并把未设值者置 null)、llm-generate(图片三分支:加载失败 400 / 路径优先 base64 兜底 / 压缩失败回退原图)、质量报告流式撰写(payload 512KB 校验 + 48k 截断,异常时发 error 事件)。扩展 AgentSettings 补全标注相关配置项。主进程测试 422/422。

Co-authored-by: Cursor <cursoragent@cursor.com>
新增 5 个支撑模块:scope(中文范围推断,含贪婪正则导致的「同一行合并」行为)、visionPolicy(81 项 COCO 类名表)、heuristic(三级优先级的类名/OCR 匹配)、validation(四条校验规则 + 逐框重试候选)、labelPool(整图 preflight 缩池,user_request 刻意不参与缩池以免误伤)。mapLabels 实现两条映射路径(vision_crop 走 nativeImage 批量裁剪 + 校验重试;heuristic 无 LLM)与 single_label 快捷路径。修正 cropImageBatch 的钳位以完全对齐 Python 的 max(left+1, ...) 语义。新增 pyRepr/pyIntListRepr 使含 {name!r} 的错误文案与映射理由逐字一致。四个标注端点至此全部迁移;golden diff 保持 11/11;主进程测试 513/513。

Co-authored-by: Cursor <cursoragent@cursor.com>
删除 vendor/local-agent(125 个文件、约 16000 行)与 src/main/localAgent/serverProcess.ts;主进程改为直接启动 Node 运行时(utilityProcess)。移除嵌入式 Python 运行时:删除 scripts/fetchPythonRuntimes.mjs、assets/python-runtime 与对应 extraResources,package 脚本不再拉取运行时。env 体系收敛:LocalAgentEnvStatus 去掉全部 Python 字段(改为仅报告服务是否运行)、InstallTarget 只剩 inference、环境向导与横幅不再要求 Python(Agent 编排面板改为纯状态展示)、skillScriptRunner 的 .py 解释器降级为系统 Python。inference 保留为可选自装(conda),安装入口在缺少内置运行时后给出明确的自建指引。README 与基线文档同步更新;冻结基准 .baseline/python 保留(原实现已删,无法重新生成)。主进程测试 513/513;golden diff 11/11。

Co-authored-by: Cursor <cursoragent@cursor.com>
electronForkSmoke 的 /chat/cancel 断言从「骨架 501」更新为「已实现 200 + {ok:true}」。三处 SSE 字段兜底补充注释,说明主字段为 camelCase、snake_case 分支仅用于历史数据渲染(运行时不会产出),保留而不删除。全部测试 935/935。

Co-authored-by: Cursor <cursoragent@cursor.com>
四类缺陷:

1) 流式故障静默(src/main/agent/loop/assistLoop.ts、sse.ts):主循环的 astream 缺 try/catch,LLM 失败时异常被 SSE 层吞掉、客户端读到 EOF 后合成 done,用户只看到回复被截断而无任何提示。新增 sse.error 事件与 streamAssist 兜底外壳;取消(isCancelled/AbortError)不报错,避免「点了停止却弹错误」。

2) 凭据错误码误报(src/main/agent/annotate/common.ts):requireCredentials 抛出的 400 provider_credentials_required 被 httpFromLlmError 落进 502 兜底分支,把「凭据没填」报成「上游坏了」。改为 AnnotateHttpError 原样透传,并读取 LlmRequestError.status 正确映射上游 4xx。

3) 运行时崩溃后永久失联(src/main/agent/host.ts):退出后无人重启且渲染层缓存 baseUrl,后续请求全部打向死端口。新增指数退避自动重启(500ms→8s、上限 5 次、稳定 30s 清零),启动阶段失败同样计入预算;stopAgentRuntime 置 intentionalStop 并取消待触发定时器。

4) 标注文档错位(src/renderer/context/AnnotationWorkspaceContext.tsx):加载流水线分开赋值 currentPairRef/loadedMetaRef/dirtyRef,在 React 异步提交窗口内防抖保存会把 A 的文档写进 B 的槽位,导致 filePath/source/annotations 全部错位。改为同步原子写 ref、加载前取消悬挂保存,并在 persistFromRefs 加「meta.filePath 与 relPath 必须一致」守卫(不一致则跳过保存而非写坏数据);加载时就地修正历史脏 filePath,使其可继续保存。annotationDoc 返回时按请求路径对齐 filePath,避免把历史脏数据当作工具字段错位反馈给模型。

验证:tsc 通过;jest 935/935 + 新增 16 个用例(退避策略 9、标注文档 7);golden diff 11/11 无回归。

Co-authored-by: Cursor <cursoragent@cursor.com>
补齐上一提交的配套缺陷:运行时会换端口与 token,但渲染层把地址缓存为模块级常量且永不失效,导致重启后请求全部打向已死端口且无法自愈(表现为 network error 后必须手动刷新页面)。

config.ts:新增 invalidateLocalAgentAuth/hasLocalAgentAuth;localAgentFetch 增加一次自愈重试——网络异常时清缓存并重新向主进程解析地址后重试(覆盖「旧端口已死」与「此前回退到默认端口」两种情形),401 且此前发过 token 时同样重试(覆盖「端口复用但 token 已换」);用户取消不重试;重试后仍失败统一抛「本机 Agent 服务未就绪」,不再把裸 Failed to fetch 漏给 UI。重试按 URL 改写 protocol+host,5 个调用点无需改动。

EnvironmentContext:订阅 localAgent:status 时失效缓存,使主进程推送的 running(新端口)立即生效。

docs/agent-protocol.md:error 事件的生产者边界由「仅渲染层合成」更正为「运行时在非取消故障时也会发射」,并说明取消不报错的语义。

验证:tsc 通过;jest 962/962(新增 config 缓存自愈 11 例);golden diff 11/11。

Co-authored-by: Cursor <cursoragent@cursor.com>
按「结果可审阅性 × 错误代价」分层,而非「是否文件改写」:

免确认(直接落盘)—— 工作区文件写入/替换/删除/移动(4 个 PROPOSAL 工具)与标注编辑(mutate_annotation)。前者 diff 可扫读、指令明确;后者是定点修改且有改前快照兜底。两者都随时可撤销。

保留确认 —— 标注生成(auto_annotate)。由模型推理产出、审阅需逐框看图、replace_matching 会覆盖已有标注,错误代价最高。

实现:
1. agentProposalApply 新增 selectAutoApplicableProposals / applyAutoApplicableProposals,按 sourceKind==='mutation' 与 file_proposal 筛选;必须等 contentFinalized 为真才落盘(否则会写入空内容/半截内容);sourceKind 缺失时用与 agentChatStore 同一份 inferAnnotationProposalKind 做内容推断(该函数已提到 pipelineKinds.ts 共用)。
2. AgentChatContext 以响应式 effect 触发自动应用(覆盖重启恢复与历史重放),带重入守卫与失败去重(失败保持 pending 并提示,不静默吞掉、不反复刷错)。
3. agentJobRegistry 的 formatAnnotationToolResult 新增 autoApplied:mutate_annotation 报 proposal_pending=false + file_written=true,因此不再进入 AwaitingConfirm 断点(该断点现仅由标注生成与终端审批触发)。
4. 撤销入口显式化:复用已有 undoAssistantChanges,落到消息的 Files Changed 汇总卡右上角与消息工具栏。
5. checkpoint 每会话上限 20 → 50,避免免确认后写入变多时最早(也最可能需回滚)的快照被先行清除。

文案同步:fileProposal 的工具结果、registry 的工具描述、contextService 的 resume 提示、协议文档与类型注释均改为「已直接落盘、可撤销」。

验证:tsc 通过;jest 977/977(新增 15 例:筛选 9、格式化 3、checkpoint 上限 1 等);golden diff 11/11 —— 其中 proposal-write / proposal-edit 的 tool_result 文案差异属本次有意变更,已在 KNOWN_DIVERGENCES 中以**字段级**豁免记录(其余字段、事件顺序与条数仍严格校验),并用负向测试确认 gate 未被削弱。

Co-authored-by: Cursor <cursoragent@cursor.com>
1) 竞态修复(方案 B):标注工具是 ASYNC,每次调用会发 tool_pending 并 resume;resume 前会同步构建 proposal_states,若此时提案仍是 pending,阶段机会误判 AWAIT_CONFIRM,把紧随其后的 auto_annotate(重新标注)拦掉——表现为「删除已落盘、但重新标注被拒」。改为 ClientToolContext 提供 applyAutoApplicable 回调,客户端工具在产出提案后 await 它完成落盘,再据实组合工具结果(成功→proposal_pending=false;失败→保持 true 并如实报告,job 停在确认断点等用户处理,绝不谎报已写盘)。AgentChatContext 中的 effect 退化为兜底(覆盖重启恢复与历史重放),并把落盘逻辑提为 flushAutoApplicable 供两条路径共用。

2) 措辞清理:模型此前会提到 Keep All,因为 RESUME_APPLIED_NEXT_HINT 声称「用户已 Keep All」、VERIFY 阶段提示写「已由用户 Keep All 并写盘」——但免确认后落盘由系统完成。改为「改动已落盘/标注变更已落盘」,AWAIT_CONFIRM 文案明确指向「标注生成提案」(该断点现在只由标注生成与终端审批触发)。同步修正 4 处「经用户确认落盘」与若干过时注释。

3) UI:删除 Files Changed 卡片里重复的撤销入口,只保留消息工具栏的撤销图标。

验证:tsc 通过;jest 979/979(新增自动落盘失败与优先级 2 例,修正 1 处措辞断言);golden diff 11/11。

Co-authored-by: Cursor <cursoragent@cursor.com>
运行时与协议:LlmClient 增加网络层重试(指数退避,取消/超时/HTTP 错误不重试);MCP 客户端区分传输类型,SSE 复用发现连接供 invoke 使用(会话绑定服务端不再「列出成功、调用失败」),连接加超时兜底、清缓存时统一关闭;新增内置 async 工具 start_terminal_command(锁定工作区 cwd,需用户批准)。

任务阶段门禁:�pplied 只看本轮且只看生成类操作(append/replace/replace_bboxes),修复「先删除再重新标注」被自身前置删除拦死;pending 仍按全会话聚合。proposal_states 新增 sourceKind / inCurrentTurn 判据并贯通 schema 与前端账本。

标注与快照:新增标注文档 ilePath 冗余字段幂等修复(归属真源是存储键),避免加载时改写字节导致 Undo 误判脏;检查点只要捕获成功即可回滚,
estore 支持 orce 跳过完整性与脏检查。

预标注体验:模型下拉空态给出可读原因,检测模式声明与权重路径证据矛盾时内联告警;补充相应单测与协议文档。

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread scripts/agent-baseline/electronForkSmoke.cjs Fixed
Comment thread src/main/agent/server.ts Fixed
Comment thread src/main/agent/server.ts Fixed
MarisaMagic and others added 4 commits September 19, 2026 16:21
CI 的 npm run lint 一直是红的(330 error / 248 warning、涉及 61 个文件),
根因是迁移期新写的文件从未经过 prettier 校验——该分支此前一次 CI 都没跑过。
其中 325 个可自动修,本次用 eslint --fix 一次性清干净。

剩余 6 个为不可自动修,逐一最小改动处理(语义等价):
- heuristic.ts: 两处 chosen = arr[0] 改数组解构
- proposalStreamer.ts: indexOfSequence 的带标签循环改为 matched 标志位;
  mergeArgs 中「已见前缀重复投递 → 忽略」的空分支补注释(no-empty 允许含注释块)
- llm/client.ts: 3 个类同属单一协议层(错误契约 / 客户端 / 流式累积器),
  拆分只会切碎契约,故加带理由的文件级 max-classes-per-file 豁免

prettier 之后 lint 从 330 error 降到 0;248 个 warning
(no-console 142 / react-hooks/exhaustive-deps 52 / no-unused-vars 41)按现状保留,不阻塞 CI。

顺带修 lockfile 与 package.json 不同步:zod 只加进了 package.json 而
package-lock.json 未更新(root deps 缺失,仅 node_modules 里有),
npm install 重新生成后 +zod 一行。修掉后 CI 才具备切换到 npm ci 的前提。

验证:npm run lint 0 error;npm exec tsc 通过;npx jest --ci 113 suites / 1036 tests 全通过。

Co-authored-by: Cursor <cursoragent@cursor.com>
编排已迁到 Node 运行时,但 workflow 一行没动,导致两处必挂:
- python-envs 的 npm run fetch-python-runtimes 脚本与 fetchPythonRuntimes.mjs
  都已删除(本地实测 Missing script),后续 smoke-test 连坐 skip;
  即便过了,scripts/ciInstallAndTest.mjs 也硬引用已删的
  vendor/local-agent/requirements.txt 与 assets/python-runtime/py312。
- frontend 的 Cache embedded Python runtimes 步指向不存在的路径,
  hashFiles 取到空哈希,退化为空转。

test.yml 重写为 5 个互不依赖的 job(原先 lint 一挂就把
  typecheck / test 全 skip,一次只能拿到三分之一信号):
- lint / typecheck / test / package 拆开,各自独立给信号
- test 先跑 npm run build,因为 jest 的 setupFiles 里
  check-build-exists.ts 会断言 dist 产物存在
- 新增 baseline-selftest:Agent 运行时的协议契约测试,此前完全没进 CI;
  该脚本零外部依赖(scenarios.mjs 无 import),故不装 node_modules
- python-envs 重做为 inference-lite:不再建 venv 装 torch/sam2 跑 90 分钟
  冒烟(嵌入式运行时已移除,产品语义也变成用户自备 conda),改为零依赖静态校验

新增 scripts/ciCheckInferenceManifests.mjs,把原来只靠注释互相声称的
「envInstaller.ts 与 requirements 保持字面同步」变成 CI 可验证的断言:
SAM-2 改写链真能命中、torch 索引 URL 与清单本地版本标签一致、
cpu/gpu 两份清单除 torch 系列外零漂移、VERIFY_IMPORTS 每个 import 名
都能落到发行包、Python 编排残留引用不得回归、推理服务仍在打包清单内。

清理死代码:删 scripts/ciInstallAndTest.mjs;.eslintignore 移除
assets/python-runtime/ 与 .ci-venvs/;.gitignore 移除对应段;
envInstaller.ts 头部注释不再声称使用嵌入式运行时。
CodeQL 注释对齐(python 矩阵现在只覆盖 vendor/inference)。

验证:npm run lint 0 error;npm exec tsc 通过;npm run build 成功
(产出 dist/main/agentRuntime.js);npx jest --ci 113 suites / 1036 tests
全通过;npm run baseline:selftest 全通过;新检查脚本全通过
(并已实测其在残留引用存在时确实报错)。

Co-authored-by: Cursor <cursoragent@cursor.com>
SECURITY.md 通篇仍是 Python 编排时代的描述,其中『本地 Agent(vendor/local-agent)
经 FastAPI require_local_token 鉴权』与『cd vendor/local-agent && python -m pytest』
两处已经跑不了。按现状改写:
- 头部定位改为 Electron + Node 版 Agent 运行时
- 本地服务鉴权:改为 utilityProcess.fork 启动 agentRuntime.js、随机 token 经
  LR_AGENT_LOCAL_TOKEN 注入、server.ts 除 /health 外常量时间 Bearer 校验、
  未配置 token 时拒绝启动(与迁移前同一失败关闭语义)
- 写盘白名单去掉已不存在的 Python 副本引用,唯一真源仍是
  src/shared/workspaceTextExtensions.ts
- 测试一节改为实际的 npm run lint / npm exec tsc / npm test / baseline:selftest,
  并说明推理侧(vendor/inference)无单测、由 CI 做语法与清单校验
- 反序列化审计去掉 vendor/local-agent(其替代实现是 TS,由 javascript 矩阵覆盖)

另:.erb/scripts/check-build-exists.ts 原先只断言 dist/main/main.js 与
dist/renderer/renderer.js。Agent 运行时是独立 webpack 入口,缺失时只在运行期
才失败,构建与 jest 都无感,故补上 agentRuntime.js 的断言
(已实测:移走产物后 jest 立即以明确提示失败)。

Co-authored-by: Cursor <cursoragent@cursor.com>
CodeQL 的 code-scanning 门禁(check 名也叫 CodeQL,与 workflow run 的 success
是两回事)在 PR 上一直红,3 个告警自分支首次推送 485419c 即存在,与本次 CI 改造
无关(只是 prettier 格式化让 server.ts 的行号移了一位)。

真问题(已修):路由 handler 抛出未预期异常时,500 响应把 err.message 回传给
调用方,命中 js/stack-trace-exposure —— 异常消息常夹带绝对路径与依赖库内部片段。
改为响应体只含 { detail: 'internal_error' },完整消息只进服务端日志
(onLog → 主进程),开发期看日志即可。
没有采用「加开关按环境回传」的方案:那样污点流在静态上依然存在,告警不会消失。

误报(按 query id 排除,附理由):
- js/cors-misconfiguration-for-credentials:本仓库的 CORS 实现正是该查询官方文档
  给出的 GOOD 写法 —— Origin 必须命中显式白名单才回显(未命中连 ACAO 头都不设),
  且启动期 assertCorsConfig 拒绝 '*'。CodeQL 看不穿 resolveCorsOrigin 这层 helper。
- js/clear-text-logging:唯一命中点是本地冒烟脚本 electronForkSmoke.cjs(不参与
  打包),被判「敏感」的实际只有 HTTP 状态码与 {"ok":true} 这类响应体。

新增 .github/codeql/codeql-config.yml 按 query id 排除这两条,不改动查询集(保持
默认 code-scanning suite),每条排除附理由与依据链接;workflow 以 config-file 引用。
SECURITY.md 同步记录该排除策略与「错误详情不外泄」的约定。

验证:npm run lint 0 error;npm exec tsc 通过;npx jest --ci 113 suites /
1036 tests 全通过(server.test.ts 新增用例断言 500 响应体不含内部细节、
但完整消息仍进服务端日志)。

Co-authored-by: Cursor <cursoragent@cursor.com>
@MarisaMagic

Copy link
Copy Markdown
Owner Author

CI 改造与 CodeQL 处置说明

分支此前从未推送过,因此这条流水线一次都没跑过。首次推送后暴露的问题分三类,已全部处理:Test 6/6 job 全绿,CodeQL 门禁 No new alerts,PR 状态 clean。

一、原 python-envs job 的两处必挂点(架构迁移遗留)

编排已迁到 Node 运行时,但 workflow 一行未动:

  • npm run fetch-python-runtimes 脚本与 scripts/fetchPythonRuntimes.mjs 均已删除 → 该步必然 Missing script,后续 smoke-test 连坐 skip。
  • 即便过了,scripts/ciInstallAndTest.mjs 也硬引用已删的 vendor/local-agent/requirements.txt 与 assets/python-runtime/py312。
  • 附带:Cache embedded Python runtimes 步指向不存在的路径,hashFiles 取到空哈希,退化为空转;Cache pip wheels 的 key 同样引用了已删文件。

二、Test job 结构缺陷

原先只有一个 frontend job,npm run lint 一挂就把 typecheck / npm test 全部 skip —— 一次只能拿到三分之一的信号。本次实测即如此:lint 失败时后两步均为 skipped。

根因是分支攒下的 330 个 lint error(涉及 61 个文件,其中 prettier/prettier 301 条)。这是迁移期新写的文件从未经过 prettier 校验所致,与本次改动无关:61 个问题文件中 59 个不在本次提交范围内。

三、CodeQL 门禁(一个容易误读的点)

CodeQL 的 workflow run 是 success,但 GitHub Advanced Security 发的 code-scanning 检查(check 名同样叫 CodeQL)是 failure —— 后者才是让 PR 显示 mergeable_state=unstable 的那个门禁。3 个告警自分支首次推送即存在。


改动清单(4 个 commit)

commit 内容
bb4c6e6 chore(lint):330 error → 0(325 自动 + 6 手工),同步 package-lock.json 补 zod
2f63a48 ci:test.yml 重写为 6 个独立 job、新增清单校验脚本、删失效脚本、codeql/eslintignore/gitignore 清理
457333e docs:SECURITY.md 对齐 Node 运行时 + check-build-exists 补 agentRuntime 断言
e19defa fix(agent):不再回传异常消息 + 排除两条 CodeQL 误报

.github/workflows/test.yml:2 job → 6 个互不依赖的 job

lint / typecheck / test / package 拆开,各自独立给信号;test 因 jest 的 setupFiles(.erb/scripts/check-build-exists.ts)会断言 dist 产物存在,故先跑一次 npm run build。

新增两个 job:

  • baseline-selftest —— Agent 运行时的协议契约测试(起 mock LLM server 校验 SSE chunk 序列与 tool_call 分片重组),此前完全没进 CI。该脚本零外部依赖,故不装 node_modules。
  • inference-lite —— 替代原 python-envs。预标注推理(vendor/inference)仍是发布产物,但它依赖的嵌入式运行时已移除、产品语义也变成「用户自备 conda」,无法再在 CI 里建环境冒烟,改为零依赖的静态校验。

新增 scripts/ciCheckInferenceManifests.mjs

把原来只靠注释互相声称的「envInstaller.ts 与 requirements 保持字面同步」变成 40 条可执行断言:SAM-2 改写链在真实清单上确实命中、torch 索引 URL 与清单本地版本标签一致、cpu/gpu 两份清单除 torch 系列外零漂移、VERIFY_IMPORTS 每个 import 名都能落到发行包、Python 编排残留引用不得回归、推理服务仍在打包清单内。

CodeQL:1 修 + 2 排除

  • 真问题(已修) js/stack-trace-exposure @ src/main/agent/server.ts:500 响应把 err.message 回传调用方。改为只回 { detail: 'internal_error' },完整消息只进服务端日志。没有采用「按环境开关回传」的方案 —— 那样污点流静态上依然存在,告警不会消失。
  • 误报(排除,附理由)
    • js/cors-misconfiguration-for-credentials:本仓库实现正是该查询官方文档给出的 GOOD 写法 —— Origin 必须命中显式白名单才回显(未命中连 ACAO 头都不设),且启动期 assertCorsConfig 拒绝 *。CodeQL 看不穿 resolveCorsOrigin 这层 helper。
    • js/clear-text-logging:唯一命中点是本地冒烟脚本 electronForkSmoke.cjs(不参与打包),被判「敏感」的实际只有 HTTP 状态码与 {"ok":true} 这类响应体。
    • 新增 .github/codeql/codeql-config.yml 按 query id 排除,不改动查询集(保持默认 code-scanning suite),每条附理由与依据链接。

验证

本地:npm run lint 0 error(248 warning 按现状保留,不阻塞)/npm exec tsc 通过/npm test -- --ci 113 suites、1036 tests 全通过/npm run build 成功(产出 dist/main/agentRuntime.js)/npm run baseline:selftest 全通过/清单检查脚本全通过。

两个断言脚本均做过「反向验证」,确认不是空跑:移走 agentRuntime.js 后 jest 立即以明确提示失败;清单检查脚本在残留引用存在时确实报错。

CI:push 与 pull_request 两轮 Test 各 6/6 job success,CodeQL 报 No new alerts in code changed by this pull request。


两点待办(未做,供决策)

  1. job runner 仍是 windows-latest。原计划改用 ubuntu-latest 提速(除 package 外),但本机无法验证 Linux 上 npm install 的 postinstall(electron-builder install-app-deps → electron-rebuild,含 @parcel/watcher 等原生模块)能否正常跑完,因此先保留当前已实证可行的 windows。若要切换,建议单独试一次。
  2. 可选切 npm ci。lockfile 与 package.json 现已同步(本次补上了遗漏的 zod),具备切换前提;切换后安装更快更可复现,但需要确认 postinstall 在 --ignore-scripts 等变体下的取舍。

@MarisaMagic
MarisaMagic merged commit 1350093 into main Sep 19, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants