fix(live): settle before acting, mark publishing before the first rename and keep live-owned fields out of new rows

- A multi-file operation records that it has started publishing in
  live-state.json before the first file is replaced; if that cannot be
  recorded nothing is published. Recovery rolls such an operation forward
  even when a published file was changed again afterwards, so an
  unpublished Codex login stash is no longer discarded.
- controller::lock_settled fails when the previous operation cannot be
  finished, and entry points refuse to save or switch instead of writing
  rows the leftover operation cannot restore. Startup, detach and exit
  log the failure per app and continue.
- Saving, adding and syncing the current provider take the proxy switch
  lock before reading mode and pointer, so entering proxy mode can no
  longer be overwritten by direct key fields. A new proxy address is
  written to every attached app even if one of them fails.
- Codex: the row-level model catalog pointer leaves with its provider; a
  profile selecting the built-in openai provider counts as the official
  route; exclusive fields the editor shows only because live has them are
  compared by value, so an untouched field changed by the client stays
  with the client. The add dialog sends the preset draft it projected, and
  the backend uses it as the field origin like an existing row.
- Claude editor keeps a legacy Bedrock apiKey at the top level where older
  versions read it; the Gemini editor recognizes a legacy Google card the
  same way switching does.
- The request path passes the provider it already resolved to the router
  instead of reading live-state.json twice per request.
This commit is contained in:
Jason committed 2026-09-27 22:38:42 +08:00
1 parent 08a80b90f2
commit b0875f4cfd
27 files changed
+1014 -190

No files matched your search

@@ -64,7 +64,8 @@ describe("useDraftEditorProjection", () => {
expect(apply).toHaveBeenCalledTimes(1);
expect(apply).toHaveBeenCalledWith({ config: "B" });
expect(onBase).toHaveBeenLastCalledWith({ config: "B" });
// 底和投影成它的草稿一起交出去,保存时后端按草稿分开预设带的字段。
expect(onBase).toHaveBeenLastCalledWith({ config: "B" }, { config: "b" });
});
it("投影失败时提示并保持没有底", async () => {