chat-recall 0.0.1 → 0.4.8

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.
Files changed (94) hide show
  1. package/LICENSE +93 -0
  2. package/dist/auto-update.d.ts +52 -0
  3. package/dist/auto-update.d.ts.map +1 -0
  4. package/dist/auto-update.js +157 -0
  5. package/dist/auto-update.js.map +1 -0
  6. package/dist/cli.d.ts +6 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +26102 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/client-events.d.ts +11 -0
  11. package/dist/client-events.d.ts.map +1 -0
  12. package/dist/client-events.js +77 -0
  13. package/dist/client-events.js.map +1 -0
  14. package/dist/collector-migrate.d.ts +27 -0
  15. package/dist/collector-migrate.d.ts.map +1 -0
  16. package/dist/collector-migrate.js +60 -0
  17. package/dist/collector-migrate.js.map +1 -0
  18. package/dist/device-auth.d.ts +37 -0
  19. package/dist/device-auth.d.ts.map +1 -0
  20. package/dist/device-auth.js +161 -0
  21. package/dist/device-auth.js.map +1 -0
  22. package/dist/escalate.d.ts +99 -0
  23. package/dist/escalate.d.ts.map +1 -0
  24. package/dist/escalate.js +219 -0
  25. package/dist/escalate.js.map +1 -0
  26. package/dist/http.d.ts +14 -0
  27. package/dist/http.d.ts.map +1 -0
  28. package/dist/http.js +49 -0
  29. package/dist/http.js.map +1 -0
  30. package/dist/install-skills.d.ts +51 -0
  31. package/dist/install-skills.d.ts.map +1 -0
  32. package/dist/install-skills.js +223 -0
  33. package/dist/install-skills.js.map +1 -0
  34. package/dist/intent-drain.d.ts +25 -0
  35. package/dist/intent-drain.d.ts.map +1 -0
  36. package/dist/intent-drain.js +189 -0
  37. package/dist/intent-drain.js.map +1 -0
  38. package/dist/keyring.d.ts +28 -0
  39. package/dist/keyring.d.ts.map +1 -0
  40. package/dist/keyring.js +163 -0
  41. package/dist/keyring.js.map +1 -0
  42. package/dist/mcp.d.ts +8 -0
  43. package/dist/mcp.d.ts.map +1 -0
  44. package/dist/mcp.js +23665 -0
  45. package/dist/mcp.js.map +1 -0
  46. package/dist/project-tasks.d.ts +54 -0
  47. package/dist/project-tasks.d.ts.map +1 -0
  48. package/dist/project-tasks.js +204 -0
  49. package/dist/project-tasks.js.map +1 -0
  50. package/dist/repair.d.ts +73 -0
  51. package/dist/repair.d.ts.map +1 -0
  52. package/dist/repair.js +267 -0
  53. package/dist/repair.js.map +1 -0
  54. package/dist/self-restart.d.ts +91 -0
  55. package/dist/self-restart.d.ts.map +1 -0
  56. package/dist/self-restart.js +137 -0
  57. package/dist/self-restart.js.map +1 -0
  58. package/dist/service-installer.d.ts +32 -0
  59. package/dist/service-installer.d.ts.map +1 -0
  60. package/dist/service-installer.js +231 -0
  61. package/dist/service-installer.js.map +1 -0
  62. package/dist/sync-client.d.ts +234 -0
  63. package/dist/sync-client.d.ts.map +1 -0
  64. package/dist/sync-client.js +1985 -0
  65. package/dist/sync-client.js.map +1 -0
  66. package/dist/sync-ledger.d.ts +131 -0
  67. package/dist/sync-ledger.d.ts.map +1 -0
  68. package/dist/sync-ledger.js +366 -0
  69. package/dist/sync-ledger.js.map +1 -0
  70. package/dist/update-notice.d.ts +16 -0
  71. package/dist/update-notice.d.ts.map +1 -0
  72. package/dist/update-notice.js +107 -0
  73. package/dist/update-notice.js.map +1 -0
  74. package/dist/verify-deep.d.ts +79 -0
  75. package/dist/verify-deep.d.ts.map +1 -0
  76. package/dist/verify-deep.js +97 -0
  77. package/dist/verify-deep.js.map +1 -0
  78. package/dist/verify-repair.d.ts +21 -0
  79. package/dist/verify-repair.d.ts.map +1 -0
  80. package/dist/verify-repair.js +58 -0
  81. package/dist/verify-repair.js.map +1 -0
  82. package/dist/watch.js +21550 -0
  83. package/hooks/chat_recall_escalate_hook.sh +51 -0
  84. package/hooks/chat_recall_resume_hook.sh +69 -0
  85. package/hooks/chat_recall_save_hook.sh +254 -0
  86. package/hooks/chat_recall_wakeup_hook.sh +39 -0
  87. package/package.json +38 -4
  88. package/skills/chat-recall/SKILL.md +76 -0
  89. package/skills/chat-recall-changes/SKILL.md +50 -0
  90. package/skills/chat-recall-find/SKILL.md +64 -0
  91. package/skills/chat-recall-memory/SKILL.md +55 -0
  92. package/skills/chat-recall-project/SKILL.md +75 -0
  93. package/skills/chat-recall-resume/SKILL.md +62 -0
  94. package/README.md +0 -7
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-tasks.js","sourceRoot":"","sources":["../src/project-tasks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * `chat-recall repair` — rebuild sessions whose history an upstream tool
3
+ * truncated in place (Claude Code 2.1.20x resume rewrite) before the shadow
4
+ * archive existed to catch it.
5
+ *
6
+ * SOURCES OF TRUTH, in order of fullness (we take whichever has the MOST
7
+ * messages, then union them):
8
+ * 1. the local shadow archive (if this machine already recovered it)
9
+ * 2. each configured server's shrink-protected raw archive
10
+ * (`GET /api/conversations/:id/raw-archive`) — the SaaS keeps the
11
+ * pre-truncation original because putRawSession never overwrites with a
12
+ * smaller capture; a frozen self-host server is an equally good backstop.
13
+ *
14
+ * FLOW per session: fetch every candidate → pick/​union the fullest → seed the
15
+ * local shadow with it → rebuild the conversation from that shadow (the normal
16
+ * sync builder now reads the shadow) → push the recovered FULL conversation to
17
+ * every server whose current copy is smaller. The server-side shrink guard
18
+ * accepts it because it GROWS the stored conversation.
19
+ *
20
+ * This is the deliberate, audited inverse of the silent truncation: it only
21
+ * ever makes a conversation FULLER, never smaller.
22
+ */
23
+ export interface RepairResult {
24
+ sessionId: string;
25
+ status: 'repaired' | 'would-repair' | 'already-full' | 'no-archive' | 'error';
26
+ fullestMessages: number;
27
+ fullestSource: string;
28
+ pushed: Array<{
29
+ server: string;
30
+ before: number;
31
+ after: number;
32
+ }>;
33
+ note?: string;
34
+ }
35
+ /**
36
+ * Repair one session: gather the fullest archive across the local shadow and
37
+ * every configured server, seed the shadow, rebuild, and push where smaller.
38
+ */
39
+ export declare function repairSession(id: string, opts?: {
40
+ dryRun?: boolean;
41
+ force?: boolean;
42
+ verbose?: boolean;
43
+ server?: string;
44
+ }): Promise<RepairResult>;
45
+ export declare function repairSessions(ids: string[], opts?: {
46
+ dryRun?: boolean;
47
+ force?: boolean;
48
+ verbose?: boolean;
49
+ server?: string;
50
+ }): Promise<RepairResult[]>;
51
+ export interface RepairAllReport {
52
+ scanned: number;
53
+ candidates: RepairResult[];
54
+ discoveryServer: string;
55
+ }
56
+ /**
57
+ * Discover every damaged session in a recency window and repair it — the
58
+ * automated sweep behind `repair --all`. A session is DAMAGED when its
59
+ * shrink-protected raw archive parses to MORE messages than the server's
60
+ * current item count (the exact fingerprint of the resume-truncation
61
+ * incident). Discovery reads the primary server only (the one we hold a token
62
+ * for); repairSession then fixes every configured target that's behind.
63
+ *
64
+ * Read-only unless opts.apply — the default is a dry run that mutates nothing.
65
+ */
66
+ export declare function repairAll(opts?: {
67
+ sinceHours?: number;
68
+ apply?: boolean;
69
+ verbose?: boolean;
70
+ minMessages?: number;
71
+ server?: string;
72
+ }): Promise<RepairAllReport>;
73
+ //# sourceMappingURL=repair.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repair.d.ts","sourceRoot":"","sources":["../src/repair.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAmDH,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,UAAU,GAAG,cAAc,GAAG,cAAc,GAAG,YAAY,GAAG,OAAO,CAAC;IAC9E,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CAsH3J;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAOjK;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,YAAY,EAAE,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;GASG;AACH,wBAAsB,SAAS,CAAC,IAAI,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,OAAO,CAAC,eAAe,CAAC,CAsCvK"}
package/dist/repair.js ADDED
@@ -0,0 +1,267 @@
1
+ /**
2
+ * `chat-recall repair` — rebuild sessions whose history an upstream tool
3
+ * truncated in place (Claude Code 2.1.20x resume rewrite) before the shadow
4
+ * archive existed to catch it.
5
+ *
6
+ * SOURCES OF TRUTH, in order of fullness (we take whichever has the MOST
7
+ * messages, then union them):
8
+ * 1. the local shadow archive (if this machine already recovered it)
9
+ * 2. each configured server's shrink-protected raw archive
10
+ * (`GET /api/conversations/:id/raw-archive`) — the SaaS keeps the
11
+ * pre-truncation original because putRawSession never overwrites with a
12
+ * smaller capture; a frozen self-host server is an equally good backstop.
13
+ *
14
+ * FLOW per session: fetch every candidate → pick/​union the fullest → seed the
15
+ * local shadow with it → rebuild the conversation from that shadow (the normal
16
+ * sync builder now reads the shadow) → push the recovered FULL conversation to
17
+ * every server whose current copy is smaller. The server-side shrink guard
18
+ * accepts it because it GROWS the stored conversation.
19
+ *
20
+ * This is the deliberate, audited inverse of the silent truncation: it only
21
+ * ever makes a conversation FULLER, never smaller.
22
+ */
23
+ import { createHash } from 'node:crypto';
24
+ import { gunzipContainer, parseTranscriptFromContainer, seedShadow, readShadowContainer } from '@chat-recall/engine/transcript/index.js';
25
+ import { getBackendForId, getBackend } from '@chat-recall/engine/core/tool-backend.js';
26
+ import { loadSettings } from '@chat-recall/engine/core/settings.js';
27
+ import { fetchWithTimeout } from './http.js';
28
+ import { loadAllCredentials } from './sync-client.js';
29
+ import { buildConversationSync } from './sync-client.js';
30
+ /** The configured sync targets (settings.sync.endpoint), each with its token
31
+ * if we have one. Mirrors how the watch daemon resolves targets. */
32
+ function resolveEndpoints(only) {
33
+ const settings = loadSettings();
34
+ const creds = loadAllCredentials();
35
+ const raw = (settings.sync.endpoint || '').split(',').map((s) => s.trim().replace(/\/+$/, '')).filter(Boolean);
36
+ const urls = only ? [only.replace(/\/+$/, '')] : raw;
37
+ // Fall back to credential URLs if settings has no endpoint recorded.
38
+ const all = urls.length > 0 ? urls : creds.map((c) => c.serverUrl.replace(/\/+$/, ''));
39
+ return all.map((url) => ({ url, token: creds.find((c) => c.serverUrl.replace(/\/+$/, '') === url)?.token }));
40
+ }
41
+ const hashPath = (p) => (p ? 'p_' + createHash('sha256').update(p).digest('hex').slice(0, 12) : '');
42
+ /** Pull cwd out of a raw container's main transcript (claude/codex jsonl) so an
43
+ * off-disk session still lands under the right project. '' when unknown. */
44
+ function cwdFromContainer(c) {
45
+ const main = c.files.find((f) => f.name.endsWith('.jsonl') && !f.name.startsWith('subagents/')) ?? c.files[0];
46
+ if (!main)
47
+ return '';
48
+ for (const line of main.text.split('\n')) {
49
+ if (!line.includes('"cwd"'))
50
+ continue;
51
+ try {
52
+ const o = JSON.parse(line);
53
+ if (typeof o.cwd === 'string' && o.cwd)
54
+ return o.cwd;
55
+ }
56
+ catch { /* skip */ }
57
+ }
58
+ return '';
59
+ }
60
+ async function fetchJson(url, token, timeoutMs = 30000) {
61
+ const ac = new AbortController();
62
+ const timer = setTimeout(() => ac.abort(), timeoutMs);
63
+ try {
64
+ const res = await fetch(url, { headers: token ? { authorization: `Bearer ${token}` } : {}, signal: ac.signal });
65
+ if (!res.ok)
66
+ return null;
67
+ return await res.json();
68
+ }
69
+ catch {
70
+ return null;
71
+ }
72
+ finally {
73
+ clearTimeout(timer);
74
+ }
75
+ }
76
+ /**
77
+ * Repair one session: gather the fullest archive across the local shadow and
78
+ * every configured server, seed the shadow, rebuild, and push where smaller.
79
+ */
80
+ export async function repairSession(id, opts = {}) {
81
+ const backend = getBackendForId(id) ?? getBackend('claude');
82
+ const tool = backend.id;
83
+ const rawId = backend.toRawId(id);
84
+ const endpoints = resolveEndpoints(opts.server);
85
+ const log = (m) => { if (opts.verbose)
86
+ console.error(` [repair ${id}] ${m}`); };
87
+ const candidates = [];
88
+ // 1. Local shadow (already-known-fullest on this machine).
89
+ const localShadow = readShadowContainer(tool, rawId);
90
+ if (localShadow) {
91
+ try {
92
+ candidates.push({ container: localShadow, messages: parseTranscriptFromContainer(localShadow).messages.length, source: 'local-shadow' });
93
+ }
94
+ catch { /* corrupt shadow — ignore */ }
95
+ }
96
+ // 2. Each server's shrink-protected raw archive + its current item count.
97
+ const currentOnServer = new Map();
98
+ for (const ep of endpoints) {
99
+ const cur = await fetchJson(`${ep.url}/api/conversations/${id}?limit=0`, ep.token);
100
+ if (cur && Array.isArray(cur.messages))
101
+ currentOnServer.set(ep.url, cur.messages.length);
102
+ const arch = await fetchJson(`${ep.url}/api/conversations/${id}/raw-archive`, ep.token);
103
+ if (arch?.gzB64) {
104
+ try {
105
+ const container = gunzipContainer(Buffer.from(arch.gzB64, 'base64'));
106
+ if (container)
107
+ candidates.push({ container, messages: parseTranscriptFromContainer(container).messages.length, source: ep.url });
108
+ }
109
+ catch { /* corrupt archive — ignore */ }
110
+ }
111
+ }
112
+ if (candidates.length === 0) {
113
+ return { sessionId: id, status: 'no-archive', fullestMessages: 0, fullestSource: '', pushed: [], note: 'no shadow or server archive found' };
114
+ }
115
+ const fullest = candidates.reduce((a, b) => (b.messages > a.messages ? b : a));
116
+ log(`fullest archive = ${fullest.messages} msg(s) from ${fullest.source}`);
117
+ if (opts.dryRun) {
118
+ // Report the archive count as a LOWER BOUND — the real rebuild also unions
119
+ // the current disk tail (post-resume continuation), which can only add more.
120
+ const needy = endpoints.filter((ep) => (currentOnServer.get(ep.url) ?? 0) < fullest.messages || opts.force);
121
+ if (needy.length === 0) {
122
+ return { sessionId: id, status: 'already-full', fullestMessages: fullest.messages, fullestSource: fullest.source, pushed: [] };
123
+ }
124
+ return {
125
+ sessionId: id, status: 'would-repair', fullestMessages: fullest.messages, fullestSource: fullest.source,
126
+ pushed: needy.map((ep) => ({ server: ep.url, before: currentOnServer.get(ep.url) ?? 0, after: fullest.messages })),
127
+ note: 'archive count is a lower bound; the rebuild also unions the on-disk tail',
128
+ };
129
+ }
130
+ // Seed the shadow so the sync builder reads the recovered-full transcript.
131
+ // buildConversationSync then re-exports the CURRENT disk file and unions it
132
+ // with this seed via the shadow, so the rebuilt conversation is the true
133
+ // union of {pre-truncation archive} ∪ {post-resume on-disk tail}.
134
+ seedShadow(rawId, fullest.container);
135
+ // Synthesize a SessionRef. On disk → use the real location (lets the builder
136
+ // read cwd + telemetry live); off disk → derive from the container.
137
+ const located = backend.findSession(rawId);
138
+ const projectPath = located?.projectPath || cwdFromContainer(fullest.container);
139
+ const mtime = fullest.container.mtime || located?.mtime || Date.now();
140
+ const ref = {
141
+ toolId: tool,
142
+ rawId,
143
+ prefixedId: id,
144
+ projectPath,
145
+ projectDir: located?.projectDir || '',
146
+ fullPath: located?.path || '',
147
+ created: new Date(mtime).toISOString(),
148
+ modified: new Date(mtime).toISOString(),
149
+ mtime,
150
+ firstPrompt: '',
151
+ messageCount: fullest.messages,
152
+ };
153
+ const cleartext = loadSettings().sync?.pathsCleartext === true;
154
+ const mapPath = (p) => (cleartext ? p : hashPath(p));
155
+ const built = await buildConversationSync(ref, Math.floor(mtime), {
156
+ mapPath, includeRaw: true, includeMeta: true, scanSecrets: false,
157
+ });
158
+ // No priorContentHash passed → the unchanged-content early-out can't fire here;
159
+ // the `'unchanged' in built` arm is only for exhaustive narrowing.
160
+ if (!built || 'unchanged' in built) {
161
+ return { sessionId: id, status: 'error', fullestMessages: fullest.messages, fullestSource: fullest.source, pushed: [], note: 'rebuild produced no conversation' };
162
+ }
163
+ // The rebuilt count is the true fullness (archive ∪ on-disk tail) and drives
164
+ // both the push decision and the report — never the pre-build candidate.
165
+ const builtEnv = built.conv.envelope;
166
+ const builtCount = Array.isArray(builtEnv?.messages) ? builtEnv.messages.length : fullest.messages;
167
+ const needy = endpoints.filter((ep) => (currentOnServer.get(ep.url) ?? 0) < builtCount || opts.force);
168
+ if (needy.length === 0) {
169
+ return { sessionId: id, status: 'already-full', fullestMessages: builtCount, fullestSource: fullest.source, pushed: [] };
170
+ }
171
+ const pushed = [];
172
+ for (const ep of needy) {
173
+ const before = currentOnServer.get(ep.url) ?? 0;
174
+ const headers = { 'content-type': 'application/json' };
175
+ if (ep.token)
176
+ headers.authorization = `Bearer ${ep.token}`;
177
+ try {
178
+ // Bounded (60s — a repair push carries a whole transcript): repair runs
179
+ // inside the intent drain, and an unbounded upload wedges every later tick.
180
+ const res = await fetchWithTimeout(`${ep.url}/api/sync`, { method: 'POST', headers, body: JSON.stringify({ conversations: [built.conv] }) }, 60_000);
181
+ if (!res.ok) {
182
+ pushed.push({ server: ep.url, before, after: before });
183
+ log(`push to ${ep.url} FAILED: HTTP ${res.status}`);
184
+ continue;
185
+ }
186
+ // Confirm the new count.
187
+ const after = await fetchJson(`${ep.url}/api/conversations/${id}?limit=0`, ep.token);
188
+ const afterN = after && Array.isArray(after.messages) ? after.messages.length : before;
189
+ pushed.push({ server: ep.url, before, after: afterN });
190
+ log(`pushed to ${ep.url}: ${before} → ${afterN} msg(s)`);
191
+ }
192
+ catch (e) {
193
+ pushed.push({ server: ep.url, before, after: before });
194
+ log(`push to ${ep.url} errored: ${e instanceof Error ? e.message : e}`);
195
+ }
196
+ }
197
+ return { sessionId: id, status: 'repaired', fullestMessages: builtCount, fullestSource: fullest.source, pushed };
198
+ }
199
+ export async function repairSessions(ids, opts = {}) {
200
+ const out = [];
201
+ for (const id of ids) {
202
+ try {
203
+ out.push(await repairSession(id, opts));
204
+ }
205
+ catch (e) {
206
+ out.push({ sessionId: id, status: 'error', fullestMessages: 0, fullestSource: '', pushed: [], note: e instanceof Error ? e.message : String(e) });
207
+ }
208
+ }
209
+ return out;
210
+ }
211
+ /**
212
+ * Discover every damaged session in a recency window and repair it — the
213
+ * automated sweep behind `repair --all`. A session is DAMAGED when its
214
+ * shrink-protected raw archive parses to MORE messages than the server's
215
+ * current item count (the exact fingerprint of the resume-truncation
216
+ * incident). Discovery reads the primary server only (the one we hold a token
217
+ * for); repairSession then fixes every configured target that's behind.
218
+ *
219
+ * Read-only unless opts.apply — the default is a dry run that mutates nothing.
220
+ */
221
+ export async function repairAll(opts = {}) {
222
+ const sinceHours = opts.sinceHours ?? 72;
223
+ const minMessages = opts.minMessages ?? 2;
224
+ const endpoints = resolveEndpoints(opts.server);
225
+ const primary = endpoints.find((e) => e.token) ?? endpoints[0];
226
+ if (!primary)
227
+ return { scanned: 0, candidates: [], discoveryServer: '' };
228
+ const log = (m) => { if (opts.verbose)
229
+ console.error(` [repair --all] ${m}`); };
230
+ // Page through the sessions modified in the window.
231
+ const ids = [];
232
+ const limit = 200;
233
+ for (let offset = 0;; offset += limit) {
234
+ const page = await fetchJson(`${primary.url}/api/conversations/recent?since_hours=${sinceHours}&limit=${limit}&offset=${offset}&include_untracked=1`, primary.token);
235
+ const sessions = page?.sessions ?? [];
236
+ for (const s of sessions)
237
+ if (s.sessionId)
238
+ ids.push(s.sessionId);
239
+ if (!page?.hasMore || sessions.length === 0)
240
+ break;
241
+ }
242
+ log(`window=${sinceHours}h → ${ids.length} session(s) to check on ${primary.url}`);
243
+ const candidates = [];
244
+ let scanned = 0;
245
+ for (const id of ids) {
246
+ scanned++;
247
+ if (opts.verbose && scanned % 50 === 0)
248
+ log(`checked ${scanned}/${ids.length}…`);
249
+ const arch = await fetchJson(`${primary.url}/api/conversations/${id}/raw-archive?count=1`, primary.token);
250
+ const archiveMsgs = typeof arch?.messages === 'number' ? arch.messages : 0;
251
+ if (archiveMsgs < minMessages)
252
+ continue; // no/thin archive — nothing to recover
253
+ const meta = await fetchJson(`${primary.url}/api/conversations/${id}/metadata`, primary.token);
254
+ const current = typeof meta?.messageCount === 'number' ? meta.messageCount : 0;
255
+ if (archiveMsgs <= current)
256
+ continue; // healthy — archive not fuller than the live view
257
+ // Damaged. Dry-run reports; apply rebuilds + pushes via the canonical path.
258
+ const r = await repairSession(id, { dryRun: !opts.apply, verbose: opts.verbose, server: opts.server });
259
+ // Skip the 'already-full' verdict repairSession may return once the on-disk
260
+ // tail union doesn't actually beat the server (rare; keeps the report clean).
261
+ if (r.status === 'already-full')
262
+ continue;
263
+ candidates.push(r);
264
+ }
265
+ return { scanned, candidates, discoveryServer: primary.url };
266
+ }
267
+ //# sourceMappingURL=repair.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repair.js","sourceRoot":"","sources":["../src/repair.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * The watch daemon notices when its own code changes, and comes back on the new
3
+ * code instead of running the old one indefinitely.
4
+ *
5
+ * ── Why ──────────────────────────────────────────────────────────────────
6
+ * A long-lived daemon holds whatever bundle it started with. Node has already
7
+ * parsed it; replacing the file on disk changes nothing about the running
8
+ * process. So a fix can be committed, built, installed and still not be in
9
+ * effect — with no signal anywhere that the process is stale.
10
+ *
11
+ * That has caused three separate incidents in this codebase:
12
+ * - the trufflehog /tmp leak kept accreting 34MB per sync for a day after the
13
+ * fix shipped, because the daemon predated it;
14
+ * - the cross-home union landed and the collector kept reading one home for
15
+ * another day, stranding 694 records of a live session;
16
+ * - the ledger ack fix, where the daemon had been restarted just BEFORE the
17
+ * commit and so needed restarting a third time.
18
+ *
19
+ * `auto-update.ts` already handles the published-release path (fetch, verify,
20
+ * `npm i -g`, restart) which is what real customers hit. This covers the other
21
+ * case: the bundle changing underneath a running daemon — a linked dev build, a
22
+ * manual `npm i -g`, or a package manager upgrading the CLI without touching the
23
+ * service.
24
+ *
25
+ * ── How it comes back matters more than how it notices ───────────────────
26
+ * Exiting is only safe when something will start us again, and that differs:
27
+ *
28
+ * systemd Restart=on-failure → must exit NON-ZERO (a clean exit stays down)
29
+ * launchd KeepAlive=true → restarts on any exit
30
+ * schtasks /sc onlogon → NEVER restarts; exiting means down until the
31
+ * next logon, which is worse than stale code,
32
+ * so we re-exec ourselves instead
33
+ * foreground (no supervisor) → exiting would just kill the user's process;
34
+ * warn loudly and keep running
35
+ *
36
+ * Getting that wrong turns a staleness fix into an outage, which is why the exit
37
+ * code is 75 (EX_TEMPFAIL) rather than 0: it satisfies the `on-failure` policy
38
+ * that is already installed on every existing machine, so this works without
39
+ * re-rendering anyone's unit file.
40
+ */
41
+ /** Non-zero so systemd's `Restart=on-failure` picks it up. EX_TEMPFAIL. */
42
+ export declare const EXIT_CODE_CODE_CHANGED = 75;
43
+ export interface CodeFingerprint {
44
+ path: string;
45
+ size: number;
46
+ mtimeMs: number;
47
+ }
48
+ /**
49
+ * Identity of the bundle we are running. The build produces a single bundled
50
+ * `watch.js` (engine included), so one stat answers "did my code change?" —
51
+ * no need to walk node_modules.
52
+ */
53
+ export declare function codeFingerprint(entry?: string): CodeFingerprint | null;
54
+ export declare function sameFingerprint(a: CodeFingerprint | null, b: CodeFingerprint | null): boolean;
55
+ export type Supervisor = 'systemd' | 'launchd' | 'windows-task' | 'none';
56
+ /**
57
+ * Who, if anyone, will restart us. systemd exports INVOCATION_ID (and
58
+ * JOURNAL_STREAM) to its units; launchd exports XPC_SERVICE_NAME. Neither is
59
+ * something a normal shell has, so their presence is a reliable "I am
60
+ * supervised" signal.
61
+ */
62
+ export declare function detectSupervisor(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): Supervisor;
63
+ export type RestartAction = {
64
+ kind: 'exit';
65
+ code: number;
66
+ } | {
67
+ kind: 'reexec';
68
+ } | {
69
+ kind: 'warn';
70
+ };
71
+ /** What to do about a detected code change, given who is supervising. */
72
+ export declare function restartActionFor(sup: Supervisor): RestartAction;
73
+ export interface SelfRestartOptions {
74
+ /** Fingerprint captured at boot. */
75
+ boot: CodeFingerprint | null;
76
+ log?: (m: string) => void;
77
+ /** Test seam. */
78
+ now?: () => CodeFingerprint | null;
79
+ supervisor?: Supervisor;
80
+ /** Test seam — defaults to actually exiting. */
81
+ exit?: (code: number) => void;
82
+ }
83
+ /**
84
+ * Check whether our bundle changed and act on it. Returns true when a restart
85
+ * was triggered (so callers can stop scheduling work).
86
+ *
87
+ * Opt out with CHAT_RECALL_SELF_RESTART=0 — the escape hatch for anyone
88
+ * debugging with a hot-reloading build who does not want the process bouncing.
89
+ */
90
+ export declare function checkSelfRestart(opts: SelfRestartOptions): boolean;
91
+ //# sourceMappingURL=self-restart.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"self-restart.d.ts","sourceRoot":"","sources":["../src/self-restart.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAKH,2EAA2E;AAC3E,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,SAAkB,GAAG,eAAe,GAAG,IAAI,CAQ/E;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,eAAe,GAAG,IAAI,EAAE,CAAC,EAAE,eAAe,GAAG,IAAI,GAAG,OAAO,CAG7F;AAED,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,cAAc,GAAG,MAAM,CAAC;AAEzE;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,EAAE,QAAQ,kBAAmB,GAAG,UAAU,CAK9G;AAED,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErB,yEAAyE;AACzE,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,GAAG,aAAa,CAU/D;AAkBD,MAAM,WAAW,kBAAkB;IACjC,oCAAoC;IACpC,IAAI,EAAE,eAAe,GAAG,IAAI,CAAC;IAC7B,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1B,iBAAiB;IACjB,GAAG,CAAC,EAAE,MAAM,eAAe,GAAG,IAAI,CAAC;IACnC,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,gDAAgD;IAChD,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAuBlE"}
@@ -0,0 +1,137 @@
1
+ /**
2
+ * The watch daemon notices when its own code changes, and comes back on the new
3
+ * code instead of running the old one indefinitely.
4
+ *
5
+ * ── Why ──────────────────────────────────────────────────────────────────
6
+ * A long-lived daemon holds whatever bundle it started with. Node has already
7
+ * parsed it; replacing the file on disk changes nothing about the running
8
+ * process. So a fix can be committed, built, installed and still not be in
9
+ * effect — with no signal anywhere that the process is stale.
10
+ *
11
+ * That has caused three separate incidents in this codebase:
12
+ * - the trufflehog /tmp leak kept accreting 34MB per sync for a day after the
13
+ * fix shipped, because the daemon predated it;
14
+ * - the cross-home union landed and the collector kept reading one home for
15
+ * another day, stranding 694 records of a live session;
16
+ * - the ledger ack fix, where the daemon had been restarted just BEFORE the
17
+ * commit and so needed restarting a third time.
18
+ *
19
+ * `auto-update.ts` already handles the published-release path (fetch, verify,
20
+ * `npm i -g`, restart) which is what real customers hit. This covers the other
21
+ * case: the bundle changing underneath a running daemon — a linked dev build, a
22
+ * manual `npm i -g`, or a package manager upgrading the CLI without touching the
23
+ * service.
24
+ *
25
+ * ── How it comes back matters more than how it notices ───────────────────
26
+ * Exiting is only safe when something will start us again, and that differs:
27
+ *
28
+ * systemd Restart=on-failure → must exit NON-ZERO (a clean exit stays down)
29
+ * launchd KeepAlive=true → restarts on any exit
30
+ * schtasks /sc onlogon → NEVER restarts; exiting means down until the
31
+ * next logon, which is worse than stale code,
32
+ * so we re-exec ourselves instead
33
+ * foreground (no supervisor) → exiting would just kill the user's process;
34
+ * warn loudly and keep running
35
+ *
36
+ * Getting that wrong turns a staleness fix into an outage, which is why the exit
37
+ * code is 75 (EX_TEMPFAIL) rather than 0: it satisfies the `on-failure` policy
38
+ * that is already installed on every existing machine, so this works without
39
+ * re-rendering anyone's unit file.
40
+ */
41
+ import { spawn } from 'child_process';
42
+ import { statSync } from 'fs';
43
+ /** Non-zero so systemd's `Restart=on-failure` picks it up. EX_TEMPFAIL. */
44
+ export const EXIT_CODE_CODE_CHANGED = 75;
45
+ /**
46
+ * Identity of the bundle we are running. The build produces a single bundled
47
+ * `watch.js` (engine included), so one stat answers "did my code change?" —
48
+ * no need to walk node_modules.
49
+ */
50
+ export function codeFingerprint(entry = process.argv[1]) {
51
+ if (!entry)
52
+ return null;
53
+ try {
54
+ const st = statSync(entry);
55
+ return { path: entry, size: st.size, mtimeMs: st.mtimeMs };
56
+ }
57
+ catch {
58
+ return null; // running from a pipe/eval — nothing to watch
59
+ }
60
+ }
61
+ export function sameFingerprint(a, b) {
62
+ if (!a || !b)
63
+ return true; // unknown ⇒ don't act
64
+ return a.path === b.path && a.size === b.size && a.mtimeMs === b.mtimeMs;
65
+ }
66
+ /**
67
+ * Who, if anyone, will restart us. systemd exports INVOCATION_ID (and
68
+ * JOURNAL_STREAM) to its units; launchd exports XPC_SERVICE_NAME. Neither is
69
+ * something a normal shell has, so their presence is a reliable "I am
70
+ * supervised" signal.
71
+ */
72
+ export function detectSupervisor(env = process.env, platform = process.platform) {
73
+ if (env.INVOCATION_ID || env.JOURNAL_STREAM)
74
+ return 'systemd';
75
+ if (env.XPC_SERVICE_NAME && env.XPC_SERVICE_NAME !== '0')
76
+ return 'launchd';
77
+ if (platform === 'win32')
78
+ return 'windows-task';
79
+ return 'none';
80
+ }
81
+ /** What to do about a detected code change, given who is supervising. */
82
+ export function restartActionFor(sup) {
83
+ switch (sup) {
84
+ case 'systemd': // Restart=on-failure
85
+ case 'launchd': // KeepAlive
86
+ return { kind: 'exit', code: EXIT_CODE_CODE_CHANGED };
87
+ case 'windows-task': // onlogon — nothing restarts us
88
+ return { kind: 'reexec' };
89
+ default:
90
+ return { kind: 'warn' };
91
+ }
92
+ }
93
+ /** Detach a replacement process running the NEW bundle, then let the caller exit. */
94
+ function reexec(log) {
95
+ const args = process.argv.slice(1);
96
+ try {
97
+ const child = spawn(process.execPath, args, {
98
+ detached: true,
99
+ stdio: 'ignore',
100
+ env: process.env,
101
+ });
102
+ child.unref();
103
+ log(`[self-restart] launched a replacement process (pid ${child.pid}) on the new bundle`);
104
+ }
105
+ catch (err) {
106
+ log(`[self-restart] could not re-exec (${err instanceof Error ? err.message : err}) — staying on the OLD code`);
107
+ }
108
+ }
109
+ /**
110
+ * Check whether our bundle changed and act on it. Returns true when a restart
111
+ * was triggered (so callers can stop scheduling work).
112
+ *
113
+ * Opt out with CHAT_RECALL_SELF_RESTART=0 — the escape hatch for anyone
114
+ * debugging with a hot-reloading build who does not want the process bouncing.
115
+ */
116
+ export function checkSelfRestart(opts) {
117
+ const log = opts.log ?? ((m) => console.error(m));
118
+ if ((process.env.CHAT_RECALL_SELF_RESTART || '').trim() === '0')
119
+ return false;
120
+ const current = (opts.now ?? (() => codeFingerprint()))();
121
+ if (sameFingerprint(opts.boot, current))
122
+ return false;
123
+ const sup = opts.supervisor ?? detectSupervisor();
124
+ const action = restartActionFor(sup);
125
+ log(`[self-restart] my own bundle changed on disk (${opts.boot?.size ?? '?'} → ${current?.size ?? '?'} bytes). ` +
126
+ `Running code is now stale; supervisor=${sup}, action=${action.kind}.`);
127
+ if (action.kind === 'warn') {
128
+ log('[self-restart] no supervisor would restart me, so I am STAYING UP on the old code — restart manually to pick up the change.');
129
+ return false;
130
+ }
131
+ if (action.kind === 'reexec')
132
+ reexec(log);
133
+ const exit = opts.exit ?? ((code) => process.exit(code));
134
+ exit(action.kind === 'exit' ? action.code : 0);
135
+ return true;
136
+ }
137
+ //# sourceMappingURL=self-restart.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"self-restart.js","sourceRoot":"","sources":["../src/self-restart.ts"],"names":[],"mappings":"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"}