billion-context 0.1.86-pr.561.380 → 0.1.86
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 +17 -0
- package/README.zh-CN.md +6 -0
- package/dist/index.js +77 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -389,6 +389,23 @@ recommended** for many concurrent conversations because of the collision
|
|
|
389
389
|
risk — until pi grows its own session-id signal. For pi multi-agent use,
|
|
390
390
|
pass an explicit `x-acp-session` header per conversation to avoid collisions.
|
|
391
391
|
|
|
392
|
+
### Windows: exclude the sessions dir from antivirus (#362)
|
|
393
|
+
|
|
394
|
+
The proxy persists each session's compression state to the sessions dir
|
|
395
|
+
(`%USERPROFILE%\.local\share\billion-context\` by default) and rewrites the
|
|
396
|
+
file every turn of a long session. On Windows, real-time antivirus (Windows
|
|
397
|
+
Defender), the search indexer, or a sync tool (OneDrive) can lock that
|
|
398
|
+
directory mid-write, so the rename fails with `EPERM` and every persist for
|
|
399
|
+
that session fails until the lock clears.
|
|
400
|
+
|
|
401
|
+
When the same session fails N consecutive writes (default `5`), the proxy
|
|
402
|
+
logs a one-time, actionable alert naming the directory to exclude. To fix it
|
|
403
|
+
at the root: add `%USERPROFILE%\.local\share\billion-context\` to your
|
|
404
|
+
antivirus **exclusions** (Windows Defender: Settings → Virus & threat
|
|
405
|
+
protection → Manage settings → Exclusions → Add an exclusion → Folder) and
|
|
406
|
+
make sure no sync tool (OneDrive / Dropbox / …) is syncing that path. Full
|
|
407
|
+
steps in [CONFIGURATION.md](CONFIGURATION.md#windows-exclude-the-sessions-dir-from-antivirus-362).
|
|
408
|
+
|
|
392
409
|
## Status
|
|
393
410
|
|
|
394
411
|
Early. Protocol handling and compression work against mock tests (500+ passing). Real-model integration testing is the next milestone. Expect rough edges.
|
package/README.zh-CN.md
CHANGED
|
@@ -265,6 +265,12 @@ Windows 下会自动发现常见 Clash/Mihomo 静态系统代理;Web UI 会显
|
|
|
265
265
|
|
|
266
266
|
**建议:** Codex 和 OpenCode 可以安全地通过代理并发跑很多会话。pi 单个 agent 没问题,但因碰撞风险**不建议**并发多会话 —— 直到 pi 自己长出 session-id 信号。pi 多 agent 场景下,每个会话发一个显式 `x-acp-session` header 来避免碰撞。
|
|
267
267
|
|
|
268
|
+
### Windows:把会话目录加入杀软排除项(#362)
|
|
269
|
+
|
|
270
|
+
代理把每个会话的压缩状态持久化到会话目录(默认 `%USERPROFILE%\.local\share\billion-context\`),长会话每一轮都会重写该文件。在 Windows 上,实时杀毒(Windows Defender)、搜索索引器或同步工具(OneDrive)可能在写入中途锁住该目录,导致 rename 以 `EPERM` 失败,在锁解除前该会话的每次持久化都会失败。
|
|
271
|
+
|
|
272
|
+
当同一会话连续 N 次写失败(默认 `5`)时,代理会打一条一次性、可操作的告警,明确指出要排除的目录。要从根上修复:把 `%USERPROFILE%\.local\share\billion-context\` 加入杀软**排除项**(Windows Defender:设置 → 病毒和威胁防护 → 管理设置 → 排除项 → 添加排除 → 文件夹),并确认没有同步工具(OneDrive / Dropbox / …)在同步该路径。完整步骤见 [CONFIGURATION.zh-CN.md](CONFIGURATION.zh-CN.md) 的「Windows:把会话目录加入杀软排除项」章节。
|
|
273
|
+
|
|
268
274
|
## 状态
|
|
269
275
|
|
|
270
276
|
早期。协议处理和压缩已通过 mock 测试(500+ 项通过)。真实模型集成测试是下一里程碑。预期会有粗糙的地方。
|
package/dist/index.js
CHANGED
|
@@ -49818,6 +49818,57 @@ function rel(p2, base) {
|
|
|
49818
49818
|
return r && !r.startsWith("..") ? r : p2;
|
|
49819
49819
|
}
|
|
49820
49820
|
|
|
49821
|
+
// src/persist-eperm.ts
|
|
49822
|
+
var LOCK_CODES = /\b(EPERM|EBUSY|EACCES)\b/;
|
|
49823
|
+
var WRITE_FAIL_RE = /^\[persist\] write failed for (.+?) \(total (\d+)x\): (.+)$/;
|
|
49824
|
+
var PersistEpermAlert = class {
|
|
49825
|
+
dir;
|
|
49826
|
+
threshold;
|
|
49827
|
+
repeatMs;
|
|
49828
|
+
platform;
|
|
49829
|
+
now;
|
|
49830
|
+
onAlert;
|
|
49831
|
+
hasAlerted = false;
|
|
49832
|
+
alertedAt = 0;
|
|
49833
|
+
constructor(opts) {
|
|
49834
|
+
this.dir = opts.dir;
|
|
49835
|
+
this.threshold = opts.threshold ?? 5;
|
|
49836
|
+
this.repeatMs = opts.repeatMs ?? 0;
|
|
49837
|
+
this.platform = opts.platform ?? process.platform;
|
|
49838
|
+
this.now = opts.now ?? Date.now;
|
|
49839
|
+
this.onAlert = opts.onAlert ?? ((d, c) => log("warn", buildAlertMessage(d, c)));
|
|
49840
|
+
}
|
|
49841
|
+
// Returns true iff an alert was emitted. Runs on every kernel log line, so
|
|
49842
|
+
// the non-matching cases bail before any allocation.
|
|
49843
|
+
observe(level, msg) {
|
|
49844
|
+
if (this.platform !== "win32") return false;
|
|
49845
|
+
if (level !== "error" && level !== "warn") return false;
|
|
49846
|
+
const m2 = WRITE_FAIL_RE.exec(msg);
|
|
49847
|
+
if (!m2) return false;
|
|
49848
|
+
const count = Number(m2[2]);
|
|
49849
|
+
if (count < this.threshold) return false;
|
|
49850
|
+
if (!LOCK_CODES.test(m2[3])) return false;
|
|
49851
|
+
if (!this.shouldAlert()) return false;
|
|
49852
|
+
this.hasAlerted = true;
|
|
49853
|
+
this.alertedAt = this.now();
|
|
49854
|
+
this.onAlert(this.dir, count);
|
|
49855
|
+
return true;
|
|
49856
|
+
}
|
|
49857
|
+
shouldAlert() {
|
|
49858
|
+
if (!this.hasAlerted) return true;
|
|
49859
|
+
if (this.repeatMs <= 0) return false;
|
|
49860
|
+
return this.now() - this.alertedAt >= this.repeatMs;
|
|
49861
|
+
}
|
|
49862
|
+
};
|
|
49863
|
+
function buildAlertMessage(dir, count) {
|
|
49864
|
+
return [
|
|
49865
|
+
`[persist] session dir ${dir} failed ${count} consecutive writes (EPERM \u2014 rename is locked).`,
|
|
49866
|
+
"Likely cause: Windows Defender / antivirus / indexer / a sync tool is locking the directory.",
|
|
49867
|
+
`Fix: add ${dir} to Windows Defender exclusions (Settings \u2192 Virus & threat protection \u2192 Manage settings \u2192 Exclusions \u2192 Add an exclusion),`,
|
|
49868
|
+
"and make sure no OneDrive / sync tool is syncing this path."
|
|
49869
|
+
].join("\n");
|
|
49870
|
+
}
|
|
49871
|
+
|
|
49821
49872
|
// src/persist.ts
|
|
49822
49873
|
var PERSIST_VERSION = 3;
|
|
49823
49874
|
var MIGRATION_MARKER = ".bili-migration-286.done";
|
|
@@ -49855,12 +49906,21 @@ var SessionStore = class {
|
|
|
49855
49906
|
const debounceMs = opts?.debounceMs ?? defaultDebounce();
|
|
49856
49907
|
this.enabled = (opts?.enabled ?? true) && debounceMs >= 0;
|
|
49857
49908
|
this.dir = opts?.dir ?? defaultDir();
|
|
49909
|
+
const baseLog = opts?.log ?? defaultLogger;
|
|
49910
|
+
const epermAlert = new PersistEpermAlert({
|
|
49911
|
+
dir: this.dir,
|
|
49912
|
+
threshold: epermAlertThreshold(),
|
|
49913
|
+
repeatMs: epermAlertRepeatMs()
|
|
49914
|
+
});
|
|
49858
49915
|
this.store = new StateStore({
|
|
49859
49916
|
dir: this.dir,
|
|
49860
49917
|
version: PERSIST_VERSION,
|
|
49861
49918
|
debounceMs: Math.max(0, debounceMs),
|
|
49862
49919
|
enabled: this.enabled,
|
|
49863
|
-
log:
|
|
49920
|
+
log: (level, msg) => {
|
|
49921
|
+
epermAlert.observe(level, msg);
|
|
49922
|
+
baseLog(level, msg);
|
|
49923
|
+
},
|
|
49864
49924
|
relPath: (id, payload) => relPathFor(id, payload.meta?.protocol ?? payload.protocol, payload.meta?.upstreamOrigin ?? payload.upstreamOrigin),
|
|
49865
49925
|
// Adopt the pre-envelope flat format this store itself wrote
|
|
49866
49926
|
// before the kernel extraction (and every v1/v2 file before it).
|
|
@@ -50128,6 +50188,22 @@ function persistEnabled() {
|
|
|
50128
50188
|
if (env === "0" || env === "false") return false;
|
|
50129
50189
|
return true;
|
|
50130
50190
|
}
|
|
50191
|
+
function epermAlertThreshold() {
|
|
50192
|
+
const env = process.env.BILI_PERSIST_EPERM_ALERT_THRESHOLD;
|
|
50193
|
+
if (env) {
|
|
50194
|
+
const n = Number.parseInt(env, 10);
|
|
50195
|
+
if (Number.isFinite(n) && n > 0) return n;
|
|
50196
|
+
}
|
|
50197
|
+
return 5;
|
|
50198
|
+
}
|
|
50199
|
+
function epermAlertRepeatMs() {
|
|
50200
|
+
const env = process.env.BILI_PERSIST_EPERM_ALERT_REPEAT_MS;
|
|
50201
|
+
if (env) {
|
|
50202
|
+
const n = Number.parseInt(env, 10);
|
|
50203
|
+
if (Number.isFinite(n) && n >= 0) return n;
|
|
50204
|
+
}
|
|
50205
|
+
return 0;
|
|
50206
|
+
}
|
|
50131
50207
|
function defaultLogger(level, m2) {
|
|
50132
50208
|
log(level, m2);
|
|
50133
50209
|
}
|