@chatpanel/gateway 0.6.42 → 0.6.43

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chatpanel/gateway",
3
- "version": "0.6.42",
3
+ "version": "0.6.43",
4
4
  "description": "Local privacy gateway \u2014 redacts PII out of OpenAI/Anthropic API traffic before it reaches a model, then restores it in the reply. Point opencode, codex, aider, Claude Code, etc. at it.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,45 @@
1
+ // Persist the observability access ring across gateway restarts.
2
+ //
3
+ // The pure ring (createAccessLog, @chatpanel/events) is in-memory by design. But the gateway
4
+ // restarts often — every update — and an empty "which agent read what" panel after each
5
+ // restart reads as "nothing is set up" when plenty is. So we back the ring with a tiny file:
6
+ // load it at start, debounce-write it on change. This is SAFE to persist because every event
7
+ // is already metadata only — client, tool, ms, and a REDACTED note (a search query's text is
8
+ // never in it). 0600, capped, under ~/.chatpanel.
9
+ //
10
+ // Kept out of the pure events module on purpose: persistence is a platform concern (node:fs),
11
+ // and the shared contract must stay dependency-free and runnable in a browser.
12
+
13
+ import { createAccessLog, ACCESS_LOG_MAX } from './observability.js';
14
+ import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
15
+ import { join, dirname } from 'node:path';
16
+ import os from 'node:os';
17
+
18
+ const PATH = process.env.CHATPANEL_ACCESS_LOG || join(os.homedir(), '.chatpanel', 'access-log.json');
19
+
20
+ export function createPersistentAccessLog({ max = ACCESS_LOG_MAX, path = PATH, persistMs = 1000 } = {}) {
21
+ const log = createAccessLog(max);
22
+
23
+ // Load prior events, oldest-first, so the ring keeps chronological order.
24
+ try {
25
+ const arr = JSON.parse(readFileSync(path, 'utf8'));
26
+ if (Array.isArray(arr)) for (const e of arr) if (e && typeof e === 'object') log.push(e);
27
+ } catch { /* no prior log, or unreadable — start empty */ }
28
+
29
+ let timer = null;
30
+ const persist = () => {
31
+ try {
32
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
33
+ // snapshot() is newest-first; store oldest-first so a reload preserves order.
34
+ writeFileSync(path, JSON.stringify(log.snapshot().reverse()), { mode: 0o600 });
35
+ } catch { /* best effort — telemetry must never break the gateway */ }
36
+ };
37
+ const schedule = () => { clearTimeout(timer); timer = setTimeout(persist, persistMs); if (timer.unref) timer.unref(); };
38
+
39
+ return {
40
+ push(evt) { const e = log.push(evt); schedule(); return e; },
41
+ snapshot: (n) => log.snapshot(n),
42
+ get size() { return log.size; },
43
+ clear() { log.clear(); persist(); },
44
+ };
45
+ }
package/src/server.js CHANGED
@@ -41,12 +41,13 @@ import { STT_MODEL_CATALOG, isKnownSttModel, isValidCustomSttId, DEFAULT_STT_MOD
41
41
  import { resolvePro, checkQuota, consume, usage } from './freegate.js';
42
42
  import { publicConfig, applyConfigPatch, applyNerModelSelection, persistConfig, configPath } from './configstore.js';
43
43
  import { resolveDestination, aggregateModelsAsync } from './router.js';
44
- import { createAccessLog, makeAccessEvent } from './observability.js';
44
+ import { makeAccessEvent } from './observability.js';
45
+ import { createPersistentAccessLog } from './access-log-store.js';
45
46
  import * as openai from './openai.js';
46
47
  import * as responses from './responses.js';
47
48
  import * as anthropic from './anthropic.js';
48
49
 
49
- export const VERSION = '0.6.42';
50
+ export const VERSION = '0.6.43';
50
51
 
51
52
  // WARM search tier — SQLite + FTS5 record store (falls back to an encrypted-JSON
52
53
  // store if SQLite can't load), fed by the extension's ingest sync + backup-ingest.
@@ -54,12 +55,12 @@ export const VERSION = '0.6.42';
54
55
  // See docs/architecture-data-tiers.
55
56
  const historyStore = await createHistoryStore();
56
57
 
57
- // OBSERVABILITY — an in-memory ring of "which agent read what, when". Populated by the
58
- // MCP server process (chatpanel-gateway mcp) reporting each tool call here, so a person
59
- // can SEE cross-agent access in ChatPanel's dashboard. In-memory by design: a working
60
- // session's activity, not an audit trail that itself becomes data to protect. The note on
61
- // each event is redacted (never a search query) by makeAccessEvent.
62
- const accessLog = createAccessLog();
58
+ // OBSERVABILITY — a ring of "which agent read what, when", persisted across restarts (the
59
+ // gateway updates often; an empty panel after each restart reads as "nothing is set up").
60
+ // Populated by the MCP process (chatpanel-gateway mcp) reporting each tool call, so a person
61
+ // can SEE cross-agent access in ChatPanel's dashboard. Safe to persist: every event is
62
+ // metadata only client/tool/ms + a REDACTED note (a search query's text is never in it).
63
+ const accessLog = createPersistentAccessLog();
63
64
 
64
65
  const KNOWN_AGENTS = new Set(['codex', 'claude', 'opencode', 'pi', 'kiro', 'antigravity']);
65
66