Skip to content

Commit 6afe8c3

Browse files
authored
Merge pull request #48 from spencerkit/develop
Merge develop into main
2 parents 324e67c + e48116e commit 6afe8c3

234 files changed

Lines changed: 34719 additions & 7270 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/green-seas-marry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@spencer-kit/coder-studio": minor
3+
---
4+
5+
Add configurable LSP runtime behavior with managed language server installation, and improve supervisor restore and editing flows across desktop and mobile.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# supervisor 编辑弹框会被实时状态更新错误地标记为已修改
2+
3+
## 标题
4+
5+
`fix(web): supervisor 编辑弹框的 hasChanges 应基于打开时快照而非实时 supervisor 状态`
6+
7+
## 问题描述
8+
9+
当前 supervisor 编辑弹框里的 `hasSettingsChanged` 不是拿草稿和“弹框打开时的初始值”比较,而是直接拿草稿和“当前实时 supervisor 状态”比较。
10+
11+
这意味着只要弹框打开期间,另一个标签页、另一个客户端,或其他后端状态更新修改了 evaluator/model/max count/scheduledAt,当前页面即使一项都没改,也可能被错误标记为“表单已修改”。
12+
13+
## 复现步骤
14+
15+
1. 打开一个已存在 supervisor 的编辑弹框。
16+
2. 保持当前弹框内容不变。
17+
3. 在另一处把这个 supervisor 的 evaluator、model、max count 或 scheduledAt 改掉。
18+
4. 让当前页面收到新的 `supervisor.state` 推送。
19+
5. 观察当前编辑弹框的 Save 按钮状态。
20+
21+
## 预期行为
22+
23+
- “是否有修改” 应只反映当前弹框内用户相对打开瞬间基线所做的本地修改。
24+
- 外部状态变化不应把一个未编辑的表单错误点亮。
25+
26+
## 实际行为
27+
28+
- 外部更新后,当前页面即使没有本地改动,也可能把 Save 点亮。
29+
- 用户如果继续保存,可能会把旧草稿值覆盖回实时值,形成误保存。
30+
31+
## 已确认事实
32+
33+
- objective 的变更判断当前已基于 `initialObjective` 快照。
34+
- evaluator/model/max count/scheduledAt 的变更判断当前仍直接依赖实时 `supervisor`
35+
- 该问题是这次新增 `hasSettingsChanged` 逻辑后引入的基线不一致。
36+
37+
## 当前判断
38+
39+
这是一个典型的编辑态并发基线问题。
40+
41+
当前实现把“表单初始值”和“外部实时状态”混成了一个基准,导致 dirty-check 语义不稳定。只要弹框开着期间外部状态变化,就会出现假阳性,严重时会让用户把旧值再次写回。
42+
43+
## 后续处理方向
44+
45+
- 为 evaluator/model/max count/scheduledAt 增加与 `initialObjective` 同级的初始快照字段。
46+
- `hasChanges` 统一基于弹框打开时的初始快照比较,而不是直接读取实时 `supervisor`
47+
- 补测试覆盖:
48+
- 打开弹框后外部状态变化,但当前页未编辑时 Save 仍应禁用
49+
- 打开弹框后外部状态变化,当前页有本地修改时仍应只按本地差异判断
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# supervisor 恢复列表会把失败 reset 后仅存的备份目标过滤掉
2+
3+
## 标题
4+
5+
`fix(server): listRecoverableTargets 不应无条件忽略 backup/reset 目录`
6+
7+
## 问题描述
8+
9+
`listRecoverableTargets()` 当前会把名称包含 `.backup-``.reset-` 的目录全部视为临时目录并过滤掉。
10+
11+
这个过滤在正常路径下可以隐藏 reset 过程中的中间产物,但在 reset 失败或进程中断的异常路径下,磁盘上可能只剩下 `backup``reset staging` 目录可用。如果此时无条件过滤,这些目录里的唯一可恢复数据就不会再出现在 restore 列表里。
12+
13+
## 复现步骤
14+
15+
1. 创建一个带有 supervisor target 的工作区。
16+
2. 触发一次 `resetTargetFiles()`
17+
3. 让 reset 在“旧目录已 rename 为 backup、新目录尚未成功 promote”这一阶段失败,或在这一阶段发生崩溃/中断。
18+
4. 重新进入恢复流程,调用 `listRecoverableTargets()`
19+
5. 观察返回的可恢复目标列表。
20+
21+
## 预期行为
22+
23+
- 即使 reset 失败,只要磁盘上仍然存在可读的 target 数据,就应该有办法在恢复列表里看到它。
24+
- 至少不应该因为目录名带有 `.backup-` / `.reset-` 就直接丢失唯一剩余的恢复入口。
25+
26+
## 实际行为
27+
28+
- `listRecoverableTargets()` 会无条件跳过 `.backup-*` / `.reset-*` 目录。
29+
- 在正式 target 目录已经消失,而仅剩 backup 或 staging 副本时,恢复列表可能直接为空。
30+
31+
## 已确认事实
32+
33+
- `resetTargetFiles()` 的失败路径下,确实可能留下:
34+
- 正式目录不存在
35+
- `backup` 目录存在
36+
- `reset staging` 目录存在
37+
- 当前已有原子测试覆盖这种磁盘残局形态。
38+
- `listRecoverableTargets()` 当前没有根据“是否存在正式 target 目录”或“哪份副本是唯一可恢复副本”做更细粒度判断。
39+
40+
## 当前判断
41+
42+
这是 restore 入口的容灾回归。
43+
44+
问题不在于“临时目录本身不该隐藏”,而在于当前过滤过于绝对,没有给异常残局保留降级恢复路径。结果是磁盘上明明还有数据,但产品层面完全失去恢复入口。
45+
46+
## 后续处理方向
47+
48+
- 不要对 `.backup-*` / `.reset-*` 做无条件过滤。
49+
- 改为更细粒度的恢复候选选择逻辑,例如:
50+
- 正式 target 存在时,优先展示正式目录并隐藏其伴随 backup/staging
51+
- 正式 target 不存在时,允许把唯一可恢复的 backup/staging 暴露出来
52+
- 需要补一组异常残局下的恢复列表测试,覆盖“只剩 backup”与“只剩 staging”的情况
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# 前端冷启动后无法展示已存在的 supervisor 状态
2+
3+
## 标题
4+
5+
`fix(web): 前端冷启动后应重新拿到已存在的 supervisor 状态`
6+
7+
## 问题描述
8+
9+
`coder-studio` 中,如果 server 仍在运行、session 也仍然有效,但用户只是刷新页面或前端重新连接,当前页面可能拿不到这个 session 已存在的 supervisor 状态。
10+
11+
现在 web 端不再在 `SessionCard` 挂载时调用 `supervisor.get` 主动补拉;与此同时,WS resync 只会回放 workspace/session 状态,不会回放 `supervisor.state`。这会导致前端冷启动后 `supervisorsAtom` 为空,即使后端内存里 supervisor 其实还在。
12+
13+
需要注意,这个问题不包含“server 重启后不恢复 supervisor runtime”的设计决策。这里讨论的是 server 没重启、只是前端冷启动/重连时的展示与编辑能力回归。
14+
15+
## 复现步骤
16+
17+
1. 打开一个仍然存活的 `full` session。
18+
2. 为该 session 创建 supervisor,确认页面上已经显示 supervisor 卡片。
19+
3. 保持 server 进程和该 session 存活。
20+
4. 刷新页面,或让前端 websocket 断开后重新连接。
21+
5. 在 supervisor 没有产生新的状态变更前,观察该 session 卡片区域。
22+
23+
## 预期行为
24+
25+
- 前端重新连接后,应该能恢复这个 session 当前已存在的 supervisor 状态。
26+
- 用户应继续看到正确的 supervisor 卡片,并能进入详情或编辑,而不是误以为当前没有 supervisor。
27+
28+
## 实际行为
29+
30+
- 前端冷启动后 `supervisorsAtom` 可能为空。
31+
- session 卡片会退回到 “Enable” 入口,像是当前没有 supervisor。
32+
- 只有等后续再次收到新的 `supervisor.state` 推送后,界面才会恢复正确显示。
33+
34+
## 已确认事实
35+
36+
- web 端此前有 `supervisor.get` 挂载补拉逻辑,当前已移除。
37+
- `WsHub.handleResync()` 当前只回放 workspace 与 session 状态,不回放 supervisor 状态。
38+
- server 重启后不自动恢复 supervisor runtime 是当前有意设计,不是这条 issue 的范围。
39+
40+
## 当前判断
41+
42+
这是一个前端冷启动/重连场景下的状态补齐回归。
43+
44+
根因是两条保护同时被拿掉了:
45+
46+
- 前端不再调用 `supervisor.get` 补拉
47+
- 后端 resync 也不发送 `supervisor.state`
48+
49+
只要 supervisor 是在当前页面初始化之前就已存在,且之后没有新的状态变更,前端就会一直缺失这份状态。
50+
51+
## 后续处理方向
52+
53+
- 二选一恢复状态补齐能力:
54+
- 恢复前端按需调用 `supervisor.get`
55+
- 或让 WS resync 同步回放当前 supervisor 状态
56+
- 明确区分两类语义:
57+
- server 重启后不恢复 supervisor runtime
58+
- 前端冷启动时仍应展示 server 当前已存在的 supervisor 状态

0 commit comments

Comments
 (0)