merge: isolate concurrent OpenCode chat runs

This commit is contained in:
2026-08-17 22:28:19 +08:00
34 changed files with 5159 additions and 434 deletions

View File

@@ -7,12 +7,13 @@
| 登录续期 | Renderer 活动信号 | Main Works Session | 连续 7 天未使用才清除会话 |
| 用户模块入口策略 | 会话恢复 / 登录 / 刷新 | Electron Main → Works `/api/auth/me` → 四布尔安全投影 → Renderer auth store → 卡片/路由/provider gate | 缺失对象或字段默认 `true`;`design` 映射 `painting`;终止性 `401` 清理 Main/Renderer 会话;全局 `/settings` 不受 Code gate |
| 项目创建 | 新建项目对话框 | Host API → Main 项目初始化 | 创建时固定 `ProjectType`;小游戏/小程序原子生成受控模板,自定义只生成项目空间 |
| 一键提交 | `ProjectPublishAction` | Renderer capability → Host API → Main 本地 npm/Vite build → built snapshot preflight → source+built+contract 上传 | 只对小游戏/小程序开放;首次 create 原子写入文字资料但不上传封面,已有 draft/published 只提交版本并沿用云端资料/封面;状态竞态固定失败,不做无条件 metadata PATCH |
| 一键提交 | `ProjectPublishAction` | Renderer capability → Host API → Main 本地 npm/Vite build → built snapshot preflight → source+built+contract 上传 | 只对小游戏/小程序开放;首次 create 通过单一 multipart 合同原子提交文字资料与必选 PNG/JPEG/WebP 封面,创建失败或冲突不上传版本;已有 draft/published 只提交版本并沿用云端资料/封面,不做无条件 metadata PATCH |
| 构建产物预检 | Main-owned built snapshot | 一次性 loopback origin → fresh Electron WebContents/CDP(桌面、移动) | 检查错误、白屏和外域;不调用 Playwright,检查与上传归档相同字节,但不产生可信 receipt |
| 提交绑定 | 云端成功上传响应 | Main → submission binding v2 | 只持久化成功的 app/version/review/hash;落盘失败返回固定告警但不反转提交 |
| 运营发布 | Works Square 审核与交付 | 公共 `play_url` | 客户端只消费服务端发布结果;真实合同校验 → OSS/CDN 生产链仍待整链验收 |
| 可信发布门禁 | source+built+artifact contract | 服务端逐字节重算/合同校验 → 不可变 Release 固化 → 人工审核 | 服务端不运行项目 Vite;仍是不可绕过权威,未来 runtime 强门禁需可信 verifier |
| AI 编程首次发送 | ChatPanel 当前 Agent | Renderer Store → Host API → Main → OpenCode session / prompt | 新建 session 已知为空时直接提交 prompt,不在关键路径等待空历史;历史未知或普通历史会话仍按默认路径加载消息 |
| AI 编程 Session 执行提交 | ChatPanel 当前 Agent / Session | Renderer per-Session run state → deferred provider sync → Host API → Main manager FIFO → per-project Agent FIFO → runtime/config + Agent readiness → OpenCode message / command / summarize | 新建 session 已知为空时不等待空历史;不同 Session 没有 reply-duration 全局锁。provider/runtime 或 Agent 未应用时以 typed `409`、`promptSent:false` 在执行前终止;普通执行路径不自动重启、reload 或 dispose 共享 runtime |
| AI 编程启动确认 | Host POST、OpenCode HTTP polling 与 Session events | Renderer `{sessionId, runToken}` watchdog → `sessionRunStates[sessionId]` | Host 接受与 user transcript 不算 ACK;busy/retry、assistant、question、permission 或 typed terminal event 才确认/结束启动。10 秒无确认只终止该 Session 为 `SESSION_START_UNCONFIRMED`,清理其队列且不自动重放 |
| AI 编程上下文压缩 | OpenCode compaction Part / `session.compacted` / `/compact` | Renderer Store → per-session transcript state → Chat mixed timeline | 手动请求先创建 immutable UI event;native identity 负责归并与回放去重;完成只更新压缩项,真实 idle 才结束 run 和释放队列 |
| AI 编程模型代理错误 | OpenCode provider 请求 | Main Host AI proxy → Works 模型上游 | 配额耗尽保持独立终止态;只有明确的分组上游饱和才投影为 OpenCode 终止状态,通用限速仍保留 `429` |
| 客户端更新检查 | 设置页 | Renderer update store → IPC → Main AppUpdater → 目标 feed | Main 记录并重抛原始错误;Renderer 只显示去重、脱敏的单条提示,稳定源缺包不伪装为最新版 |
@@ -43,6 +44,8 @@
- 本地开发适配器将旧单会话 schema v2 原子迁移为带默认 Conversation 的 schema v3;打包应用不使用该本地适配器作为云端失败回退。
- 注销和退出会关闭本地事件流并清除本机 Conversation Session-id 缓存;服务端持久 Session 保留,下一次访问从 Conversation API 重新读取。
- AI 编程 Store 的 `sessionMessagesBySessionId` own-key 是加载状态契约:键缺失表示历史未知,存在且值为 `[]` 表示已知为空。只有后者可使用不读取历史的快速选择;普通历史会话选择继续刷新消息。
- AI 编程 Store 的运行态、错误、startup deadline 和内部 pending queue 按 Session 持有;top-level error 只表示真实全局错误。任何不确定的远程失败只清理对应 Session 的旧队列,手动重试不得复活失败前已排队的 prompt。
- Main 以 manager identity + runtime generation provenance 持有 runtime-config latch,并以 `{runtimeGeneration, desiredFingerprint, appliedFingerprint}` 持有项目 Agent readiness。只有 owned `fresh` generation 可确认 fingerprint 已应用;live `/agent` 只验证 id,attached/unknown generation 和同 id 内容更新均不能越过门禁。
- AI 编程压缩状态由 transcript 中的 `compactionsById` / `compactionOrder` 单一持有。运行中 hydration 保留 manual pending identity 并用 native Part 合并;completed 不得回退为 running。`session.compacted` 不是 run idle,不能据此释放 queued prompt;失败、中止或 runtime generation 变化只清理对应未完成事件。
- Main Host AI proxy 可为固定 OpenCode 重试契约做窄化的内部状态投影:配额耗尽投影为 `402`,明确上游分组饱和的 `429` 投影为终止 `400`,其他 `429` 原样保留;升级 OpenCode 时必须重新验证该契约。
- Main AppUpdater 持有 feed、原始异常、下载和安装状态;设置页只消费状态投影。一次 electron-updater `error` 事件覆盖的并发检查不会在 Renderer 重复发错,但独立的后续检查仍有自己的报告生命周期。

