Skip to content

Latest commit

 

History

History
91 lines (77 loc) · 33.1 KB

File metadata and controls

91 lines (77 loc) · 33.1 KB
name MainProcess
description Governs Electron main-process ownership, IPC handlers, service layering, infrastructure, domain purity, and bundled MCP servers.
keywords
main
electron
ipc
services
infra
domain

MainProcess

概览

src/main/ 负责 Electron 主进程启动、窗口生命周期、IPC handler、服务编排、基础设施能力和纯领域辅助逻辑。主进程内部按依赖方向分层:

  • src/main/bootstrap/ 处理 Electron app 生命周期和窗口创建。
  • src/main/ipc/ 注册 IPC handler,并统一做输入校验和响应包装。
  • src/main/services/ 编排用例,连接 IPC、domain 和 infra。
  • src/main/domain/ 存放纯领域知识和无副作用 helper。
  • src/main/infra/ 封装文件系统、路径、存储、进程、MCP、集成等操作系统或外部能力。
  • src/mcp-servers/ 存放内置 MCP server,不依赖 Electron 或 src/main 实现。

主进程业务目录按六个 domain 分组:platformworkspacesessionproposalinsightautomation。IPC handler、services 和 pure domain helpers 应使用这些 domain 作为第一层所有权边界。

