| name | MainProcess | ||||||
|---|---|---|---|---|---|---|---|
| description | Governs Electron main-process ownership, IPC handlers, service layering, infrastructure, domain purity, and bundled MCP servers. | ||||||
| keywords |
|
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 分组:platform、workspace、session、proposal、insight、automation。IPC handler、services 和 pure domain helpers 应使用这些 domain 作为第一层所有权边界。
证据:src/main/bootstrap/index.ts、src/main/ipc/index.ts、src/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作为最小单实例门:该入口只允许静态依赖 Electronapp与准备 dev 路径所需的 Nodefs/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.ts、src/main/bootstrap/index.ts、src/main/bootstrap/workspace-window-manager.ts、test/main/index.spec.ts、test/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.ts、src/main/bootstrap/startup.ts、src/main/bootstrap/runtime.ts、src/main/bootstrap/shutdown.ts、src/main/bootstrap/lifecycle.ts、test/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.ts、src/main/bootstrap/startup.ts、src/main/bootstrap/runtime.ts、src/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.ts、src/main/bootstrap/workspace-window-manager.ts、src/main/services/session/spawn/spawned-session-manager.ts、src/main/services/session/chat/session-probe-service.ts、src/main/infra/process/acp-process-pool.ts、src/main/bootstrap/lifecycle.ts、src/main/ipc/_kit/wrap-handler.ts、src/main/ipc/_kit/stream-channel.ts、src/main/infra/process/auxiliary-process-registry.ts、test/main/bootstrap/shutdown.spec.ts。 - MUST 让请求-响应型 IPC handler 通过
_kit辅助函数完成校验和响应归一化。main handler 使用 shared zod schema 校验 renderer 原始输入,并通过wrapHandler返回IpcResponse<T>。证据:src/main/ipc/platform/settings.ts、src/main/ipc/_kit/schema.ts、src/main/ipc/_kit/wrap-handler.ts、src/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.tsdomain registry;src/main/ipc/index.ts只注册六个 domain registry。在src/preload/api/<domain>/<area>.ts、src/preload/index.ts和src/preload/index.d.ts暴露window.api.<domain>.<area>;renderer 需要该 API 时,在src/renderer/src/api/<domain>/<area>.ts提供 wrapper。证据:src/main/ipc/session/chat.ts、src/main/ipc/session/index.ts、src/main/ipc/index.ts、src/preload/api/session/chat.ts、src/renderer/src/api/session/chat.ts、src/shared/ipc/session/chat.channels.ts。 - MUST 让
BrowserWindow生命周期归src/main/bootstrap/window.ts和src/main/bootstrap/workspace-window-manager.ts所有。IPC 或 services 不应保存单个全局 Workspace 窗口引用;需要向 Workspace 窗口发送事件时,通过WorkspaceWindowManager.sendToWorkspace(workspaceId, ...),需要全局 fanout 时通过sendToAll(...)。证据:src/main/bootstrap/workspace-window-manager.ts、src/main/ipc/session/chat.ts、src/main/ipc/proposal/browser.ts、src/main/ipc/platform/acp-agents.ts。 - MUST 将 launcher/workspace 窗口上下文作为显式 IPC 契约维护。新增 Workspace 窗口行为时,应通过
WindowChannels、WindowContext和WorkspaceWindowManager.getContextByWebContents()建立 sender 到{ role, workspaceId }的映射,不要从 renderer 状态或 caller path 反推窗口归属。Workspace-scoped handler 必须使用requireWorkspaceSender()校验 caller 提交的workspaceId。证据:src/shared/types/window.ts、src/shared/ipc/workspace/window.channels.ts、src/main/ipc/workspace/window.ts、src/main/ipc/_kit/workspace-scope.ts。 - MUST 让
src/main/ipc/**handler 通过 services 访问业务能力,不直接持有文件系统、路径、进程创建等 infra 细节;src/main/ipc/_kit/**是 IPC 基础设施例外。现有 ESLint 规则已禁止 IPC 直接导入fs、path和child_process。证据:eslint.config.mjs、src/main/ipc/_kit/**。 - MUST 让
src/main/services/**作为主进程用例编排层。services 可以组合domain和infra,但不要让infra反向依赖 services。证据:eslint.config.mjs、src/main/services/automation/task/task-service.ts、src/main/infra/**。 - MUST 让 ACP session-update 映射保持“Agent 无关基线 + 显式 Agent adapter”边界:
acp-mapper.ts只作为稳定 facade 和分发入口,内部实现统一收拢在无index.ts的acp-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.ts、src/main/services/session/chat/acp-mapper/tool-call-mapper.ts、src/main/services/session/chat/acp-mapper/agent-adapters/types.ts、src/main/domain/session/chat/message-assembler.ts、test/main/services/session/chat/acp-mapper/agent-adapters/registry.spec.ts。 - MUST 让 main service 跨 domain 调用只通过
src/main/services/<target-domain>/_public进入;不得从另一个 domain importsrc/main/services/<target-domain>/<area>/**。_public只能位于 domain 根级,禁止 area 级_public,并且必须显式 export 稳定方法,禁止export *。证据:eslint.config.mjs、src/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.ts与workflow-action-runner.ts只能作为 execution adapter 通过 hook 接入,不得各自维护第二套 Run 状态机或触发入口。跨 domain 的 Workspace/session 能力只能通过src/main/services/automation/_public/index.ts与src/main/services/session/_public/index.ts的窄 public surface 进入。证据:src/main/services/automation/workflow/workflow-engine.ts、src/main/services/automation/workflow/workflow-agent-runner.ts、src/main/services/automation/workflow/workflow-action-runner.ts、test/main/services/automation/workflow/workflow-engine.spec.ts。 - MUST 让 Workflow domain 的
yaml-parser.ts、preflight.ts和state-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.ts、src/main/ipc/automation/index.ts、src/main/ipc/automation/workflow-run.ts的WorkflowRunWakeDispatcher、test/main/ipc/automation/workflow-run.spec.ts、workflow-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.ts、src/main/infra/storage/workflow-definition-store.ts、src/main/infra/storage/workflow-run-store.ts、test/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:knowledgearea;删除只能接受knowledgeEntryNameSchema校验后的 name,并在 sender Workspace 校验后限制到该 Workspace knowledge 目录,不得暴露任意 path 删除。证据:src/main/infra/storage/workspace-paths.ts、src/main/infra/storage/knowledge.ts、src/mcp-servers/fyllo-cortex/src/utils/knowledge.ts、src/main/ipc/insight/knowledge.ts、src/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-qualifiedProposalRef/ Folder commit key。Repository origin 唯一且不可覆盖,reference 幂等追加;同一 index 的完整 read-modify-write 必须按文件串行并以唯一临时文件原子替换。Subject 写入成功后 reverse index 失败或冲突必须返回显式结果,不得回滚或伪报完整成功。证据:src/main/infra/storage/lineage-store.ts、src/main/infra/storage/repository-lineage-store.ts、src/main/services/insight/lineage/lineage-service.ts、src/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.ts、src/main/migrations/legacy-project-path.ts。 - MUST 使用
ProposalRef { folderId, changeId }作为 proposal browser、status watcher 和fyllo-specsApply/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.ts、src/main/services/proposal/browser/proposal-service.ts、src/mcp-servers/fyllo-specs/src/tools/apply-change.ts、src/mcp-servers/fyllo-specs/src/tools/archive-change.ts、test/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空结果、missing和error不得互相折叠,跨 Folder 对象必须携带SpecRef、GuidelineRef或ProposalRef,partial 汇总只计入 ready Folder。证据:src/main/services/insight/repository-browser/aggregate.ts、src/main/services/insight/specs/specs-browser-service.ts、src/main/services/insight/guidelines/guidelines-browser-service.ts、src/main/services/proposal/browser/proposal-service.ts、src/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.ts、src/main/infra/storage/workspace-integration-store.ts、src/main/infra/storage/workspace-paths.ts、src/main/services/automation/workflow/workflow-service.ts、src/main/infra/storage/workflow-definition-store.ts、src/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.ts、src/main/services/workspace/folder/folder-registry-service.ts、src/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.ts、src/main/infra/storage/workspace-store.ts、src/main/migrations/legacy-project-store.ts。 - MUST 保持
src/main/domain/**纯净:不得依赖 Electron、Electron toolkit、services、infra、IPC、bootstrap、文件系统、路径、操作系统环境或进程创建。需要这些值时,从 services 或 infra 传入数据。证据:eslint.config.mjs、src/main/domain/**。 - MUST 保持
src/main/infra/**不依赖 services 或 IPC。infra 可以使用 domain 的纯 helper,但不能编排业务用例。证据:eslint.config.mjs。 - MUST 使用
cross-spawn创建进程,不得从child_processvalue-importspawn或spawnSync。该要求由eslint.config.mjs强制执行。证据:eslint.config.mjs、src/main/infra/process/**。 - MUST 保持
src/mcp-servers/**不依赖 Electron 或@main/*;src/mcp-servers/fyllo-specs/src/tools/**不直接 spawn 进程,也不直接导入@fission-ai/openspec,而是通过 runtime 层。证据:eslint.config.mjs、src/mcp-servers/fyllo-specs/src/runtime-openspec/**。 - MUST 让每个 bundled MCP server 在
src/mcp-servers/<server-name>/下独立维护tsconfig.json、README.md、CHANGELOG.md与src/version.ts;src/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.ts、src/main/services/platform/acp-agent/connection-warmup.ts、src/main/infra/process/acp-process-pool.ts、src/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-stdio,fyllo-spawn固定http-only,HTTP 不可用时必须省略而不得退化为 stdio。HTTP proxy 必须校验 per-activation capability、剥离 callerAuthorization与全部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.ts、src/main/infra/mcp/bundled-mcp-host.ts、src/main/infra/mcp/bundled-mcp-servers.ts、src/main/infra/mcp/mcp-access-grant-registry.ts、src/main/services/session/chat/acp-session.ts、src/main/services/session/chat/session-probe-service.ts、src/mcp-servers/shared/request-context.ts、src/mcp-servers/shared/workspace-context.ts、src/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-specs、fyllo-cortex,排除fyllo-spawn、fyllo-workflow,且非 chat caller 由 bridge 返回WORKFLOW_INVALID_CALLER。证据:src/main/infra/mcp/bundled-mcp-host.ts、bundled-mcp-registry.ts、bundled-mcp-servers.ts、src/main/services/automation/workflow/workflow-rpc-bridge.ts、src/mcp-servers/fyllo-workflow/**、test/main/infra/mcp/bundled-mcp-host.spec.ts、test/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.ts、src/main/infra/mcp/bundled-mcp-host.ts、src/main/services/session/spawn/spawn-rpc-bridge.ts、src/main/services/session/spawn/spawned-session-manager.ts、src/main/bootstrap/runtime.ts。 - MUST 让
background=truespawned 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-scopedresponseId + cursor。证据:src/main/services/session/spawn/spawned-session-manager.ts、src/main/infra/storage/spawned-session-store.ts、src/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.ts、AcpSession、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.ts、src/main/services/session/chat/chat-turn-gate.ts、src/main/services/session/spawn/spawn-notification-service.ts、src/main/services/automation/workflow/workflow-decision-service.ts、src/main/infra/storage/workflow-decision-store.ts、src/main/services/session/chat/session-registry.ts、src/main/bootstrap/workspace-window-manager.ts、src/main/bootstrap/shutdown.ts。 - MUST 让每个通过 IPC 启动的 bundled MCP HTTP 子进程入口使用进程级
AbortController统一处理SIGTERM、SIGINT和父进程 IPCdisconnect;disconnect必须触发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.ts、src/mcp-servers/fyllo-cortex/src/index.ts、src/mcp-servers/fyllo-spawn/src/index.ts、src/mcp-servers/shared/http-server.ts、test/mcp-servers/child-process-lifecycle.spec.ts - MUST 让
session:spawned-sessioninspection 保持 owner-scoped 只读边界:IPC 先验证 Workspace sender 与父 Chat Session,再由 query service 从 spawned meta、turn records 和结构化 messages 建立 durable 基线,仅叠加 identity 完全匹配的ActiveTurnlive snapshot。Running 内容必须来自同一MessageAssembler.snapshot()与 ACP turn driver,不得复制 event switch;renderer 只获得 opaqueresponseId,不得获得或拼接 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.ts、src/main/services/session/spawn/spawned-session-query-service.ts、src/main/services/session/spawn/spawned-session-manager.ts、src/main/domain/session/chat/message-assembler.ts。 - MUST 让
session:spawned-session的 list 与 detail 读取责任分离:父级 list 只能读取 owner-scopedmeta.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.ts、src/main/services/session/spawn/spawned-session-query-service.ts、src/shared/ipc/session/spawned-session.schemas.ts、src/main/ipc/session/spawned-session.ts。
pnpm lint
pnpm typecheck:node
pnpm exec vitest run --project main- 当
electron.vite.config.ts、tsconfig.node.json、eslint.config.mjs、src/main/**、src/preload/**、src/shared/ipc/**或src/mcp-servers/**发生变化时,重新检查本文档。