@chatpanel/gateway 0.6.39 → 0.6.41
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 +1 -1
- package/src/history-store.js +9 -0
- package/src/mcp.js +21 -2
- package/src/server.js +7 -3
- package/src/sqlite-store.js +8 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chatpanel/gateway",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.41",
|
|
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": {
|
package/src/history-store.js
CHANGED
|
@@ -178,6 +178,15 @@ export class HistoryStore {
|
|
|
178
178
|
return this.records.size;
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
// Wipe every record — the user purging the on-disk warm copy. Returns how many were dropped.
|
|
182
|
+
clear() {
|
|
183
|
+
const n = this.records.size;
|
|
184
|
+
this.records.clear();
|
|
185
|
+
this.index = new SearchIndex();
|
|
186
|
+
this.schedulePersist();
|
|
187
|
+
return n;
|
|
188
|
+
}
|
|
189
|
+
|
|
181
190
|
search(query, opts) {
|
|
182
191
|
return this.index.search(query, opts);
|
|
183
192
|
}
|
package/src/mcp.js
CHANGED
|
@@ -21,6 +21,25 @@ import { ensureGatewayToken } from './gateway-token.js';
|
|
|
21
21
|
const PROTOCOL_VERSION = '2024-11-05';
|
|
22
22
|
const SERVER = { name: 'chatpanel-history', version: '1.0.0' };
|
|
23
23
|
|
|
24
|
+
// MCP servers may return `instructions` from initialize; hosts (Codex, Claude Code, …) fold
|
|
25
|
+
// them into the model's context. This is where we STEER tool selection so a person doesn't
|
|
26
|
+
// have to say "use ChatPanel": it tells the model that the user's personal meetings/notes/
|
|
27
|
+
// chats live here and must be reached through these tools, not by grepping the working dir.
|
|
28
|
+
const INSTRUCTIONS = [
|
|
29
|
+
'ChatPanel holds the USER\'S OWN meetings, notes and past chats (transcripts, decisions,',
|
|
30
|
+
'action items, summaries). It is the source of truth for anything personal/organizational —',
|
|
31
|
+
'NOT the code in the working directory.',
|
|
32
|
+
'',
|
|
33
|
+
'When the user asks about a meeting, a call, a demo, notes, a past conversation, "what did',
|
|
34
|
+
'we decide", "the outcome of…", "action items", "what did X say", or anything scoped to a',
|
|
35
|
+
'day/person/topic in THEIR history — call search_history FIRST (search by content, not the',
|
|
36
|
+
'meeting title), then get_record for the full text. Do NOT grep the working directory, git',
|
|
37
|
+
'history, README or MEMORY files for the user\'s personal meeting/chat content — it is not',
|
|
38
|
+
'there; it is in ChatPanel. list_skills/open_skill expose the user\'s installed skills the',
|
|
39
|
+
'same way. Every result states how fresh the local copy is; if something recent is missing,',
|
|
40
|
+
'it may not have synced yet — say so rather than concluding it does not exist.',
|
|
41
|
+
].join('\n');
|
|
42
|
+
|
|
24
43
|
// The calling agent's self-reported name (from MCP `initialize` clientInfo), so the
|
|
25
44
|
// observability dashboard can say WHICH agent read what. Untrusted; the gateway coerces it.
|
|
26
45
|
let clientName = 'unknown';
|
|
@@ -82,7 +101,7 @@ async function bridgeJson(path) {
|
|
|
82
101
|
const TOOLS = [
|
|
83
102
|
{
|
|
84
103
|
name: 'search_history',
|
|
85
|
-
description: '
|
|
104
|
+
description: 'Search the user\'s ChatPanel history — their past chats, meeting/call transcripts, and notes — by keyword relevance. USE THIS FIRST, without being asked, whenever the user\'s question is about a meeting, call, demo, standup, note, or past conversation: "outcome of the meeting", "what did we decide/agree", "action items", "what did <person> say", "notes from yesterday/last week", a topic or person scoped to their history. That content lives ONLY in ChatPanel — do NOT grep the working directory, git history, README, or MEMORY files for it; it is not there. This is a LOCAL WARM COPY that syncs from ChatPanel; very recent items (a meeting from the last few hours) may not be here yet — every result reports how current the index is, so if something is missing it likely has not synced (say so rather than concluding it does not exist). Meeting titles are often generic ("Zoom Meeting"), so search by CONTENT (topics, names, decisions), not the title. Then call get_record with a returned id for the full text.',
|
|
86
105
|
inputSchema: {
|
|
87
106
|
type: 'object',
|
|
88
107
|
properties: {
|
|
@@ -223,7 +242,7 @@ export async function handleRpc(msg) {
|
|
|
223
242
|
switch (method) {
|
|
224
243
|
case 'initialize':
|
|
225
244
|
clientName = params?.clientInfo?.name || clientName;
|
|
226
|
-
return ok({ protocolVersion: PROTOCOL_VERSION, capabilities: { tools: {} }, serverInfo: SERVER });
|
|
245
|
+
return ok({ protocolVersion: PROTOCOL_VERSION, capabilities: { tools: {} }, serverInfo: SERVER, instructions: INSTRUCTIONS });
|
|
227
246
|
case 'tools/list':
|
|
228
247
|
return ok({ tools: TOOLS });
|
|
229
248
|
case 'tools/call': {
|
package/src/server.js
CHANGED
|
@@ -46,7 +46,7 @@ import * as openai from './openai.js';
|
|
|
46
46
|
import * as responses from './responses.js';
|
|
47
47
|
import * as anthropic from './anthropic.js';
|
|
48
48
|
|
|
49
|
-
export const VERSION = '0.6.
|
|
49
|
+
export const VERSION = '0.6.41';
|
|
50
50
|
|
|
51
51
|
// WARM search tier — SQLite + FTS5 record store (falls back to an encrypted-JSON
|
|
52
52
|
// store if SQLite can't load), fed by the extension's ingest sync + backup-ingest.
|
|
@@ -534,8 +534,8 @@ export function createGateway(cfg = loadConfig()) {
|
|
|
534
534
|
// WRITES are not: a drive-by localhost page or a random local process must not be
|
|
535
535
|
// able to inject records the model then trusts as the user's real history. Ingest
|
|
536
536
|
// requires the extension Origin (which its POST always carries) or the gateway token.
|
|
537
|
-
if (pathname === '/v1/history/ingest' && req.method === 'POST' && !isAdminAuthorized(req)) {
|
|
538
|
-
return sendJson(res, 403, { error: { message: 'history
|
|
537
|
+
if ((pathname === '/v1/history/ingest' || pathname === '/v1/history/clear') && req.method === 'POST' && !isAdminAuthorized(req)) {
|
|
538
|
+
return sendJson(res, 403, { error: { message: 'history write — extension origin or gateway token required', type: 'forbidden' } });
|
|
539
539
|
}
|
|
540
540
|
// The access log is who-read-what — sensitive, and writable only by the local MCP
|
|
541
541
|
// process (which sends the gateway token). Extension Origin or token for both the
|
|
@@ -596,6 +596,10 @@ export function createGateway(cfg = loadConfig()) {
|
|
|
596
596
|
return sendJson(res, 400, { error: { message: `ingest failed: ${e.message}`, type: 'ingest_error' } });
|
|
597
597
|
}
|
|
598
598
|
}
|
|
599
|
+
if (pathname === '/v1/history/clear' && req.method === 'POST') {
|
|
600
|
+
const dropped = historyStore.clear();
|
|
601
|
+
return sendJson(res, 200, { ok: true, dropped, size: historyStore.size });
|
|
602
|
+
}
|
|
599
603
|
if (pathname === '/v1/history/search' && req.method === 'POST') {
|
|
600
604
|
try {
|
|
601
605
|
const body = JSON.parse((await readBody(req, cfg.maxBodyBytes)).toString('utf8')) || {};
|
package/src/sqlite-store.js
CHANGED
|
@@ -153,6 +153,14 @@ export class SqliteHistoryStore {
|
|
|
153
153
|
const body = this.db.get('SELECT text FROM fts WHERE id = ?', [id]);
|
|
154
154
|
return { ...meta, text: body?.text || '' };
|
|
155
155
|
}
|
|
156
|
+
|
|
157
|
+
// Wipe every record — the user purging the on-disk warm copy. Returns how many were dropped.
|
|
158
|
+
clear() {
|
|
159
|
+
const n = this.size;
|
|
160
|
+
this.db.exec('DELETE FROM records');
|
|
161
|
+
this.db.exec('DELETE FROM fts');
|
|
162
|
+
return n;
|
|
163
|
+
}
|
|
156
164
|
}
|
|
157
165
|
|
|
158
166
|
// Pick the best available warm engine. SQLite when it loads; otherwise the
|