View File

@@ -15,9 +15,12 @@
| `electron/services/project-packager.ts` | 受控项目扫描、静态 ZIP 生成和敏感/历史控制文件排除 | 只允许可发布 `ProjectType`,不提供 Compose 或手工 ZIP 路径 |
| `electron/services/works-submission-binding.ts` | submission binding v2 持久化与旧 schema 迁移 | 旧中间态终止为 `legacy_retired`;文件名暂作安装兼容 |
| `electron/api/works-play-url.ts` | 公共播放 URL 的共享安全校验 | 公共 `play_url` 必须同源 HTTPS 且精确匹配 App 路径 |
| `src/pages/Chat/OpencodeChatPanel.tsx` | AI 编程 Agent 选择、首次 session 创建、prompt 提交与消息/压缩混合时间线 | 新 session 仅在消息缓存 own-key 已知时使用不读取历史的快速选择;历史压缩项静态渲染,实时转换才进入 live region |
| `src/pages/Chat/OpencodeChatPanel.tsx` | AI 编程 Agent 选择、首次 session 创建、per-Session prompt 提交与消息/压缩混合时间线 | 新 session 仅在消息缓存 own-key 已知时使用不读取历史的快速选择;选中 Session 只投影自己的 run error,top-level error 仅用于真实全局错误 |
| `src/lib/opencode-session-state.ts` | OpenCode 会话消息与上下文压缩时间线的规范化、hydration 和事件归并 | 压缩 UI identity 不变;native Part/event identity 用于回放去重,completed 状态不可降级 |
| `src/stores/opencode.ts` | OpenCode runtime、session、消息缓存、提交与压缩生命周期 | `sessionMessagesBySessionId` 键缺失表示未知,存在空数组表示已知为空;`session.compacted` 只完成压缩项,真实 idle 才释放 run 队列 |
| `src/stores/opencode.ts` | OpenCode runtime、session、消息缓存、per-Session 提交/启动确认与压缩生命周期 | 每个 Session 独立持有 run/error/queue;10 秒 run-token watchdog 不把 Host 接受或 user transcript 当 ACK,超时只终止对应 Session 且不自动重放;`session.compacted` 只完成压缩项,真实 idle 才释放 run 队列 |
| `electron/api/routes/opencode.ts` | Main-owned OpenCode execution acceptance、provider/runtime freshness 与项目 Agent 门禁 | message/command/summarize 共用 bounded manager FIFO;Agent-scoped execution 再进入 per-project FIFO,typed pending 必须发生在 runtime 调用前,普通执行路径不自动 restart/reload/dispose |
| `electron/opencode/project-agent-runtime.ts` | 项目 Agent desired/applied fingerprint、runtime generation provenance、live registry acceptance 与配置 mutation 串行化 | live `/agent` 只证明 id;只有 owned fresh generation 可应用内容 fingerprint,attached/unknown fail closed,用户修改的 retired Agent 文件不得被删除 |
| `electron/opencode/runtime-config-readiness.ts` | manager + generation scoped provider/runtime stale latch | timeout/partial persistence 保持 sticky uncertain 状态;只有成功显式 apply 后的合格 fresh generation 可解除,迟到 lease 不能改写 readiness |
| `electron/api/routes/ai-proxy.ts` | Main-owned 模型代理、凭据边界与上游响应投影 | 仅对明确上游分组饱和做终止态兼容投影,通用限速保持 `429` |
| `shared/opencode-error-details.ts` | OpenCode 上游饱和错误的窄化共享分类 | 不以通用 `rate_limit_exceeded` 单独判定饱和 |
| `electron/main/updater.ts` | 目标 feed 解析、electron-updater 生命周期与原始诊断 | 缺少稳定 manifest 保持错误;事件/Promise 重复失败按检查实例去重 |
@@ -46,7 +49,7 @@
## Dependency Direction
- Renderer UI → Renderer API contract → Main Host routes → Main services → Works Square;Renderer 不反向读取 Main 凭据、文件系统或归档。
- AI 编程 Renderer → Host API → Main OpenCode routes/runtime;OpenCode provider 请求再经 Main AI proxy 访问模型上游,Renderer 不直接持有上游凭据或本地 runtime URL。
- AI 编程 Renderer per-Session run state → Host API → Main manager-scoped acceptance → per-project Agent acceptance → OpenCode runtime;OpenCode provider 请求再经 Main AI proxy 访问模型上游。Renderer 不直接持有上游凭据或本地 runtime URL,Main 临界区只覆盖配置/请求 acceptance,不覆盖模型回复时长。
- Project configuration 决定产品分流;Main release builder 生成 source/built/contract,服务端独立重算和校验决定发布安全,本地 `ProjectType` 不是授权结论。
- Built artifact preflight 检查最终上传的同字节快照,但客户端可被绕过且不产生可信 receipt;服务端仍是合同、摘要和不可变 Release 安全权威。
- Robot Renderer → typed AI hardware API → Main Host route → Robot Hotspot Module → Windows/macOS Adapter。云端 Binding 仍由 Main 代理;热点选择/连接移入页面,但家庭 Wi-Fi 凭据输入仍只留在固件 Portal,系统 Wi-Fi 保留为兜底。
@@ -62,6 +65,8 @@
- `runtime_url` 是一个客户端版本的迁移回退;删除前必须确认服务端和存量数据稳定提供 `play_url`。
- `ai-proxy.ts` 的上游饱和状态投影依赖当前固定 OpenCode 的重试语义和窄化错误文案;升级 runtime 或调整上游错误格式时必须复核,不能把所有 `429` 统一终止。
- OpenCode 压缩时间线依赖 native compaction Part、Session run token 与 runtime generation 的关联;hydration 必须保持 completed 单调,不能用 `session.compacted` 提前结束 run 或释放 queued prompt。
- OpenCode execution acceptance 同时涉及 provider persistence、runtime lifecycle、Agent config mutation 和 runtime HTTP。锁顺序必须保持 manager → project,所有等待与 HTTP 都使用同一 hard deadline/AbortSignal;timeout 后 lease 必须撤销,queued cancellation 不能让后续 mutation 绕过前驱。
- OpenCode Agent hot reload 没有 authoritative whole-instance quiescence oracle。不得从 `/session/status` 推断 dispose/reload 安全,也不得以 live 同 id 代替内容 fingerprint;若需要即时热更新,必须先获得 upstream directory-scoped invalidation 或权威 quiescence seam。
- `electron/main/updater.ts` 的稳定源错误归一化必须保持窄化:只识别 Works Square 对应 manifest 的 404,不得吞掉其他 feed/网络/签名错误;Renderer 的脱敏边界不能取代 Main 原始日志。
- 多 Conversation 事件处理必须区分对话快照与 Workspace 任务更新;不得用任务时间戳推进 Conversation 流水位,也不得让旧会话的迟到流覆盖当前会话。
- Quote 编辑、重报价、确认和项目删除都跨 Renderer/Main/Works Square。异步结果必须核对当前 Workspace + Conversation;删除当前项目时必须先使旧选择和事件流失效,再加载剩余 Workspace。
@@ -74,4 +79,4 @@
## Last Updated
2026-08-16
2026-08-17