codex-model-change 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/cx.py +63 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -124,6 +124,19 @@ App 没读到 `DEEPSEEK_API_KEY`。执行上面「桌面 App 的一次性配置
|
|
|
124
124
|
**Q: 提示 `deepseek 拒绝了 exec 自定义工具` 之类的 400?**
|
|
125
125
|
模型目录(`~/.codex/cx-catalog.json`)被改坏了,重跑一次 `cx use deepseek` 会重新生成。
|
|
126
126
|
|
|
127
|
+
**Q: 跑完 `cx fix-all` 后,重开 App 某个会话“今天聊的内容不见了”?**
|
|
128
|
+
|
|
129
|
+
1.0.5 之前确实存在这个坑:`cx fix-all` 清洗老会话时会删掉部分 `reasoning` 行,而会话文件(rollout)
|
|
130
|
+
里每条记录带一个必须**从 0 起连续递增**的 `ordinal`。删行不重编号就留下缺口,App 的投影缓存
|
|
131
|
+
(`~/.codex/thread_history_1.sqlite`)会**永久卡在缺口处**,于是之后的新内容再也进不了界面——
|
|
132
|
+
看起来就像“丢了”(其实原始 rollout 文件里还在,只是没被投影出来)。
|
|
133
|
+
|
|
134
|
+
1.0.5 已修复:`fix-all` 现在会在改写后自动重编号 `ordinal` 消除缺口,并清掉该会话的投影缓存,
|
|
135
|
+
让 App 下次打开时从会话文件完整重建。
|
|
136
|
+
|
|
137
|
+
若用的是 1.0.5 之前的版本且已中招:升级后**完全退出 App**,再重新打开该会话即可恢复;
|
|
138
|
+
仍不显示时,可手动删掉 `~/.codex/thread_history_1.sqlite` 中对应 `thread_id` 的行,强制其重建。
|
|
139
|
+
|
|
127
140
|
## 隐私说明
|
|
128
141
|
|
|
129
142
|
- API key 仅保存在本机 `~/.cx/deepseek.key`(权限 600)和 launchd 环境中,**不写入任何配置文件明文**,不外发。
|
package/cx.py
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"""
|
|
14
14
|
import json, os, re, glob, sqlite3, subprocess, sys, shutil, time, urllib.request, urllib.error
|
|
15
15
|
|
|
16
|
-
CX_VERSION = "1.0.
|
|
16
|
+
CX_VERSION = "1.0.6"
|
|
17
17
|
|
|
18
18
|
CODEX_HOME = os.environ.get("CODEX_HOME", os.path.expanduser("~/.codex"))
|
|
19
19
|
CONFIG = os.path.join(CODEX_HOME, "config.toml")
|
|
@@ -153,6 +153,62 @@ def clean_ocx_injections(text):
|
|
|
153
153
|
out.append(ln)
|
|
154
154
|
return "\n".join(out)
|
|
155
155
|
|
|
156
|
+
|
|
157
|
+
# ---------- App 投影缓存对齐 ----------
|
|
158
|
+
# 背景(踩过的坑,务必保留):
|
|
159
|
+
# rollout 每条记录带顶层 "ordinal" 字段,且 App 要求它从 0 起、逐条 +1 连续。
|
|
160
|
+
# cx fix-all 会两处改动 rollout:
|
|
161
|
+
# 1) rewrite_rollout 重新序列化 turn_context/session_meta → 整文件字节偏移漂移;
|
|
162
|
+
# 2) sanitize_rollout 删除 reasoning 行 → ordinal 出现缺口。
|
|
163
|
+
# 而 App 的投影(thread_history_1.sqlite)按 ordinal + 字节偏移增量推进,一旦遇到缺口
|
|
164
|
+
# 就永久卡死("expected ordinal N, got N+1"),新内容再也进不了投影缓存 ——
|
|
165
|
+
# 表现就是"跑完 fix-all 后重开 App,今天聊的内容消失了"(live 会话内存里其实还在)。
|
|
166
|
+
# 因此 fix-all 必须:先重编号 ordinal 消除缺口,再清掉该会话的投影缓存,
|
|
167
|
+
# 让 App 下次打开时从(已连续的)rollout 完整重建。重建是唯一 100% 安全的做法。
|
|
168
|
+
import re as _re
|
|
169
|
+
_ORD_PAT = _re.compile(rb'"ordinal":\s*(\d+)')
|
|
170
|
+
_ORD_SUB = _re.compile(rb'"ordinal":\s*\d+')
|
|
171
|
+
|
|
172
|
+
def read_ordinals(path):
|
|
173
|
+
"""按行序读出每行顶层 ordinal 值(缺失为 None)"""
|
|
174
|
+
out = []
|
|
175
|
+
for ln in open(path, "rb"):
|
|
176
|
+
m = _ORD_PAT.search(ln)
|
|
177
|
+
out.append(int(m.group(1)) if m else None)
|
|
178
|
+
return out
|
|
179
|
+
|
|
180
|
+
def renumber_ordinals(path):
|
|
181
|
+
"""把每行 ordinal 重写为行号,消除删行造成的缺口。返回改写行数。
|
|
182
|
+
正常 rollout 每行都有 ordinal 且原本 ordinal==行号,故以行号为准可完美复原连续性。"""
|
|
183
|
+
lines = open(path, "rb").readlines()
|
|
184
|
+
changed = 0
|
|
185
|
+
for i, ln in enumerate(lines):
|
|
186
|
+
new = _ORD_SUB.sub(b'"ordinal":' + str(i).encode(), ln, count=1)
|
|
187
|
+
if new != ln:
|
|
188
|
+
lines[i] = new; changed += 1
|
|
189
|
+
if changed:
|
|
190
|
+
open(path, "wb").writelines(lines)
|
|
191
|
+
return changed
|
|
192
|
+
|
|
193
|
+
def reset_app_projection(thread_id):
|
|
194
|
+
"""清掉 App 对某会话的投影缓存,强制其下次打开时从 rollout 重建。
|
|
195
|
+
rollout 被改写/删行后这是唯一安全做法:避免 ordinal 缺口与字节偏移错位残留。
|
|
196
|
+
(thread_history_projection_state 上有 DELETE 触发器,会连带清 thread_realtime_items)"""
|
|
197
|
+
db_path = os.path.join(CODEX_HOME, "thread_history_1.sqlite")
|
|
198
|
+
if not (thread_id and os.path.exists(db_path)):
|
|
199
|
+
return False
|
|
200
|
+
db = sqlite3.connect(db_path)
|
|
201
|
+
try:
|
|
202
|
+
has = lambda t: db.execute(
|
|
203
|
+
"SELECT 1 FROM sqlite_master WHERE type='table' AND name=?", (t,)).fetchone()
|
|
204
|
+
for t in ("thread_items", "thread_turns", "thread_history_projection_state"):
|
|
205
|
+
if has(t):
|
|
206
|
+
db.execute(f"DELETE FROM {t} WHERE thread_id=?", (thread_id,))
|
|
207
|
+
db.commit()
|
|
208
|
+
return True
|
|
209
|
+
finally:
|
|
210
|
+
db.close()
|
|
211
|
+
|
|
156
212
|
# ---------- 命令 ----------
|
|
157
213
|
def cmd_use(which):
|
|
158
214
|
if which not in ("deepseek", "gpt"): err("用法: cx use deepseek|gpt")
|
|
@@ -348,8 +404,14 @@ def cmd_fix_all():
|
|
|
348
404
|
if rp and os.path.exists(rp):
|
|
349
405
|
bakj = rp + f".bak.cx-fixall-{ts}"
|
|
350
406
|
if not os.path.exists(bakj): shutil.copy2(rp, bakj)
|
|
407
|
+
old_ords = read_ordinals(rp) # 改写前快照(仅用于诊断是否有缺口)
|
|
351
408
|
if needs_model: rewrite_rollout(rp, model, provider)
|
|
352
409
|
if provider == "openai": sanitize_rollout(rp)
|
|
410
|
+
# 关键:删行会产生 ordinal 缺口 + 重排会移动字节偏移;必须重编号消除缺口,
|
|
411
|
+
# 并清投影缓存让 App 从 rollout 重建,否则重开 App 后该会话内容会"消失"
|
|
412
|
+
fixed = renumber_ordinals(rp)
|
|
413
|
+
reset_app_projection(tid)
|
|
414
|
+
if fixed: print(f" · {tid[:8]} 重编号 {fixed} 行并重置 App 投影")
|
|
353
415
|
if needs_model:
|
|
354
416
|
db.execute("UPDATE threads SET model=?, model_provider=? WHERE id=?", (model, provider, tid))
|
|
355
417
|
n += 1
|
package/package.json
CHANGED