证据:src/main/bootstrap/index.tssrc/main/ipc/index.tssrc/main/services/**src/main/domain/**src/main/infra/**src/mcp-servers/**eslint.config.mjs

区域与所有权

目录 / 模块 负责内容 关键入口
src/main/bootstrap/ Electron app 生命周期、窗口创建、启动期 wiring src/main/bootstrap/index.ts, src/main/bootstrap/window.ts, src/main/bootstrap/workspace-window-manager.ts
src/main/ipc/ IPC handler 注册、schema 校验、响应归一化;area handler 按 src/main/ipc/<domain>/<area>.ts 组织,domain registry 位于 src/main/ipc/<domain>/index.ts src/main/ipc/index.ts, src/main/ipc/_kit/**
src/main/services/ 主进程用例编排,按 src/main/services/<domain>/** 组织 src/main/services/**
src/main/domain/ 纯领域知识和无副作用 helper,按 src/main/domain/<domain>/** 组织 src/main/domain/**
src/main/infra/ 文件系统、进程、存储、MCP、路径和外部集成能力 src/main/infra/**
src/preload/ 对 renderer 安全暴露主进程能力 src/preload/index.ts, src/preload/api/**
src/shared/ 跨进程 channel、schema、类型、常量和错误契约,IPC contract 按 domain/area 组织 src/shared/ipc/**
src/mcp-servers/ 内置 MCP server src/mcp-servers/**

边界

  • MUST 让 Electron/Vite main 和 preload 入口与 electron.vite.config.ts 一致:main 从 src/main/index.ts 构建,preload 从 src/preload/index.ts 构建。证据:electron.vite.config.ts
  • MUST 让 src/main/index.ts 作为最小单实例门:该入口只允许静态依赖 Electron app 与准备 dev 路径所需的 Node fs/path 内建模块。生产模式必须保留 Electron 默认 userData;开发模式必须在申请锁前幂等创建当前 worktree 的 data/ 根目录并调用 app.setPath("userData", devDataRoot),使 dev 按 worktree 隔离、同一 worktree 仍单实例且不与打包应用抢锁。锁前唯一允许的文件系统副作用是创建该根目录,不得读取或写入任何业务子目录;随后必须在加载 @main/bootstrap 前同步取得 app.requestSingleInstanceLock()。未取得锁的进程只请求退出,不得加载 bootstrap 或启动业务 app-data writer。持锁实例必须在动态加载 bootstrap 前监听 second-instance,并通过 startApp() 返回的 PrimaryInstanceController 请求窗口注意力;controller 必须等 migration、IPC/event 注册和首窗创建完成后再复用 WorkspaceWindowManager.focusLastActiveWindow() / openLauncherWindow(),不得绕过启动顺序直接操作 BrowserWindow。证据:src/main/index.tssrc/main/bootstrap/index.tssrc/main/bootstrap/workspace-window-manager.tstest/main/index.spec.tstest/main/bootstrap/index.spec.ts
  • MUST 让 app lifecycle 的 canonical 职责保持浅层且可发现:bootstrap/index.ts 只编排 app 事件与 startup gate,startup.ts 只拥有静态 startup window 和 first-visible barrier,runtime.ts 在 barrier 后接入 migration、IPC、MCP、正式 renderer 与 background work,shutdown.ts#SHUTDOWN_PHASES 单点列出退出 task/owner/API/force/PID ownership,lifecycle.ts 只提供 shutdown fence 与 phase runner;完整顺序和资源清单同步记录在 src/main/bootstrap/README.md。不得在 service/infra import 时注册全局 startup/shutdown task,也不得依赖 import 顺序决定资源释放。证据:src/main/bootstrap/index.tssrc/main/bootstrap/startup.tssrc/main/bootstrap/runtime.tssrc/main/bootstrap/shutdown.tssrc/main/bootstrap/lifecycle.tstest/main/bootstrap/lifecycle-boundaries.spec.ts
  • MUST 按 startup shell → required gate → runtime → renderer critical → background 的顺序启动:app.ready 后先立即显示同主题背景的静态 shell;shell first-visible barrier 结算前不得启动 shell PATH、migration、业务 IPC、bundled MCP 或 ACP。required migration 和 cutover validation 通过后才可 reserve 同一窗口并导航正式 renderer;startup document 不得获得业务 context 或 fanout。证据:src/main/bootstrap/index.tssrc/main/bootstrap/startup.tssrc/main/bootstrap/runtime.tssrc/main/bootstrap/workspace-window-manager.ts
  • MUST 按 snapshot-and-hide → quiesce → settle-spawned-sessions → terminate → finalize 的顺序退出,同 phase 独立 task 并行,全部资源共享 4 秒绝对 deadline 并预留 500ms force 区间。首次 quit 必须先建立全局 fence、显式保存并隐藏窗口;已进入写盘区间的 required migration 先在现有安全点结算,再开始资源 deadline。新增 timer、watcher、fire-and-forget Promise 或 child process 时必须在 SHUTDOWN_PHASES 声明 owner、幂等 quiesce/dispose/force API 和 PID/process-group ownership;禁止恢复 registerDisposable 或为单个资源分配独立完整 deadline。业务 wrapHandler、stream 和后台提交入口必须拒绝 fence 后的新工作。Spawned Session 与 Draft session probe 必须在 ACP process pool terminate 前统一结算或失效,清理路径只能读取已有 ready 进程,不得调用会 spawn 的 acquire API;全局退出已接管 runtime 清理后,app.exit() 销毁 Workspace 窗口不得再重复触发同一异步清理。证据:src/main/bootstrap/shutdown.tssrc/main/bootstrap/workspace-window-manager.tssrc/main/services/session/spawn/spawned-session-manager.tssrc/main/services/session/chat/session-probe-service.tssrc/main/infra/process/acp-process-pool.tssrc/main/bootstrap/lifecycle.tssrc/main/ipc/_kit/wrap-handler.tssrc/main/ipc/_kit/stream-channel.tssrc/main/infra/process/auxiliary-process-registry.tstest/main/bootstrap/shutdown.spec.ts
  • MUST 让请求-响应型 IPC handler 通过 _kit 辅助函数完成校验和响应归一化。main handler 使用 shared zod schema 校验 renderer 原始输入,并通过 wrapHandler 返回 IpcResponse<T>。证据:src/main/ipc/platform/settings.tssrc/main/ipc/_kit/schema.tssrc/main/ipc/_kit/wrap-handler.tssrc/shared/types/ipc.ts
  • MUST 按 domain-first 跨进程路径新增 IPC 能力:在 src/shared/ipc/<domain>/<area>.channels.ts 定义 <domain>:<area>:<action> channel,在 src/shared/ipc/<domain>/<area>.schemas.ts 定义输入 schema,在 src/main/ipc/<domain>/<area>.ts 定义 handler,并接入 src/main/ipc/<domain>/index.ts domain registry;src/main/ipc/index.ts 只注册六个 domain registry。在 src/preload/api/<domain>/<area>.tssrc/preload/index.tssrc/preload/index.d.ts 暴露 window.api.<domain>.<area>;renderer 需要该 API 时,在 src/renderer/src/api/<domain>/<area>.ts 提供 wrapper。证据:src/main/ipc/session/chat.tssrc/main/ipc/session/index.tssrc/main/ipc/index.tssrc/preload/api/session/chat.tssrc/renderer/src/api/session/chat.tssrc/shared/ipc/session/chat.channels.ts
  • MUST 让 BrowserWindow 生命周期归 src/main/bootstrap/window.tssrc/main/bootstrap/workspace-window-manager.ts 所有。IPC 或 services 不应保存单个全局 Workspace 窗口引用;需要向 Workspace 窗口发送事件时,通过 WorkspaceWindowManager.sendToWorkspace(workspaceId, ...),需要全局 fanout 时通过 sendToAll(...)。证据:src/main/bootstrap/workspace-window-manager.tssrc/main/ipc/session/chat.tssrc/main/ipc/proposal/browser.tssrc/main/ipc/platform/acp-agents.ts
  • MUST 将 launcher/workspace 窗口上下文作为显式 IPC 契约维护。新增 Workspace 窗口行为时,应通过 WindowChannelsWindowContextWorkspaceWindowManager.getContextByWebContents() 建立 sender 到 { role, workspaceId } 的映射,不要从 renderer 状态或 caller path 反推窗口归属。Workspace-scoped handler 必须使用 requireWorkspaceSender() 校验 caller 提交的 workspaceId。证据:src/shared/types/window.tssrc/shared/ipc/workspace/window.channels.tssrc/main/ipc/workspace/window.tssrc/main/ipc/_kit/workspace-scope.ts
  • MUST 让 src/main/ipc/** handler 通过 services 访问业务能力,不直接持有文件系统、路径、进程创建等 infra 细节;src/main/ipc/_kit/** 是 IPC 基础设施例外。现有 ESLint 规则已禁止 IPC 直接导入 fspathchild_process。证据:eslint.config.mjssrc/main/ipc/_kit/**
  • MUST 让 src/main/services/** 作为主进程用例编排层。services 可以组合 domaininfra,但不要让 infra 反向依赖 services。证据:eslint.config.mjssrc/main/services/automation/task/task-service.tssrc/main/infra/**
  • MUST 让 ACP session-update 映射保持“Agent 无关基线 + 显式 Agent adapter”边界:acp-mapper.ts 只作为稳定 facade 和分发入口,内部实现统一收拢在无 index.tsacp-mapper/ 目录;公共字段提取归 acp-mapper/update-normalizers.ts / acp-mapper/tool-call-mapper.ts,依赖特定 Agent 元数据的 thought/tool-call 展示增强归 acp-mapper/agent-adapters/** 并通过 registry.ts 注册。adapter 不得复制完整协议映射或从 tool call 推导宿主工作流副作用,跨事件 tool-call 组装状态继续归 MessageAssembler 所有。证据:src/main/services/session/chat/acp-mapper.tssrc/main/services/session/chat/acp-mapper/tool-call-mapper.tssrc/main/services/session/chat/acp-mapper/agent-adapters/types.tssrc/main/domain/session/chat/message-assembler.tstest/main/services/session/chat/acp-mapper/agent-adapters/registry.spec.ts
  • MUST 让 main service 跨 domain 调用只通过 src/main/services/<target-domain>/_public 进入;不得从另一个 domain import src/main/services/<target-domain>/<area>/**_public 只能位于 domain 根级,禁止 area 级 _public,并且必须显式 export 稳定方法,禁止 export *。证据:eslint.config.mjssrc/main/services/session/_public/index.ts
  • MUST 将 _public 视为 lower-level capability 出口而不是默认 facade;domain 内部可以有 area-facade.ts 做完整业务编排,但跨 domain 仍必须通过根级 _public 暴露的窄方法进入。
  • MUST 让 src/main/services/automation/workflow/workflow-engine.ts 成为 Workflow Run 的唯一 owner:trigger、{workspaceId,parentSessionId} active conflict、per-Run serialized event queue、snapshot persistence、wake、reconcile 和 begin/dispose/force shutdown 都归 Engine;workflow-agent-runner.tsworkflow-action-runner.ts 只能作为 execution adapter 通过 hook 接入,不得各自维护第二套 Run 状态机或触发入口。跨 domain 的 Workspace/session 能力只能通过 src/main/services/automation/_public/index.tssrc/main/services/session/_public/index.ts 的窄 public surface 进入。证据:src/main/services/automation/workflow/workflow-engine.tssrc/main/services/automation/workflow/workflow-agent-runner.tssrc/main/services/automation/workflow/workflow-action-runner.tstest/main/services/automation/workflow/workflow-engine.spec.ts
  • MUST 让 Workflow domain 的 yaml-parser.tspreflight.tsstate-machine.ts 保持无副作用;parser/schema validation 与 Phase 1 capability preflight 必须分离,unsupported definition 可以保存但只能在 Run 创建前拒绝,错误需要携带 stage/feature refs。文件系统、ACP、Action process 和 Workspace/session descriptor 由 service/infra 注入,不得从 domain 直接访问。证据:src/main/domain/automation/workflow/**test/main/domain/automation/workflow/**eslint.config.mjs
  • MUST 让 Workflow Run IPC 采用独立 automation:workflow-run:* area:shared channels/schemas、Main handler、Preload API 和 Renderer wrapper 一一对应;handler 必须验证 sender Workspace、parentSession 和 run owner,Renderer 只能通过 list/getDetail/decide 读或提交决策,不能直接写 snapshot。Engine wake 只能经 WorkspaceWindowManager.sendToWorkspace() 发送 {workspaceId,runId},并由 dispatcher debounce;不得把 snapshot、token、命令输出或 transcript 放进事件。证据:src/main/ipc/automation/workflow-run.tssrc/main/ipc/automation/index.tssrc/main/ipc/automation/workflow-run.tsWorkflowRunWakeDispatchertest/main/ipc/automation/workflow-run.spec.tsworkflow-run-wake.spec.ts
  • MUST 保持 storage-backed service 的磁盘 path、JSON key 和 schema 独立于文件目录移动;移动 service 文件时不得顺手改变持久化格式,除非对应 OpenSpec proposal 明确包含 migration。
  • MUST 将 Workspace-owned durable data 统一放在 workspaceDataDir(workspaceId) 下;sessions、tasks、knowledge、lineage、v2 workflow definitions/runs、integration 与 MCP events 必须复用 src/main/infra/storage/workspace-paths.ts,不得接受 repository path 选择 app-data namespace。Repository reverse data 才使用 folderDataDir(folderId);旧 apply-runs 和全局 workflow staging 不属于新的 runtime 读取范围。证据:src/main/infra/storage/workspace-paths.tssrc/main/infra/storage/workflow-definition-store.tssrc/main/infra/storage/workflow-run-store.tstest/main/infra/storage/workspace-storage-inventory.spec.ts
  • MUST 将 Workspace knowledge 放在 knowledgeDir(workspaceId) 下,并将该 Workspace-owned root 与 Folder evidence root 分开传递。File/package anchor 与 commit source 必须携带 folderId,并通过当前 Workspace descriptor 或固定 Session snapshot 验证 owner;缺失或未授权 owner 返回 unknown,不得回退 primary Folder。Raw markdown review、browser index 和单条删除都属于 insight:knowledge area;删除只能接受 knowledgeEntryNameSchema 校验后的 name,并在 sender Workspace 校验后限制到该 Workspace knowledge 目录,不得暴露任意 path 删除。证据:src/main/infra/storage/workspace-paths.tssrc/main/infra/storage/knowledge.tssrc/mcp-servers/fyllo-cortex/src/utils/knowledge.tssrc/main/ipc/insight/knowledge.tssrc/main/services/insight/knowledge/knowledge-document-service.ts
  • MUST 将 lineage 拆分为 Workspace subject index 与 Folder repository reverse index:subject 只写入 workspaceLineageDir(workspaceId),proposal/commit origin 与 reference 只写入 folderLineageDir(folderId);proposal 与 commit 操作必须携带 owner-qualified ProposalRef / Folder commit key。Repository origin 唯一且不可覆盖,reference 幂等追加;同一 index 的完整 read-modify-write 必须按文件串行并以唯一临时文件原子替换。Subject 写入成功后 reverse index 失败或冲突必须返回显式结果,不得回滚或伪报完整成功。证据:src/main/infra/storage/lineage-store.tssrc/main/infra/storage/repository-lineage-store.tssrc/main/services/insight/lineage/lineage-service.tssrc/shared/types/lineage.ts
  • MUST 通过 resolveWorkspace() / resolveRepositoryTarget()workspaceId 获取 repository cwd、Folder membership 和 registered worktree;normal runtime 不得用 path-derived ID、encodeProjectPath() 或 renderer 自报绝对 path 定位 Workspace。证据:src/main/services/workspace/resolver/workspace-resolver.tssrc/main/migrations/legacy-project-path.ts
  • MUST 使用 ProposalRef { folderId, changeId } 作为 proposal browser、status watcher 和 fyllo-specs Apply/Archive MCP resolver 的完整身份;不得用裸 changeId、Workspace primary 或 caller absolute path 猜测 owner。Apply/Archive 的 target 必须在每次 tool invocation 内由 resolver 验证并固定,Renderer 只能通过 Chat 用户消息进入该 MCP 路径;不得恢复或新增 Proposal stage-stream、run store 或旧 WorkflowStage owner。证据:src/shared/types/proposal.tssrc/main/services/proposal/browser/proposal-service.tssrc/mcp-servers/fyllo-specs/src/tools/apply-change.tssrc/mcp-servers/fyllo-specs/src/tools/archive-change.tstest/main/infra/workflow-source-boundary.spec.ts
  • MUST 让 Specs、Guidelines、Proposal 与 Overview 这类 repository-owned browser 先通过 resolveWorkspace(workspaceId) 获取完整 Folder 集合,再以 src/main/services/insight/repository-browser/aggregate.ts 的 per-Folder envelope 执行 leaf reader;ready 空结果、missingerror 不得互相折叠,跨 Folder 对象必须携带 SpecRefGuidelineRefProposalRef,partial 汇总只计入 ready Folder。证据:src/main/services/insight/repository-browser/aggregate.tssrc/main/services/insight/specs/specs-browser-service.tssrc/main/services/insight/guidelines/guidelines-browser-service.tssrc/main/services/proposal/browser/proposal-service.tssrc/main/services/insight/overview/overview-service.ts
  • MUST 让 Task、Workspace-owned v2 Workflow definition/Run 与 Workspace Integration config 以 workspaceId 选择 Workspace-owned storage。Task 的 targetFolderIds 和 repository-bound Integration 的 folderId 是 Folder identity 软引用:读取时必须按当前 membership 投影 current/stale,成员移除不得删除或重绑引用,也不得回退 primary;保存新的 source-control/CI integration 时必须显式绑定当前成员,Workspace-level resource 保持 unbound。Workflow definition 以随机 workflowId 存在于 workflows/<workflow-id>/definition.yaml,Run 及 transcript/action output 位于其 runs/<run-id>/;不得提供 built-in template、global staging、copy-on-save 或按 name 发现。证据:src/main/infra/storage/task-store.tssrc/main/infra/storage/workspace-integration-store.tssrc/main/infra/storage/workspace-paths.tssrc/main/services/automation/workflow/workflow-service.tssrc/main/infra/storage/workflow-definition-store.tssrc/main/infra/storage/workflow-run-store.ts
  • MUST 将 Workspace 创建、成员编辑、soft delete 与 restore 编排收敛到 workspace lifecycle service;Folder 重定位与新 Folder identity 解析必须共享 Folder registry 的全局 mutation queue,并在写入前重新检查所有引用 Workspace 的路径与 runtime/session 引用。证据:src/main/services/workspace/workspace/workspace-lifecycle-service.tssrc/main/services/workspace/folder/folder-registry-service.tssrc/main/services/workspace/workspace/workspace-reference-inspector.ts
  • MUST 让永久 Workspace 清理遵循 meta-last:先持久化 purging,幂等删除 Workspace-owned data 与 window state,只在持久化 legacyAppDataKey 可证明归属时删除 legacy source,最后删除 Workspace meta;不得从当前 Folder path、workspaceId 或目录扫描猜测 legacy source。证据:src/main/services/workspace/workspace/workspace-cleanup-service.tssrc/main/infra/storage/workspace-store.tssrc/main/migrations/legacy-project-store.ts
  • MUST 保持 src/main/domain/** 纯净:不得依赖 Electron、Electron toolkit、services、infra、IPC、bootstrap、文件系统、路径、操作系统环境或进程创建。需要这些值时,从 services 或 infra 传入数据。证据:eslint.config.mjssrc/main/domain/**
  • MUST 保持 src/main/infra/** 不依赖 services 或 IPC。infra 可以使用 domain 的纯 helper,但不能编排业务用例。证据:eslint.config.mjs
  • MUST 使用 cross-spawn 创建进程,不得从 child_process value-import spawnspawnSync。该要求由 eslint.config.mjs 强制执行。证据:eslint.config.mjssrc/main/infra/process/**
  • MUST 保持 src/mcp-servers/** 不依赖 Electron 或 @main/*src/mcp-servers/fyllo-specs/src/tools/** 不直接 spawn 进程,也不直接导入 @fission-ai/openspec,而是通过 runtime 层。证据:eslint.config.mjssrc/mcp-servers/fyllo-specs/src/runtime-openspec/**
  • MUST 让每个 bundled MCP server 在 src/mcp-servers/<server-name>/ 下独立维护 tsconfig.jsonREADME.mdCHANGELOG.mdsrc/version.tssrc/index.ts 只负责进程入口和 abort lifecycle,src/server.ts 只负责 transport/server wiring,src/tools/index.ts 只显式组装注册函数,每个 Agent-facing tool 必须在 src/tools/<tool-name>.ts 独立定义。多个 tool 共用的 caller、响应或错误映射应放入不注册 tool 的共享模块,不得重新合并进 registry。对应测试放在 test/mcp-servers/<server-name>/,并由 server 自身 tsconfig 纳入。证据:src/mcp-servers/fyllo-specs/src/mcp-servers/fyllo-cortex/src/mcp-servers/fyllo-spawn/test/mcp-servers/fyllo-spawn/tools.spec.ts
  • MUST 让全局 ACP Agent 连接预热归 main app lifecycle 所有,不得由 renderer store 状态或业务 IPC 临时驱动。正式 renderer critical phase 结算并发出 interactive signal 后才调度首次预热;1.5 秒 fallback 只防止 renderer 未发信号,正式界面和用户 ACP 请求均不得等待预热。Coordinator 必须在 ACP process pool terminate 前 quiesce,取消首次 Immediate 与未启动队列;用户 probe/chat 继续直接复用 process-pool promise,不排在后台队列之后。Process pool 在 shutdown 后拒绝新启动,并保留 intentional stop 供升级、卸载和 custom 配置失效使用。证据:src/main/services/platform/lifecycle/renderer-readiness.tssrc/main/services/platform/acp-agent/connection-warmup.tssrc/main/infra/process/acp-process-pool.tssrc/renderer/src/main.ts
  • MUST 让 src/main/infra/mcp/bundled-mcp-host.ts 统一拥有 bundled MCP HTTP proxy、后端子进程、name -> backendPort 内存路由、仅供 proxy/backend 使用的内部 token、有限重启和 lifecycle 清理;ACP chat/probe 只能通过 createBundledMcpActivation() 获取绑定 Agent、Session、server allowlist 与不可变 Workspace v2 descriptor 的 server spec,不得读取后端端口或内部 token。每个 registry entry 必须显式声明 transport policy:普通 server 默认 http-or-stdiofyllo-spawn 固定 http-only,HTTP 不可用时必须省略而不得退化为 stdio。HTTP proxy 必须校验 per-activation capability、剥离 caller Authorization 与全部 X-Fyllo-* 后再注入内部认证和唯一 Workspace context;stdio 必须只接收 FYLLO_WORKSPACE_JSON。renderer window 启动不得等待 MCP readiness,首次门控只发生在 ACP lifecycle 请求前。src/mcp-servers/** 继续保持不依赖 Electron 或 @main/*,所有 transport 都必须通过 shared Workspace resolver 访问 Folder allowlist/data/event/session context,不得回退 cwd、legacy Project env/header 或按 HTTP 请求修改 process.env。证据:src/main/infra/mcp/bundled-mcp-registry.tssrc/main/infra/mcp/bundled-mcp-host.tssrc/main/infra/mcp/bundled-mcp-servers.tssrc/main/infra/mcp/mcp-access-grant-registry.tssrc/main/services/session/chat/acp-session.tssrc/main/services/session/chat/session-probe-service.tssrc/mcp-servers/shared/request-context.tssrc/mcp-servers/shared/workspace-context.tssrc/mcp-servers/shared/workspace-resolver.ts
  • MUST 让 bundled MCP Host 使用 server-neutral request/response envelope、requestId pending map、per-server codec/handler registry 和统一 child-process lifecycle;fyllo-spawn 继续使用原 codec,fyllo-workflow 使用独立 protocol/schema 并固定为 HTTP-only。业务 bridge 只能由 runtime.ts 显式注册,MCP child 不得读取 Main storage、接受 caller 自报的 parent/path/YAML 或执行 workflow。普通 Chat activation 可包含 fyllo-workflow;Workflow fresh Agent 的 ACP server 与 access grant allowlist 必须精确为 fyllo-specsfyllo-cortex,排除 fyllo-spawnfyllo-workflow,且非 chat caller 由 bridge 返回 WORKFLOW_INVALID_CALLER。证据:src/main/infra/mcp/bundled-mcp-host.tsbundled-mcp-registry.tsbundled-mcp-servers.tssrc/main/services/automation/workflow/workflow-rpc-bridge.tssrc/mcp-servers/fyllo-workflow/**test/main/infra/mcp/bundled-mcp-host.spec.tstest/main/services/automation/workflow/workflow-rpc-bridge.spec.ts
  • MUST 让 bundled MCP child-to-Main RPC 只通过 bundled-mcp-host.ts#registerBundledMcpRpcHandler 的 typed transport port 建立:infra 负责 versioned envelope、requestId、cancel、child generation fencing 与 disconnect rejection,services 在 runtime.ts 显式注册业务 bridge;infra 不得 import services,MCP child 不得自行读取 Main storage 或复制 ACP runtime。fyllo-spawn 的业务编排归 src/main/services/session/spawn/**,并复用现有 ACP process pool、AcpSession、turn driver、Workspace snapshot 校验与 app shutdown。证据:src/shared/types/fyllo-spawn-rpc.tssrc/main/infra/mcp/bundled-mcp-host.tssrc/main/services/session/spawn/spawn-rpc-bridge.tssrc/main/services/session/spawn/spawned-session-manager.tssrc/main/bootstrap/runtime.ts
  • MUST 让 background=true spawned turn 成为 Main app-owned runtime:RPC 只在 prompt-dispatched 里程碑及其 ACP ID/config/turn record 已持久化后返回 accepted,active reservation、busy、父级 4 与全局 8 容量、10 分钟 inactivity watchdog 和 5 秒 cancel grace 必须保留到 terminal finalizer;不得增加绝对时长或累计 Session 上限。每轮 turns/<turnId>.json 与其中的 notification outbox 是状态事实来源,成功顺序固定为 assistant message → immutable response → completed turn/responseId/pending → meta projection,读取结果仍只接受 owner-scoped responseId + cursor。证据:src/main/services/session/spawn/spawned-session-manager.tssrc/main/infra/storage/spawned-session-store.tssrc/main/services/session/chat/acp-session.ts
  • MUST 让 completion notification 采用 Main 生成、持久化、at-most-once 的 owner-scoped outbox:renderer 的 list/dispatch 只提交 Workspace 与 opaque notificationId,Main 从 record 解析父 Session,并以 per-Workspace/Session Chat gate 串行普通用户 turn 与自动 reminder;普通 Chat 和自动 reminder 必须共用 chat-turn-service.tsAcpSession、turn driver、meta queue 和 assistant 持久化,不得复制 ACP event switch。Workspace window close 只取消 window-owned chat/probe,app-owned spawn/notification turn继续;应用 shutdown 先拒绝新 spawn/claim,在 store 可写时写 APP_SHUTDOWN,再 fence store和终止 process pool。父删除使用逻辑 fence → cancel/settle → suppress → storage fence/delete;重启只把遗留非终态写为 APP_RESTARTED,不得 resume ACP 或重投 dispatched 通知。该 owner-scoped outbox + ChatTurnGate 模式现在有两个独立实例:spawn completion notification(spawn-notification-service.ts)与 workflow proposal 取消提醒(workflow-decision-service.ts);两者共用 ChatTurnGate/chat-turn-service.ts 的 turn 互斥与投递骨架,但存储、类型和 IPC channel 保持独立,不合并成单一协议。证据:src/main/services/session/chat/chat-turn-service.tssrc/main/services/session/chat/chat-turn-gate.tssrc/main/services/session/spawn/spawn-notification-service.tssrc/main/services/automation/workflow/workflow-decision-service.tssrc/main/infra/storage/workflow-decision-store.tssrc/main/services/session/chat/session-registry.tssrc/main/bootstrap/workspace-window-manager.tssrc/main/bootstrap/shutdown.ts
  • MUST 让每个通过 IPC 启动的 bundled MCP HTTP 子进程入口使用进程级 AbortController 统一处理 SIGTERMSIGINT 和父进程 IPC disconnectdisconnect 必须触发 controller.abort(),HTTP 启动器必须响应该 signal 并关闭 listener,避免 detached 子进程在 Electron 主进程异常退出后成为孤儿进程。新增 bundled MCP server 时必须加入同一机制,并在 test/mcp-servers/child-process-lifecycle.spec.ts 增加对应入口覆盖。证据:src/mcp-servers/fyllo-specs/src/index.tssrc/mcp-servers/fyllo-cortex/src/index.tssrc/mcp-servers/fyllo-spawn/src/index.tssrc/mcp-servers/shared/http-server.tstest/mcp-servers/child-process-lifecycle.spec.ts
  • MUST 让 session:spawned-session inspection 保持 owner-scoped 只读边界:IPC 先验证 Workspace sender 与父 Chat Session,再由 query service 从 spawned meta、turn records 和结构化 messages 建立 durable 基线,仅叠加 identity 完全匹配的 ActiveTurn live snapshot。Running 内容必须来自同一 MessageAssembler.snapshot() 与 ACP turn driver,不得复制 event switch;renderer 只获得 opaque responseId,不得获得或拼接 response/app-data 路径。独立 view wake 只提示重新查询,按 spawned owner 合并且 timer 归 SpawnedSessionManager 的 quiesce/dispose/force lifecycle;它不得复用、claim 或改变 completion notification outbox。证据:src/shared/ipc/session/spawned-session.*src/main/ipc/session/spawned-session.tssrc/main/services/session/spawn/spawned-session-query-service.tssrc/main/services/session/spawn/spawned-session-manager.tssrc/main/domain/session/chat/message-assembler.ts
  • MUST 让 session:spawned-session 的 list 与 detail 读取责任分离:父级 list 只能读取 owner-scoped meta.json 与确定最新状态所需的最新 turn record,不得读取每个 spawned Session 的完整 messages.jsonl;单 Session detail 才能读取目标 meta、全部 turns 与结构化 messages。SpawnedSessionQueryService 必须在 Main 侧按 turn 时间窗口和持久化顺序完成消息归组,并将匹配 identity 的 live snapshot 只合并到对应最新 turn;renderer 不得复制归组规则或以 list 结果拼装 detail。view wake 是 level-triggered 的事实提示,不携带或承载 status/content;Main durable store、typed RPC 和 query projection 是唯一事实源。证据:src/main/infra/storage/spawned-session-store.tssrc/main/services/session/spawn/spawned-session-query-service.tssrc/shared/ipc/session/spawned-session.schemas.tssrc/main/ipc/session/spawned-session.ts

验证

pnpm lint
pnpm typecheck:node
pnpm exec vitest run --project main

失效信号

  • electron.vite.config.tstsconfig.node.jsoneslint.config.mjssrc/main/**src/preload/**src/shared/ipc/**src/mcp-servers/** 发生变化时,重新检查本文档。