memonaut-pi 0.1.2 → 0.2.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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACX,YAAY,EAEZ,MAAM,iCAAiC,CAAC;AAwBzC,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA+HxD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACX,YAAY,EAEZ,MAAM,iCAAiC,CAAC;AA8BzC,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA+IxD"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { silenceSqliteWarning } from 'memonaut';
2
2
  import { Type } from 'typebox';
3
- import { runSearch, runSql, runThread } from './queries.js';
3
+ import { flushIndex, runSearch, runSql, runThread, } from './queries.js';
4
4
  /**
5
5
  * memonaut as a pi extension: three read-only tools over the transcript index.
6
6
  *
@@ -21,6 +21,18 @@ function result(outcome) {
21
21
  };
22
22
  }
23
23
  export default function extension(pi) {
24
+ // Keep the index warm at the two moments a transcript stops growing, which is
25
+ // what a cron job would otherwise be for. A warm catch-up is ~120 ms, it is
26
+ // TTL-debounced, it never builds from scratch (that would hang a quit for
27
+ // ~35 s) and it never throws, because the next query syncs anyway.
28
+ pi.on('agent_settled', () => {
29
+ flushIndex();
30
+ });
31
+ pi.on('session_shutdown', () => {
32
+ // The session file is final now, so the TTL is not worth respecting: this
33
+ // is the last chance to fold this conversation in before the process dies.
34
+ flushIndex({ force: true });
35
+ });
24
36
  pi.registerTool({
25
37
  name: 'recall_search',
26
38
  label: 'Recall Search',
@@ -99,8 +111,11 @@ export default function extension(pi) {
99
111
  'aggregates rather than searches ("which projects did I work on in July", "how many sessions ' +
100
112
  'mention this tool", "when did this conversation start"). Tables: file(id, path, cwd, project, ' +
101
113
  'name, parent_id, lineage_id, private, started, last_activity, entry_count), entry(id, lineage_id, ' +
102
- 'entry_key, role, tool, ts), membership(entry_id, file_id, seq), chunk(entry_id, kind, text), and ' +
103
- 'the FTS index chunk_fts. Prefer recall_search for anything that is really a text search.',
114
+ 'entry_key, role, tool, ts), membership(entry_id, file_id, seq), chunk(entry_id, kind, text). ' +
115
+ 'Transcripts the user marked private are filtered out of all four, so counts here are counts of ' +
116
+ 'what you are allowed to see. Schema-qualified names (main.file) and ATTACH are refused because ' +
117
+ 'they would bypass that filter. Prefer recall_search for anything that is really a text search; ' +
118
+ 'the chunk_fts index is not queryable here.',
104
119
  promptSnippet: 'Run a read-only SQL aggregate over the conversation index',
105
120
  promptGuidelines: [
106
121
  'Use recall_sql for counting and grouping questions about past sessions, and recall_search for finding what was said.',
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/queries.d.ts CHANGED
@@ -16,9 +16,30 @@ export interface ToolOptions {
16
16
  maxChars?: number;
17
17
  /** Skip the incremental catch-up if the index was synced this recently. */
18
18
  syncTtlMs?: number;
19
+ /** Build the index inline when there is none. Default true. */
20
+ autoBuild?: boolean;
19
21
  config?: Config;
20
22
  now?: number;
21
23
  }
24
+ export interface ReadinessNote {
25
+ /** Prefixed to the tool output when the agent should know something. */
26
+ note?: string;
27
+ details: Record<string, unknown>;
28
+ }
29
+ /**
30
+ * Catch the index up outside of any query, for a caller that knows a
31
+ * transcript just stopped growing (a session ending, an agent going idle).
32
+ *
33
+ * Never builds: a cold build at session shutdown would hang a quit for ~35 s.
34
+ * Never throws: a failed flush is not worth interrupting anything for, because
35
+ * the next query syncs anyway.
36
+ * Never overruns its budget by more than one lineage: a session that has been
37
+ * away for a month must not stall on the way out. Whatever is left stays
38
+ * pending, and `indexed_at` is not advanced, so the next query still syncs.
39
+ */
40
+ export declare function flushIndex(opts?: ToolOptions & {
41
+ force?: boolean;
42
+ }): boolean;
22
43
  export interface SearchParams {
23
44
  query: string;
24
45
  project?: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"AAAA,OAAO,EAWN,KAAK,MAAM,EAEX,MAAM,UAAU,CAAC;AAElB;;;;;;GAMG;AAEH,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC3B,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAmDD,MAAM,WAAW,YAAY;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAiCD,wBAAgB,SAAS,CACxB,MAAM,EAAE,YAAY,EACpB,IAAI,GAAE,WAAgB,GACpB,WAAW,CA+Eb;AAED,MAAM,WAAW,YAAY;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,SAAS,CACxB,MAAM,EAAE,YAAY,EACpB,IAAI,GAAE,WAAgB,GACpB,WAAW,CA8Db;AAED,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,GAAG,WAAW,CAkCvE"}
