threadnote 1.0.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/README.md +4 -0
- package/dist/mcp_server.cjs +4280 -1449
- package/dist/threadnote.cjs +488 -179
- package/docs/troubleshooting.md +4 -0
- package/manager/app.js +13 -1
- package/package.json +1 -1
package/dist/threadnote.cjs
CHANGED
|
@@ -3616,6 +3616,41 @@ async function withSpinner(message, action) {
|
|
|
3616
3616
|
(0, import_node_readline.cursorTo)(import_node_process.stdout, 0);
|
|
3617
3617
|
}
|
|
3618
3618
|
}
|
|
3619
|
+
function startProgress(message) {
|
|
3620
|
+
if (import_node_process.stdout.isTTY !== true || process.env.CI !== void 0 || process.env.THREADNOTE_NO_SPINNER !== void 0) {
|
|
3621
|
+
console.log(message);
|
|
3622
|
+
return {
|
|
3623
|
+
update(nextMessage) {
|
|
3624
|
+
console.log(nextMessage);
|
|
3625
|
+
},
|
|
3626
|
+
stop() {
|
|
3627
|
+
return;
|
|
3628
|
+
}
|
|
3629
|
+
};
|
|
3630
|
+
}
|
|
3631
|
+
const frames = ["-", "\\", "|", "/"];
|
|
3632
|
+
let frameIndex = 0;
|
|
3633
|
+
let currentMessage = message;
|
|
3634
|
+
const render = () => {
|
|
3635
|
+
(0, import_node_readline.clearLine)(import_node_process.stdout, 0);
|
|
3636
|
+
(0, import_node_readline.cursorTo)(import_node_process.stdout, 0);
|
|
3637
|
+
import_node_process.stdout.write(`${muted(frames[frameIndex])} ${currentMessage}`);
|
|
3638
|
+
frameIndex = (frameIndex + 1) % frames.length;
|
|
3639
|
+
};
|
|
3640
|
+
const timer = setInterval(render, 100);
|
|
3641
|
+
render();
|
|
3642
|
+
return {
|
|
3643
|
+
update(nextMessage) {
|
|
3644
|
+
currentMessage = nextMessage;
|
|
3645
|
+
render();
|
|
3646
|
+
},
|
|
3647
|
+
stop() {
|
|
3648
|
+
clearInterval(timer);
|
|
3649
|
+
(0, import_node_readline.clearLine)(import_node_process.stdout, 0);
|
|
3650
|
+
(0, import_node_readline.cursorTo)(import_node_process.stdout, 0);
|
|
3651
|
+
}
|
|
3652
|
+
};
|
|
3653
|
+
}
|
|
3619
3654
|
|
|
3620
3655
|
// src/utils.ts
|
|
3621
3656
|
function isJsonObject(value) {
|
|
@@ -3753,15 +3788,11 @@ async function maybeRun(dryRun, executable, args, options = {}) {
|
|
|
3753
3788
|
}
|
|
3754
3789
|
async function runCommand(executable, args, options = {}) {
|
|
3755
3790
|
return new Promise((resolvePromise, rejectPromise) => {
|
|
3756
|
-
const child = (0, import_node_child_process.spawn)(executable, args, { cwd: options.cwd });
|
|
3757
|
-
const stdoutChunks = [];
|
|
3758
|
-
const stderrChunks = [];
|
|
3759
3791
|
const maxOutputBytes = options.maxOutputBytes ?? defaultCommandMaxOutputBytes();
|
|
3760
|
-
let stdoutBytes = 0;
|
|
3761
|
-
let stderrBytes = 0;
|
|
3762
3792
|
let finished = false;
|
|
3763
|
-
let failureResult;
|
|
3764
3793
|
let killTimer;
|
|
3794
|
+
let killEscalationTimer;
|
|
3795
|
+
let failureMessage;
|
|
3765
3796
|
let sentTerminationSignal = false;
|
|
3766
3797
|
const timeoutMs = options.timeoutMs ?? defaultCommandTimeoutMs();
|
|
3767
3798
|
const finish = (result) => {
|
|
@@ -3772,22 +3803,47 @@ async function runCommand(executable, args, options = {}) {
|
|
|
3772
3803
|
if (killTimer) {
|
|
3773
3804
|
clearTimeout(killTimer);
|
|
3774
3805
|
}
|
|
3806
|
+
if (killEscalationTimer) {
|
|
3807
|
+
clearTimeout(killEscalationTimer);
|
|
3808
|
+
}
|
|
3775
3809
|
if (result.exitCode !== 0 && options.allowFailure !== true) {
|
|
3776
3810
|
rejectPromise(new Error(`${formatShellCommand(executable, args)} failed: ${result.stderr || result.stdout}`));
|
|
3777
3811
|
return;
|
|
3778
3812
|
}
|
|
3779
3813
|
resolvePromise(result);
|
|
3780
3814
|
};
|
|
3815
|
+
const child = (0, import_node_child_process.execFile)(
|
|
3816
|
+
executable,
|
|
3817
|
+
[...args],
|
|
3818
|
+
{
|
|
3819
|
+
cwd: options.cwd,
|
|
3820
|
+
encoding: "utf8",
|
|
3821
|
+
maxBuffer: maxOutputBytes
|
|
3822
|
+
},
|
|
3823
|
+
(err, stdout2, stderr) => {
|
|
3824
|
+
finish(
|
|
3825
|
+
commandResultFromExecFileCallback({
|
|
3826
|
+
args,
|
|
3827
|
+
err,
|
|
3828
|
+
executable,
|
|
3829
|
+
failureMessage,
|
|
3830
|
+
maxOutputBytes,
|
|
3831
|
+
stderr,
|
|
3832
|
+
stdout: stdout2
|
|
3833
|
+
})
|
|
3834
|
+
);
|
|
3835
|
+
}
|
|
3836
|
+
);
|
|
3781
3837
|
const failAndKill = (message) => {
|
|
3782
|
-
if (
|
|
3838
|
+
if (failureMessage) {
|
|
3783
3839
|
return;
|
|
3784
3840
|
}
|
|
3785
|
-
|
|
3841
|
+
failureMessage = message;
|
|
3786
3842
|
if (!sentTerminationSignal) {
|
|
3787
3843
|
sentTerminationSignal = true;
|
|
3788
3844
|
child.kill("SIGTERM");
|
|
3789
3845
|
}
|
|
3790
|
-
setTimeout(() => {
|
|
3846
|
+
killEscalationTimer = setTimeout(() => {
|
|
3791
3847
|
if (!finished) {
|
|
3792
3848
|
child.kill("SIGKILL");
|
|
3793
3849
|
}
|
|
@@ -3799,54 +3855,20 @@ async function runCommand(executable, args, options = {}) {
|
|
|
3799
3855
|
}, timeoutMs);
|
|
3800
3856
|
killTimer.unref?.();
|
|
3801
3857
|
}
|
|
3802
|
-
child.stdout.on("data", (chunk) => {
|
|
3803
|
-
if (failureResult) {
|
|
3804
|
-
return;
|
|
3805
|
-
}
|
|
3806
|
-
const text = String(chunk);
|
|
3807
|
-
stdoutBytes += Buffer.byteLength(text);
|
|
3808
|
-
if (stdoutBytes + stderrBytes > maxOutputBytes) {
|
|
3809
|
-
failAndKill(`${formatShellCommand(executable, args)} exceeded output limit of ${maxOutputBytes} bytes`);
|
|
3810
|
-
return;
|
|
3811
|
-
}
|
|
3812
|
-
stdoutChunks.push(text);
|
|
3813
|
-
});
|
|
3814
|
-
child.stderr.on("data", (chunk) => {
|
|
3815
|
-
if (failureResult) {
|
|
3816
|
-
return;
|
|
3817
|
-
}
|
|
3818
|
-
const text = String(chunk);
|
|
3819
|
-
stderrBytes += Buffer.byteLength(text);
|
|
3820
|
-
if (stdoutBytes + stderrBytes > maxOutputBytes) {
|
|
3821
|
-
failAndKill(`${formatShellCommand(executable, args)} exceeded output limit of ${maxOutputBytes} bytes`);
|
|
3822
|
-
return;
|
|
3823
|
-
}
|
|
3824
|
-
stderrChunks.push(text);
|
|
3825
|
-
});
|
|
3826
|
-
child.on("error", (err) => {
|
|
3827
|
-
if (finished) {
|
|
3828
|
-
return;
|
|
3829
|
-
}
|
|
3830
|
-
if (killTimer) {
|
|
3831
|
-
clearTimeout(killTimer);
|
|
3832
|
-
}
|
|
3833
|
-
if (options.allowFailure === true) {
|
|
3834
|
-
finish({ exitCode: 127, stderr: errorMessage(err), stdout: "" });
|
|
3835
|
-
} else {
|
|
3836
|
-
finished = true;
|
|
3837
|
-
rejectPromise(err);
|
|
3838
|
-
}
|
|
3839
|
-
});
|
|
3840
|
-
child.on("close", (code) => {
|
|
3841
|
-
const result = failureResult ?? {
|
|
3842
|
-
exitCode: code ?? 1,
|
|
3843
|
-
stderr: stderrChunks.join(""),
|
|
3844
|
-
stdout: stdoutChunks.join("")
|
|
3845
|
-
};
|
|
3846
|
-
finish(result);
|
|
3847
|
-
});
|
|
3848
3858
|
});
|
|
3849
3859
|
}
|
|
3860
|
+
function commandResultFromExecFileCallback(params) {
|
|
3861
|
+
if (params.failureMessage) {
|
|
3862
|
+
return { exitCode: 124, stderr: params.failureMessage, stdout: params.stdout };
|
|
3863
|
+
}
|
|
3864
|
+
if (!params.err) {
|
|
3865
|
+
return { exitCode: 0, stderr: params.stderr, stdout: params.stdout };
|
|
3866
|
+
}
|
|
3867
|
+
const error = params.err;
|
|
3868
|
+
const exitCode = typeof error.code === "number" ? error.code : error.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER" ? 124 : 127;
|
|
3869
|
+
const stderr = error.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER" ? `${formatShellCommand(params.executable, params.args)} exceeded output limit of ${params.maxOutputBytes} bytes` : params.stderr || errorMessage(error);
|
|
3870
|
+
return { exitCode, stderr, stdout: params.stdout };
|
|
3871
|
+
}
|
|
3850
3872
|
function defaultCommandTimeoutMs() {
|
|
3851
3873
|
return positiveIntegerFromEnv("THREADNOTE_COMMAND_TIMEOUT_MS") ?? 10 * 60 * 1e3;
|
|
3852
3874
|
}
|
|
@@ -3887,6 +3909,9 @@ async function resolveRepoName(cwd = getInvocationCwd()) {
|
|
|
3887
3909
|
async function runInteractive(executable, args) {
|
|
3888
3910
|
return new Promise((resolvePromise) => {
|
|
3889
3911
|
const child = (0, import_node_child_process.spawn)(executable, args, { stdio: "inherit" });
|
|
3912
|
+
child.on("error", () => {
|
|
3913
|
+
resolvePromise(1);
|
|
3914
|
+
});
|
|
3890
3915
|
child.on("close", (code) => {
|
|
3891
3916
|
resolvePromise(code ?? 1);
|
|
3892
3917
|
});
|
|
@@ -4241,8 +4266,192 @@ function exactRecallTerms(query) {
|
|
|
4241
4266
|
}
|
|
4242
4267
|
return terms.sort((left, right) => exactRecallTermScore(right) - exactRecallTermScore(left)).slice(0, 4);
|
|
4243
4268
|
}
|
|
4244
|
-
|
|
4245
|
-
|
|
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");
|
|
4246
4455
|
}
|
|
4247
4456
|
function exactRecallTermScore(term) {
|
|
4248
4457
|
let score = term.length;
|
|
@@ -7529,9 +7738,19 @@ var AUTO_REPAIR_STATE_FILE = "index-auto-repair.json";
|
|
|
7529
7738
|
var AUTO_REPAIR_TTL_MS = 6 * 60 * 60 * 1e3;
|
|
7530
7739
|
var MAX_SCAN_DEPTH = 5;
|
|
7531
7740
|
var MAX_REPAIR_TARGETS = 4;
|
|
7532
|
-
var
|
|
7741
|
+
var MAINTENANCE_COLLAPSE_DEPTH = 3;
|
|
7742
|
+
var MAINTENANCE_MAX_REPAIR_TARGETS = 512;
|
|
7743
|
+
var MAINTENANCE_CONSECUTIVE_FAILURE_LIMIT = 5;
|
|
7533
7744
|
async function repairStaleRecallIndex(config, ov, options = {}) {
|
|
7745
|
+
options.onProgress?.({ type: "scan-start" });
|
|
7534
7746
|
const targets = await findStaleRecallIndexTargets(config, options);
|
|
7747
|
+
const repairTargets = targets.slice(0, options.maxTargets ?? MAX_REPAIR_TARGETS);
|
|
7748
|
+
const consecutiveFailureLimit = options.consecutiveFailureLimit;
|
|
7749
|
+
options.onProgress?.({
|
|
7750
|
+
repairTargetCount: repairTargets.length,
|
|
7751
|
+
totalTargets: targets.length,
|
|
7752
|
+
type: "scan-complete"
|
|
7753
|
+
});
|
|
7535
7754
|
if (targets.length === 0) {
|
|
7536
7755
|
return { repairedUris: [], skippedRecentUris: [], warnings: [] };
|
|
7537
7756
|
}
|
|
@@ -7540,26 +7759,41 @@ async function repairStaleRecallIndex(config, ov, options = {}) {
|
|
|
7540
7759
|
const repairedUris = [];
|
|
7541
7760
|
const skippedRecentUris = [];
|
|
7542
7761
|
const warnings = [];
|
|
7543
|
-
|
|
7762
|
+
let consecutiveFailures = 0;
|
|
7763
|
+
for (const [targetIndex, target] of repairTargets.entries()) {
|
|
7764
|
+
const progressBase = { index: targetIndex + 1, target, total: repairTargets.length };
|
|
7544
7765
|
const previous = state.entries[target.uri];
|
|
7545
7766
|
if (previous?.signature === target.signature && now - Date.parse(previous.repairedAt) < AUTO_REPAIR_TTL_MS && options.dryRun !== true && options.ignoreBackoff !== true) {
|
|
7767
|
+
options.onProgress?.({ ...progressBase, type: "repair-skip-recent" });
|
|
7546
7768
|
skippedRecentUris.push(target.uri);
|
|
7547
7769
|
continue;
|
|
7548
7770
|
}
|
|
7549
7771
|
if (options.dryRun === true) {
|
|
7772
|
+
options.onProgress?.({ ...progressBase, type: "repair-dry-run" });
|
|
7550
7773
|
repairedUris.push(target.uri);
|
|
7551
7774
|
continue;
|
|
7552
7775
|
}
|
|
7776
|
+
options.onProgress?.({ ...progressBase, type: "repair-start" });
|
|
7553
7777
|
const result = await runCommand(
|
|
7554
7778
|
ov,
|
|
7555
7779
|
withIdentity(config, ["reindex", target.uri, "--mode", "semantic_and_vectors", "--wait", "true"]),
|
|
7556
7780
|
{ allowFailure: true }
|
|
7557
7781
|
);
|
|
7558
7782
|
if (result.exitCode === 0) {
|
|
7783
|
+
consecutiveFailures = 0;
|
|
7559
7784
|
repairedUris.push(target.uri);
|
|
7560
7785
|
state.entries[target.uri] = { repairedAt: new Date(now).toISOString(), signature: target.signature };
|
|
7561
7786
|
} else {
|
|
7787
|
+
consecutiveFailures += 1;
|
|
7562
7788
|
warnings.push(indexRepairWarning(target.uri, result));
|
|
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
|
+
}
|
|
7563
7797
|
}
|
|
7564
7798
|
}
|
|
7565
7799
|
if (options.dryRun !== true && repairedUris.length > 0) {
|
|
@@ -7568,6 +7802,9 @@ async function repairStaleRecallIndex(config, ov, options = {}) {
|
|
|
7568
7802
|
return { repairedUris, skippedRecentUris, warnings };
|
|
7569
7803
|
}
|
|
7570
7804
|
async function findStaleRecallIndexTargets(config, options = {}) {
|
|
7805
|
+
if (await summaryAutoGenerationDisabled(config)) {
|
|
7806
|
+
return [];
|
|
7807
|
+
}
|
|
7571
7808
|
const roots = await scanRoots(config, options);
|
|
7572
7809
|
const byUri = /* @__PURE__ */ new Map();
|
|
7573
7810
|
for (const root of roots) {
|
|
@@ -7576,16 +7813,14 @@ async function findStaleRecallIndexTargets(config, options = {}) {
|
|
|
7576
7813
|
}
|
|
7577
7814
|
const sidecars = await staleSidecars(root.path, root.uri);
|
|
7578
7815
|
if (options.collapseToRoots === true) {
|
|
7579
|
-
|
|
7580
|
-
|
|
7581
|
-
|
|
7582
|
-
|
|
7816
|
+
for (const sidecar of sidecars) {
|
|
7817
|
+
const uri = collapseStaleSidecarUri(root.uri, sidecar.relativePath, options.collapseDepth);
|
|
7818
|
+
const current = byUri.get(uri) ?? { parts: [], staleCount: 0, uri };
|
|
7819
|
+
current.parts.push(`${sidecar.relativePath}
|
|
7583
7820
|
${sidecar.content}`);
|
|
7584
|
-
|
|
7585
|
-
|
|
7586
|
-
|
|
7587
|
-
uri: root.uri
|
|
7588
|
-
});
|
|
7821
|
+
current.staleCount += 1;
|
|
7822
|
+
byUri.set(uri, current);
|
|
7823
|
+
}
|
|
7589
7824
|
continue;
|
|
7590
7825
|
}
|
|
7591
7826
|
for (const sidecar of sidecars) {
|
|
@@ -7602,24 +7837,34 @@ ${sidecar.content}`);
|
|
|
7602
7837
|
uri: target.uri
|
|
7603
7838
|
}));
|
|
7604
7839
|
}
|
|
7840
|
+
function collapseStaleSidecarUri(rootUri, relativePath, depth = 0) {
|
|
7841
|
+
const normalizedRootUri = trimLocalRootUri(rootUri);
|
|
7842
|
+
if (depth <= 0) {
|
|
7843
|
+
return normalizedRootUri;
|
|
7844
|
+
}
|
|
7845
|
+
const parentParts = relativePath.split("/").slice(0, -1);
|
|
7846
|
+
const collapsedParts = parentParts.slice(0, depth);
|
|
7847
|
+
return collapsedParts.length === 0 ? normalizedRootUri : `${normalizedRootUri}/${collapsedParts.join("/")}`;
|
|
7848
|
+
}
|
|
7605
7849
|
function formatRecallIndexRepairMessages(result, options = {}) {
|
|
7606
7850
|
const messages = [];
|
|
7607
|
-
|
|
7851
|
+
const maxUris = options.maxUris ?? result.repairedUris.length;
|
|
7852
|
+
for (const uri of result.repairedUris.slice(0, maxUris)) {
|
|
7608
7853
|
messages.push(
|
|
7609
7854
|
`${options.dryRun === true ? "Would auto-reindex stale recall scope" : "Auto-reindexed stale recall scope"}: ${uri}`
|
|
7610
7855
|
);
|
|
7611
7856
|
}
|
|
7857
|
+
const hiddenUriCount = result.repairedUris.length - maxUris;
|
|
7858
|
+
if (hiddenUriCount > 0) {
|
|
7859
|
+
messages.push(
|
|
7860
|
+
options.dryRun === true ? `Would auto-reindex ${hiddenUriCount} more stale recall scope(s).` : `Auto-reindexed ${hiddenUriCount} more stale recall scope(s).`
|
|
7861
|
+
);
|
|
7862
|
+
}
|
|
7612
7863
|
for (const warning2 of result.warnings) {
|
|
7613
7864
|
messages.push(`Auto-index repair warning: ${warning2}`);
|
|
7614
7865
|
}
|
|
7615
7866
|
return messages;
|
|
7616
7867
|
}
|
|
7617
|
-
function filterStaleRecallSummaryRows(output2) {
|
|
7618
|
-
return output2.split(/\r?\n/).filter((line) => !isStaleRecallSummaryRow(line)).join("\n").trim();
|
|
7619
|
-
}
|
|
7620
|
-
function isStaleRecallSummaryRow(line) {
|
|
7621
|
-
return /viking:\/\/\S+\/\.(?:abstract|overview)\.md\b/.test(line) && isStaleSummary(line);
|
|
7622
|
-
}
|
|
7623
7868
|
async function scanRoots(config, options) {
|
|
7624
7869
|
const accountRoot = (0, import_node_path4.join)(config.agentContextHome, "data", "viking", config.account);
|
|
7625
7870
|
const roots = [
|
|
@@ -7708,6 +7953,18 @@ async function staleSidecars(rootPath, rootUri) {
|
|
|
7708
7953
|
function isSummarySidecar(name) {
|
|
7709
7954
|
return name === ".abstract.md" || name === ".overview.md";
|
|
7710
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
|
+
}
|
|
7711
7968
|
function isStaleSummary(content) {
|
|
7712
7969
|
return content.includes("[Directory overview is not ready]") || content.includes("[Directory abstract is not ready]");
|
|
7713
7970
|
}
|
|
@@ -9686,71 +9943,77 @@ async function runRecall(config, options) {
|
|
|
9686
9943
|
for (const message of indexRepairMessages) {
|
|
9687
9944
|
console.log(message);
|
|
9688
9945
|
}
|
|
9946
|
+
const dryRun = options.dryRun === true;
|
|
9689
9947
|
const inferredUri = options.uri ?? (options.inferScope === false ? void 0 : await inferRecallUri(config, projectQuery));
|
|
9690
|
-
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
|
+
];
|
|
9691
9960
|
if (inferredUri) {
|
|
9692
|
-
args.push("--uri", inferredUri);
|
|
9693
9961
|
console.log(`Recall scope: ${inferredUri}`);
|
|
9694
9962
|
}
|
|
9695
|
-
|
|
9696
|
-
|
|
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 }));
|
|
9697
9967
|
}
|
|
9698
|
-
const
|
|
9699
|
-
|
|
9700
|
-
|
|
9701
|
-
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 }));
|
|
9702
9971
|
}
|
|
9703
|
-
const
|
|
9704
|
-
|
|
9705
|
-
|
|
9706
|
-
|
|
9707
|
-
|
|
9708
|
-
|
|
9709
|
-
);
|
|
9710
|
-
if (seededOutput) {
|
|
9711
|
-
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);
|
|
9712
9978
|
}
|
|
9713
9979
|
const exactOutput = await printExactMemoryMatches(config, ov, query, {
|
|
9714
|
-
dryRun
|
|
9715
|
-
includeArchived: options.includeArchived === true
|
|
9980
|
+
dryRun,
|
|
9981
|
+
includeArchived: options.includeArchived === true,
|
|
9982
|
+
project
|
|
9716
9983
|
});
|
|
9717
9984
|
if (exactOutput) {
|
|
9718
9985
|
recallOutputs.push(exactOutput);
|
|
9719
9986
|
}
|
|
9720
9987
|
await printRecallHygieneNudges(config, recallOutputs.join("\n"));
|
|
9721
9988
|
}
|
|
9722
|
-
async function
|
|
9723
|
-
|
|
9724
|
-
|
|
9725
|
-
|
|
9726
|
-
|
|
9727
|
-
if (!project) {
|
|
9728
|
-
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 [];
|
|
9729
9994
|
}
|
|
9730
|
-
|
|
9731
|
-
if (
|
|
9732
|
-
|
|
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
|
+
});
|
|
9733
10000
|
}
|
|
9734
|
-
|
|
9735
|
-
|
|
9736
|
-
|
|
10001
|
+
if (result.exitCode !== 0) {
|
|
10002
|
+
console.log(`WARN recall search failed: ${result.stderr.trim() || result.stdout.trim() || "ov search error"}`);
|
|
10003
|
+
return [];
|
|
9737
10004
|
}
|
|
9738
|
-
|
|
9739
|
-
Also searching seeded resources: ${projectResourceUri}`);
|
|
9740
|
-
return runRecallSearch(config, ov, args, { dryRun: options.dryRun === true });
|
|
10005
|
+
return parseRecallHits(result.stdout);
|
|
9741
10006
|
}
|
|
9742
|
-
|
|
9743
|
-
const
|
|
9744
|
-
|
|
9745
|
-
|
|
9746
|
-
|
|
9747
|
-
|
|
9748
|
-
|
|
9749
|
-
|
|
9750
|
-
if (output2) {
|
|
9751
|
-
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]);
|
|
9752
10015
|
}
|
|
9753
|
-
return
|
|
10016
|
+
return stripped;
|
|
9754
10017
|
}
|
|
9755
10018
|
async function runRead(config, uri, options) {
|
|
9756
10019
|
assertVikingUri(uri);
|
|
@@ -10088,29 +10351,25 @@ async function printExactMemoryMatches(config, ov, query, options) {
|
|
|
10088
10351
|
if (terms.length === 0) {
|
|
10089
10352
|
return void 0;
|
|
10090
10353
|
}
|
|
10091
|
-
const scopes = exactMemoryScopes(config, options.includeArchived);
|
|
10092
|
-
const
|
|
10093
|
-
|
|
10094
|
-
|
|
10095
|
-
|
|
10096
|
-
|
|
10097
|
-
|
|
10098
|
-
|
|
10099
|
-
|
|
10100
|
-
|
|
10101
|
-
|
|
10102
|
-
|
|
10103
|
-
|
|
10104
|
-
|
|
10105
|
-
}
|
|
10106
|
-
}
|
|
10107
|
-
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) {
|
|
10108
10368
|
return void 0;
|
|
10109
10369
|
}
|
|
10110
|
-
console.log(
|
|
10111
|
-
|
|
10112
|
-
|
|
10113
|
-
return output2;
|
|
10370
|
+
console.log(`
|
|
10371
|
+
${text}`);
|
|
10372
|
+
return text;
|
|
10114
10373
|
}
|
|
10115
10374
|
async function storeMemory(config, options) {
|
|
10116
10375
|
if (options.replaceUri) {
|
|
@@ -10309,21 +10568,15 @@ async function legacyLifecycleHandoffCandidates(config, limit) {
|
|
|
10309
10568
|
function lifecycleMigrationUri(config, metadata, hash) {
|
|
10310
10569
|
return `${memoryDirectoryUri(config, metadata)}/legacy-${hash.slice(0, 16)}.md`;
|
|
10311
10570
|
}
|
|
10312
|
-
function exactMemoryScopes(config, includeArchived) {
|
|
10313
|
-
|
|
10314
|
-
|
|
10315
|
-
|
|
10316
|
-
|
|
10317
|
-
|
|
10318
|
-
|
|
10319
|
-
|
|
10320
|
-
|
|
10321
|
-
// Seeded project resources (READMEs, AGENTS.md, SKILL.md, docs/**) live
|
|
10322
|
-
// under viking://resources/repos/<project>. Include them so an exact-term
|
|
10323
|
-
// grep in a recall surfaces matches in seeded guidance, not only memories.
|
|
10324
|
-
"viking://resources/repos"
|
|
10325
|
-
];
|
|
10326
|
-
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
|
+
});
|
|
10327
10580
|
}
|
|
10328
10581
|
function memoryUriFor(config, memory, metadata) {
|
|
10329
10582
|
const filename = shouldUseStableMemoryUri(metadata) ? `${uriSegment(metadata.topic ?? "current")}.md` : `threadnote-${safeTimestamp()}-${sha256(memory).slice(0, 12)}.md`;
|
|
@@ -11615,14 +11868,16 @@ async function runUpdate(config, options) {
|
|
|
11615
11868
|
}
|
|
11616
11869
|
const runtime = await resolveUpdateRuntime(options.runtime ?? "auto");
|
|
11617
11870
|
const updateCommand = updatePackageCommand(runtime, registry);
|
|
11618
|
-
await
|
|
11871
|
+
await runStreamingSubcommand(options.dryRun === true, updateCommand.executable, updateCommand.args);
|
|
11619
11872
|
if (options.repair === false) {
|
|
11620
11873
|
console.log("Skipping repair because --no-repair was provided.");
|
|
11621
11874
|
await printWhatsNewIfAvailable(info2);
|
|
11622
11875
|
return;
|
|
11623
11876
|
}
|
|
11624
11877
|
const threadnoteCommand = await installedThreadnoteCommand(runtime);
|
|
11625
|
-
|
|
11878
|
+
console.log("");
|
|
11879
|
+
console.log("Repairing local Threadnote setup after package update.");
|
|
11880
|
+
await runStreamingSubcommand(options.dryRun === true, threadnoteCommand, ["repair", "--no-post-update"]);
|
|
11626
11881
|
if (options.postUpdate !== false) {
|
|
11627
11882
|
const postUpdateArgs = [
|
|
11628
11883
|
"post-update",
|
|
@@ -11632,15 +11887,7 @@ async function runUpdate(config, options) {
|
|
|
11632
11887
|
info2.latestVersion,
|
|
11633
11888
|
...options.yes === true ? ["--yes"] : []
|
|
11634
11889
|
];
|
|
11635
|
-
|
|
11636
|
-
await maybeRun(true, threadnoteCommand, postUpdateArgs);
|
|
11637
|
-
} else {
|
|
11638
|
-
console.log(`Running: ${formatShellCommand(threadnoteCommand, postUpdateArgs)}`);
|
|
11639
|
-
const postUpdateExitCode = await runInteractive(threadnoteCommand, postUpdateArgs);
|
|
11640
|
-
if (postUpdateExitCode !== 0) {
|
|
11641
|
-
throw new Error(`${formatShellCommand(threadnoteCommand, postUpdateArgs)} exited with ${postUpdateExitCode}.`);
|
|
11642
|
-
}
|
|
11643
|
-
}
|
|
11890
|
+
await runStreamingSubcommand(options.dryRun === true, threadnoteCommand, postUpdateArgs);
|
|
11644
11891
|
} else {
|
|
11645
11892
|
console.log("Skipping post-update migration prompts because --no-post-update was provided.");
|
|
11646
11893
|
}
|
|
@@ -11728,7 +11975,7 @@ async function ensurePinnedOpenVikingInstalled(config, options) {
|
|
|
11728
11975
|
const wasRunning = await isOpenVikingHealthy(config);
|
|
11729
11976
|
const usingLaunchd = await isLaunchAgentInstalled();
|
|
11730
11977
|
const threadnoteCommand = currentThreadnoteCommand() ?? await findExecutable([NPM_PACKAGE_NAME]) ?? NPM_PACKAGE_NAME;
|
|
11731
|
-
await
|
|
11978
|
+
await runStreamingSubcommand(options.dryRun, threadnoteCommand, ["install", "--force", "--no-start"]);
|
|
11732
11979
|
if (options.dryRun) {
|
|
11733
11980
|
if (wasRunning || usingLaunchd) {
|
|
11734
11981
|
console.log("Would restart OpenViking server so the new binary takes effect.");
|
|
@@ -11742,10 +11989,12 @@ async function ensurePinnedOpenVikingInstalled(config, options) {
|
|
|
11742
11989
|
if (usingLaunchd) {
|
|
11743
11990
|
const launchAgentPath = launchAgentPlistPath();
|
|
11744
11991
|
await runCommand("launchctl", ["unload", launchAgentPath], { allowFailure: true });
|
|
11992
|
+
await waitForOpenVikingPortClosed(config, 15e3);
|
|
11745
11993
|
await runCommand("launchctl", ["load", launchAgentPath], { allowFailure: true });
|
|
11746
11994
|
} else {
|
|
11747
|
-
await
|
|
11748
|
-
await
|
|
11995
|
+
await runStreamingSubcommand(false, threadnoteCommand, ["stop"]);
|
|
11996
|
+
await waitForOpenVikingPortClosed(config, 15e3);
|
|
11997
|
+
await runStreamingSubcommand(false, threadnoteCommand, ["start"]);
|
|
11749
11998
|
}
|
|
11750
11999
|
const healthyAfter = await waitForOpenVikingHealthy(config, 1e4);
|
|
11751
12000
|
if (!healthyAfter) {
|
|
@@ -11755,6 +12004,17 @@ async function ensurePinnedOpenVikingInstalled(config, options) {
|
|
|
11755
12004
|
console.log("Check the server log or run: threadnote start");
|
|
11756
12005
|
}
|
|
11757
12006
|
}
|
|
12007
|
+
async function runStreamingSubcommand(dryRun, executable, args) {
|
|
12008
|
+
if (dryRun) {
|
|
12009
|
+
await maybeRun(true, executable, args);
|
|
12010
|
+
return;
|
|
12011
|
+
}
|
|
12012
|
+
console.log(`Running: ${formatShellCommand(executable, args)}`);
|
|
12013
|
+
const exitCode = await runInteractive(executable, args);
|
|
12014
|
+
if (exitCode !== 0) {
|
|
12015
|
+
throw new Error(`${formatShellCommand(executable, args)} exited with ${exitCode}.`);
|
|
12016
|
+
}
|
|
12017
|
+
}
|
|
11758
12018
|
function openVikingHealthEndpoint(config) {
|
|
11759
12019
|
return `http://${config.host}:${config.port}/health`;
|
|
11760
12020
|
}
|
|
@@ -11778,12 +12038,27 @@ async function waitForOpenVikingHealthy(config, timeoutMs) {
|
|
|
11778
12038
|
if (await isOpenVikingHealthy(config)) {
|
|
11779
12039
|
return true;
|
|
11780
12040
|
}
|
|
11781
|
-
await
|
|
11782
|
-
setTimeout(resolvePromise, 500);
|
|
11783
|
-
});
|
|
12041
|
+
await sleep(500);
|
|
11784
12042
|
}
|
|
11785
12043
|
return isOpenVikingHealthy(config);
|
|
11786
12044
|
}
|
|
12045
|
+
async function waitForOpenVikingPortClosed(config, timeoutMs) {
|
|
12046
|
+
console.log(`Waiting for OpenViking port ${config.host}:${config.port} to close before restart.`);
|
|
12047
|
+
const deadline = Date.now() + timeoutMs;
|
|
12048
|
+
while (Date.now() < deadline) {
|
|
12049
|
+
if (!await isTcpPortOpen(config.host, config.port, 300)) {
|
|
12050
|
+
return true;
|
|
12051
|
+
}
|
|
12052
|
+
await sleep(300);
|
|
12053
|
+
}
|
|
12054
|
+
if (!await isTcpPortOpen(config.host, config.port, 300)) {
|
|
12055
|
+
return true;
|
|
12056
|
+
}
|
|
12057
|
+
console.log(
|
|
12058
|
+
`Warning: OpenViking port ${config.host}:${config.port} is still in use after ${timeoutMs / 1e3}s; start may fail.`
|
|
12059
|
+
);
|
|
12060
|
+
return false;
|
|
12061
|
+
}
|
|
11787
12062
|
function launchAgentPlistPath() {
|
|
11788
12063
|
return (0, import_node_path12.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents", "io.threadnote.openviking.plist");
|
|
11789
12064
|
}
|
|
@@ -11900,7 +12175,7 @@ async function runApplicablePostUpdateMigrations(config, options) {
|
|
|
11900
12175
|
console.log(` ${formatMigrationCommand(threadnoteCommand, migration.commandArgs)}`);
|
|
11901
12176
|
continue;
|
|
11902
12177
|
}
|
|
11903
|
-
await
|
|
12178
|
+
await runStreamingSubcommand(options.dryRun, threadnoteCommand, migration.commandArgs);
|
|
11904
12179
|
if (!options.dryRun) {
|
|
11905
12180
|
handledMigrationIds.add(migration.id);
|
|
11906
12181
|
for (const instruction of migration.instructions) {
|
|
@@ -12314,16 +12589,41 @@ async function repairRecallIndex(config, dryRun) {
|
|
|
12314
12589
|
console.log("Skipping recall index repair: neither ov nor openviking was found in PATH.");
|
|
12315
12590
|
return;
|
|
12316
12591
|
}
|
|
12592
|
+
const progress = startProgress("Scanning recall index freshness across memories and seeded resources.");
|
|
12317
12593
|
try {
|
|
12318
12594
|
const result = await repairStaleRecallIndex(config, ov, {
|
|
12595
|
+
collapseDepth: MAINTENANCE_COLLAPSE_DEPTH,
|
|
12319
12596
|
collapseToRoots: true,
|
|
12597
|
+
consecutiveFailureLimit: MAINTENANCE_CONSECUTIVE_FAILURE_LIMIT,
|
|
12320
12598
|
dryRun,
|
|
12321
12599
|
ignoreBackoff: true,
|
|
12322
12600
|
includeAgentSkills: true,
|
|
12323
12601
|
includeManifestResources: true,
|
|
12324
|
-
maxTargets: MAINTENANCE_MAX_REPAIR_TARGETS
|
|
12602
|
+
maxTargets: MAINTENANCE_MAX_REPAIR_TARGETS,
|
|
12603
|
+
onProgress: (event) => {
|
|
12604
|
+
if (event.type === "scan-complete") {
|
|
12605
|
+
if (event.totalTargets === 0) {
|
|
12606
|
+
progress.update("No stale recall index scopes found.");
|
|
12607
|
+
} else {
|
|
12608
|
+
progress.update(
|
|
12609
|
+
`Found ${event.totalTargets} stale recall index scope(s); repairing ${event.repairTargetCount}.`
|
|
12610
|
+
);
|
|
12611
|
+
}
|
|
12612
|
+
} else if (event.type === "repair-start") {
|
|
12613
|
+
progress.update(
|
|
12614
|
+
`Reindexing ${event.index}/${event.total}: ${event.target.uri} (${event.target.staleCount} stale summaries).`
|
|
12615
|
+
);
|
|
12616
|
+
} else if (event.type === "repair-dry-run") {
|
|
12617
|
+
progress.update(
|
|
12618
|
+
`Planning reindex ${event.index}/${event.total}: ${event.target.uri} (${event.target.staleCount} stale summaries).`
|
|
12619
|
+
);
|
|
12620
|
+
} else if (event.type === "repair-skip-recent") {
|
|
12621
|
+
progress.update(`Skipping recently repaired scope ${event.index}/${event.total}: ${event.target.uri}.`);
|
|
12622
|
+
}
|
|
12623
|
+
}
|
|
12325
12624
|
});
|
|
12326
|
-
|
|
12625
|
+
progress.stop();
|
|
12626
|
+
const messages = formatRecallIndexRepairMessages(result, { dryRun, maxUris: 20 });
|
|
12327
12627
|
if (messages.length === 0) {
|
|
12328
12628
|
console.log("Recall index freshness OK.");
|
|
12329
12629
|
return;
|
|
@@ -12332,6 +12632,7 @@ async function repairRecallIndex(config, dryRun) {
|
|
|
12332
12632
|
console.log(message);
|
|
12333
12633
|
}
|
|
12334
12634
|
} catch (err) {
|
|
12635
|
+
progress.stop();
|
|
12335
12636
|
console.log(`WARN could not repair recall index freshness: ${errorMessage(err)}`);
|
|
12336
12637
|
}
|
|
12337
12638
|
}
|
|
@@ -12687,6 +12988,13 @@ async function manifestCheck(path) {
|
|
|
12687
12988
|
}
|
|
12688
12989
|
async function recallIndexFreshnessCheck(config) {
|
|
12689
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
|
+
}
|
|
12690
12998
|
const targets = await findStaleRecallIndexTargets(config, {
|
|
12691
12999
|
collapseToRoots: true,
|
|
12692
13000
|
includeAgentSkills: true,
|
|
@@ -13522,7 +13830,8 @@ async function handleRequest(context, request, response) {
|
|
|
13522
13830
|
await runCaptured(
|
|
13523
13831
|
() => runRecall(context.config, {
|
|
13524
13832
|
query: requireString(body.query, "query"),
|
|
13525
|
-
nodeLimit: optionalString(body.nodeLimit)
|
|
13833
|
+
nodeLimit: optionalString(body.nodeLimit),
|
|
13834
|
+
project: optionalString(body.project)
|
|
13526
13835
|
})
|
|
13527
13836
|
)
|
|
13528
13837
|
);
|
|
@@ -14381,7 +14690,7 @@ async function main() {
|
|
|
14381
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) => {
|
|
14382
14691
|
await runMigrateLifecycle(getRuntimeConfig(program2), options);
|
|
14383
14692
|
});
|
|
14384
|
-
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) => {
|
|
14385
14694
|
await runRecall(getRuntimeConfig(program2), options);
|
|
14386
14695
|
});
|
|
14387
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) => {
|