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.
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -3
- package/dist/index.js.map +1 -1
- package/dist/queries.d.ts +21 -0
- package/dist/queries.d.ts.map +1 -1
- package/dist/queries.js +331 -41
- package/dist/queries.js.map +1 -1
- package/package.json +2 -2
- package/skills/memonaut/SKILL.md +4 -2
- package/src/index.ts +25 -3
- package/src/queries.ts +378 -50
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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)
|
|
103
|
-
'the
|
|
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":"AAAA,OAAO,EAAC,oBAAoB,EAAC,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAC,IAAI,EAAC,MAAM,SAAS,CAAC;AAK7B,OAAO,
|
|
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[];
|
package/dist/queries.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"
|
|
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
|
|
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
|
-
|
|
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)}
|
|
16
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const db =
|
|
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 ${
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
return
|
|
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
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
return
|
|
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 {
|
|
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: ${
|
|
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
|
|
504
|
+
db.close();
|
|
215
505
|
}
|
|
216
506
|
}
|
|
217
507
|
//# sourceMappingURL=queries.js.map
|
package/dist/queries.js.map
CHANGED
|
@@ -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.
|
|
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.
|
|
51
|
+
"memonaut": "0.4.0"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
54
|
"@earendil-works/pi-coding-agent": "*",
|
package/skills/memonaut/SKILL.md
CHANGED
|
@@ -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.
|
|
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 {
|
|
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)
|
|
143
|
-
'the
|
|
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
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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)}
|
|
60
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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:
|
|
192
|
-
lines,
|
|
193
|
-
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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:
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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)
|
|
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:
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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: ${
|
|
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
|
|
649
|
+
db.close();
|
|
322
650
|
}
|
|
323
651
|
}
|