threadnote 1.1.0 → 1.1.1
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/mcp_server.cjs +296 -93
- package/dist/threadnote.cjs +313 -104
- package/manager/app.js +13 -1
- package/package.json +1 -1
package/dist/mcp_server.cjs
CHANGED
|
@@ -36085,8 +36085,192 @@ function exactRecallTerms(query) {
|
|
|
36085
36085
|
}
|
|
36086
36086
|
return terms.sort((left, right) => exactRecallTermScore(right) - exactRecallTermScore(left)).slice(0, 4);
|
|
36087
36087
|
}
|
|
36088
|
-
|
|
36089
|
-
|
|
36088
|
+
var RECALL_SCORE_THRESHOLD = process.env.THREADNOTE_RECALL_THRESHOLD?.trim() || "0.45";
|
|
36089
|
+
var EXACT_SCOPE_INTENT_PATTERNS = [
|
|
36090
|
+
["preferences", /\b(preferences?|prefer|styles?|tone|voice|writing|persona|communication)\b/i],
|
|
36091
|
+
["handoffs", /\b(handoffs?|status|next step|in progress|wip|current work|where .* left)\b/i],
|
|
36092
|
+
["durable", /\b(durable|feature knowledge|design decisions?|invariants?|api contract|gotchas?)\b/i],
|
|
36093
|
+
["incidents", /\b(incidents?|outage|post-?mortem|on-?call|escalation)\b/i]
|
|
36094
|
+
];
|
|
36095
|
+
function exactRecallScopeIntents(query) {
|
|
36096
|
+
const intents = /* @__PURE__ */ new Set();
|
|
36097
|
+
for (const [intent, pattern] of EXACT_SCOPE_INTENT_PATTERNS) {
|
|
36098
|
+
if (pattern.test(query)) {
|
|
36099
|
+
intents.add(intent);
|
|
36100
|
+
}
|
|
36101
|
+
}
|
|
36102
|
+
return intents;
|
|
36103
|
+
}
|
|
36104
|
+
function isSummarySidecarUri(uri) {
|
|
36105
|
+
return /\.(?:overview|abstract)\.md(?:#|$)/.test(uri);
|
|
36106
|
+
}
|
|
36107
|
+
function grepUrisFromJson(output) {
|
|
36108
|
+
const start = output.search(/^\{/m);
|
|
36109
|
+
if (start < 0) {
|
|
36110
|
+
return [];
|
|
36111
|
+
}
|
|
36112
|
+
try {
|
|
36113
|
+
const parsed = JSON.parse(output.slice(start));
|
|
36114
|
+
const result = isJsonObject(parsed) ? parsed.result : void 0;
|
|
36115
|
+
const matches = isJsonObject(result) ? result.matches : void 0;
|
|
36116
|
+
if (!Array.isArray(matches)) {
|
|
36117
|
+
return [];
|
|
36118
|
+
}
|
|
36119
|
+
const uris = [];
|
|
36120
|
+
for (const match of matches) {
|
|
36121
|
+
if (isJsonObject(match) && typeof match.uri === "string" && !isSummarySidecarUri(match.uri)) {
|
|
36122
|
+
uris.push(match.uri);
|
|
36123
|
+
}
|
|
36124
|
+
}
|
|
36125
|
+
return uris;
|
|
36126
|
+
} catch (_err) {
|
|
36127
|
+
return [];
|
|
36128
|
+
}
|
|
36129
|
+
}
|
|
36130
|
+
function recallSnippet(value) {
|
|
36131
|
+
if (typeof value !== "string") {
|
|
36132
|
+
return "";
|
|
36133
|
+
}
|
|
36134
|
+
const oneLine = value.replace(/\s+/g, " ").trim();
|
|
36135
|
+
return oneLine.length > 180 ? `${oneLine.slice(0, 180)}\u2026` : oneLine;
|
|
36136
|
+
}
|
|
36137
|
+
function parseRecallHits(output) {
|
|
36138
|
+
const start = output.search(/^\{/m);
|
|
36139
|
+
if (start < 0) {
|
|
36140
|
+
return [];
|
|
36141
|
+
}
|
|
36142
|
+
try {
|
|
36143
|
+
const parsed = JSON.parse(output.slice(start));
|
|
36144
|
+
const result = isJsonObject(parsed) ? parsed.result : void 0;
|
|
36145
|
+
if (!isJsonObject(result)) {
|
|
36146
|
+
return [];
|
|
36147
|
+
}
|
|
36148
|
+
const hits = [];
|
|
36149
|
+
for (const key of ["memories", "resources", "skills"]) {
|
|
36150
|
+
const items = result[key];
|
|
36151
|
+
if (!Array.isArray(items)) {
|
|
36152
|
+
continue;
|
|
36153
|
+
}
|
|
36154
|
+
for (const item of items) {
|
|
36155
|
+
if (!isJsonObject(item) || typeof item.uri !== "string" || isSummarySidecarUri(item.uri)) {
|
|
36156
|
+
continue;
|
|
36157
|
+
}
|
|
36158
|
+
hits.push({
|
|
36159
|
+
contextType: typeof item.context_type === "string" ? item.context_type : "result",
|
|
36160
|
+
score: typeof item.score === "number" ? item.score : 0,
|
|
36161
|
+
snippet: recallSnippet(item.abstract ?? item.overview),
|
|
36162
|
+
uri: item.uri
|
|
36163
|
+
});
|
|
36164
|
+
}
|
|
36165
|
+
}
|
|
36166
|
+
return hits;
|
|
36167
|
+
} catch (_err) {
|
|
36168
|
+
return [];
|
|
36169
|
+
}
|
|
36170
|
+
}
|
|
36171
|
+
function mergeRecallHits(passes) {
|
|
36172
|
+
const byDocument = /* @__PURE__ */ new Map();
|
|
36173
|
+
for (const pass of passes) {
|
|
36174
|
+
for (const hit of pass) {
|
|
36175
|
+
const documentUri = hit.uri.replace(/#.*$/, "");
|
|
36176
|
+
const existing = byDocument.get(documentUri);
|
|
36177
|
+
if (!existing || hit.score > existing.score) {
|
|
36178
|
+
byDocument.set(documentUri, { ...hit, uri: documentUri });
|
|
36179
|
+
}
|
|
36180
|
+
}
|
|
36181
|
+
}
|
|
36182
|
+
return [...byDocument.values()].sort((left, right) => right.score - left.score);
|
|
36183
|
+
}
|
|
36184
|
+
function formatRecallHits(hits, maxHits) {
|
|
36185
|
+
if (hits.length === 0) {
|
|
36186
|
+
return void 0;
|
|
36187
|
+
}
|
|
36188
|
+
const shown = hits.slice(0, maxHits);
|
|
36189
|
+
const lines = shown.flatMap((hit, index) => {
|
|
36190
|
+
const head = `${index + 1}. ${hit.contextType} \xB7 score ${hit.score.toFixed(2)} \xB7 ${hit.uri}`;
|
|
36191
|
+
return hit.snippet ? [head, ` ${hit.snippet}`] : [head];
|
|
36192
|
+
});
|
|
36193
|
+
if (hits.length > maxHits) {
|
|
36194
|
+
lines.push(`(+${hits.length - maxHits} more \u2014 refine the query or read a URI above)`);
|
|
36195
|
+
}
|
|
36196
|
+
return lines.join("\n");
|
|
36197
|
+
}
|
|
36198
|
+
function exactMemoryScopeUris(params) {
|
|
36199
|
+
const { agentMemoriesUri, includeArchived, intents, projectName, projectResourceUri, userBase } = params;
|
|
36200
|
+
const durable = projectName ? `${userBase}/durable/projects/${projectName}` : `${userBase}/durable/projects`;
|
|
36201
|
+
const handoffs = projectName ? `${userBase}/handoffs/active/${projectName}` : `${userBase}/handoffs/active`;
|
|
36202
|
+
const incidents = projectName ? `${userBase}/incidents/active/${projectName}` : `${userBase}/incidents/active`;
|
|
36203
|
+
if (intents.size > 0) {
|
|
36204
|
+
const scopes2 = [];
|
|
36205
|
+
if (intents.has("preferences")) {
|
|
36206
|
+
scopes2.push(`${userBase}/preferences`);
|
|
36207
|
+
}
|
|
36208
|
+
if (intents.has("durable")) {
|
|
36209
|
+
scopes2.push(durable);
|
|
36210
|
+
}
|
|
36211
|
+
if (intents.has("handoffs")) {
|
|
36212
|
+
scopes2.push(handoffs);
|
|
36213
|
+
}
|
|
36214
|
+
if (intents.has("incidents")) {
|
|
36215
|
+
scopes2.push(incidents);
|
|
36216
|
+
}
|
|
36217
|
+
scopes2.push(`${userBase}/shared`);
|
|
36218
|
+
if (includeArchived) {
|
|
36219
|
+
if (intents.has("durable")) {
|
|
36220
|
+
scopes2.push(`${userBase}/durable/archived`);
|
|
36221
|
+
}
|
|
36222
|
+
if (intents.has("handoffs")) {
|
|
36223
|
+
scopes2.push(`${userBase}/handoffs/archived`);
|
|
36224
|
+
}
|
|
36225
|
+
if (intents.has("incidents")) {
|
|
36226
|
+
scopes2.push(`${userBase}/incidents/archived`);
|
|
36227
|
+
}
|
|
36228
|
+
}
|
|
36229
|
+
return scopes2;
|
|
36230
|
+
}
|
|
36231
|
+
const scopes = [
|
|
36232
|
+
`${userBase}/preferences`,
|
|
36233
|
+
durable,
|
|
36234
|
+
handoffs,
|
|
36235
|
+
incidents,
|
|
36236
|
+
`${userBase}/shared`,
|
|
36237
|
+
agentMemoriesUri,
|
|
36238
|
+
projectResourceUri ?? "viking://resources/repos"
|
|
36239
|
+
];
|
|
36240
|
+
return includeArchived ? [...scopes, `${userBase}/durable/archived`, `${userBase}/handoffs/archived`, `${userBase}/incidents/archived`] : scopes;
|
|
36241
|
+
}
|
|
36242
|
+
async function collectExactMatches(terms, scopes, runGrep) {
|
|
36243
|
+
const pairs2 = terms.flatMap((term) => scopes.map((scope) => ({ scope, term })));
|
|
36244
|
+
const outputs = await Promise.all(pairs2.map((pair) => runGrep(pair.term, pair.scope)));
|
|
36245
|
+
const byUri = /* @__PURE__ */ new Map();
|
|
36246
|
+
let order = 0;
|
|
36247
|
+
for (const [index, pair] of pairs2.entries()) {
|
|
36248
|
+
const json3 = outputs[index];
|
|
36249
|
+
if (!json3) {
|
|
36250
|
+
continue;
|
|
36251
|
+
}
|
|
36252
|
+
for (const raw of grepUrisFromJson(json3)) {
|
|
36253
|
+
const uri = raw.replace(/#.*$/, "");
|
|
36254
|
+
const existing = byUri.get(uri);
|
|
36255
|
+
if (existing) {
|
|
36256
|
+
existing.terms.add(pair.term);
|
|
36257
|
+
} else {
|
|
36258
|
+
byUri.set(uri, { order: order++, terms: /* @__PURE__ */ new Set([pair.term]) });
|
|
36259
|
+
}
|
|
36260
|
+
}
|
|
36261
|
+
}
|
|
36262
|
+
return [...byUri.entries()].sort((left, right) => right[1].terms.size - left[1].terms.size || left[1].order - right[1].order).map(([uri, value]) => ({ terms: [...value.terms], uri }));
|
|
36263
|
+
}
|
|
36264
|
+
function formatExactMatchPointers(matches, maxUris = 8) {
|
|
36265
|
+
if (matches.length === 0) {
|
|
36266
|
+
return void 0;
|
|
36267
|
+
}
|
|
36268
|
+
const shown = matches.slice(0, maxUris);
|
|
36269
|
+
const lines = shown.map((match) => `- ${match.uri} (${match.terms.join(", ")})`);
|
|
36270
|
+
if (matches.length > maxUris) {
|
|
36271
|
+
lines.push(`(+${matches.length - maxUris} more exact matches \u2014 refine the query to narrow)`);
|
|
36272
|
+
}
|
|
36273
|
+
return ["Exact term matches (read the URI for full content):", ...lines].join("\n");
|
|
36090
36274
|
}
|
|
36091
36275
|
function exactRecallTermScore(term) {
|
|
36092
36276
|
let score = term.length;
|
|
@@ -36197,6 +36381,7 @@ async function repairStaleRecallIndex(config2, ov, options = {}) {
|
|
|
36197
36381
|
options.onProgress?.({ type: "scan-start" });
|
|
36198
36382
|
const targets = await findStaleRecallIndexTargets(config2, options);
|
|
36199
36383
|
const repairTargets = targets.slice(0, options.maxTargets ?? MAX_REPAIR_TARGETS);
|
|
36384
|
+
const consecutiveFailureLimit = options.consecutiveFailureLimit;
|
|
36200
36385
|
options.onProgress?.({
|
|
36201
36386
|
repairTargetCount: repairTargets.length,
|
|
36202
36387
|
totalTargets: targets.length,
|
|
@@ -36210,6 +36395,7 @@ async function repairStaleRecallIndex(config2, ov, options = {}) {
|
|
|
36210
36395
|
const repairedUris = [];
|
|
36211
36396
|
const skippedRecentUris = [];
|
|
36212
36397
|
const warnings = [];
|
|
36398
|
+
let consecutiveFailures = 0;
|
|
36213
36399
|
for (const [targetIndex, target] of repairTargets.entries()) {
|
|
36214
36400
|
const progressBase = { index: targetIndex + 1, target, total: repairTargets.length };
|
|
36215
36401
|
const previous = state.entries[target.uri];
|
|
@@ -36230,11 +36416,20 @@ async function repairStaleRecallIndex(config2, ov, options = {}) {
|
|
|
36230
36416
|
{ allowFailure: true }
|
|
36231
36417
|
);
|
|
36232
36418
|
if (result.exitCode === 0) {
|
|
36419
|
+
consecutiveFailures = 0;
|
|
36233
36420
|
repairedUris.push(target.uri);
|
|
36234
36421
|
state.entries[target.uri] = { repairedAt: new Date(now).toISOString(), signature: target.signature };
|
|
36235
36422
|
} else {
|
|
36423
|
+
consecutiveFailures += 1;
|
|
36236
36424
|
warnings.push(indexRepairWarning(target.uri, result));
|
|
36237
36425
|
await options.onRepairFailure?.(target, result);
|
|
36426
|
+
const remaining = repairTargets.length - (targetIndex + 1);
|
|
36427
|
+
if (consecutiveFailureLimit !== void 0 && consecutiveFailures >= consecutiveFailureLimit && remaining > 0) {
|
|
36428
|
+
warnings.push(
|
|
36429
|
+
`Stopped recall index repair after ${consecutiveFailures} consecutive reindex failures; skipped ${remaining} remaining scope(s). Re-run \`threadnote repair\` once OpenViking is idle.`
|
|
36430
|
+
);
|
|
36431
|
+
break;
|
|
36432
|
+
}
|
|
36238
36433
|
}
|
|
36239
36434
|
}
|
|
36240
36435
|
if (options.dryRun !== true && repairedUris.length > 0) {
|
|
@@ -36243,6 +36438,9 @@ async function repairStaleRecallIndex(config2, ov, options = {}) {
|
|
|
36243
36438
|
return { repairedUris, skippedRecentUris, warnings };
|
|
36244
36439
|
}
|
|
36245
36440
|
async function findStaleRecallIndexTargets(config2, options = {}) {
|
|
36441
|
+
if (await summaryAutoGenerationDisabled(config2)) {
|
|
36442
|
+
return [];
|
|
36443
|
+
}
|
|
36246
36444
|
const roots = await scanRoots(config2, options);
|
|
36247
36445
|
const byUri = /* @__PURE__ */ new Map();
|
|
36248
36446
|
for (const root of roots) {
|
|
@@ -36303,12 +36501,6 @@ function formatRecallIndexRepairMessages(result, options = {}) {
|
|
|
36303
36501
|
}
|
|
36304
36502
|
return messages;
|
|
36305
36503
|
}
|
|
36306
|
-
function filterStaleRecallSummaryRows(output) {
|
|
36307
|
-
return output.split(/\r?\n/).filter((line) => !isStaleRecallSummaryRow(line)).join("\n").trim();
|
|
36308
|
-
}
|
|
36309
|
-
function isStaleRecallSummaryRow(line) {
|
|
36310
|
-
return /viking:\/\/\S+\/\.(?:abstract|overview)\.md\b/.test(line) && isStaleSummary(line);
|
|
36311
|
-
}
|
|
36312
36504
|
async function scanRoots(config2, options) {
|
|
36313
36505
|
const accountRoot = (0, import_node_path2.join)(config2.agentContextHome, "data", "viking", config2.account);
|
|
36314
36506
|
const roots = [
|
|
@@ -36397,6 +36589,18 @@ async function staleSidecars(rootPath, rootUri) {
|
|
|
36397
36589
|
function isSummarySidecar(name) {
|
|
36398
36590
|
return name === ".abstract.md" || name === ".overview.md";
|
|
36399
36591
|
}
|
|
36592
|
+
async function summaryAutoGenerationDisabled(config2) {
|
|
36593
|
+
const raw = await readFileIfExists((0, import_node_path2.join)(config2.agentContextHome, "ov.conf"));
|
|
36594
|
+
if (!raw) {
|
|
36595
|
+
return false;
|
|
36596
|
+
}
|
|
36597
|
+
try {
|
|
36598
|
+
const parsed = JSON.parse(raw);
|
|
36599
|
+
return isJsonObject(parsed) && parsed.auto_generate_l0 === false && parsed.auto_generate_l1 === false;
|
|
36600
|
+
} catch (_err) {
|
|
36601
|
+
return false;
|
|
36602
|
+
}
|
|
36603
|
+
}
|
|
36400
36604
|
function isStaleSummary(content) {
|
|
36401
36605
|
return content.includes("[Directory overview is not ready]") || content.includes("[Directory abstract is not ready]");
|
|
36402
36606
|
}
|
|
@@ -37692,7 +37896,7 @@ function registerTools(server, config2) {
|
|
|
37692
37896
|
server,
|
|
37693
37897
|
config2,
|
|
37694
37898
|
"recall_context",
|
|
37695
|
-
`Search Threadnote context across personal memories and seeded project resources. Returns semantic hits from indexed Threadnote context (handoffs, durable feature memories, preferences, shared team memories) \u2014 and, when the query mentions a project name from the seed manifest, also from that project's seeded guidance (README, AGENTS.md, CLAUDE.md, SKILL.md, docs/**) under viking://resources/repos/<project>. Queries that mention this/current branch are enriched with local git/workspace terms when callerCwd is provided. Include the repo or project name in the query to make the project-guidance pass fire. Required: pass JSON arguments with a non-empty query, for example {"query":"unity-ui-ccc latest handoff"}.`
|
|
37899
|
+
`Search Threadnote context across personal memories and seeded project resources. Returns semantic hits from indexed Threadnote context (handoffs, durable feature memories, preferences, shared team memories) \u2014 and, when the query mentions a project name from the seed manifest, also from that project's seeded guidance (README, AGENTS.md, CLAUDE.md, SKILL.md, docs/**) under viking://resources/repos/<project>. Queries that mention this/current branch are enriched with local git/workspace terms when callerCwd is provided. Include the repo or project name in the query to make the project-guidance pass fire. Results are filtered by a default relevance threshold (0.5); if a recall comes back empty or too sparse, retry with a lower threshold (e.g. {"query":"...","threshold":0.2}) to broaden. Required: pass JSON arguments with a non-empty query, for example {"query":"unity-ui-ccc latest handoff"}.`
|
|
37696
37900
|
);
|
|
37697
37901
|
registerSearchTool(
|
|
37698
37902
|
server,
|
|
@@ -38213,10 +38417,13 @@ function registerSearchTool(server, config2, name, description) {
|
|
|
38213
38417
|
uri: external_exports.string().optional().describe("Optional viking:// subtree to search"),
|
|
38214
38418
|
callerCwd: external_exports.string().optional().describe("Optional absolute caller workspace path used to resolve this/current branch queries"),
|
|
38215
38419
|
nodeLimit: external_exports.number().int().positive().max(100).optional().describe("Maximum result count"),
|
|
38216
|
-
includeArchived: external_exports.boolean().optional().describe("Include archived memories in exact memory/resource matches")
|
|
38420
|
+
includeArchived: external_exports.boolean().optional().describe("Include archived memories in exact memory/resource matches"),
|
|
38421
|
+
threshold: external_exports.number().min(0).max(1).optional().describe(
|
|
38422
|
+
"Minimum relevance score 0-1 (default 0.5); lower it (toward 0) to broaden when a recall comes back empty"
|
|
38423
|
+
)
|
|
38217
38424
|
}
|
|
38218
38425
|
},
|
|
38219
|
-
async ({ callerCwd, includeArchived, nodeLimit, query, uri }) => {
|
|
38426
|
+
async ({ callerCwd, includeArchived, nodeLimit, query, threshold, uri }) => {
|
|
38220
38427
|
const checkedQuery = requiredText(query, name, "query", { query: "unity-ui-ccc latest handoff" });
|
|
38221
38428
|
if (!checkedQuery.ok) {
|
|
38222
38429
|
return checkedQuery.error;
|
|
@@ -38230,7 +38437,8 @@ function registerSearchTool(server, config2, name, description) {
|
|
|
38230
38437
|
query: checkedQuery.value,
|
|
38231
38438
|
pinnedUri: checkedUri.value,
|
|
38232
38439
|
nodeLimit,
|
|
38233
|
-
includeArchived: includeArchived === true
|
|
38440
|
+
includeArchived: includeArchived === true,
|
|
38441
|
+
threshold: threshold === void 0 ? void 0 : String(threshold)
|
|
38234
38442
|
});
|
|
38235
38443
|
}
|
|
38236
38444
|
);
|
|
@@ -38261,36 +38469,34 @@ async function runRecallTool(config2, params) {
|
|
|
38261
38469
|
} catch (err) {
|
|
38262
38470
|
indexRepairMessages = [`Auto-index repair warning: ${errorMessage(err)}`];
|
|
38263
38471
|
}
|
|
38264
|
-
const
|
|
38265
|
-
const
|
|
38266
|
-
|
|
38267
|
-
|
|
38268
|
-
|
|
38269
|
-
|
|
38270
|
-
|
|
38271
|
-
|
|
38272
|
-
|
|
38273
|
-
|
|
38274
|
-
|
|
38275
|
-
|
|
38472
|
+
const project = params.pinnedUri ? void 0 : await inferProjectFromQuery(config2.manifestPath, projectQuery);
|
|
38473
|
+
const limitArgs = params.nodeLimit ? ["--node-limit", String(params.nodeLimit)] : [];
|
|
38474
|
+
const threshold = params.threshold ?? RECALL_SCORE_THRESHOLD;
|
|
38475
|
+
const pinnedArgs = params.pinnedUri ? ["--uri", params.pinnedUri] : [];
|
|
38476
|
+
const base = await recallSearchHits(config2, ["search", query, ...pinnedArgs, ...limitArgs], threshold);
|
|
38477
|
+
const passes = [base.hits];
|
|
38478
|
+
const seededUri = project ? trimTrailingSlash(project.uri) : void 0;
|
|
38479
|
+
if (seededUri?.startsWith("viking://") && seededUri !== params.pinnedUri) {
|
|
38480
|
+
const seeded = await recallSearchHits(
|
|
38481
|
+
config2,
|
|
38482
|
+
["search", params.query, "--uri", seededUri, ...limitArgs],
|
|
38483
|
+
threshold
|
|
38484
|
+
);
|
|
38485
|
+
passes.push(seeded.hits);
|
|
38276
38486
|
}
|
|
38277
38487
|
const sections = [];
|
|
38278
|
-
const
|
|
38279
|
-
|
|
38280
|
-
|
|
38281
|
-
|
|
38488
|
+
const semanticSection = formatRecallHits(mergeRecallHits(passes), params.nodeLimit ?? 12);
|
|
38489
|
+
if (semanticSection) {
|
|
38490
|
+
sections.push(semanticSection);
|
|
38491
|
+
} else if (!base.ok) {
|
|
38492
|
+
sections.push(`Recall semantic search unavailable: ${base.errorText || "ov search failed"}`);
|
|
38282
38493
|
}
|
|
38283
38494
|
if (indexRepairMessages.length > 0) {
|
|
38284
38495
|
sections.push(indexRepairMessages.join("\n"));
|
|
38285
38496
|
}
|
|
38286
|
-
const
|
|
38287
|
-
if (seededSection) {
|
|
38288
|
-
sections.push(seededSection);
|
|
38289
|
-
}
|
|
38290
|
-
const exactMatches = await exactMemoryMatchesText(config2, query, params.includeArchived);
|
|
38497
|
+
const exactMatches = await exactMemoryMatchesText(config2, query, params.includeArchived, project);
|
|
38291
38498
|
if (exactMatches) {
|
|
38292
|
-
sections.push(
|
|
38293
|
-
${exactMatches}`);
|
|
38499
|
+
sections.push(exactMatches);
|
|
38294
38500
|
}
|
|
38295
38501
|
const hygieneHints = await recallHygieneHintsSection(config2, sections.join("\n\n"));
|
|
38296
38502
|
if (hygieneHints) {
|
|
@@ -38302,10 +38508,31 @@ ${exactMatches}`);
|
|
|
38302
38508
|
for (const warning2 of syncWarnings) {
|
|
38303
38509
|
sections.push(`Auto-sync warning: ${warning2}`);
|
|
38304
38510
|
}
|
|
38305
|
-
if (sections.length
|
|
38306
|
-
return
|
|
38511
|
+
if (sections.length === 0) {
|
|
38512
|
+
return { content: [{ type: "text", text: "No recall results found." }] };
|
|
38513
|
+
}
|
|
38514
|
+
const onlyErrorNote = !base.ok && !semanticSection && sections.length === 1;
|
|
38515
|
+
return { content: [{ type: "text", text: sections.join("\n\n") }], isError: onlyErrorNote || void 0 };
|
|
38516
|
+
}
|
|
38517
|
+
async function recallSearchHits(config2, searchArgs, threshold) {
|
|
38518
|
+
let result = await runOpenVikingTool(config2, [
|
|
38519
|
+
...searchArgs,
|
|
38520
|
+
"--threshold",
|
|
38521
|
+
threshold,
|
|
38522
|
+
"--level",
|
|
38523
|
+
"2",
|
|
38524
|
+
"--output",
|
|
38525
|
+
"json"
|
|
38526
|
+
]);
|
|
38527
|
+
if (result.isError === true) {
|
|
38528
|
+
result = await runOpenVikingTool(config2, [...searchArgs, "--output", "json"]);
|
|
38529
|
+
}
|
|
38530
|
+
const firstContent = result.content[0];
|
|
38531
|
+
const text = firstContent?.type === "text" ? firstContent.text : "";
|
|
38532
|
+
if (result.isError === true) {
|
|
38533
|
+
return { errorText: text.trim(), hits: [], ok: false };
|
|
38307
38534
|
}
|
|
38308
|
-
return {
|
|
38535
|
+
return { errorText: "", hits: parseRecallHits(text), ok: true };
|
|
38309
38536
|
}
|
|
38310
38537
|
async function recallHygieneHintsSection(config2, recallText) {
|
|
38311
38538
|
const uris = activePersonalMemoryUrisFromText(recallText, config2.user);
|
|
@@ -38316,50 +38543,22 @@ async function recallHygieneHintsSection(config2, recallText) {
|
|
|
38316
38543
|
const nudges = recallHygieneNudges(recallText, { records, user: config2.user });
|
|
38317
38544
|
return nudges.length > 0 ? ["Memory hygiene hints:", ...nudges.map((nudge) => `- ${nudge}`)].join("\n") : void 0;
|
|
38318
38545
|
}
|
|
38319
|
-
async function
|
|
38320
|
-
if (params.pinnedUri) {
|
|
38321
|
-
return void 0;
|
|
38322
|
-
}
|
|
38323
|
-
const project = await inferProjectFromQuery(config2.manifestPath, projectQuery);
|
|
38324
|
-
if (!project) {
|
|
38325
|
-
return void 0;
|
|
38326
|
-
}
|
|
38327
|
-
const projectResourceUri = trimTrailingSlash(project.uri);
|
|
38328
|
-
if (!projectResourceUri.startsWith("viking://")) {
|
|
38329
|
-
return void 0;
|
|
38330
|
-
}
|
|
38331
|
-
const result = await runOpenVikingMcpTool(config2, "search", {
|
|
38332
|
-
limit: params.nodeLimit,
|
|
38333
|
-
query: params.query,
|
|
38334
|
-
target_uri: projectResourceUri
|
|
38335
|
-
});
|
|
38336
|
-
if (result.isError === true) {
|
|
38337
|
-
return void 0;
|
|
38338
|
-
}
|
|
38339
|
-
const body = filterStaleRecallSummaryRows(textFromCallToolResult(result));
|
|
38340
|
-
if (!body) {
|
|
38341
|
-
return void 0;
|
|
38342
|
-
}
|
|
38343
|
-
return `Seeded project resources (${projectResourceUri}):
|
|
38344
|
-
${body}`;
|
|
38345
|
-
}
|
|
38346
|
-
async function exactMemoryMatchesText(config2, query, includeArchived) {
|
|
38546
|
+
async function exactMemoryMatchesText(config2, query, includeArchived, project) {
|
|
38347
38547
|
const terms = exactRecallTerms(query);
|
|
38348
38548
|
if (terms.length === 0) {
|
|
38349
38549
|
return void 0;
|
|
38350
38550
|
}
|
|
38351
|
-
const
|
|
38352
|
-
const
|
|
38353
|
-
|
|
38354
|
-
|
|
38355
|
-
|
|
38356
|
-
|
|
38357
|
-
|
|
38358
|
-
|
|
38359
|
-
|
|
38360
|
-
|
|
38361
|
-
|
|
38362
|
-
return outputs.length > 0 ? outputs.join("\n\n") : void 0;
|
|
38551
|
+
const ov = await requiredOpenVikingCli2();
|
|
38552
|
+
const scopes = exactMemoryScopes(config2, includeArchived, query, project);
|
|
38553
|
+
const matches = await collectExactMatches(terms, scopes, async (term, scope) => {
|
|
38554
|
+
const result = await runCommand(
|
|
38555
|
+
ov,
|
|
38556
|
+
withIdentity2(config2, ["grep", term, "--uri", scope, "--node-limit", "5", "--output", "json"]),
|
|
38557
|
+
{ allowFailure: true }
|
|
38558
|
+
);
|
|
38559
|
+
return result.exitCode === 0 ? result.stdout : void 0;
|
|
38560
|
+
});
|
|
38561
|
+
return formatExactMatchPointers(matches);
|
|
38363
38562
|
}
|
|
38364
38563
|
function registerReadTool(server, config2, name, description) {
|
|
38365
38564
|
server.registerTool(
|
|
@@ -38896,21 +39095,15 @@ function vikingDirectoryChain(directoryUri) {
|
|
|
38896
39095
|
}
|
|
38897
39096
|
return chain;
|
|
38898
39097
|
}
|
|
38899
|
-
function exactMemoryScopes(config2, includeArchived) {
|
|
38900
|
-
|
|
38901
|
-
|
|
38902
|
-
|
|
38903
|
-
|
|
38904
|
-
|
|
38905
|
-
|
|
38906
|
-
|
|
38907
|
-
|
|
38908
|
-
// Seeded project resources live outside the user/memories tree. Include
|
|
38909
|
-
// them so exact-term grep surfaces matches in seeded READMEs, AGENTS.md,
|
|
38910
|
-
// SKILL.md, and docs/** alongside personal memory hits.
|
|
38911
|
-
"viking://resources/repos"
|
|
38912
|
-
];
|
|
38913
|
-
return includeArchived ? [...scopes, `${userBase}/durable/archived`, `${userBase}/handoffs/archived`, `${userBase}/incidents/archived`] : scopes;
|
|
39098
|
+
function exactMemoryScopes(config2, includeArchived, query, project) {
|
|
39099
|
+
return exactMemoryScopeUris({
|
|
39100
|
+
agentMemoriesUri: `viking://agent/${uriSegment2(config2.agentId)}/memories`,
|
|
39101
|
+
includeArchived,
|
|
39102
|
+
intents: exactRecallScopeIntents(query),
|
|
39103
|
+
projectName: project ? uriSegment2(project.name) : void 0,
|
|
39104
|
+
projectResourceUri: project ? trimTrailingSlash(project.uri) : void 0,
|
|
39105
|
+
userBase: `viking://user/${uriSegment2(config2.user)}/memories`
|
|
39106
|
+
});
|
|
38914
39107
|
}
|
|
38915
39108
|
function formatMemoryDocument2(title, metadata, body) {
|
|
38916
39109
|
const header = [
|
|
@@ -39080,6 +39273,16 @@ async function runOpenVikingCliReadTool(config2, uri) {
|
|
|
39080
39273
|
return { content: [{ type: "text", text: errorMessage(err) }], isError: true };
|
|
39081
39274
|
}
|
|
39082
39275
|
}
|
|
39276
|
+
async function runOpenVikingTool(config2, args) {
|
|
39277
|
+
try {
|
|
39278
|
+
const ov = await requiredOpenVikingCli2();
|
|
39279
|
+
const result = await runCommand(ov, withIdentity2(config2, args));
|
|
39280
|
+
const text = [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join("\n");
|
|
39281
|
+
return { content: [{ type: "text", text: text || "OK" }] };
|
|
39282
|
+
} catch (err) {
|
|
39283
|
+
return { content: [{ type: "text", text: errorMessage(err) }], isError: true };
|
|
39284
|
+
}
|
|
39285
|
+
}
|
|
39083
39286
|
async function runOpenVikingMcpTool(config2, toolName, args) {
|
|
39084
39287
|
const client = new Client({ name: "threadnote-openviking-proxy", version: "1.1.0" });
|
|
39085
39288
|
try {
|
package/dist/threadnote.cjs
CHANGED
|
@@ -3909,6 +3909,9 @@ async function resolveRepoName(cwd = getInvocationCwd()) {
|
|
|
3909
3909
|
async function runInteractive(executable, args) {
|
|
3910
3910
|
return new Promise((resolvePromise) => {
|
|
3911
3911
|
const child = (0, import_node_child_process.spawn)(executable, args, { stdio: "inherit" });
|
|
3912
|
+
child.on("error", () => {
|
|
3913
|
+
resolvePromise(1);
|
|
3914
|
+
});
|
|
3912
3915
|
child.on("close", (code) => {
|
|
3913
3916
|
resolvePromise(code ?? 1);
|
|
3914
3917
|
});
|
|
@@ -4263,8 +4266,192 @@ function exactRecallTerms(query) {
|
|
|
4263
4266
|
}
|
|
4264
4267
|
return terms.sort((left, right) => exactRecallTermScore(right) - exactRecallTermScore(left)).slice(0, 4);
|
|
4265
4268
|
}
|
|
4266
|
-
|
|
4267
|
-
|
|
4269
|
+
var RECALL_SCORE_THRESHOLD = process.env.THREADNOTE_RECALL_THRESHOLD?.trim() || "0.45";
|
|
4270
|
+
var EXACT_SCOPE_INTENT_PATTERNS = [
|
|
4271
|
+
["preferences", /\b(preferences?|prefer|styles?|tone|voice|writing|persona|communication)\b/i],
|
|
4272
|
+
["handoffs", /\b(handoffs?|status|next step|in progress|wip|current work|where .* left)\b/i],
|
|
4273
|
+
["durable", /\b(durable|feature knowledge|design decisions?|invariants?|api contract|gotchas?)\b/i],
|
|
4274
|
+
["incidents", /\b(incidents?|outage|post-?mortem|on-?call|escalation)\b/i]
|
|
4275
|
+
];
|
|
4276
|
+
function exactRecallScopeIntents(query) {
|
|
4277
|
+
const intents = /* @__PURE__ */ new Set();
|
|
4278
|
+
for (const [intent, pattern] of EXACT_SCOPE_INTENT_PATTERNS) {
|
|
4279
|
+
if (pattern.test(query)) {
|
|
4280
|
+
intents.add(intent);
|
|
4281
|
+
}
|
|
4282
|
+
}
|
|
4283
|
+
return intents;
|
|
4284
|
+
}
|
|
4285
|
+
function isSummarySidecarUri(uri) {
|
|
4286
|
+
return /\.(?:overview|abstract)\.md(?:#|$)/.test(uri);
|
|
4287
|
+
}
|
|
4288
|
+
function grepUrisFromJson(output2) {
|
|
4289
|
+
const start = output2.search(/^\{/m);
|
|
4290
|
+
if (start < 0) {
|
|
4291
|
+
return [];
|
|
4292
|
+
}
|
|
4293
|
+
try {
|
|
4294
|
+
const parsed = JSON.parse(output2.slice(start));
|
|
4295
|
+
const result = isJsonObject(parsed) ? parsed.result : void 0;
|
|
4296
|
+
const matches = isJsonObject(result) ? result.matches : void 0;
|
|
4297
|
+
if (!Array.isArray(matches)) {
|
|
4298
|
+
return [];
|
|
4299
|
+
}
|
|
4300
|
+
const uris = [];
|
|
4301
|
+
for (const match of matches) {
|
|
4302
|
+
if (isJsonObject(match) && typeof match.uri === "string" && !isSummarySidecarUri(match.uri)) {
|
|
4303
|
+
uris.push(match.uri);
|
|
4304
|
+
}
|
|
4305
|
+
}
|
|
4306
|
+
return uris;
|
|
4307
|
+
} catch (_err) {
|
|
4308
|
+
return [];
|
|
4309
|
+
}
|
|
4310
|
+
}
|
|
4311
|
+
function recallSnippet(value) {
|
|
4312
|
+
if (typeof value !== "string") {
|
|
4313
|
+
return "";
|
|
4314
|
+
}
|
|
4315
|
+
const oneLine = value.replace(/\s+/g, " ").trim();
|
|
4316
|
+
return oneLine.length > 180 ? `${oneLine.slice(0, 180)}\u2026` : oneLine;
|
|
4317
|
+
}
|
|
4318
|
+
function parseRecallHits(output2) {
|
|
4319
|
+
const start = output2.search(/^\{/m);
|
|
4320
|
+
if (start < 0) {
|
|
4321
|
+
return [];
|
|
4322
|
+
}
|
|
4323
|
+
try {
|
|
4324
|
+
const parsed = JSON.parse(output2.slice(start));
|
|
4325
|
+
const result = isJsonObject(parsed) ? parsed.result : void 0;
|
|
4326
|
+
if (!isJsonObject(result)) {
|
|
4327
|
+
return [];
|
|
4328
|
+
}
|
|
4329
|
+
const hits = [];
|
|
4330
|
+
for (const key of ["memories", "resources", "skills"]) {
|
|
4331
|
+
const items = result[key];
|
|
4332
|
+
if (!Array.isArray(items)) {
|
|
4333
|
+
continue;
|
|
4334
|
+
}
|
|
4335
|
+
for (const item of items) {
|
|
4336
|
+
if (!isJsonObject(item) || typeof item.uri !== "string" || isSummarySidecarUri(item.uri)) {
|
|
4337
|
+
continue;
|
|
4338
|
+
}
|
|
4339
|
+
hits.push({
|
|
4340
|
+
contextType: typeof item.context_type === "string" ? item.context_type : "result",
|
|
4341
|
+
score: typeof item.score === "number" ? item.score : 0,
|
|
4342
|
+
snippet: recallSnippet(item.abstract ?? item.overview),
|
|
4343
|
+
uri: item.uri
|
|
4344
|
+
});
|
|
4345
|
+
}
|
|
4346
|
+
}
|
|
4347
|
+
return hits;
|
|
4348
|
+
} catch (_err) {
|
|
4349
|
+
return [];
|
|
4350
|
+
}
|
|
4351
|
+
}
|
|
4352
|
+
function mergeRecallHits(passes) {
|
|
4353
|
+
const byDocument = /* @__PURE__ */ new Map();
|
|
4354
|
+
for (const pass of passes) {
|
|
4355
|
+
for (const hit of pass) {
|
|
4356
|
+
const documentUri = hit.uri.replace(/#.*$/, "");
|
|
4357
|
+
const existing = byDocument.get(documentUri);
|
|
4358
|
+
if (!existing || hit.score > existing.score) {
|
|
4359
|
+
byDocument.set(documentUri, { ...hit, uri: documentUri });
|
|
4360
|
+
}
|
|
4361
|
+
}
|
|
4362
|
+
}
|
|
4363
|
+
return [...byDocument.values()].sort((left, right) => right.score - left.score);
|
|
4364
|
+
}
|
|
4365
|
+
function formatRecallHits(hits, maxHits) {
|
|
4366
|
+
if (hits.length === 0) {
|
|
4367
|
+
return void 0;
|
|
4368
|
+
}
|
|
4369
|
+
const shown = hits.slice(0, maxHits);
|
|
4370
|
+
const lines = shown.flatMap((hit, index) => {
|
|
4371
|
+
const head = `${index + 1}. ${hit.contextType} \xB7 score ${hit.score.toFixed(2)} \xB7 ${hit.uri}`;
|
|
4372
|
+
return hit.snippet ? [head, ` ${hit.snippet}`] : [head];
|
|
4373
|
+
});
|
|
4374
|
+
if (hits.length > maxHits) {
|
|
4375
|
+
lines.push(`(+${hits.length - maxHits} more \u2014 refine the query or read a URI above)`);
|
|
4376
|
+
}
|
|
4377
|
+
return lines.join("\n");
|
|
4378
|
+
}
|
|
4379
|
+
function exactMemoryScopeUris(params) {
|
|
4380
|
+
const { agentMemoriesUri, includeArchived, intents, projectName, projectResourceUri, userBase } = params;
|
|
4381
|
+
const durable = projectName ? `${userBase}/durable/projects/${projectName}` : `${userBase}/durable/projects`;
|
|
4382
|
+
const handoffs = projectName ? `${userBase}/handoffs/active/${projectName}` : `${userBase}/handoffs/active`;
|
|
4383
|
+
const incidents = projectName ? `${userBase}/incidents/active/${projectName}` : `${userBase}/incidents/active`;
|
|
4384
|
+
if (intents.size > 0) {
|
|
4385
|
+
const scopes2 = [];
|
|
4386
|
+
if (intents.has("preferences")) {
|
|
4387
|
+
scopes2.push(`${userBase}/preferences`);
|
|
4388
|
+
}
|
|
4389
|
+
if (intents.has("durable")) {
|
|
4390
|
+
scopes2.push(durable);
|
|
4391
|
+
}
|
|
4392
|
+
if (intents.has("handoffs")) {
|
|
4393
|
+
scopes2.push(handoffs);
|
|
4394
|
+
}
|
|
4395
|
+
if (intents.has("incidents")) {
|
|
4396
|
+
scopes2.push(incidents);
|
|
4397
|
+
}
|
|
4398
|
+
scopes2.push(`${userBase}/shared`);
|
|
4399
|
+
if (includeArchived) {
|
|
4400
|
+
if (intents.has("durable")) {
|
|
4401
|
+
scopes2.push(`${userBase}/durable/archived`);
|
|
4402
|
+
}
|
|
4403
|
+
if (intents.has("handoffs")) {
|
|
4404
|
+
scopes2.push(`${userBase}/handoffs/archived`);
|
|
4405
|
+
}
|
|
4406
|
+
if (intents.has("incidents")) {
|
|
4407
|
+
scopes2.push(`${userBase}/incidents/archived`);
|
|
4408
|
+
}
|
|
4409
|
+
}
|
|
4410
|
+
return scopes2;
|
|
4411
|
+
}
|
|
4412
|
+
const scopes = [
|
|
4413
|
+
`${userBase}/preferences`,
|
|
4414
|
+
durable,
|
|
4415
|
+
handoffs,
|
|
4416
|
+
incidents,
|
|
4417
|
+
`${userBase}/shared`,
|
|
4418
|
+
agentMemoriesUri,
|
|
4419
|
+
projectResourceUri ?? "viking://resources/repos"
|
|
4420
|
+
];
|
|
4421
|
+
return includeArchived ? [...scopes, `${userBase}/durable/archived`, `${userBase}/handoffs/archived`, `${userBase}/incidents/archived`] : scopes;
|
|
4422
|
+
}
|
|
4423
|
+
async function collectExactMatches(terms, scopes, runGrep) {
|
|
4424
|
+
const pairs2 = terms.flatMap((term) => scopes.map((scope) => ({ scope, term })));
|
|
4425
|
+
const outputs = await Promise.all(pairs2.map((pair) => runGrep(pair.term, pair.scope)));
|
|
4426
|
+
const byUri = /* @__PURE__ */ new Map();
|
|
4427
|
+
let order = 0;
|
|
4428
|
+
for (const [index, pair] of pairs2.entries()) {
|
|
4429
|
+
const json2 = outputs[index];
|
|
4430
|
+
if (!json2) {
|
|
4431
|
+
continue;
|
|
4432
|
+
}
|
|
4433
|
+
for (const raw of grepUrisFromJson(json2)) {
|
|
4434
|
+
const uri = raw.replace(/#.*$/, "");
|
|
4435
|
+
const existing = byUri.get(uri);
|
|
4436
|
+
if (existing) {
|
|
4437
|
+
existing.terms.add(pair.term);
|
|
4438
|
+
} else {
|
|
4439
|
+
byUri.set(uri, { order: order++, terms: /* @__PURE__ */ new Set([pair.term]) });
|
|
4440
|
+
}
|
|
4441
|
+
}
|
|
4442
|
+
}
|
|
4443
|
+
return [...byUri.entries()].sort((left, right) => right[1].terms.size - left[1].terms.size || left[1].order - right[1].order).map(([uri, value]) => ({ terms: [...value.terms], uri }));
|
|
4444
|
+
}
|
|
4445
|
+
function formatExactMatchPointers(matches, maxUris = 8) {
|
|
4446
|
+
if (matches.length === 0) {
|
|
4447
|
+
return void 0;
|
|
4448
|
+
}
|
|
4449
|
+
const shown = matches.slice(0, maxUris);
|
|
4450
|
+
const lines = shown.map((match) => `- ${match.uri} (${match.terms.join(", ")})`);
|
|
4451
|
+
if (matches.length > maxUris) {
|
|
4452
|
+
lines.push(`(+${matches.length - maxUris} more exact matches \u2014 refine the query to narrow)`);
|
|
4453
|
+
}
|
|
4454
|
+
return ["Exact term matches (read the URI for full content):", ...lines].join("\n");
|
|
4268
4455
|
}
|
|
4269
4456
|
function exactRecallTermScore(term) {
|
|
4270
4457
|
let score = term.length;
|
|
@@ -7553,10 +7740,12 @@ var MAX_SCAN_DEPTH = 5;
|
|
|
7553
7740
|
var MAX_REPAIR_TARGETS = 4;
|
|
7554
7741
|
var MAINTENANCE_COLLAPSE_DEPTH = 3;
|
|
7555
7742
|
var MAINTENANCE_MAX_REPAIR_TARGETS = 512;
|
|
7743
|
+
var MAINTENANCE_CONSECUTIVE_FAILURE_LIMIT = 5;
|
|
7556
7744
|
async function repairStaleRecallIndex(config, ov, options = {}) {
|
|
7557
7745
|
options.onProgress?.({ type: "scan-start" });
|
|
7558
7746
|
const targets = await findStaleRecallIndexTargets(config, options);
|
|
7559
7747
|
const repairTargets = targets.slice(0, options.maxTargets ?? MAX_REPAIR_TARGETS);
|
|
7748
|
+
const consecutiveFailureLimit = options.consecutiveFailureLimit;
|
|
7560
7749
|
options.onProgress?.({
|
|
7561
7750
|
repairTargetCount: repairTargets.length,
|
|
7562
7751
|
totalTargets: targets.length,
|
|
@@ -7570,6 +7759,7 @@ async function repairStaleRecallIndex(config, ov, options = {}) {
|
|
|
7570
7759
|
const repairedUris = [];
|
|
7571
7760
|
const skippedRecentUris = [];
|
|
7572
7761
|
const warnings = [];
|
|
7762
|
+
let consecutiveFailures = 0;
|
|
7573
7763
|
for (const [targetIndex, target] of repairTargets.entries()) {
|
|
7574
7764
|
const progressBase = { index: targetIndex + 1, target, total: repairTargets.length };
|
|
7575
7765
|
const previous = state.entries[target.uri];
|
|
@@ -7590,11 +7780,20 @@ async function repairStaleRecallIndex(config, ov, options = {}) {
|
|
|
7590
7780
|
{ allowFailure: true }
|
|
7591
7781
|
);
|
|
7592
7782
|
if (result.exitCode === 0) {
|
|
7783
|
+
consecutiveFailures = 0;
|
|
7593
7784
|
repairedUris.push(target.uri);
|
|
7594
7785
|
state.entries[target.uri] = { repairedAt: new Date(now).toISOString(), signature: target.signature };
|
|
7595
7786
|
} else {
|
|
7787
|
+
consecutiveFailures += 1;
|
|
7596
7788
|
warnings.push(indexRepairWarning(target.uri, result));
|
|
7597
7789
|
await options.onRepairFailure?.(target, result);
|
|
7790
|
+
const remaining = repairTargets.length - (targetIndex + 1);
|
|
7791
|
+
if (consecutiveFailureLimit !== void 0 && consecutiveFailures >= consecutiveFailureLimit && remaining > 0) {
|
|
7792
|
+
warnings.push(
|
|
7793
|
+
`Stopped recall index repair after ${consecutiveFailures} consecutive reindex failures; skipped ${remaining} remaining scope(s). Re-run \`threadnote repair\` once OpenViking is idle.`
|
|
7794
|
+
);
|
|
7795
|
+
break;
|
|
7796
|
+
}
|
|
7598
7797
|
}
|
|
7599
7798
|
}
|
|
7600
7799
|
if (options.dryRun !== true && repairedUris.length > 0) {
|
|
@@ -7603,6 +7802,9 @@ async function repairStaleRecallIndex(config, ov, options = {}) {
|
|
|
7603
7802
|
return { repairedUris, skippedRecentUris, warnings };
|
|
7604
7803
|
}
|
|
7605
7804
|
async function findStaleRecallIndexTargets(config, options = {}) {
|
|
7805
|
+
if (await summaryAutoGenerationDisabled(config)) {
|
|
7806
|
+
return [];
|
|
7807
|
+
}
|
|
7606
7808
|
const roots = await scanRoots(config, options);
|
|
7607
7809
|
const byUri = /* @__PURE__ */ new Map();
|
|
7608
7810
|
for (const root of roots) {
|
|
@@ -7663,12 +7865,6 @@ function formatRecallIndexRepairMessages(result, options = {}) {
|
|
|
7663
7865
|
}
|
|
7664
7866
|
return messages;
|
|
7665
7867
|
}
|
|
7666
|
-
function filterStaleRecallSummaryRows(output2) {
|
|
7667
|
-
return output2.split(/\r?\n/).filter((line) => !isStaleRecallSummaryRow(line)).join("\n").trim();
|
|
7668
|
-
}
|
|
7669
|
-
function isStaleRecallSummaryRow(line) {
|
|
7670
|
-
return /viking:\/\/\S+\/\.(?:abstract|overview)\.md\b/.test(line) && isStaleSummary(line);
|
|
7671
|
-
}
|
|
7672
7868
|
async function scanRoots(config, options) {
|
|
7673
7869
|
const accountRoot = (0, import_node_path4.join)(config.agentContextHome, "data", "viking", config.account);
|
|
7674
7870
|
const roots = [
|
|
@@ -7757,6 +7953,18 @@ async function staleSidecars(rootPath, rootUri) {
|
|
|
7757
7953
|
function isSummarySidecar(name) {
|
|
7758
7954
|
return name === ".abstract.md" || name === ".overview.md";
|
|
7759
7955
|
}
|
|
7956
|
+
async function summaryAutoGenerationDisabled(config) {
|
|
7957
|
+
const raw = await readFileIfExists((0, import_node_path4.join)(config.agentContextHome, "ov.conf"));
|
|
7958
|
+
if (!raw) {
|
|
7959
|
+
return false;
|
|
7960
|
+
}
|
|
7961
|
+
try {
|
|
7962
|
+
const parsed = JSON.parse(raw);
|
|
7963
|
+
return isJsonObject(parsed) && parsed.auto_generate_l0 === false && parsed.auto_generate_l1 === false;
|
|
7964
|
+
} catch (_err) {
|
|
7965
|
+
return false;
|
|
7966
|
+
}
|
|
7967
|
+
}
|
|
7760
7968
|
function isStaleSummary(content) {
|
|
7761
7969
|
return content.includes("[Directory overview is not ready]") || content.includes("[Directory abstract is not ready]");
|
|
7762
7970
|
}
|
|
@@ -9735,71 +9943,77 @@ async function runRecall(config, options) {
|
|
|
9735
9943
|
for (const message of indexRepairMessages) {
|
|
9736
9944
|
console.log(message);
|
|
9737
9945
|
}
|
|
9946
|
+
const dryRun = options.dryRun === true;
|
|
9738
9947
|
const inferredUri = options.uri ?? (options.inferScope === false ? void 0 : await inferRecallUri(config, projectQuery));
|
|
9739
|
-
const
|
|
9948
|
+
const project = await inferProjectFromQuery(config.manifestPath, options.project ?? projectQuery);
|
|
9949
|
+
const nodeLimit = options.nodeLimit ? parsePositiveInteger(options.nodeLimit, "node limit") : void 0;
|
|
9950
|
+
const searchArgs = (scopeUri) => [
|
|
9951
|
+
"search",
|
|
9952
|
+
query,
|
|
9953
|
+
"--threshold",
|
|
9954
|
+
options.threshold ?? RECALL_SCORE_THRESHOLD,
|
|
9955
|
+
"--level",
|
|
9956
|
+
"2",
|
|
9957
|
+
...scopeUri ? ["--uri", scopeUri] : [],
|
|
9958
|
+
...nodeLimit ? ["--node-limit", String(nodeLimit)] : []
|
|
9959
|
+
];
|
|
9740
9960
|
if (inferredUri) {
|
|
9741
|
-
args.push("--uri", inferredUri);
|
|
9742
9961
|
console.log(`Recall scope: ${inferredUri}`);
|
|
9743
9962
|
}
|
|
9744
|
-
|
|
9745
|
-
|
|
9963
|
+
const passes = [await recallSearchHits(config, ov, searchArgs(inferredUri), { dryRun })];
|
|
9964
|
+
if (options.project && project) {
|
|
9965
|
+
const projectMemoryUri = `viking://user/${uriSegment(config.user)}/memories/durable/projects/${uriSegment(project.name)}`;
|
|
9966
|
+
passes.push(await recallSearchHits(config, ov, searchArgs(projectMemoryUri), { dryRun }));
|
|
9746
9967
|
}
|
|
9747
|
-
const
|
|
9748
|
-
|
|
9749
|
-
|
|
9750
|
-
recallOutputs.push(baseOutput);
|
|
9968
|
+
const seededUri = project ? trimTrailingSlash(project.uri) : void 0;
|
|
9969
|
+
if (seededUri?.startsWith("viking://") && seededUri !== inferredUri && !options.uri && options.inferScope !== false) {
|
|
9970
|
+
passes.push(await recallSearchHits(config, ov, searchArgs(seededUri), { dryRun }));
|
|
9751
9971
|
}
|
|
9752
|
-
const
|
|
9753
|
-
|
|
9754
|
-
|
|
9755
|
-
|
|
9756
|
-
|
|
9757
|
-
|
|
9758
|
-
);
|
|
9759
|
-
if (seededOutput) {
|
|
9760
|
-
recallOutputs.push(seededOutput);
|
|
9972
|
+
const recallOutputs = [];
|
|
9973
|
+
const semanticSection = formatRecallHits(mergeRecallHits(passes), nodeLimit ?? 12);
|
|
9974
|
+
if (semanticSection) {
|
|
9975
|
+
console.log(`
|
|
9976
|
+
${semanticSection}`);
|
|
9977
|
+
recallOutputs.push(semanticSection);
|
|
9761
9978
|
}
|
|
9762
9979
|
const exactOutput = await printExactMemoryMatches(config, ov, query, {
|
|
9763
|
-
dryRun
|
|
9764
|
-
includeArchived: options.includeArchived === true
|
|
9980
|
+
dryRun,
|
|
9981
|
+
includeArchived: options.includeArchived === true,
|
|
9982
|
+
project
|
|
9765
9983
|
});
|
|
9766
9984
|
if (exactOutput) {
|
|
9767
9985
|
recallOutputs.push(exactOutput);
|
|
9768
9986
|
}
|
|
9769
9987
|
await printRecallHygieneNudges(config, recallOutputs.join("\n"));
|
|
9770
9988
|
}
|
|
9771
|
-
async function
|
|
9772
|
-
|
|
9773
|
-
|
|
9774
|
-
|
|
9775
|
-
|
|
9776
|
-
if (!project) {
|
|
9777
|
-
return void 0;
|
|
9989
|
+
async function recallSearchHits(config, ov, args, options) {
|
|
9990
|
+
const jsonArgs = withIdentity(config, [...args, "--output", "json"]);
|
|
9991
|
+
console.log(`${options.dryRun ? "Would run" : "Running"}: ${formatShellCommand(ov, jsonArgs)}`);
|
|
9992
|
+
if (options.dryRun) {
|
|
9993
|
+
return [];
|
|
9778
9994
|
}
|
|
9779
|
-
|
|
9780
|
-
if (
|
|
9781
|
-
|
|
9995
|
+
let result = await runCommand(ov, jsonArgs, { allowFailure: true });
|
|
9996
|
+
if (result.exitCode !== 0) {
|
|
9997
|
+
result = await runCommand(ov, withIdentity(config, [...stripAdvancedSearchFlags(args), "--output", "json"]), {
|
|
9998
|
+
allowFailure: true
|
|
9999
|
+
});
|
|
9782
10000
|
}
|
|
9783
|
-
|
|
9784
|
-
|
|
9785
|
-
|
|
10001
|
+
if (result.exitCode !== 0) {
|
|
10002
|
+
console.log(`WARN recall search failed: ${result.stderr.trim() || result.stdout.trim() || "ov search error"}`);
|
|
10003
|
+
return [];
|
|
9786
10004
|
}
|
|
9787
|
-
|
|
9788
|
-
Also searching seeded resources: ${projectResourceUri}`);
|
|
9789
|
-
return runRecallSearch(config, ov, args, { dryRun: options.dryRun === true });
|
|
10005
|
+
return parseRecallHits(result.stdout);
|
|
9790
10006
|
}
|
|
9791
|
-
|
|
9792
|
-
const
|
|
9793
|
-
|
|
9794
|
-
|
|
9795
|
-
|
|
9796
|
-
|
|
9797
|
-
|
|
9798
|
-
|
|
9799
|
-
if (output2) {
|
|
9800
|
-
console.log(output2);
|
|
10007
|
+
function stripAdvancedSearchFlags(args) {
|
|
10008
|
+
const stripped = [];
|
|
10009
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
10010
|
+
if (args[index] === "--threshold" || args[index] === "--level") {
|
|
10011
|
+
index += 1;
|
|
10012
|
+
continue;
|
|
10013
|
+
}
|
|
10014
|
+
stripped.push(args[index]);
|
|
9801
10015
|
}
|
|
9802
|
-
return
|
|
10016
|
+
return stripped;
|
|
9803
10017
|
}
|
|
9804
10018
|
async function runRead(config, uri, options) {
|
|
9805
10019
|
assertVikingUri(uri);
|
|
@@ -10137,29 +10351,25 @@ async function printExactMemoryMatches(config, ov, query, options) {
|
|
|
10137
10351
|
if (terms.length === 0) {
|
|
10138
10352
|
return void 0;
|
|
10139
10353
|
}
|
|
10140
|
-
const scopes = exactMemoryScopes(config, options.includeArchived);
|
|
10141
|
-
const
|
|
10142
|
-
|
|
10143
|
-
|
|
10144
|
-
|
|
10145
|
-
|
|
10146
|
-
|
|
10147
|
-
|
|
10148
|
-
|
|
10149
|
-
|
|
10150
|
-
|
|
10151
|
-
|
|
10152
|
-
|
|
10153
|
-
|
|
10154
|
-
}
|
|
10155
|
-
}
|
|
10156
|
-
if (outputs.length === 0) {
|
|
10354
|
+
const scopes = exactMemoryScopes(config, options.includeArchived, query, options.project);
|
|
10355
|
+
const grepArgs = (term, scope) => withIdentity(config, ["grep", term, "--uri", scope, "--node-limit", "5", "--output", "json"]);
|
|
10356
|
+
if (options.dryRun) {
|
|
10357
|
+
const planned = terms.flatMap((term) => scopes.map((scope) => formatShellCommand(ov, grepArgs(term, scope))));
|
|
10358
|
+
console.log("\nExact memory/resource matches:");
|
|
10359
|
+
console.log(planned.join("\n"));
|
|
10360
|
+
return planned.join("\n");
|
|
10361
|
+
}
|
|
10362
|
+
const matches = await collectExactMatches(terms, scopes, async (term, scope) => {
|
|
10363
|
+
const result = await runCommand(ov, grepArgs(term, scope), { allowFailure: true });
|
|
10364
|
+
return result.exitCode === 0 ? result.stdout : void 0;
|
|
10365
|
+
});
|
|
10366
|
+
const text = formatExactMatchPointers(matches);
|
|
10367
|
+
if (!text) {
|
|
10157
10368
|
return void 0;
|
|
10158
10369
|
}
|
|
10159
|
-
console.log(
|
|
10160
|
-
|
|
10161
|
-
|
|
10162
|
-
return output2;
|
|
10370
|
+
console.log(`
|
|
10371
|
+
${text}`);
|
|
10372
|
+
return text;
|
|
10163
10373
|
}
|
|
10164
10374
|
async function storeMemory(config, options) {
|
|
10165
10375
|
if (options.replaceUri) {
|
|
@@ -10358,21 +10568,15 @@ async function legacyLifecycleHandoffCandidates(config, limit) {
|
|
|
10358
10568
|
function lifecycleMigrationUri(config, metadata, hash) {
|
|
10359
10569
|
return `${memoryDirectoryUri(config, metadata)}/legacy-${hash.slice(0, 16)}.md`;
|
|
10360
10570
|
}
|
|
10361
|
-
function exactMemoryScopes(config, includeArchived) {
|
|
10362
|
-
|
|
10363
|
-
|
|
10364
|
-
|
|
10365
|
-
|
|
10366
|
-
|
|
10367
|
-
|
|
10368
|
-
|
|
10369
|
-
|
|
10370
|
-
// Seeded project resources (READMEs, AGENTS.md, SKILL.md, docs/**) live
|
|
10371
|
-
// under viking://resources/repos/<project>. Include them so an exact-term
|
|
10372
|
-
// grep in a recall surfaces matches in seeded guidance, not only memories.
|
|
10373
|
-
"viking://resources/repos"
|
|
10374
|
-
];
|
|
10375
|
-
return includeArchived ? [...scopes, `${userBase}/durable/archived`, `${userBase}/handoffs/archived`, `${userBase}/incidents/archived`] : scopes;
|
|
10571
|
+
function exactMemoryScopes(config, includeArchived, query, project) {
|
|
10572
|
+
return exactMemoryScopeUris({
|
|
10573
|
+
agentMemoriesUri: `viking://agent/${uriSegment(config.agentId)}/memories`,
|
|
10574
|
+
includeArchived,
|
|
10575
|
+
intents: exactRecallScopeIntents(query),
|
|
10576
|
+
projectName: project ? uriSegment(project.name) : void 0,
|
|
10577
|
+
projectResourceUri: project ? trimTrailingSlash(project.uri) : void 0,
|
|
10578
|
+
userBase: `viking://user/${uriSegment(config.user)}/memories`
|
|
10579
|
+
});
|
|
10376
10580
|
}
|
|
10377
10581
|
function memoryUriFor(config, memory, metadata) {
|
|
10378
10582
|
const filename = shouldUseStableMemoryUri(metadata) ? `${uriSegment(metadata.topic ?? "current")}.md` : `threadnote-${safeTimestamp()}-${sha256(memory).slice(0, 12)}.md`;
|
|
@@ -11664,7 +11868,7 @@ async function runUpdate(config, options) {
|
|
|
11664
11868
|
}
|
|
11665
11869
|
const runtime = await resolveUpdateRuntime(options.runtime ?? "auto");
|
|
11666
11870
|
const updateCommand = updatePackageCommand(runtime, registry);
|
|
11667
|
-
await
|
|
11871
|
+
await runStreamingSubcommand(options.dryRun === true, updateCommand.executable, updateCommand.args);
|
|
11668
11872
|
if (options.repair === false) {
|
|
11669
11873
|
console.log("Skipping repair because --no-repair was provided.");
|
|
11670
11874
|
await printWhatsNewIfAvailable(info2);
|
|
@@ -11673,7 +11877,7 @@ async function runUpdate(config, options) {
|
|
|
11673
11877
|
const threadnoteCommand = await installedThreadnoteCommand(runtime);
|
|
11674
11878
|
console.log("");
|
|
11675
11879
|
console.log("Repairing local Threadnote setup after package update.");
|
|
11676
|
-
await
|
|
11880
|
+
await runStreamingSubcommand(options.dryRun === true, threadnoteCommand, ["repair", "--no-post-update"]);
|
|
11677
11881
|
if (options.postUpdate !== false) {
|
|
11678
11882
|
const postUpdateArgs = [
|
|
11679
11883
|
"post-update",
|
|
@@ -11683,7 +11887,7 @@ async function runUpdate(config, options) {
|
|
|
11683
11887
|
info2.latestVersion,
|
|
11684
11888
|
...options.yes === true ? ["--yes"] : []
|
|
11685
11889
|
];
|
|
11686
|
-
await
|
|
11890
|
+
await runStreamingSubcommand(options.dryRun === true, threadnoteCommand, postUpdateArgs);
|
|
11687
11891
|
} else {
|
|
11688
11892
|
console.log("Skipping post-update migration prompts because --no-post-update was provided.");
|
|
11689
11893
|
}
|
|
@@ -11771,7 +11975,7 @@ async function ensurePinnedOpenVikingInstalled(config, options) {
|
|
|
11771
11975
|
const wasRunning = await isOpenVikingHealthy(config);
|
|
11772
11976
|
const usingLaunchd = await isLaunchAgentInstalled();
|
|
11773
11977
|
const threadnoteCommand = currentThreadnoteCommand() ?? await findExecutable([NPM_PACKAGE_NAME]) ?? NPM_PACKAGE_NAME;
|
|
11774
|
-
await
|
|
11978
|
+
await runStreamingSubcommand(options.dryRun, threadnoteCommand, ["install", "--force", "--no-start"]);
|
|
11775
11979
|
if (options.dryRun) {
|
|
11776
11980
|
if (wasRunning || usingLaunchd) {
|
|
11777
11981
|
console.log("Would restart OpenViking server so the new binary takes effect.");
|
|
@@ -11788,9 +11992,9 @@ async function ensurePinnedOpenVikingInstalled(config, options) {
|
|
|
11788
11992
|
await waitForOpenVikingPortClosed(config, 15e3);
|
|
11789
11993
|
await runCommand("launchctl", ["load", launchAgentPath], { allowFailure: true });
|
|
11790
11994
|
} else {
|
|
11791
|
-
await
|
|
11995
|
+
await runStreamingSubcommand(false, threadnoteCommand, ["stop"]);
|
|
11792
11996
|
await waitForOpenVikingPortClosed(config, 15e3);
|
|
11793
|
-
await
|
|
11997
|
+
await runStreamingSubcommand(false, threadnoteCommand, ["start"]);
|
|
11794
11998
|
}
|
|
11795
11999
|
const healthyAfter = await waitForOpenVikingHealthy(config, 1e4);
|
|
11796
12000
|
if (!healthyAfter) {
|
|
@@ -11800,7 +12004,7 @@ async function ensurePinnedOpenVikingInstalled(config, options) {
|
|
|
11800
12004
|
console.log("Check the server log or run: threadnote start");
|
|
11801
12005
|
}
|
|
11802
12006
|
}
|
|
11803
|
-
async function
|
|
12007
|
+
async function runStreamingSubcommand(dryRun, executable, args) {
|
|
11804
12008
|
if (dryRun) {
|
|
11805
12009
|
await maybeRun(true, executable, args);
|
|
11806
12010
|
return;
|
|
@@ -11971,7 +12175,7 @@ async function runApplicablePostUpdateMigrations(config, options) {
|
|
|
11971
12175
|
console.log(` ${formatMigrationCommand(threadnoteCommand, migration.commandArgs)}`);
|
|
11972
12176
|
continue;
|
|
11973
12177
|
}
|
|
11974
|
-
await
|
|
12178
|
+
await runStreamingSubcommand(options.dryRun, threadnoteCommand, migration.commandArgs);
|
|
11975
12179
|
if (!options.dryRun) {
|
|
11976
12180
|
handledMigrationIds.add(migration.id);
|
|
11977
12181
|
for (const instruction of migration.instructions) {
|
|
@@ -12390,15 +12594,12 @@ async function repairRecallIndex(config, dryRun) {
|
|
|
12390
12594
|
const result = await repairStaleRecallIndex(config, ov, {
|
|
12391
12595
|
collapseDepth: MAINTENANCE_COLLAPSE_DEPTH,
|
|
12392
12596
|
collapseToRoots: true,
|
|
12597
|
+
consecutiveFailureLimit: MAINTENANCE_CONSECUTIVE_FAILURE_LIMIT,
|
|
12393
12598
|
dryRun,
|
|
12394
12599
|
ignoreBackoff: true,
|
|
12395
12600
|
includeAgentSkills: true,
|
|
12396
12601
|
includeManifestResources: true,
|
|
12397
12602
|
maxTargets: MAINTENANCE_MAX_REPAIR_TARGETS,
|
|
12398
|
-
onRepairFailure: async () => {
|
|
12399
|
-
progress.update("A recall index reindex failed; checking OpenViking health before continuing.");
|
|
12400
|
-
await repairServerHealth(config, dryRun);
|
|
12401
|
-
},
|
|
12402
12603
|
onProgress: (event) => {
|
|
12403
12604
|
if (event.type === "scan-complete") {
|
|
12404
12605
|
if (event.totalTargets === 0) {
|
|
@@ -12787,6 +12988,13 @@ async function manifestCheck(path) {
|
|
|
12787
12988
|
}
|
|
12788
12989
|
async function recallIndexFreshnessCheck(config) {
|
|
12789
12990
|
try {
|
|
12991
|
+
if (await summaryAutoGenerationDisabled(config)) {
|
|
12992
|
+
return {
|
|
12993
|
+
name: "recall index freshness",
|
|
12994
|
+
status: "ok",
|
|
12995
|
+
detail: "OpenViking L0/L1 summary auto-generation disabled in ov.conf; directory summary placeholders are expected and not reindexed"
|
|
12996
|
+
};
|
|
12997
|
+
}
|
|
12790
12998
|
const targets = await findStaleRecallIndexTargets(config, {
|
|
12791
12999
|
collapseToRoots: true,
|
|
12792
13000
|
includeAgentSkills: true,
|
|
@@ -13622,7 +13830,8 @@ async function handleRequest(context, request, response) {
|
|
|
13622
13830
|
await runCaptured(
|
|
13623
13831
|
() => runRecall(context.config, {
|
|
13624
13832
|
query: requireString(body.query, "query"),
|
|
13625
|
-
nodeLimit: optionalString(body.nodeLimit)
|
|
13833
|
+
nodeLimit: optionalString(body.nodeLimit),
|
|
13834
|
+
project: optionalString(body.project)
|
|
13626
13835
|
})
|
|
13627
13836
|
)
|
|
13628
13837
|
);
|
|
@@ -14481,7 +14690,7 @@ async function main() {
|
|
|
14481
14690
|
program2.command("migrate-lifecycle").description("Move clear legacy handoff memories into lifecycle-aware archive paths").option("--apply", "Perform the migration; without this, prints a dry run").option("--dry-run", "Print migration actions without writing or removing memories").option("--limit <count>", "Maximum number of legacy handoffs to migrate").action(async (options) => {
|
|
14482
14691
|
await runMigrateLifecycle(getRuntimeConfig(program2), options);
|
|
14483
14692
|
});
|
|
14484
|
-
program2.command("recall").description("Search shared OpenViking context").requiredOption("--query <query>", "Search query").option("--dry-run", "Print ov command without searching").option("--include-archived", "Include archived memories in exact memory/resource matches").option("-n, --node-limit <count>", "Maximum number of search results").option("--no-infer-scope", "Disable query-based scope inference").option("--uri <uri>", "Restrict search to a viking:// URI").action(async (options) => {
|
|
14693
|
+
program2.command("recall").description("Search shared OpenViking context").requiredOption("--query <query>", "Search query").option("--dry-run", "Print ov command without searching").option("--include-archived", "Include archived memories in exact memory/resource matches").option("-n, --node-limit <count>", "Maximum number of search results").option("--no-infer-scope", "Disable query-based scope inference").option("--project <name>", "Prioritize a project: add a scoped pass over its memories alongside the global search").option("--threshold <score>", "Minimum relevance score 0-1 (default 0.45); lower to broaden when recall is empty").option("--uri <uri>", "Restrict search to a viking:// URI").action(async (options) => {
|
|
14485
14694
|
await runRecall(getRuntimeConfig(program2), options);
|
|
14486
14695
|
});
|
|
14487
14696
|
program2.command("compact").description("Plan or apply scoped memory hygiene for active personal memories").requiredOption("--project <name>", "Project/repo namespace to inspect").option("--apply", "Apply the compact plan; without this, prints a dry run").option("--dry-run", "Print the compact plan without changing anything").option("--kind <kind>", "durable, handoff, or incident", parseCompactKind).option("--topic <name>", "Stable topic name to inspect").action(async (options) => {
|
package/manager/app.js
CHANGED
|
@@ -34760,6 +34760,7 @@
|
|
|
34760
34760
|
const [toast, setToast] = (0, import_react2.useState)("");
|
|
34761
34761
|
const [output, setOutput] = (0, import_react2.useState)("");
|
|
34762
34762
|
const [recallQuery, setRecallQuery] = (0, import_react2.useState)("");
|
|
34763
|
+
const [recallProject, setRecallProject] = (0, import_react2.useState)("");
|
|
34763
34764
|
const [readUri, setReadUri] = (0, import_react2.useState)("");
|
|
34764
34765
|
const [compactProject, setCompactProject] = (0, import_react2.useState)("");
|
|
34765
34766
|
const [compactTopic, setCompactTopic] = (0, import_react2.useState)("");
|
|
@@ -35671,12 +35672,23 @@
|
|
|
35671
35672
|
placeholder: "Search memories and seeded resources"
|
|
35672
35673
|
}
|
|
35673
35674
|
),
|
|
35675
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
35676
|
+
"input",
|
|
35677
|
+
{
|
|
35678
|
+
value: recallProject,
|
|
35679
|
+
onChange: (event) => setRecallProject(event.target.value),
|
|
35680
|
+
placeholder: "project scope (blank = all)"
|
|
35681
|
+
}
|
|
35682
|
+
),
|
|
35674
35683
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
35675
35684
|
"button",
|
|
35676
35685
|
{
|
|
35677
35686
|
onClick: () => void runAction(
|
|
35678
35687
|
"Recall complete",
|
|
35679
|
-
() => api("/api/recall", {
|
|
35688
|
+
() => api("/api/recall", {
|
|
35689
|
+
query: recallQuery,
|
|
35690
|
+
...recallProject.trim() ? { project: recallProject.trim() } : {}
|
|
35691
|
+
}).then((result) => result)
|
|
35680
35692
|
),
|
|
35681
35693
|
children: "Search"
|
|
35682
35694
|
}
|