chat-recall 0.5.31 → 0.6.0

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
@@ -163,6 +163,23 @@ any of it. Secrets are masked client-side before anything leaves the machine.
163
163
  }
164
164
  ```
165
165
 
166
+ `chat-recall-mcp` is a relay, not the server. It finds the background daemon for
167
+ your tool profile, starts one if there is none, and passes bytes to it. Every
168
+ session on a machine therefore shares one loaded engine instead of carrying its
169
+ own: a session costs about 50 MB rather than 87 MB, against a single shared
170
+ daemon, and background sync runs in one process instead of racing a lock in all
171
+ of them. The daemon exits 15 minutes after the last session closes.
172
+
173
+ Two knobs, both optional:
174
+
175
+ ```bash
176
+ CHAT_RECALL_NO_DAEMON=1 # load the whole server in each session instead
177
+ CHAT_RECALL_DAEMON_IDLE_SECS=0 # keep the daemon resident (default 900)
178
+ ```
179
+
180
+ If the daemon cannot be reached for any reason, the session loads the server
181
+ itself and works exactly as it did before — slower, never broken.
182
+
166
183
  Then install the hooks (one command sets up auto-save, pre-compact backup, and the resume-hint that warns when you're about to redo work):
167
184
 
168
185
  ```bash
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Bulk delete — purge many sessions from every logged-in server at once.
3
+ *
4
+ * Deletion existed only one session at a time (`DELETE /api/conversations/:id`,
5
+ * and `recall_forget` over MCP). That is fine for "forget that conversation"
6
+ * and useless for the case that actually produces junk: a tool that fabricates
7
+ * sessions. A misfiring model-probe wrote 475 one-line transcripts into one
8
+ * project — 94% of its indexed history — and clearing them meant 475 HTTP
9
+ * round trips driven by hand.
10
+ *
11
+ * ── Why the sync endpoint rather than N deletes ────────────────────────────
12
+ * `POST /api/sync { tombstones: [...] }` already purges AND tombstones a whole
13
+ * array in one request, and it is the same path the collector uses when it
14
+ * notices a transcript disappeared locally. Reusing it means bulk deletion is
15
+ * not a second implementation of "remove a session everywhere" that has to stay
16
+ * in agreement with the first one — it IS the first one, called with more rows.
17
+ *
18
+ * ── Resurrection ───────────────────────────────────────────────────────────
19
+ * A tombstone is why this sticks. The server records every purged id and the
20
+ * ingest path skips anything in that set (`deadSet` in routes/sync.ts), so a
21
+ * later sync — from this machine with a stale ledger, or from a second device
22
+ * that still has the transcript — cannot put it back. The consequence is worth
23
+ * stating plainly to the caller: deleting a session whose local transcript
24
+ * still exists means that transcript can never be indexed again.
25
+ */
26
+ import { type Credentials } from './sync-client.js';
27
+ /** Rows as `/api/conversations/recent` returns them. */
28
+ export interface SessionRow {
29
+ sessionId: string;
30
+ firstPrompt?: string;
31
+ projectId?: string;
32
+ tool?: string;
33
+ }
34
+ export interface SelectOptions {
35
+ /** Logical project id, e.g. `git:github.com/me/repo`. */
36
+ project?: string;
37
+ /** Keep only sessions whose first prompt is EXACTLY this, trimmed. */
38
+ match?: string;
39
+ /** Keep only sessions from this tool (claude, codex, …). */
40
+ tool?: string;
41
+ /** Stop after this many matches. */
42
+ limit?: number;
43
+ }
44
+ export interface DeleteOutcome {
45
+ requested: number;
46
+ perTarget: Record<string, {
47
+ deleted: number;
48
+ error?: string;
49
+ }>;
50
+ /** Deleted on at least one server. */
51
+ deleted: number;
52
+ }
53
+ /** Ids per tombstone POST. Large enough to be one request for a typical
54
+ * cleanup, small enough that a failure loses a bounded amount of work. */
55
+ export declare const BATCH = 250;
56
+ /**
57
+ * Enumerate the sessions a selector matches, newest first.
58
+ *
59
+ * Paging walks the WHOLE project rather than trusting page 0 — the same trap
60
+ * `/api/data/delete` had to fix: rows come back mtime-ordered, so a filter that
61
+ * matches only old sessions finds nothing on the first page and would report an
62
+ * empty selection on a project with thousands of rows.
63
+ */
64
+ export declare function selectSessions(target: Credentials, opts: SelectOptions, fetchImpl?: typeof fetch): Promise<SessionRow[]>;
65
+ /**
66
+ * Purge + tombstone `ids` on every logged-in server.
67
+ *
68
+ * Per-target failures are recorded, never thrown: with two servers configured,
69
+ * one being unreachable must not hide that the other succeeded — the caller
70
+ * has to be able to say which copies are actually gone.
71
+ */
72
+ export declare function bulkDelete(ids: string[], opts?: {
73
+ targets?: Credentials[];
74
+ fetchImpl?: typeof fetch;
75
+ onProgress?: (done: number, total: number) => void;
76
+ }): Promise<DeleteOutcome>;
77
+ /** One recorded deletion. */
78
+ export interface Tombstone {
79
+ session_id: string;
80
+ deleted_at: number;
81
+ }
82
+ /**
83
+ * What has been deleted on a server, newest first.
84
+ *
85
+ * A purged session leaves nothing else behind to find it by, so this list is
86
+ * the only route back from a delete.
87
+ */
88
+ export declare function listTombstones(target: Credentials, limit?: number, fetchImpl?: typeof fetch): Promise<{
89
+ total: number;
90
+ tombstones: Tombstone[];
91
+ }>;
92
+ /**
93
+ * Lift tombstones so those sessions may be re-uploaded.
94
+ *
95
+ * Restores no content by itself — it removes the server's refusal. The
96
+ * transcript has to still exist on some device, which is why the CLI tells the
97
+ * caller to re-sync afterwards rather than claiming the data is back.
98
+ */
99
+ export declare function restoreSessions(ids: string[], opts?: {
100
+ targets?: Credentials[];
101
+ fetchImpl?: typeof fetch;
102
+ }): Promise<{
103
+ requested: number;
104
+ restored: number;
105
+ perTarget: Record<string, {
106
+ restored: number;
107
+ notDeleted: number;
108
+ error?: string;
109
+ }>;
110
+ }>;
111
+ /**
112
+ * Does this invocation need an explicit confirmation?
113
+ *
114
+ * Only when the caller DID NOT ENUMERATE the set. `delete <id>` has always
115
+ * deleted that id immediately and scripts depend on it — the repo's own privacy
116
+ * E2E calls it non-interactively, and demanding --yes for ids typed on the
117
+ * command line broke that contract for no safety gain: naming a session IS the
118
+ * confirmation.
119
+ *
120
+ * A selector is different. --project/--match COMPUTE the set, so its size is
121
+ * discovered rather than intended, and that is where a wrong pattern quietly
122
+ * takes hundreds with it. --stdin sits in the same bucket: the ids came from
123
+ * somewhere the person may not have read, and stdin is already a pipe, so there
124
+ * is nothing left to prompt on.
125
+ */
126
+ export declare function needsConfirmation(opts: {
127
+ argvIds: number;
128
+ project?: string;
129
+ stdin?: boolean;
130
+ }): boolean;
131
+ //# sourceMappingURL=bulk-delete.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bulk-delete.d.ts","sourceRoot":"","sources":["../src/bulk-delete.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGxE,wDAAwD;AACxD,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/D,sCAAsC;IACtC,OAAO,EAAE,MAAM,CAAC;CACjB;AAID;2EAC2E;AAC3E,eAAO,MAAM,KAAK,MAAM,CAAC;AAIzB;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,aAAa,EACnB,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,CAAC,UAAU,EAAE,CAAC,CA2BvB;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC9B,GAAG,EAAE,MAAM,EAAE,EACb,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IAAC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CAAO,GACnH,OAAO,CAAC,aAAa,CAAC,CAmDxB;AAED,6BAA6B;AAC7B,MAAM,WAAW,SAAS;IAAG,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE;AAErE;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,WAAW,EACnB,KAAK,SAAM,EACX,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,SAAS,EAAE,CAAA;CAAE,CAAC,CAMrD;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,MAAM,EAAE,EACb,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;CAAO,GAC/D,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,CAAC,CA8BvI;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAGvG"}
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Bulk delete — purge many sessions from every logged-in server at once.
3
+ *
4
+ * Deletion existed only one session at a time (`DELETE /api/conversations/:id`,
5
+ * and `recall_forget` over MCP). That is fine for "forget that conversation"
6
+ * and useless for the case that actually produces junk: a tool that fabricates
7
+ * sessions. A misfiring model-probe wrote 475 one-line transcripts into one
8
+ * project — 94% of its indexed history — and clearing them meant 475 HTTP
9
+ * round trips driven by hand.
10
+ *
11
+ * ── Why the sync endpoint rather than N deletes ────────────────────────────
12
+ * `POST /api/sync { tombstones: [...] }` already purges AND tombstones a whole
13
+ * array in one request, and it is the same path the collector uses when it
14
+ * notices a transcript disappeared locally. Reusing it means bulk deletion is
15
+ * not a second implementation of "remove a session everywhere" that has to stay
16
+ * in agreement with the first one — it IS the first one, called with more rows.
17
+ *
18
+ * ── Resurrection ───────────────────────────────────────────────────────────
19
+ * A tombstone is why this sticks. The server records every purged id and the
20
+ * ingest path skips anything in that set (`deadSet` in routes/sync.ts), so a
21
+ * later sync — from this machine with a stale ledger, or from a second device
22
+ * that still has the transcript — cannot put it back. The consequence is worth
23
+ * stating plainly to the caller: deleting a session whose local transcript
24
+ * still exists means that transcript can never be indexed again.
25
+ */
26
+ import { loadAllCredentials } from './sync-client.js';
27
+ import { getLedgerData, persistLedgerData } from './sync-ledger.js';
28
+ /** Page size for listing; the server caps `limit` at 200. */
29
+ const PAGE = 200;
30
+ /** Ids per tombstone POST. Large enough to be one request for a typical
31
+ * cleanup, small enough that a failure loses a bounded amount of work. */
32
+ export const BATCH = 250;
33
+ const base = (url) => url.replace(/\/+$/, '');
34
+ /**
35
+ * Enumerate the sessions a selector matches, newest first.
36
+ *
37
+ * Paging walks the WHOLE project rather than trusting page 0 — the same trap
38
+ * `/api/data/delete` had to fix: rows come back mtime-ordered, so a filter that
39
+ * matches only old sessions finds nothing on the first page and would report an
40
+ * empty selection on a project with thousands of rows.
41
+ */
42
+ export async function selectSessions(target, opts, fetchImpl = fetch) {
43
+ const found = [];
44
+ const want = opts.match?.trim();
45
+ for (let offset = 0;; offset += PAGE) {
46
+ const qs = new URLSearchParams({ limit: String(PAGE), offset: String(offset) });
47
+ if (opts.project)
48
+ qs.set('project', opts.project);
49
+ if (opts.tool)
50
+ qs.set('tool', opts.tool);
51
+ const res = await fetchImpl(`${base(target.serverUrl)}/api/conversations/recent?${qs}`, {
52
+ headers: { authorization: `Bearer ${target.token}` },
53
+ });
54
+ if (!res.ok)
55
+ throw new Error(`list failed: HTTP ${res.status}`);
56
+ const body = await res.json();
57
+ const page = body.sessions ?? [];
58
+ if (page.length === 0)
59
+ break;
60
+ for (const row of page) {
61
+ if (!row.sessionId)
62
+ continue;
63
+ if (want !== undefined && (row.firstPrompt ?? '').trim() !== want)
64
+ continue;
65
+ found.push(row);
66
+ if (opts.limit && found.length >= opts.limit)
67
+ return found;
68
+ }
69
+ if (page.length < PAGE)
70
+ break;
71
+ }
72
+ return found;
73
+ }
74
+ /**
75
+ * Purge + tombstone `ids` on every logged-in server.
76
+ *
77
+ * Per-target failures are recorded, never thrown: with two servers configured,
78
+ * one being unreachable must not hide that the other succeeded — the caller
79
+ * has to be able to say which copies are actually gone.
80
+ */
81
+ export async function bulkDelete(ids, opts = {}) {
82
+ const unique = [...new Set(ids.filter(Boolean))];
83
+ const targets = opts.targets ?? loadAllCredentials();
84
+ const fetchImpl = opts.fetchImpl ?? fetch;
85
+ const out = { requested: unique.length, perTarget: {}, deleted: 0 };
86
+ if (unique.length === 0 || targets.length === 0)
87
+ return out;
88
+ const succeeded = new Set();
89
+ for (const t of targets) {
90
+ let deleted = 0;
91
+ try {
92
+ for (let i = 0; i < unique.length; i += BATCH) {
93
+ const slice = unique.slice(i, i + BATCH);
94
+ const res = await fetchImpl(`${base(t.serverUrl)}/api/sync`, {
95
+ method: 'POST',
96
+ headers: { 'content-type': 'application/json', authorization: `Bearer ${t.token}` },
97
+ body: JSON.stringify({
98
+ tombstones: slice.map((session_id) => ({ session_id, deleted_at: Date.now() })),
99
+ }),
100
+ });
101
+ if (!res.ok)
102
+ throw new Error(`HTTP ${res.status}`);
103
+ deleted += slice.length;
104
+ for (const id of slice)
105
+ succeeded.add(id);
106
+ opts.onProgress?.(deleted, unique.length);
107
+ }
108
+ out.perTarget[t.serverUrl] = { deleted };
109
+ }
110
+ catch (e) {
111
+ out.perTarget[t.serverUrl] = { deleted, error: e instanceof Error ? e.message : 'failed' };
112
+ }
113
+ }
114
+ out.deleted = succeeded.size;
115
+ // Drop the ids from this device's ledger. Without it the collector keeps a
116
+ // record of having shipped them and re-offers them on the next full walk —
117
+ // harmless (the server's deadSet refuses) but it means every later sync
118
+ // carries traffic for sessions that are deliberately gone.
119
+ for (const t of targets) {
120
+ if (!out.perTarget[t.serverUrl] || out.perTarget[t.serverUrl].error)
121
+ continue;
122
+ try {
123
+ const data = getLedgerData(t.serverUrl);
124
+ let touched = false;
125
+ for (const id of succeeded) {
126
+ if (id in data) {
127
+ delete data[id];
128
+ touched = true;
129
+ }
130
+ }
131
+ if (touched)
132
+ persistLedgerData(t.serverUrl, data);
133
+ }
134
+ catch { /* local bookkeeping — the delete itself already landed */ }
135
+ }
136
+ return out;
137
+ }
138
+ /**
139
+ * What has been deleted on a server, newest first.
140
+ *
141
+ * A purged session leaves nothing else behind to find it by, so this list is
142
+ * the only route back from a delete.
143
+ */
144
+ export async function listTombstones(target, limit = 500, fetchImpl = fetch) {
145
+ const res = await fetchImpl(`${base(target.serverUrl)}/api/data/tombstones?limit=${limit}`, {
146
+ headers: { authorization: `Bearer ${target.token}` },
147
+ });
148
+ if (!res.ok)
149
+ throw new Error(`HTTP ${res.status}`);
150
+ return await res.json();
151
+ }
152
+ /**
153
+ * Lift tombstones so those sessions may be re-uploaded.
154
+ *
155
+ * Restores no content by itself — it removes the server's refusal. The
156
+ * transcript has to still exist on some device, which is why the CLI tells the
157
+ * caller to re-sync afterwards rather than claiming the data is back.
158
+ */
159
+ export async function restoreSessions(ids, opts = {}) {
160
+ const unique = [...new Set(ids.filter(Boolean))];
161
+ const targets = opts.targets ?? loadAllCredentials();
162
+ const fetchImpl = opts.fetchImpl ?? fetch;
163
+ const out = { requested: unique.length, restored: 0, perTarget: {} };
164
+ if (unique.length === 0 || targets.length === 0)
165
+ return out;
166
+ let best = 0;
167
+ for (const t of targets) {
168
+ try {
169
+ let restored = 0, notDeleted = 0;
170
+ for (let i = 0; i < unique.length; i += BATCH) {
171
+ const res = await fetchImpl(`${base(t.serverUrl)}/api/data/restore`, {
172
+ method: 'POST',
173
+ headers: { 'content-type': 'application/json', authorization: `Bearer ${t.token}` },
174
+ body: JSON.stringify({ session_ids: unique.slice(i, i + BATCH) }),
175
+ });
176
+ if (!res.ok)
177
+ throw new Error(`HTTP ${res.status}`);
178
+ const body = await res.json();
179
+ restored += body.restored ?? 0;
180
+ notDeleted += (body.notDeleted ?? []).length;
181
+ }
182
+ out.perTarget[t.serverUrl] = { restored, notDeleted };
183
+ best = Math.max(best, restored);
184
+ }
185
+ catch (e) {
186
+ out.perTarget[t.serverUrl] = { restored: 0, notDeleted: 0, error: e instanceof Error ? e.message : 'failed' };
187
+ }
188
+ }
189
+ out.restored = best;
190
+ return out;
191
+ }
192
+ /**
193
+ * Does this invocation need an explicit confirmation?
194
+ *
195
+ * Only when the caller DID NOT ENUMERATE the set. `delete <id>` has always
196
+ * deleted that id immediately and scripts depend on it — the repo's own privacy
197
+ * E2E calls it non-interactively, and demanding --yes for ids typed on the
198
+ * command line broke that contract for no safety gain: naming a session IS the
199
+ * confirmation.
200
+ *
201
+ * A selector is different. --project/--match COMPUTE the set, so its size is
202
+ * discovered rather than intended, and that is where a wrong pattern quietly
203
+ * takes hundreds with it. --stdin sits in the same bucket: the ids came from
204
+ * somewhere the person may not have read, and stdin is already a pipe, so there
205
+ * is nothing left to prompt on.
206
+ */
207
+ export function needsConfirmation(opts) {
208
+ const enumeratedByCaller = opts.argvIds > 0 && !opts.project && !opts.stdin;
209
+ return !enumeratedByCaller;
210
+ }
211
+ //# sourceMappingURL=bulk-delete.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bulk-delete.js","sourceRoot":"","sources":["../src/bulk-delete.ts"],"names":[],"mappings":"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"}