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 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: opts?.log ?? defaultLogger,
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
  }