1
+ {"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"AAEA,OAAO,EAeN,KAAK,MAAM,EAEX,MAAM,UAAU,CAAC;AAElB;;;;;;GAMG;AAEH,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC3B,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAqHD,MAAM,WAAW,aAAa;IAC7B,wEAAwE;IACxE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AA0FD;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CACzB,IAAI,GAAE,WAAW,GAAG;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAM,GACxC,OAAO,CAeT;AA8BD,MAAM,WAAW,YAAY;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAiCD,wBAAgB,SAAS,CACxB,MAAM,EAAE,YAAY,EACpB,IAAI,GAAE,WAAgB,GACpB,WAAW,CAuFb;AAED,MAAM,WAAW,YAAY;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,SAAS,CACxB,MAAM,EAAE,YAAY,EACpB,IAAI,GAAE,WAAgB,GACpB,WAAW,CAmEb;AA6ED,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,GAAG,WAAW,CAgDvE"}
package/dist/queries.js CHANGED
@@ -1,4 +1,6 @@
1
- import { indexStats, loadConfig, openDb, readThread, relativeTime, resolveThread, search, syncIfStale, tildify, } from 'memonaut';
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import { getMeta, index, indexStats, loadConfig, MissingIndexError, openDb, readThread, relativeTime, resolveThread, search, syncIfStale, tildify, } from 'memonaut';
2
4
  const DEFAULT_MAX_CHARS = 6000;
3
5
  function options(opts) {
4
6
  const config = opts.config ?? loadConfig();
@@ -6,17 +8,205 @@ function options(opts) {
6
8
  maxChars: opts.maxChars ??
7
9
  Number(process.env.MEMONAUT_TOOL_MAX_CHARS ?? DEFAULT_MAX_CHARS),
8
10
  syncTtlMs: opts.syncTtlMs ?? Number(process.env.MEMONAUT_TOOL_SYNC_TTL_MS ?? 15_000),
11
+ autoBuild: opts.autoBuild ?? process.env.MEMONAUT_NO_AUTO_BUILD !== '1',
9
12
  config,
10
13
  };
11
14
  }
12
- function missingIndex(config) {
15
+ /**
16
+ * An error message, with anything that identifies a transcript removed.
17
+ *
18
+ * `fs` failures quote the path they failed on, and a transcript path contains
19
+ * the mangled cwd, so an EACCES inside a private conversation would otherwise
20
+ * disclose that conversation's existence and location through a tool result.
21
+ * Invariant 8 is absolute, and it covers existence, not just content.
22
+ */
23
+ function safeMessage(err) {
24
+ const raw = String(err?.message ?? err);
25
+ return raw
26
+ .replace(/\S*\.jsonl/g, '<transcript>')
27
+ .split(os.homedir())
28
+ .join('~');
29
+ }
30
+ function missingIndex(config, why) {
13
31
  return {
14
32
  isError: true,
15
- text: `No transcript index exists yet at ${tildify(config.dbPath)}.\n` +
16
- 'Tell the user to run `recall index` once (it takes well under a minute), ' +
33
+ text: `No transcript index exists yet at ${tildify(config.dbPath)}` +
34
+ (why ? `, and building one failed: ${why}` : '.') +
35
+ '\nTell the user to run `recall index` once (it takes well under a minute), ' +
17
36
  'or run it yourself with bash if they have already agreed to that.',
18
37
  };
19
38
  }
39
+ /** The index is there but unusable: a different problem, with a different fix. */
40
+ function unusableIndex(config, why) {
41
+ return {
42
+ isError: true,
43
+ text: `The transcript index at ${tildify(config.dbPath)} exists but could not be opened: ${why}\n` +
44
+ 'The index is a derived cache, so nothing is lost by rebuilding it: tell the user to run ' +
45
+ '`recall index --full`. Do not report this as "no past conversations".',
46
+ };
47
+ }
48
+ /**
49
+ * Open the index for reading, or explain the failure as a tool result.
50
+ *
51
+ * `ready()` only proves the file existed a moment ago. It can still be gone,
52
+ * truncated, or written by a newer schema by the time we open it, and an
53
+ * exception thrown out of `execute()` is worse for an agent than an outcome
54
+ * carrying instructions.
55
+ */
56
+ function openIndex(config, opts = {}) {
57
+ let db;
58
+ try {
59
+ db = openDb(config.dbPath, { readOnly: true, ...opts });
60
+ // SQLite opens lazily, so a file that is not a database at all only fails
61
+ // on the first real statement, which would otherwise be somewhere deep in
62
+ // a query. One trivial read moves that failure to where it is handled.
63
+ db.prepare('SELECT 1 FROM meta LIMIT 1').get();
64
+ return db;
65
+ }
66
+ catch (err) {
67
+ db?.close();
68
+ return fs.existsSync(config.dbPath)
69
+ ? unusableIndex(config, safeMessage(err))
70
+ : missingIndex(config);
71
+ }
72
+ }
73
+ /** Epoch ms of the last COMPLETED catch-up, or null if one never finished. */
74
+ function lastIndexedAt(config) {
75
+ try {
76
+ const db = openDb(config.dbPath, { readOnly: true });
77
+ try {
78
+ const value = Number(getMeta(db, 'indexed_at') ?? NaN);
79
+ return Number.isFinite(value) ? value : null;
80
+ }
81
+ finally {
82
+ db.close();
83
+ }
84
+ }
85
+ catch {
86
+ return null;
87
+ }
88
+ }
89
+ /**
90
+ * One cold build per process, at most.
91
+ *
92
+ * A first build is ~35 s for 4,831 transcripts. Paying that inside a tool call
93
+ * is far better than answering "no index" and hoping somebody runs the CLI,
94
+ * but paying it three times in one turn because the data directory is not
95
+ * writable is not, so that failure is remembered rather than retried.
96
+ *
97
+ * Narrow by construction: this memo is only consulted when the DB FILE is
98
+ * absent, and `openDb` creates the file early, so it covers the cannot-create
99
+ * case and not a build that died partway. That one is retried, which is the
100
+ * behaviour you want anyway, since it resumes from its watermarks.
101
+ */
102
+ let coldBuildFailed = null;
103
+ /**
104
+ * Make sure there is an index and that it is caught up, or explain why not.
105
+ *
106
+ * Three outcomes, deliberately kept apart:
107
+ * - no index at all -> build it once, inline, and say so;
108
+ * - index present but the catch-up failed (locked by a concurrent writer,
109
+ * disk full) -> answer from what IS indexed and flag the staleness, since
110
+ * a slightly old answer beats no answer;
111
+ * - build failed -> the only case that is a real error.
112
+ */
113
+ function ready(config, ttlMs, autoBuild) {
114
+ try {
115
+ const result = syncIfStale(config, ttlMs);
116
+ return {
117
+ details: {
118
+ synced: result.ran,
119
+ ...(result.stats ? { syncedFiles: result.stats.filesIndexed } : {}),
120
+ },
121
+ };
122
+ }
123
+ catch (err) {
124
+ if (!(err instanceof MissingIndexError)) {
125
+ // The index exists; only the catch-up failed, most often because another
126
+ // process holds the write lock for longer than the busy timeout.
127
+ //
128
+ // How stale it is must be READ, never assumed: a cold build that was
129
+ // killed leaves a file that exists, has no `indexed_at`, and may hold
130
+ // almost nothing. Telling an agent "missing the last few minutes" there
131
+ // would turn a 95%-empty index into a confident "we never discussed it".
132
+ const why = safeMessage(err);
133
+ const at = lastIndexedAt(config);
134
+ const how = at === null
135
+ ? 'and it has never finished a build, so it is INCOMPLETE by an unknown amount. Treat "no matches" as "unknown", not as "no", and say so'
136
+ : `and it was last brought up to date ${relativeTime(new Date(at).toISOString())}, so anything since then is missing`;
137
+ return {
138
+ note: `(the index could not be updated just now: ${why}, ${how}.)`,
139
+ details: {
140
+ synced: false,
141
+ syncError: why,
142
+ indexedAt: at === null ? null : new Date(at).toISOString(),
143
+ },
144
+ };
145
+ }
146
+ if (!autoBuild)
147
+ return missingIndex(config);
148
+ if (coldBuildFailed !== null)
149
+ return missingIndex(config, coldBuildFailed);
150
+ try {
151
+ const stats = index({ config });
152
+ return {
153
+ note: `(no index existed, so one was built just now: ${stats.filesIndexed} conversations in ${(stats.durationMs / 1000).toFixed(1)}s. Worth telling the user, since it happens once.)`,
154
+ details: {
155
+ built: true,
156
+ syncedFiles: stats.filesIndexed,
157
+ buildMs: stats.durationMs,
158
+ },
159
+ };
160
+ }
161
+ catch (buildErr) {
162
+ coldBuildFailed = safeMessage(buildErr);
163
+ return missingIndex(config, coldBuildFailed);
164
+ }
165
+ }
166
+ }
167
+ function isOutcome(value) {
168
+ return typeof value.text === 'string';
169
+ }
170
+ /**
171
+ * How long a lifecycle flush may spend before leaving the rest to the next one.
172
+ * A warm catch-up is ~120 ms, so this only bites after a long gap, which is
173
+ * precisely when a session must not stall on its way out.
174
+ */
175
+ const FLUSH_BUDGET_MS = 2_000;
176
+ /**
177
+ * A truncated flush deliberately leaves `indexed_at` alone, which also means
178
+ * the 15 s TTL can never short-circuit until one run completes. Without this,
179
+ * an agent that is far behind pays the full budget on EVERY turn. Queries are
180
+ * unaffected: they sync unbudgeted and will finish the job properly.
181
+ */
182
+ const TRUNCATED_FLUSH_RETRY_MS = 60_000;
183
+ let lastTruncatedFlush = 0;
184
+ /**
185
+ * Catch the index up outside of any query, for a caller that knows a
186
+ * transcript just stopped growing (a session ending, an agent going idle).
187
+ *
188
+ * Never builds: a cold build at session shutdown would hang a quit for ~35 s.
189
+ * Never throws: a failed flush is not worth interrupting anything for, because
190
+ * the next query syncs anyway.
191
+ * Never overruns its budget by more than one lineage: a session that has been
192
+ * away for a month must not stall on the way out. Whatever is left stays
193
+ * pending, and `indexed_at` is not advanced, so the next query still syncs.
194
+ */
195
+ export function flushIndex(opts = {}) {
196
+ try {
197
+ const { config, syncTtlMs } = options(opts);
198
+ const now = opts.now ?? Date.now();
199
+ if (now - lastTruncatedFlush < TRUNCATED_FLUSH_RETRY_MS)
200
+ return false;
201
+ const result = syncIfStale(config, opts.force ? 0 : syncTtlMs, FLUSH_BUDGET_MS);
202
+ if (result.stats?.budgetExhausted)
203
+ lastTruncatedFlush = now;
204
+ return result.ran;
205
+ }
206
+ catch {
207
+ return false;
208
+ }
209
+ }
20
210
  /** Strip FTS5 highlight markers down to something readable in a tool result. */
21
211
  function cleanSnippet(snippet) {
22
212
  return snippet
@@ -25,6 +215,10 @@ function cleanSnippet(snippet) {
25
215
  .replace(/\u0002/g, '>>')
26
216
  .trim();
27
217
  }
218
+ /** Freshness notes go FIRST: an agent that stops reading early still sees them. */
219
+ function withNote(text, note) {
220
+ return note ? `${note}\n\n${text}` : text;
221
+ }
28
222
  function truncate(lines, maxChars, tail) {
29
223
  const out = [];
30
224
  let used = 0;
@@ -60,15 +254,15 @@ function renderHit(hit, position, now) {
60
254
  return lines;
61
255
  }
62
256
  export function runSearch(params, opts = {}) {
63
- const { config, maxChars, syncTtlMs } = options(opts);
257
+ const { config, maxChars, syncTtlMs, autoBuild } = options(opts);
64
258
  const now = opts.now ?? Date.now();
65
- try {
66
- syncIfStale(config, syncTtlMs);
67
- }
68
- catch {
69
- return missingIndex(config);
70
- }
71
- const db = openDb(config.dbPath, { readOnly: true });
259
+ const state = ready(config, syncTtlMs, autoBuild);
260
+ if (isOutcome(state))
261
+ return state;
262
+ const opened = openIndex(config);
263
+ if (isOutcome(opened))
264
+ return opened;
265
+ const db = opened;
72
266
  try {
73
267
  const limit = Math.min(Math.max(params.limit ?? 8, 1), 50);
74
268
  const outcome = search(db, {
@@ -89,11 +283,15 @@ export function runSearch(params, opts = {}) {
89
283
  }, now);
90
284
  if (outcome.hits.length === 0) {
91
285
  const stats = indexStats(db);
286
+ // The count must exclude private transcripts. They were not searched, so
287
+ // including them would both overstate the search AND disclose how many
288
+ // conversations the user is holding back.
289
+ const searched = Number(stats.files) - Number(stats.privateFiles ?? 0);
92
290
  return {
93
- text: `No matches for: ${outcome.usedQuery}\n` +
94
- `Searched ${stats.files} conversations across ${stats.projects} projects.\n` +
95
- 'Try fewer words, a quoted phrase, OR between alternatives, or drop the filters.',
96
- details: { hits: 0, usedQuery: outcome.usedQuery },
291
+ text: withNote(`No matches for: ${outcome.usedQuery}\n` +
292
+ `Searched ${searched} conversations across ${stats.projects} projects.\n` +
293
+ 'Try fewer words, a quoted phrase, OR between alternatives, or drop the filters.', state.note),
294
+ details: { hits: 0, usedQuery: outcome.usedQuery, ...state.details },
97
295
  };
98
296
  }
99
297
  const scope = params.project?.length
@@ -107,8 +305,9 @@ export function runSearch(params, opts = {}) {
107
305
  lines.push('');
108
306
  lines.push('Use recall_thread with a thread number to read one in full.');
109
307
  return {
110
- text: truncate(lines, maxChars, 'Narrow with project/since or lower limit.'),
308
+ text: withNote(truncate(lines, maxChars, 'Narrow with project/since or lower limit.'), state.note),
111
309
  details: {
310
+ ...state.details,
112
311
  usedQuery: outcome.usedQuery,
113
312
  quotedFallback: outcome.quotedFallback,
114
313
  hits: outcome.hits.map((h) => ({
@@ -124,19 +323,24 @@ export function runSearch(params, opts = {}) {
124
323
  },
125
324
  };
126
325
  }
326
+ catch (err) {
327
+ // Nothing may escape `execute()`: an agent can act on an error outcome
328
+ // that carries instructions, and can do nothing at all with a stack trace.
329
+ return unusableIndex(config, safeMessage(err));
330
+ }
127
331
  finally {
128
332
  db.close();
129
333
  }
130
334
  }
131
335
  export function runThread(params, opts = {}) {
132
- const { config, maxChars } = options(opts);
133
- let db;
134
- try {
135
- db = openDb(config.dbPath, { readOnly: true });
136
- }
137
- catch {
138
- return missingIndex(config);
139
- }
336
+ const { config, maxChars, syncTtlMs, autoBuild } = options(opts);
337
+ const state = ready(config, syncTtlMs, autoBuild);
338
+ if (isOutcome(state))
339
+ return state;
340
+ const opened = openIndex(config);
341
+ if (isOutcome(opened))
342
+ return opened;
343
+ const db = opened;
140
344
  try {
141
345
  const thread = resolveThread(db, String(params.ref));
142
346
  if (!thread)
@@ -169,8 +373,9 @@ export function runThread(params, opts = {}) {
169
373
  lines.push(`(showing ${from}-${end} of ${thread.entry_count}; call again with from=${end} to continue)`);
170
374
  }
171
375
  return {
172
- text: truncate(lines, maxChars, `Call again with from=${from + Math.floor(limit / 2)} and a smaller limit.`),
376
+ text: withNote(truncate(lines, maxChars, `Call again with from=${from + Math.floor(limit / 2)} and a smaller limit.`), state.note),
173
377
  details: {
378
+ ...state.details,
174
379
  threadId: thread.id,
175
380
  from,
176
381
  count: entries.length,
@@ -178,40 +383,125 @@ export function runThread(params, opts = {}) {
178
383
  },
179
384
  };
180
385
  }
386
+ catch (err) {
387
+ return unusableIndex(config, safeMessage(err));
388
+ }
181
389
  finally {
182
- db?.close();
390
+ db.close();
183
391
  }
184
392
  }
393
+ /**
394
+ * Reasons an arbitrary query is refused before it runs.
395
+ *
396
+ * The private filter below works by SHADOWING, so anything that can dodge name
397
+ * resolution has to be refused: a schema qualifier reaches the real table, and
398
+ * ATTACH introduces a schema whose names we never shadowed. `chunk_fts` is
399
+ * refused because an FTS index cannot be shadowed by a view that still
400
+ * supports MATCH, and full-text search is what recall_search is for anyway.
401
+ */
402
+ function sqlRefusal(sql) {
403
+ // Comments and string literals first: they can legally contain any of this.
404
+ const bare = sql
405
+ .replace(/--[^\n]*/g, ' ')
406
+ .replace(/\/\*[\s\S]*?\*\//g, ' ')
407
+ .replace(/'[^']*'/g, "''")
408
+ .replace(/"([^"]*)"/g, '$1')
409
+ .toLowerCase();
410
+ if (/\battach\b/.test(bare))
411
+ return 'ATTACH is not allowed: only the transcript index is queryable.';
412
+ if (/\b(main|temp|sqlite_temp_master|sqlite_master)\s*\./.test(bare))
413
+ return ('Schema-qualified names (main.file, temp.file) are not allowed, because they bypass ' +
414
+ 'the filter that hides transcripts the user marked private. Use the bare table name.');
415
+ if (/\bchunk_fts\b/.test(bare))
416
+ return 'chunk_fts is not queryable here. Use recall_search for full-text search.';
417
+ return null;
418
+ }
419
+ /**
420
+ * Hide private transcripts from arbitrary SQL, by shadowing.
421
+ *
422
+ * SQLite resolves an unqualified name in the TEMP schema first, so a temp view
423
+ * called `file` is what `SELECT ... FROM file` finds, while the real table
424
+ * stays reachable only as `main.file`, which `sqlRefusal` blocks. That keeps
425
+ * the documented schema exactly as it was: no `_public` suffixes to teach, no
426
+ * query rewriting, and no cost at all when nothing is private, which is the
427
+ * common case.
428
+ *
429
+ * Visibility follows `visibleSets()` in core: it is decided per FILE, and an
430
+ * entry belongs to whoever owns it (`first_file`). An entry owned by a private
431
+ * transcript stays hidden even where a public fork inherited it, which errs
432
+ * towards hiding.
433
+ *
434
+ * Needs a connection WITHOUT `query_only`, which refuses temp writes as well;
435
+ * `readOnly` alone already makes the index itself unwritable.
436
+ *
437
+ * Measured on a 617k-entry index when private transcripts DO exist: a full
438
+ * `COUNT(*) FROM entry` goes 343 ms -> 812 ms, and `COUNT(*) FROM chunk` goes
439
+ * 99 ms -> 191 ms, the join being cheap because it hits `entry.id`, the
440
+ * primary key. Not worth an index on `entry.first_file`: that would be a
441
+ * schema bump, hence a full rebuild for everyone, to speed up unfiltered
442
+ * aggregates for the subset of users who mark anything private.
443
+ */
444
+ function hidePrivate(db) {
445
+ const privateIds = db
446
+ .prepare('SELECT id FROM main.file WHERE private = 1')
447
+ .all().map((row) => Number(row.id));
448
+ if (privateIds.length === 0)
449
+ return;
450
+ const ids = privateIds.join(',');
451
+ db.exec(`
452
+ CREATE TEMP VIEW file AS
453
+ SELECT * FROM main.file WHERE id NOT IN (${ids});
454
+ CREATE TEMP VIEW entry AS
455
+ SELECT * FROM main.entry WHERE first_file IS NULL OR first_file NOT IN (${ids});
456
+ CREATE TEMP VIEW membership AS
457
+ SELECT * FROM main.membership WHERE file_id NOT IN (${ids});
458
+ CREATE TEMP VIEW chunk AS
459
+ SELECT c.* FROM main.chunk c JOIN main.entry e ON e.id = c.entry_id
460
+ WHERE e.first_file IS NULL OR e.first_file NOT IN (${ids});
461
+ `);
462
+ }
185
463
  export function runSql(sql, opts = {}) {
186
- const { config, maxChars } = options(opts);
187
- let db;
188
- try {
189
- db = openDb(config.dbPath, { readOnly: true });
190
- }
191
- catch {
192
- return missingIndex(config);
193
- }
464
+ const { config, maxChars, syncTtlMs, autoBuild } = options(opts);
465
+ const refusal = sqlRefusal(sql);
466
+ if (refusal)
467
+ return { isError: true, text: refusal };
468
+ const state = ready(config, syncTtlMs, autoBuild);
469
+ if (isOutcome(state))
470
+ return state;
471
+ const opened = openIndex(config, { writableTemp: true });
472
+ if (isOutcome(opened))
473
+ return opened;
474
+ const db = opened;
194
475
  try {
476
+ hidePrivate(db);
195
477
  const rows = db.prepare(sql).all();
196
478
  if (rows.length === 0)
197
- return { text: '(no rows)', details: { rows: 0 } };
479
+ return {
480
+ text: withNote('(no rows)', state.note),
481
+ details: { rows: 0, ...state.details },
482
+ };
198
483
  const lines = rows.slice(0, 200).map((row) => JSON.stringify(row));
199
484
  return {
200
- text: truncate([`${rows.length} row(s):`, ...lines], maxChars, 'Add a LIMIT or aggregate instead.'),
201
- details: { rows: rows.length },
485
+ text: withNote(truncate([`${rows.length} row(s):`, ...lines], maxChars, 'Add a LIMIT or aggregate instead.'), state.note),
486
+ details: { rows: rows.length, ...state.details },
202
487
  };
203
488
  }
204
489
  catch (err) {
490
+ // A broken index is not a broken query, and handing back a schema lecture
491
+ // for "file is not a database" would send the agent rewriting valid SQL.
492
+ const message = safeMessage(err);
493
+ if (/not a database|malformed|disk image|corrupt/i.test(message))
494
+ return unusableIndex(config, message);
205
495
  return {
206
496
  isError: true,
207
- text: `SQL failed: ${err.message}\n` +
497
+ text: `SQL failed: ${message}\n` +
208
498
  'Tables: file(id, path, cwd, project, name, parent_id, lineage_id, private, started, last_activity, entry_count), ' +
209
499
  'entry(id, lineage_id, entry_key, role, tool, ts), membership(entry_id, file_id, seq), chunk(entry_id, kind, text). ' +
210
- 'The connection is read-only.',
500
+ 'The connection is read-only, private transcripts are filtered out, and chunk_fts is not queryable here.',
211
501
  };
212
502
  }
213
503
  finally {
214
- db?.close();
504
+ db.close();
215
505
  }
216
506
  }
217
507
  //# sourceMappingURL=queries.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"queries.js","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"queries.js","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memonaut-pi",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "pi tool + skill for memonaut: let the agent search past conversation transcripts",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
@@ -48,7 +48,7 @@
48
48
  ]
49
49
  },
50
50
  "dependencies": {
51
- "memonaut": "0.3.0"
51
+ "memonaut": "0.4.0"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "@earendil-works/pi-coding-agent": "*",
@@ -107,11 +107,13 @@ recall show 2585 --from 120
107
107
  recall sql "select project, count(*) n from file group by 1 order by n desc limit 10"
108
108
  ```
109
109
 
110
- Add `--json` for machine-readable output. If it reports no index, the user needs to run `recall index` once.
110
+ Add `--json` for machine-readable output. `recall status` reports how fresh the index is; `recall status --json` gives `indexedAt`, `ageMs`, `changed`, `unseen`.
111
+
112
+ You do not have to keep the index fresh. Every query runs an incremental catch-up first (~120 ms warm): all three tools, and `recall search` / `recall show` / `recall sql` on the CLI. If no index exists at all, the first query builds one (~35 s, once) and tells you it did. Pass that on to the user when it happens, and otherwise do not offer to run `recall index`: it is not what makes results current.
111
113
 
112
114
  ## What is not there
113
115
 
114
- - **Private conversations.** The user's config lists working directories that are indexed for them but never exposed to tools. If a thread comes back as private, do not try to route around it; tell the user.
116
+ - **Private conversations.** The user's config lists working directories that are indexed for them but never exposed to tools. This holds for `recall_sql` too: the tables it queries are filtered, so a `COUNT(*)` is a count of what you may see, and `main.file`, `ATTACH` and `chunk_fts` are refused because they would go around that filter. If a thread comes back as private, do not try to route around it; tell the user.
115
117
  - **Full tool output.** Tool results are stored truncated or not at all (they are 42% of the bytes and rarely what anyone is looking for). The discussion is indexed; the command output usually is not. For full fidelity the original transcript is still on disk, and `recall search --regex` reads it.
116
118
  - **Anything the user chose to ignore.** Ignored directories were never read at all.
117
119
 
package/src/index.ts CHANGED
@@ -4,7 +4,13 @@ import type {
4
4
  ExtensionAPI,
5
5
  ToolDefinition,
6
6
  } from '@earendil-works/pi-coding-agent';
7
- import {runSearch, runSql, runThread, type ToolOutcome} from './queries.js';
7
+ import {
8
+ flushIndex,
9
+ runSearch,
10
+ runSql,
11
+ runThread,
12
+ type ToolOutcome,
13
+ } from './queries.js';
8
14
 
9
15
  /**
10
16
  * memonaut as a pi extension: three read-only tools over the transcript index.
@@ -28,6 +34,19 @@ function result(outcome: ToolOutcome) {
28
34
  }
29
35
 
30
36
  export default function extension(pi: ExtensionAPI): void {
37
+ // Keep the index warm at the two moments a transcript stops growing, which is
38
+ // what a cron job would otherwise be for. A warm catch-up is ~120 ms, it is
39
+ // TTL-debounced, it never builds from scratch (that would hang a quit for
40
+ // ~35 s) and it never throws, because the next query syncs anyway.
41
+ pi.on('agent_settled', () => {
42
+ flushIndex();
43
+ });
44
+ pi.on('session_shutdown', () => {
45
+ // The session file is final now, so the TTL is not worth respecting: this
46
+ // is the last chance to fold this conversation in before the process dies.
47
+ flushIndex({force: true});
48
+ });
49
+
31
50
  pi.registerTool({
32
51
  name: 'recall_search',
33
52
  label: 'Recall Search',
@@ -139,8 +158,11 @@ export default function extension(pi: ExtensionAPI): void {
139
158
  'aggregates rather than searches ("which projects did I work on in July", "how many sessions ' +
140
159
  'mention this tool", "when did this conversation start"). Tables: file(id, path, cwd, project, ' +
141
160
  'name, parent_id, lineage_id, private, started, last_activity, entry_count), entry(id, lineage_id, ' +
142
- 'entry_key, role, tool, ts), membership(entry_id, file_id, seq), chunk(entry_id, kind, text), and ' +
143
- 'the FTS index chunk_fts. Prefer recall_search for anything that is really a text search.',
161
+ 'entry_key, role, tool, ts), membership(entry_id, file_id, seq), chunk(entry_id, kind, text). ' +
162
+ 'Transcripts the user marked private are filtered out of all four, so counts here are counts of ' +
163
+ 'what you are allowed to see. Schema-qualified names (main.file) and ATTACH are refused because ' +
164
+ 'they would bypass that filter. Prefer recall_search for anything that is really a text search; ' +
165
+ 'the chunk_fts index is not queryable here.',
144
166
  promptSnippet: 'Run a read-only SQL aggregate over the conversation index',
145
167
  promptGuidelines: [
146
168
  'Use recall_sql for counting and grouping questions about past sessions, and recall_search for finding what was said.',
package/src/queries.ts CHANGED
@@ -1,7 +1,13 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
1
3
  import {
4
+ getMeta,
5
+ index,
2
6
  indexStats,
3
7
  loadConfig,
8
+ MissingIndexError,
4
9
  openDb,
10
+ type DB,
5
11
  readThread,
6
12
  relativeTime,
7
13
  resolveThread,
@@ -32,15 +38,19 @@ export interface ToolOptions {
32
38
  maxChars?: number;
33
39
  /** Skip the incremental catch-up if the index was synced this recently. */
34
40
  syncTtlMs?: number;
41
+ /** Build the index inline when there is none. Default true. */
42
+ autoBuild?: boolean;
35
43
  config?: Config;
36
44
  now?: number;
37
45
  }
38
46
 
39
47
  const DEFAULT_MAX_CHARS = 6000;
40
48
 
41
- function options(
42
- opts: ToolOptions,
43
- ): Required<Pick<ToolOptions, 'maxChars' | 'syncTtlMs'>> & {config: Config} {
49
+ function options(opts: ToolOptions): Required<
50
+ Pick<ToolOptions, 'maxChars' | 'syncTtlMs' | 'autoBuild'>
51
+ > & {
52
+ config: Config;
53
+ } {
44
54
  const config = opts.config ?? loadConfig();
45
55
  return {
46
56
  maxChars:
@@ -48,20 +58,231 @@ function options(
48
58
  Number(process.env.MEMONAUT_TOOL_MAX_CHARS ?? DEFAULT_MAX_CHARS),
49
59
  syncTtlMs:
50
60
  opts.syncTtlMs ?? Number(process.env.MEMONAUT_TOOL_SYNC_TTL_MS ?? 15_000),
61
+ autoBuild: opts.autoBuild ?? process.env.MEMONAUT_NO_AUTO_BUILD !== '1',
51
62
  config,
52
63
  };
53
64
  }
54
65
 
55
- function missingIndex(config: Config): ToolOutcome {
66
+ /**
67
+ * An error message, with anything that identifies a transcript removed.
68
+ *
69
+ * `fs` failures quote the path they failed on, and a transcript path contains
70
+ * the mangled cwd, so an EACCES inside a private conversation would otherwise
71
+ * disclose that conversation's existence and location through a tool result.
72
+ * Invariant 8 is absolute, and it covers existence, not just content.
73
+ */
74
+ function safeMessage(err: unknown): string {
75
+ const raw = String((err as Error)?.message ?? err);
76
+ return raw
77
+ .replace(/\S*\.jsonl/g, '<transcript>')
78
+ .split(os.homedir())
79
+ .join('~');
80
+ }
81
+
82
+ function missingIndex(config: Config, why?: string): ToolOutcome {
56
83
  return {
57
84
  isError: true,
58
85
  text:
59
- `No transcript index exists yet at ${tildify(config.dbPath)}.\n` +
60
- 'Tell the user to run `recall index` once (it takes well under a minute), ' +
86
+ `No transcript index exists yet at ${tildify(config.dbPath)}` +
87
+ (why ? `, and building one failed: ${why}` : '.') +
88
+ '\nTell the user to run `recall index` once (it takes well under a minute), ' +
61
89
  'or run it yourself with bash if they have already agreed to that.',
62
90
  };
63
91
  }
64
92
 
93
+ /** The index is there but unusable: a different problem, with a different fix. */
94
+ function unusableIndex(config: Config, why: string): ToolOutcome {
95
+ return {
96
+ isError: true,
97
+ text:
98
+ `The transcript index at ${tildify(config.dbPath)} exists but could not be opened: ${why}\n` +
99
+ 'The index is a derived cache, so nothing is lost by rebuilding it: tell the user to run ' +
100
+ '`recall index --full`. Do not report this as "no past conversations".',
101
+ };
102
+ }
103
+
104
+ /**
105
+ * Open the index for reading, or explain the failure as a tool result.
106
+ *
107
+ * `ready()` only proves the file existed a moment ago. It can still be gone,
108
+ * truncated, or written by a newer schema by the time we open it, and an
109
+ * exception thrown out of `execute()` is worse for an agent than an outcome
110
+ * carrying instructions.
111
+ */
112
+ function openIndex(
113
+ config: Config,
114
+ opts: {writableTemp?: boolean} = {},
115
+ ): DB | ToolOutcome {
116
+ let db: DB | undefined;
117
+ try {
118
+ db = openDb(config.dbPath, {readOnly: true, ...opts});
119
+ // SQLite opens lazily, so a file that is not a database at all only fails
120
+ // on the first real statement, which would otherwise be somewhere deep in
121
+ // a query. One trivial read moves that failure to where it is handled.
122
+ db.prepare('SELECT 1 FROM meta LIMIT 1').get();
123
+ return db;
124
+ } catch (err) {
125
+ db?.close();
126
+ return fs.existsSync(config.dbPath)
127
+ ? unusableIndex(config, safeMessage(err))
128
+ : missingIndex(config);
129
+ }
130
+ }
131
+
132
+ /** Epoch ms of the last COMPLETED catch-up, or null if one never finished. */
133
+ function lastIndexedAt(config: Config): number | null {
134
+ try {
135
+ const db = openDb(config.dbPath, {readOnly: true});
136
+ try {
137
+ const value = Number(getMeta(db, 'indexed_at') ?? NaN);
138
+ return Number.isFinite(value) ? value : null;
139
+ } finally {
140
+ db.close();
141
+ }
142
+ } catch {
143
+ return null;
144
+ }
145
+ }
146
+
147
+ /**
148
+ * One cold build per process, at most.
149
+ *
150
+ * A first build is ~35 s for 4,831 transcripts. Paying that inside a tool call
151
+ * is far better than answering "no index" and hoping somebody runs the CLI,
152
+ * but paying it three times in one turn because the data directory is not
153
+ * writable is not, so that failure is remembered rather than retried.
154
+ *
155
+ * Narrow by construction: this memo is only consulted when the DB FILE is
156
+ * absent, and `openDb` creates the file early, so it covers the cannot-create
157
+ * case and not a build that died partway. That one is retried, which is the
158
+ * behaviour you want anyway, since it resumes from its watermarks.
159
+ */
160
+ let coldBuildFailed: string | null = null;
161
+
162
+ export interface ReadinessNote {
163
+ /** Prefixed to the tool output when the agent should know something. */
164
+ note?: string;
165
+ details: Record<string, unknown>;
166
+ }
167
+
168
+ /**
169
+ * Make sure there is an index and that it is caught up, or explain why not.
170
+ *
171
+ * Three outcomes, deliberately kept apart:
172
+ * - no index at all -> build it once, inline, and say so;
173
+ * - index present but the catch-up failed (locked by a concurrent writer,
174
+ * disk full) -> answer from what IS indexed and flag the staleness, since
175
+ * a slightly old answer beats no answer;
176
+ * - build failed -> the only case that is a real error.
177
+ */
178
+ function ready(
179
+ config: Config,
180
+ ttlMs: number,
181
+ autoBuild: boolean,
182
+ ): ReadinessNote | ToolOutcome {
183
+ try {
184
+ const result = syncIfStale(config, ttlMs);
185
+ return {
186
+ details: {
187
+ synced: result.ran,
188
+ ...(result.stats ? {syncedFiles: result.stats.filesIndexed} : {}),
189
+ },
190
+ };
191
+ } catch (err) {
192
+ if (!(err instanceof MissingIndexError)) {
193
+ // The index exists; only the catch-up failed, most often because another
194
+ // process holds the write lock for longer than the busy timeout.
195
+ //
196
+ // How stale it is must be READ, never assumed: a cold build that was
197
+ // killed leaves a file that exists, has no `indexed_at`, and may hold
198
+ // almost nothing. Telling an agent "missing the last few minutes" there
199
+ // would turn a 95%-empty index into a confident "we never discussed it".
200
+ const why = safeMessage(err);
201
+ const at = lastIndexedAt(config);
202
+ const how =
203
+ at === null
204
+ ? 'and it has never finished a build, so it is INCOMPLETE by an unknown amount. Treat "no matches" as "unknown", not as "no", and say so'
205
+ : `and it was last brought up to date ${relativeTime(new Date(at).toISOString())}, so anything since then is missing`;
206
+ return {
207
+ note: `(the index could not be updated just now: ${why}, ${how}.)`,
208
+ details: {
209
+ synced: false,
210
+ syncError: why,
211
+ indexedAt: at === null ? null : new Date(at).toISOString(),
212
+ },
213
+ };
214
+ }
215
+ if (!autoBuild) return missingIndex(config);
216
+ if (coldBuildFailed !== null) return missingIndex(config, coldBuildFailed);
217
+ try {
218
+ const stats = index({config});
219
+ return {
220
+ note: `(no index existed, so one was built just now: ${stats.filesIndexed} conversations in ${(stats.durationMs / 1000).toFixed(1)}s. Worth telling the user, since it happens once.)`,
221
+ details: {
222
+ built: true,
223
+ syncedFiles: stats.filesIndexed,
224
+ buildMs: stats.durationMs,
225
+ },
226
+ };
227
+ } catch (buildErr) {
228
+ coldBuildFailed = safeMessage(buildErr);
229
+ return missingIndex(config, coldBuildFailed);
230
+ }
231
+ }
232
+ }
233
+
234
+ function isOutcome(
235
+ value: ReadinessNote | ToolOutcome | DB,
236
+ ): value is ToolOutcome {
237
+ return typeof (value as ToolOutcome).text === 'string';
238
+ }
239
+
240
+ /**
241
+ * How long a lifecycle flush may spend before leaving the rest to the next one.
242
+ * A warm catch-up is ~120 ms, so this only bites after a long gap, which is
243
+ * precisely when a session must not stall on its way out.
244
+ */
245
+ const FLUSH_BUDGET_MS = 2_000;
246
+
247
+ /**
248
+ * A truncated flush deliberately leaves `indexed_at` alone, which also means
249
+ * the 15 s TTL can never short-circuit until one run completes. Without this,
250
+ * an agent that is far behind pays the full budget on EVERY turn. Queries are
251
+ * unaffected: they sync unbudgeted and will finish the job properly.
252
+ */
253
+ const TRUNCATED_FLUSH_RETRY_MS = 60_000;
254
+ let lastTruncatedFlush = 0;
255
+
256
+ /**
257
+ * Catch the index up outside of any query, for a caller that knows a
258
+ * transcript just stopped growing (a session ending, an agent going idle).
259
+ *
260
+ * Never builds: a cold build at session shutdown would hang a quit for ~35 s.
261
+ * Never throws: a failed flush is not worth interrupting anything for, because
262
+ * the next query syncs anyway.
263
+ * Never overruns its budget by more than one lineage: a session that has been
264
+ * away for a month must not stall on the way out. Whatever is left stays
265
+ * pending, and `indexed_at` is not advanced, so the next query still syncs.
266
+ */
267
+ export function flushIndex(
268
+ opts: ToolOptions & {force?: boolean} = {},
269
+ ): boolean {
270
+ try {
271
+ const {config, syncTtlMs} = options(opts);
272
+ const now = opts.now ?? Date.now();
273
+ if (now - lastTruncatedFlush < TRUNCATED_FLUSH_RETRY_MS) return false;
274
+ const result = syncIfStale(
275
+ config,
276
+ opts.force ? 0 : syncTtlMs,
277
+ FLUSH_BUDGET_MS,
278
+ );
279
+ if (result.stats?.budgetExhausted) lastTruncatedFlush = now;
280
+ return result.ran;
281
+ } catch {
282
+ return false;
283
+ }
284
+ }
285
+
65
286
  /** Strip FTS5 highlight markers down to something readable in a tool result. */
66
287
  function cleanSnippet(snippet: string): string {
67
288
  return snippet
@@ -71,6 +292,11 @@ function cleanSnippet(snippet: string): string {
71
292
  .trim();
72
293
  }
73
294
 
295
+ /** Freshness notes go FIRST: an agent that stops reading early still sees them. */
296
+ function withNote(text: string, note: string | undefined): string {
297
+ return note ? `${note}\n\n${text}` : text;
298
+ }
299
+
74
300
  function truncate(lines: string[], maxChars: number, tail: string): string {
75
301
  const out: string[] = [];
76
302
  let used = 0;
@@ -133,15 +359,13 @@ export function runSearch(
133
359
  params: SearchParams,
134
360
  opts: ToolOptions = {},
135
361
  ): ToolOutcome {
136
- const {config, maxChars, syncTtlMs} = options(opts);
362
+ const {config, maxChars, syncTtlMs, autoBuild} = options(opts);
137
363
  const now = opts.now ?? Date.now();
138
- try {
139
- syncIfStale(config, syncTtlMs);
140
- } catch {
141
- return missingIndex(config);
142
- }
143
-
144
- const db = openDb(config.dbPath, {readOnly: true});
364
+ const state = ready(config, syncTtlMs, autoBuild);
365
+ if (isOutcome(state)) return state;
366
+ const opened = openIndex(config);
367
+ if (isOutcome(opened)) return opened;
368
+ const db = opened;
145
369
  try {
146
370
  const limit = Math.min(Math.max(params.limit ?? 8, 1), 50);
147
371
  const outcome = search(
@@ -167,12 +391,18 @@ export function runSearch(
167
391
 
168
392
  if (outcome.hits.length === 0) {
169
393
  const stats = indexStats(db);
394
+ // The count must exclude private transcripts. They were not searched, so
395
+ // including them would both overstate the search AND disclose how many
396
+ // conversations the user is holding back.
397
+ const searched = Number(stats.files) - Number(stats.privateFiles ?? 0);
170
398
  return {
171
- text:
399
+ text: withNote(
172
400
  `No matches for: ${outcome.usedQuery}\n` +
173
- `Searched ${stats.files} conversations across ${stats.projects} projects.\n` +
174
- 'Try fewer words, a quoted phrase, OR between alternatives, or drop the filters.',
175
- details: {hits: 0, usedQuery: outcome.usedQuery},
401
+ `Searched ${searched} conversations across ${stats.projects} projects.\n` +
402
+ 'Try fewer words, a quoted phrase, OR between alternatives, or drop the filters.',
403
+ state.note,
404
+ ),
405
+ details: {hits: 0, usedQuery: outcome.usedQuery, ...state.details},
176
406
  };
177
407
  }
178
408
 
@@ -188,12 +418,12 @@ export function runSearch(
188
418
  lines.push('Use recall_thread with a thread number to read one in full.');
189
419
 
190
420
  return {
191
- text: truncate(
192
- lines,
193
- maxChars,
194
- 'Narrow with project/since or lower limit.',
421
+ text: withNote(
422
+ truncate(lines, maxChars, 'Narrow with project/since or lower limit.'),
423
+ state.note,
195
424
  ),
196
425
  details: {
426
+ ...state.details,
197
427
  usedQuery: outcome.usedQuery,
198
428
  quotedFallback: outcome.quotedFallback,
199
429
  hits: outcome.hits.map((h) => ({
@@ -208,6 +438,10 @@ export function runSearch(
208
438
  })),
209
439
  },
210
440
  };
441
+ } catch (err) {
442
+ // Nothing may escape `execute()`: an agent can act on an error outcome
443
+ // that carries instructions, and can do nothing at all with a stack trace.
444
+ return unusableIndex(config, safeMessage(err));
211
445
  } finally {
212
446
  db.close();
213
447
  }
@@ -223,13 +457,12 @@ export function runThread(
223
457
  params: ThreadParams,
224
458
  opts: ToolOptions = {},
225
459
  ): ToolOutcome {
226
- const {config, maxChars} = options(opts);
227
- let db;
228
- try {
229
- db = openDb(config.dbPath, {readOnly: true});
230
- } catch {
231
- return missingIndex(config);
232
- }
460
+ const {config, maxChars, syncTtlMs, autoBuild} = options(opts);
461
+ const state = ready(config, syncTtlMs, autoBuild);
462
+ if (isOutcome(state)) return state;
463
+ const opened = openIndex(config);
464
+ if (isOutcome(opened)) return opened;
465
+ const db = opened;
233
466
  try {
234
467
  const thread = resolveThread(db, String(params.ref));
235
468
  if (!thread)
@@ -269,55 +502,150 @@ export function runThread(
269
502
  }
270
503
 
271
504
  return {
272
- text: truncate(
273
- lines,
274
- maxChars,
275
- `Call again with from=${from + Math.floor(limit / 2)} and a smaller limit.`,
505
+ text: withNote(
506
+ truncate(
507
+ lines,
508
+ maxChars,
509
+ `Call again with from=${from + Math.floor(limit / 2)} and a smaller limit.`,
510
+ ),
511
+ state.note,
276
512
  ),
277
513
  details: {
514
+ ...state.details,
278
515
  threadId: thread.id,
279
516
  from,
280
517
  count: entries.length,
281
518
  total: thread.entry_count,
282
519
  },
283
520
  };
521
+ } catch (err) {
522
+ return unusableIndex(config, safeMessage(err));
284
523
  } finally {
285
- db?.close();
524
+ db.close();
286
525
  }
287
526
  }
288
527
 
528
+ /**
529
+ * Reasons an arbitrary query is refused before it runs.
530
+ *
531
+ * The private filter below works by SHADOWING, so anything that can dodge name
532
+ * resolution has to be refused: a schema qualifier reaches the real table, and
533
+ * ATTACH introduces a schema whose names we never shadowed. `chunk_fts` is
534
+ * refused because an FTS index cannot be shadowed by a view that still
535
+ * supports MATCH, and full-text search is what recall_search is for anyway.
536
+ */
537
+ function sqlRefusal(sql: string): string | null {
538
+ // Comments and string literals first: they can legally contain any of this.
539
+ const bare = sql
540
+ .replace(/--[^\n]*/g, ' ')
541
+ .replace(/\/\*[\s\S]*?\*\//g, ' ')
542
+ .replace(/'[^']*'/g, "''")
543
+ .replace(/"([^"]*)"/g, '$1')
544
+ .toLowerCase();
545
+ if (/\battach\b/.test(bare))
546
+ return 'ATTACH is not allowed: only the transcript index is queryable.';
547
+ if (/\b(main|temp|sqlite_temp_master|sqlite_master)\s*\./.test(bare))
548
+ return (
549
+ 'Schema-qualified names (main.file, temp.file) are not allowed, because they bypass ' +
550
+ 'the filter that hides transcripts the user marked private. Use the bare table name.'
551
+ );
552
+ if (/\bchunk_fts\b/.test(bare))
553
+ return 'chunk_fts is not queryable here. Use recall_search for full-text search.';
554
+ return null;
555
+ }
556
+
557
+ /**
558
+ * Hide private transcripts from arbitrary SQL, by shadowing.
559
+ *
560
+ * SQLite resolves an unqualified name in the TEMP schema first, so a temp view
561
+ * called `file` is what `SELECT ... FROM file` finds, while the real table
562
+ * stays reachable only as `main.file`, which `sqlRefusal` blocks. That keeps
563
+ * the documented schema exactly as it was: no `_public` suffixes to teach, no
564
+ * query rewriting, and no cost at all when nothing is private, which is the
565
+ * common case.
566
+ *
567
+ * Visibility follows `visibleSets()` in core: it is decided per FILE, and an
568
+ * entry belongs to whoever owns it (`first_file`). An entry owned by a private
569
+ * transcript stays hidden even where a public fork inherited it, which errs
570
+ * towards hiding.
571
+ *
572
+ * Needs a connection WITHOUT `query_only`, which refuses temp writes as well;
573
+ * `readOnly` alone already makes the index itself unwritable.
574
+ *
575
+ * Measured on a 617k-entry index when private transcripts DO exist: a full
576
+ * `COUNT(*) FROM entry` goes 343 ms -> 812 ms, and `COUNT(*) FROM chunk` goes
577
+ * 99 ms -> 191 ms, the join being cheap because it hits `entry.id`, the
578
+ * primary key. Not worth an index on `entry.first_file`: that would be a
579
+ * schema bump, hence a full rebuild for everyone, to speed up unfiltered
580
+ * aggregates for the subset of users who mark anything private.
581
+ */
582
+ function hidePrivate(db: DB): void {
583
+ const privateIds = (
584
+ db
585
+ .prepare('SELECT id FROM main.file WHERE private = 1')
586
+ .all() as unknown as Array<{id: number}>
587
+ ).map((row) => Number(row.id));
588
+ if (privateIds.length === 0) return;
589
+ const ids = privateIds.join(',');
590
+ db.exec(`
591
+ CREATE TEMP VIEW file AS
592
+ SELECT * FROM main.file WHERE id NOT IN (${ids});
593
+ CREATE TEMP VIEW entry AS
594
+ SELECT * FROM main.entry WHERE first_file IS NULL OR first_file NOT IN (${ids});
595
+ CREATE TEMP VIEW membership AS
596
+ SELECT * FROM main.membership WHERE file_id NOT IN (${ids});
597
+ CREATE TEMP VIEW chunk AS
598
+ SELECT c.* FROM main.chunk c JOIN main.entry e ON e.id = c.entry_id
599
+ WHERE e.first_file IS NULL OR e.first_file NOT IN (${ids});
600
+ `);
601
+ }
602
+
289
603
  export function runSql(sql: string, opts: ToolOptions = {}): ToolOutcome {
290
- const {config, maxChars} = options(opts);
291
- let db;
292
- try {
293
- db = openDb(config.dbPath, {readOnly: true});
294
- } catch {
295
- return missingIndex(config);
296
- }
604
+ const {config, maxChars, syncTtlMs, autoBuild} = options(opts);
605
+ const refusal = sqlRefusal(sql);
606
+ if (refusal) return {isError: true, text: refusal};
607
+ const state = ready(config, syncTtlMs, autoBuild);
608
+ if (isOutcome(state)) return state;
609
+ const opened = openIndex(config, {writableTemp: true});
610
+ if (isOutcome(opened)) return opened;
611
+ const db = opened;
297
612
  try {
613
+ hidePrivate(db);
298
614
  const rows = db.prepare(sql).all() as unknown as Array<
299
615
  Record<string, unknown>
300
616
  >;
301
- if (rows.length === 0) return {text: '(no rows)', details: {rows: 0}};
617
+ if (rows.length === 0)
618
+ return {
619
+ text: withNote('(no rows)', state.note),
620
+ details: {rows: 0, ...state.details},
621
+ };
302
622
  const lines = rows.slice(0, 200).map((row) => JSON.stringify(row));
303
623
  return {
304
- text: truncate(
305
- [`${rows.length} row(s):`, ...lines],
306
- maxChars,
307
- 'Add a LIMIT or aggregate instead.',
624
+ text: withNote(
625
+ truncate(
626
+ [`${rows.length} row(s):`, ...lines],
627
+ maxChars,
628
+ 'Add a LIMIT or aggregate instead.',
629
+ ),
630
+ state.note,
308
631
  ),
309
- details: {rows: rows.length},
632
+ details: {rows: rows.length, ...state.details},
310
633
  };
311
634
  } catch (err) {
635
+ // A broken index is not a broken query, and handing back a schema lecture
636
+ // for "file is not a database" would send the agent rewriting valid SQL.
637
+ const message = safeMessage(err);
638
+ if (/not a database|malformed|disk image|corrupt/i.test(message))
639
+ return unusableIndex(config, message);
312
640
  return {
313
641
  isError: true,
314
642
  text:
315
- `SQL failed: ${(err as Error).message}\n` +
643
+ `SQL failed: ${message}\n` +
316
644
  'Tables: file(id, path, cwd, project, name, parent_id, lineage_id, private, started, last_activity, entry_count), ' +
317
645
  'entry(id, lineage_id, entry_key, role, tool, ts), membership(entry_id, file_id, seq), chunk(entry_id, kind, text). ' +
318
- 'The connection is read-only.',
646
+ 'The connection is read-only, private transcripts are filtered out, and chunk_fts is not queryable here.',
319
647
  };
320
648
  } finally {
321
- db?.close();
649
+ db.close();
322
650
  }
323
651
  }