threadnote 1.4.3 → 1.4.5
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/config/post-update-migrations.json +0 -15
- package/dist/threadnote.cjs +186 -244
- package/docs/index.html +2 -2
- package/docs/troubleshooting.md +9 -11
- package/package.json +1 -1
|
@@ -18,21 +18,6 @@
|
|
|
18
18
|
"If Threadnote reported any original files were still being processed, rerun the printed threadnote forget <uri> command later."
|
|
19
19
|
],
|
|
20
20
|
"requiresLegacyHandoffs": true
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
"id": "ov-semantic-poison-hotfix-2734",
|
|
24
|
-
"introducedIn": "1.4.3",
|
|
25
|
-
"title": "Patch OpenViking against the semantic-queue poison loop (#2734)",
|
|
26
|
-
"description": [
|
|
27
|
-
"OpenViking 0.4.x can deadlock its semantic queue: a memory file reindexed with mode=semantic_and_vectors enqueues a directory-level semantic message whose URI is a file; the processor lists it, fails, and the message re-enqueues forever (it is AGFS-persisted, so it survives a server restart).",
|
|
28
|
-
"This patches the installed OpenViking to skip non-directory/missing memory URIs (upstream fix PR #2735) and restarts the server so any stuck message drains on the next dequeue.",
|
|
29
|
-
"It is a no-op when the installed OpenViking already includes the fix, a backup of the original file is kept, and the patched file is compile-checked before it is written."
|
|
30
|
-
],
|
|
31
|
-
"commandArgs": ["repair-semantic-queue", "--apply"],
|
|
32
|
-
"instructions": [
|
|
33
|
-
"If the semantic queue was stuck it should now drain. Verify with: ov observer queue",
|
|
34
|
-
"This is a temporary local patch; it is superseded automatically once Threadnote pins an OpenViking release that includes the fix. Re-run manually any time with: threadnote repair-semantic-queue --apply"
|
|
35
|
-
]
|
|
36
21
|
}
|
|
37
22
|
]
|
|
38
23
|
}
|
package/dist/threadnote.cjs
CHANGED
|
@@ -3394,7 +3394,7 @@ var program = new Command();
|
|
|
3394
3394
|
// src/constants.ts
|
|
3395
3395
|
var DEFAULT_HOST = "127.0.0.1";
|
|
3396
3396
|
var DEFAULT_PORT = 1933;
|
|
3397
|
-
var DEFAULT_OPENVIKING_VERSION = "0.4.
|
|
3397
|
+
var DEFAULT_OPENVIKING_VERSION = "0.4.5";
|
|
3398
3398
|
var OPENVIKING_TOOL_PYTHON = "3.12";
|
|
3399
3399
|
var DEFAULT_ACCOUNT = "local";
|
|
3400
3400
|
var DEFAULT_AGENT_ID = "threadnote";
|
|
@@ -13703,6 +13703,7 @@ function isUpdateNotificationDisabled() {
|
|
|
13703
13703
|
}
|
|
13704
13704
|
|
|
13705
13705
|
// src/lifecycle.ts
|
|
13706
|
+
var INSTALL_OUTPUT_TAIL_CHARS = 64e3;
|
|
13706
13707
|
async function runDoctor(config, options) {
|
|
13707
13708
|
const checks = await collectDoctorChecks(config, options);
|
|
13708
13709
|
for (const check of checks) {
|
|
@@ -14487,32 +14488,169 @@ async function runInstallCommands(config, preferred, force, dryRun) {
|
|
|
14487
14488
|
continue;
|
|
14488
14489
|
}
|
|
14489
14490
|
console.log(`Running: ${formatShellCommand(installCommand.executable, installCommand.args)}`);
|
|
14490
|
-
const
|
|
14491
|
-
if (exitCode !== 0) {
|
|
14492
|
-
|
|
14493
|
-
|
|
14491
|
+
const result = await runInstallCommand(installCommand);
|
|
14492
|
+
if (result.exitCode !== 0) {
|
|
14493
|
+
const commandOutput = `${result.stderr}
|
|
14494
|
+
${result.stdout}`;
|
|
14495
|
+
const retry = openVikingSourceBuildRetryForArchiveFailure(installCommand, commandOutput);
|
|
14496
|
+
if (retry) {
|
|
14497
|
+
console.error("");
|
|
14498
|
+
console.error(
|
|
14499
|
+
"The prebuilt llama-cpp-python wheel failed ZIP archive validation; retrying with a local source build."
|
|
14500
|
+
);
|
|
14501
|
+
console.error("This avoids the rejected wheel and can take 10-20 minutes.");
|
|
14502
|
+
console.log(`Running: ${formatInstallCommand(retry.command, retry.env)}`);
|
|
14503
|
+
const retryResult = await runInstallCommand(retry.command, retry.env);
|
|
14504
|
+
if (retryResult.exitCode === 0) {
|
|
14505
|
+
continue;
|
|
14506
|
+
}
|
|
14507
|
+
printOpenVikingInstallFailureHelp(retry.command, `${retryResult.stderr}
|
|
14508
|
+
${retryResult.stdout}`);
|
|
14509
|
+
throw new Error(
|
|
14510
|
+
`${formatInstallCommand(retry.command, retry.env)} exited with ${retryResult.exitCode} after automatic source-build retry.`
|
|
14511
|
+
);
|
|
14512
|
+
}
|
|
14513
|
+
printOpenVikingInstallFailureHelp(installCommand, commandOutput);
|
|
14514
|
+
throw new Error(
|
|
14515
|
+
`${formatShellCommand(installCommand.executable, installCommand.args)} exited with ${result.exitCode}.`
|
|
14516
|
+
);
|
|
14494
14517
|
}
|
|
14495
14518
|
}
|
|
14496
14519
|
}
|
|
14497
|
-
function
|
|
14498
|
-
|
|
14499
|
-
|
|
14500
|
-
|
|
14501
|
-
|
|
14502
|
-
|
|
14503
|
-
|
|
14504
|
-
|
|
14505
|
-
|
|
14520
|
+
async function runInstallCommand(command2, env = {}) {
|
|
14521
|
+
return new Promise((resolvePromise) => {
|
|
14522
|
+
let stdout2 = "";
|
|
14523
|
+
let stderr = "";
|
|
14524
|
+
let settled = false;
|
|
14525
|
+
const child = (0, import_node_child_process4.spawn)(command2.executable, command2.args, {
|
|
14526
|
+
env: Object.keys(env).length === 0 ? process.env : { ...process.env, ...env },
|
|
14527
|
+
stdio: ["inherit", "pipe", "pipe"]
|
|
14528
|
+
});
|
|
14529
|
+
child.stdout?.on("data", (chunk) => {
|
|
14530
|
+
const text = String(chunk);
|
|
14531
|
+
import_node_process4.stdout.write(text);
|
|
14532
|
+
stdout2 = appendInstallOutputTail(stdout2, text);
|
|
14533
|
+
});
|
|
14534
|
+
child.stderr?.on("data", (chunk) => {
|
|
14535
|
+
const text = String(chunk);
|
|
14536
|
+
import_node_process4.stderr.write(text);
|
|
14537
|
+
stderr = appendInstallOutputTail(stderr, text);
|
|
14538
|
+
});
|
|
14539
|
+
child.on("error", (err) => {
|
|
14540
|
+
if (settled) {
|
|
14541
|
+
return;
|
|
14542
|
+
}
|
|
14543
|
+
settled = true;
|
|
14544
|
+
const message = errorMessage(err);
|
|
14545
|
+
import_node_process4.stderr.write(`${message}
|
|
14546
|
+
`);
|
|
14547
|
+
resolvePromise({ exitCode: 1, stderr: appendInstallOutputTail(stderr, message), stdout: stdout2 });
|
|
14548
|
+
});
|
|
14549
|
+
child.on("close", (code) => {
|
|
14550
|
+
if (settled) {
|
|
14551
|
+
return;
|
|
14552
|
+
}
|
|
14553
|
+
settled = true;
|
|
14554
|
+
resolvePromise({ exitCode: code ?? 1, stderr, stdout: stdout2 });
|
|
14555
|
+
});
|
|
14556
|
+
});
|
|
14557
|
+
}
|
|
14558
|
+
function appendInstallOutputTail(current, chunk) {
|
|
14559
|
+
const next = `${current}${chunk}`;
|
|
14560
|
+
return next.length <= INSTALL_OUTPUT_TAIL_CHARS ? next : next.slice(next.length - INSTALL_OUTPUT_TAIL_CHARS);
|
|
14561
|
+
}
|
|
14562
|
+
function printOpenVikingInstallFailureHelp(failedCommand, commandOutput) {
|
|
14563
|
+
for (const line of openVikingInstallFailureHelpLines(failedCommand, commandOutput)) {
|
|
14564
|
+
console.error(line);
|
|
14565
|
+
}
|
|
14566
|
+
}
|
|
14567
|
+
function openVikingInstallFailureHelpLines(failedCommand, commandOutput = "") {
|
|
14568
|
+
if (isLlamaWheelArchiveExtractionFailure(commandOutput)) {
|
|
14569
|
+
return [
|
|
14570
|
+
"",
|
|
14571
|
+
"OpenViking install did not complete.",
|
|
14572
|
+
"The prebuilt llama-cpp-python wheel failed ZIP archive validation. Threadnote only falls back to a source build automatically when the failed command came from a prebuilt wheel index."
|
|
14573
|
+
];
|
|
14574
|
+
}
|
|
14575
|
+
const lines = [
|
|
14576
|
+
"",
|
|
14577
|
+
"OpenViking install did not complete.",
|
|
14578
|
+
"openviking[local-embed] includes llama-cpp-python, which compiles from source when no prebuilt wheel matches your Python/platform \u2014 that build can run 10-20 minutes and is memory-heavy, so it may be killed by the OS (out of memory) or look stuck.",
|
|
14579
|
+
"The package-manager output above contains the underlying build or download error."
|
|
14580
|
+
];
|
|
14506
14581
|
if (failedCommand.executable === "uv") {
|
|
14507
14582
|
if (failedCommand.args.includes("--python")) {
|
|
14508
|
-
|
|
14509
|
-
"If uv could not fetch
|
|
14583
|
+
lines.push(
|
|
14584
|
+
"If uv could not fetch managed CPython, Threadnote cannot complete the local install until that Python download is available."
|
|
14510
14585
|
);
|
|
14511
14586
|
}
|
|
14512
|
-
console.error("If it was killed mid-build, clear the partial install first, then retry:");
|
|
14513
|
-
console.error(" uv cache clean");
|
|
14514
|
-
console.error(' rm -rf "$(uv tool dir)/openviking"');
|
|
14515
14587
|
}
|
|
14588
|
+
return lines;
|
|
14589
|
+
}
|
|
14590
|
+
function isLlamaWheelArchiveExtractionFailure(output2) {
|
|
14591
|
+
const normalized = output2.toLowerCase();
|
|
14592
|
+
return (normalized.includes("llama-cpp-python") || normalized.includes("llama_cpp_python")) && (normalized.includes("failed to extract archive") || normalized.includes("zip file contains trailing contents after the end-of-central-directory record"));
|
|
14593
|
+
}
|
|
14594
|
+
function openVikingSourceBuildRetryForArchiveFailure(failedCommand, commandOutput) {
|
|
14595
|
+
if (!isLlamaWheelArchiveExtractionFailure(commandOutput)) {
|
|
14596
|
+
return void 0;
|
|
14597
|
+
}
|
|
14598
|
+
const command2 = withoutExtraIndexUrl(failedCommand);
|
|
14599
|
+
if (!command2) {
|
|
14600
|
+
return void 0;
|
|
14601
|
+
}
|
|
14602
|
+
return { command: command2, env: sourceBuildEnvironment(failedCommand) };
|
|
14603
|
+
}
|
|
14604
|
+
function withoutExtraIndexUrl(command2) {
|
|
14605
|
+
const args = [];
|
|
14606
|
+
let changed = false;
|
|
14607
|
+
for (let index = 0; index < command2.args.length; index += 1) {
|
|
14608
|
+
const arg = command2.args[index];
|
|
14609
|
+
const next = command2.args[index + 1];
|
|
14610
|
+
if (arg === "--extra-index-url" && next !== void 0) {
|
|
14611
|
+
changed = true;
|
|
14612
|
+
index += 1;
|
|
14613
|
+
continue;
|
|
14614
|
+
}
|
|
14615
|
+
if (arg === "--pip-args" && next?.includes("--extra-index-url") === true) {
|
|
14616
|
+
changed = true;
|
|
14617
|
+
index += 1;
|
|
14618
|
+
continue;
|
|
14619
|
+
}
|
|
14620
|
+
args.push(arg);
|
|
14621
|
+
}
|
|
14622
|
+
return changed ? { ...command2, args } : void 0;
|
|
14623
|
+
}
|
|
14624
|
+
function formatInstallCommand(command2, env = {}) {
|
|
14625
|
+
return [...formatEnvironmentAssignments(env), formatShellCommand(command2.executable, command2.args)].join(" ");
|
|
14626
|
+
}
|
|
14627
|
+
function formatEnvironmentAssignments(env) {
|
|
14628
|
+
return Object.entries(env).map(
|
|
14629
|
+
([key, value]) => key === "CMAKE_ARGS" ? `${key}="${value.replaceAll('"', '\\"')}"` : `${key}=${shellQuote(value)}`
|
|
14630
|
+
);
|
|
14631
|
+
}
|
|
14632
|
+
function sourceBuildEnvironment(command2) {
|
|
14633
|
+
const env = {};
|
|
14634
|
+
if (extraIndexUrl(command2)?.replace(/\/+$/, "").toLowerCase().endsWith("/metal") === true) {
|
|
14635
|
+
env.CMAKE_ARGS = "-DGGML_METAL=on";
|
|
14636
|
+
}
|
|
14637
|
+
env.CMAKE_BUILD_PARALLEL_LEVEL = "2";
|
|
14638
|
+
return env;
|
|
14639
|
+
}
|
|
14640
|
+
function extraIndexUrl(command2) {
|
|
14641
|
+
for (let index = 0; index < command2.args.length; index += 1) {
|
|
14642
|
+
const arg = command2.args[index];
|
|
14643
|
+
if (arg === "--extra-index-url") {
|
|
14644
|
+
return command2.args[index + 1];
|
|
14645
|
+
}
|
|
14646
|
+
if (arg === "--pip-args") {
|
|
14647
|
+
const match = command2.args[index + 1]?.match(/(?:^|\s)--extra-index-url\s+(\S+)/);
|
|
14648
|
+
if (match) {
|
|
14649
|
+
return match[1];
|
|
14650
|
+
}
|
|
14651
|
+
}
|
|
14652
|
+
}
|
|
14653
|
+
return void 0;
|
|
14516
14654
|
}
|
|
14517
14655
|
async function offerToInstallUv() {
|
|
14518
14656
|
if (import_node_process4.stdin.isTTY !== true || import_node_process4.stdout.isTTY !== true) {
|
|
@@ -15009,199 +15147,6 @@ function parsePackageManager(value) {
|
|
|
15009
15147
|
throw new Error(`Invalid package manager: ${value}`);
|
|
15010
15148
|
}
|
|
15011
15149
|
|
|
15012
|
-
// src/semantic_queue_repair.ts
|
|
15013
|
-
var import_node_fs8 = require("node:fs");
|
|
15014
|
-
var import_promises14 = require("node:fs/promises");
|
|
15015
|
-
var import_node_os7 = require("node:os");
|
|
15016
|
-
var import_node_path15 = require("node:path");
|
|
15017
|
-
var HOTFIX_MARKER = "THREADNOTE-HOTFIX-2734";
|
|
15018
|
-
var ENV_OVERRIDE = "THREADNOTE_OPENVIKING_SEMANTIC_PROCESSOR";
|
|
15019
|
-
function patchSemanticProcessorSource(source) {
|
|
15020
|
-
if (source.includes(HOTFIX_MARKER)) {
|
|
15021
|
-
return { status: "already-fixed" };
|
|
15022
|
-
}
|
|
15023
|
-
const lines = source.split("\n");
|
|
15024
|
-
const defIndex = lines.findIndex((line) => /^\s*async def _process_memory_directory\b/.test(line));
|
|
15025
|
-
if (defIndex < 0) {
|
|
15026
|
-
return { status: "no-anchor" };
|
|
15027
|
-
}
|
|
15028
|
-
const lsPattern = /^(\s+)entries = await viking_fs\.ls\(dir_uri\b/;
|
|
15029
|
-
let lsIndex = -1;
|
|
15030
|
-
let lsIndent = "";
|
|
15031
|
-
for (let i = defIndex + 1; i < lines.length; i += 1) {
|
|
15032
|
-
const match = lsPattern.exec(lines[i]);
|
|
15033
|
-
if (match) {
|
|
15034
|
-
lsIndex = i;
|
|
15035
|
-
lsIndent = match[1];
|
|
15036
|
-
break;
|
|
15037
|
-
}
|
|
15038
|
-
if (/^\s*async def \w/.test(lines[i])) {
|
|
15039
|
-
break;
|
|
15040
|
-
}
|
|
15041
|
-
}
|
|
15042
|
-
if (lsIndex < 0) {
|
|
15043
|
-
return { status: "no-anchor" };
|
|
15044
|
-
}
|
|
15045
|
-
if (lines.slice(defIndex, lsIndex).some((line) => line.includes("viking_fs.stat(dir_uri"))) {
|
|
15046
|
-
return { status: "already-fixed" };
|
|
15047
|
-
}
|
|
15048
|
-
let tryIndex = -1;
|
|
15049
|
-
let base = "";
|
|
15050
|
-
for (let j = lsIndex - 1; j > defIndex; j -= 1) {
|
|
15051
|
-
const text = lines[j];
|
|
15052
|
-
if (text.trim() === "" || text.trimStart().startsWith("#")) {
|
|
15053
|
-
continue;
|
|
15054
|
-
}
|
|
15055
|
-
const match = /^(\s*)try:\s*$/.exec(text);
|
|
15056
|
-
if (match && match[1].length < lsIndent.length) {
|
|
15057
|
-
tryIndex = j;
|
|
15058
|
-
base = match[1];
|
|
15059
|
-
}
|
|
15060
|
-
break;
|
|
15061
|
-
}
|
|
15062
|
-
if (tryIndex < 0) {
|
|
15063
|
-
return { status: "no-anchor" };
|
|
15064
|
-
}
|
|
15065
|
-
const unit = " ".repeat(lsIndent.length - base.length);
|
|
15066
|
-
const at = (level, text) => `${base}${unit.repeat(level)}${text}`;
|
|
15067
|
-
const guard = [
|
|
15068
|
-
at(0, `# ${HOTFIX_MARKER}: skip non-directory / missing memory URIs so a memory file`),
|
|
15069
|
-
at(0, "# reindexed with mode=semantic_and_vectors cannot poison the semantic queue."),
|
|
15070
|
-
at(0, "# Temporary local hot-fix; superseded once OpenViking ships upstream PR #2735."),
|
|
15071
|
-
at(0, "try:"),
|
|
15072
|
-
at(1, "_tn_dir_stat = await viking_fs.stat(dir_uri, ctx=ctx)"),
|
|
15073
|
-
at(0, "except Exception as _tn_err:"),
|
|
15074
|
-
at(1, 'if isinstance(_tn_err, FileNotFoundError) or "not found" in str(_tn_err).lower():'),
|
|
15075
|
-
at(2, "_mark_done()"),
|
|
15076
|
-
at(2, "return"),
|
|
15077
|
-
at(1, "_tn_dir_stat = None"),
|
|
15078
|
-
at(0, 'if _tn_dir_stat is not None and not _tn_dir_stat.get("isDir", _tn_dir_stat.get("is_dir", False)):'),
|
|
15079
|
-
at(1, "_mark_done()"),
|
|
15080
|
-
at(1, "return")
|
|
15081
|
-
];
|
|
15082
|
-
const patched = [...lines.slice(0, tryIndex), ...guard, ...lines.slice(tryIndex)].join("\n");
|
|
15083
|
-
return { status: "patched", source: patched };
|
|
15084
|
-
}
|
|
15085
|
-
async function semanticProcessorCandidates(root) {
|
|
15086
|
-
const lib = (0, import_node_path15.join)(root, "lib");
|
|
15087
|
-
if (!(0, import_node_fs8.existsSync)(lib)) {
|
|
15088
|
-
return [];
|
|
15089
|
-
}
|
|
15090
|
-
let entries;
|
|
15091
|
-
try {
|
|
15092
|
-
entries = await (0, import_promises14.readdir)(lib);
|
|
15093
|
-
} catch {
|
|
15094
|
-
return [];
|
|
15095
|
-
}
|
|
15096
|
-
return entries.filter((name) => name.startsWith("python")).map((name) => (0, import_node_path15.join)(lib, name, "site-packages", "openviking", "storage", "queuefs", "semantic_processor.py"));
|
|
15097
|
-
}
|
|
15098
|
-
async function locateSemanticProcessorPath() {
|
|
15099
|
-
const override = process.env[ENV_OVERRIDE]?.trim();
|
|
15100
|
-
if (override) {
|
|
15101
|
-
const resolved = expandPath(override);
|
|
15102
|
-
if ((0, import_node_fs8.existsSync)(resolved)) {
|
|
15103
|
-
return resolved;
|
|
15104
|
-
}
|
|
15105
|
-
}
|
|
15106
|
-
const roots = [];
|
|
15107
|
-
const server = await findOpenVikingServer();
|
|
15108
|
-
if (server) {
|
|
15109
|
-
roots.push((0, import_node_path15.dirname)((0, import_node_path15.dirname)(server)));
|
|
15110
|
-
}
|
|
15111
|
-
roots.push((0, import_node_path15.join)((0, import_node_os7.homedir)(), ".local", "share", "uv", "tools", "openviking"));
|
|
15112
|
-
roots.push((0, import_node_path15.join)((0, import_node_os7.homedir)(), ".local", "pipx", "venvs", "openviking"));
|
|
15113
|
-
for (const root of roots) {
|
|
15114
|
-
for (const candidate of await semanticProcessorCandidates(root)) {
|
|
15115
|
-
if ((0, import_node_fs8.existsSync)(candidate)) {
|
|
15116
|
-
return candidate;
|
|
15117
|
-
}
|
|
15118
|
-
}
|
|
15119
|
-
}
|
|
15120
|
-
if (server) {
|
|
15121
|
-
const venvPython = (0, import_node_path15.join)((0, import_node_path15.dirname)(server), process.platform === "win32" ? "python.exe" : "python3");
|
|
15122
|
-
if ((0, import_node_fs8.existsSync)(venvPython)) {
|
|
15123
|
-
const result = await runCommand(
|
|
15124
|
-
venvPython,
|
|
15125
|
-
[
|
|
15126
|
-
"-c",
|
|
15127
|
-
'import openviking, os; print(os.path.join(os.path.dirname(openviking.__file__), "storage", "queuefs", "semantic_processor.py"))'
|
|
15128
|
-
],
|
|
15129
|
-
{ allowFailure: true }
|
|
15130
|
-
);
|
|
15131
|
-
const path2 = result.stdout.trim();
|
|
15132
|
-
if (result.exitCode === 0 && path2 && (0, import_node_fs8.existsSync)(path2)) {
|
|
15133
|
-
return path2;
|
|
15134
|
-
}
|
|
15135
|
-
}
|
|
15136
|
-
}
|
|
15137
|
-
return void 0;
|
|
15138
|
-
}
|
|
15139
|
-
async function assertPatchedSourceCompiles(source) {
|
|
15140
|
-
const python = await findExecutable(["python3", "python"]);
|
|
15141
|
-
if (!python) {
|
|
15142
|
-
console.warn("WARN python3 not found; skipping compile validation of the patched OpenViking source.");
|
|
15143
|
-
return;
|
|
15144
|
-
}
|
|
15145
|
-
const dir = await (0, import_promises14.mkdtemp)((0, import_node_path15.join)((0, import_node_os7.tmpdir)(), "threadnote-ov-patch-"));
|
|
15146
|
-
const file = (0, import_node_path15.join)(dir, "semantic_processor_patched.py");
|
|
15147
|
-
try {
|
|
15148
|
-
await (0, import_promises14.writeFile)(file, source, "utf8");
|
|
15149
|
-
const result = await runCommand(python, ["-m", "py_compile", file], { allowFailure: true });
|
|
15150
|
-
if (result.exitCode !== 0) {
|
|
15151
|
-
throw new Error(`patched OpenViking source failed to compile: ${result.stderr.trim() || result.stdout.trim()}`);
|
|
15152
|
-
}
|
|
15153
|
-
} finally {
|
|
15154
|
-
await (0, import_promises14.rm)(dir, { force: true, recursive: true });
|
|
15155
|
-
}
|
|
15156
|
-
}
|
|
15157
|
-
async function runRepairSemanticQueue(config, options) {
|
|
15158
|
-
const apply = options.apply === true && options.dryRun !== true;
|
|
15159
|
-
const path2 = await locateSemanticProcessorPath();
|
|
15160
|
-
if (!path2) {
|
|
15161
|
-
console.error(
|
|
15162
|
-
`Could not locate the installed OpenViking semantic_processor.py. Set ${ENV_OVERRIDE}=/path/to/openviking/storage/queuefs/semantic_processor.py and retry.`
|
|
15163
|
-
);
|
|
15164
|
-
process.exitCode = 1;
|
|
15165
|
-
return;
|
|
15166
|
-
}
|
|
15167
|
-
console.log(`OpenViking semantic processor: ${path2}`);
|
|
15168
|
-
let original;
|
|
15169
|
-
try {
|
|
15170
|
-
original = await (0, import_promises14.readFile)(path2, "utf8");
|
|
15171
|
-
} catch (err) {
|
|
15172
|
-
console.error(`Could not read ${path2}: ${errorMessage(err)}`);
|
|
15173
|
-
process.exitCode = 1;
|
|
15174
|
-
return;
|
|
15175
|
-
}
|
|
15176
|
-
const result = patchSemanticProcessorSource(original);
|
|
15177
|
-
if (result.status === "already-fixed") {
|
|
15178
|
-
console.log("OpenViking already guards non-directory memory URIs (#2734/#2735); nothing to patch.");
|
|
15179
|
-
return;
|
|
15180
|
-
}
|
|
15181
|
-
if (result.status === "no-anchor") {
|
|
15182
|
-
console.warn(
|
|
15183
|
-
"WARN Could not find the _process_memory_directory ls(dir_uri) anchor; this OpenViking layout is unexpected, so no changes were made."
|
|
15184
|
-
);
|
|
15185
|
-
return;
|
|
15186
|
-
}
|
|
15187
|
-
if (!apply) {
|
|
15188
|
-
console.log("Dry run: would patch OpenViking to skip non-directory/missing memory URIs, then restart the server.");
|
|
15189
|
-
console.log("Re-run with --apply to perform it.");
|
|
15190
|
-
return;
|
|
15191
|
-
}
|
|
15192
|
-
await assertPatchedSourceCompiles(result.source);
|
|
15193
|
-
const backup = `${path2}.threadnote-bak`;
|
|
15194
|
-
if (!(0, import_node_fs8.existsSync)(backup)) {
|
|
15195
|
-
await (0, import_promises14.copyFile)(path2, backup);
|
|
15196
|
-
}
|
|
15197
|
-
await (0, import_promises14.writeFile)(path2, result.source, "utf8");
|
|
15198
|
-
console.log(`Patched OpenViking semantic processor (backup: ${backup}).`);
|
|
15199
|
-
console.log("Restarting the OpenViking server so the patch loads and any stuck message drains...");
|
|
15200
|
-
await runStop(config, {});
|
|
15201
|
-
await runStart(config, {});
|
|
15202
|
-
console.log("Done. A stuck semantic message drains on the next dequeue; verify with: ov observer queue");
|
|
15203
|
-
}
|
|
15204
|
-
|
|
15205
15150
|
// src/version_command.ts
|
|
15206
15151
|
async function runVersion(config, options) {
|
|
15207
15152
|
const currentVersion = await currentPackageVersion();
|
|
@@ -15245,9 +15190,9 @@ function printWhatsNew(whatsNew) {
|
|
|
15245
15190
|
// src/manager.ts
|
|
15246
15191
|
var import_node_http2 = require("node:http");
|
|
15247
15192
|
var import_node_crypto2 = require("node:crypto");
|
|
15248
|
-
var
|
|
15249
|
-
var
|
|
15250
|
-
var
|
|
15193
|
+
var import_promises14 = require("node:fs/promises");
|
|
15194
|
+
var import_node_os7 = require("node:os");
|
|
15195
|
+
var import_node_path15 = require("node:path");
|
|
15251
15196
|
var STATIC_FILES = {
|
|
15252
15197
|
"/": { contentType: "text/html; charset=utf-8", path: "index.html" },
|
|
15253
15198
|
"/index.html": { contentType: "text/html; charset=utf-8", path: "index.html" },
|
|
@@ -15318,18 +15263,18 @@ async function readManagedMemory(config, uri) {
|
|
|
15318
15263
|
if (!path2) {
|
|
15319
15264
|
throw new Error(`Manager can only read current-user memory URIs: ${uri}`);
|
|
15320
15265
|
}
|
|
15321
|
-
const [content, pathStat] = await Promise.all([(0,
|
|
15322
|
-
const relativePath = (0,
|
|
15266
|
+
const [content, pathStat] = await Promise.all([(0, import_promises14.readFile)(path2, "utf8"), (0, import_promises14.stat)(path2)]);
|
|
15267
|
+
const relativePath = (0, import_node_path15.relative)(localMemoriesRoot(config), path2).split(import_node_path15.sep).join("/");
|
|
15323
15268
|
const record = parseMemoryDocument(uri, content);
|
|
15324
15269
|
return {
|
|
15325
15270
|
content,
|
|
15326
15271
|
node: {
|
|
15327
15272
|
isDir: false,
|
|
15328
15273
|
isShared: isInSharedNamespace(config, uri),
|
|
15329
|
-
isSystem: isSystemMemoryName(path2.split(
|
|
15274
|
+
isSystem: isSystemMemoryName(path2.split(import_node_path15.sep).at(-1) ?? ""),
|
|
15330
15275
|
metadata: record?.metadata,
|
|
15331
15276
|
modTime: pathStat.mtime.toISOString(),
|
|
15332
|
-
name: path2.split(
|
|
15277
|
+
name: path2.split(import_node_path15.sep).at(-1) ?? uri,
|
|
15333
15278
|
relativePath,
|
|
15334
15279
|
sharedTeam: sharedTeamNameForUri(config, uri),
|
|
15335
15280
|
size: pathStat.size,
|
|
@@ -15617,7 +15562,7 @@ async function handleRequest(context, request, response) {
|
|
|
15617
15562
|
}
|
|
15618
15563
|
async function serveStatic(context, url, response) {
|
|
15619
15564
|
const file = STATIC_FILES[url.pathname] ?? STATIC_FILES["/"];
|
|
15620
|
-
const content = await (0,
|
|
15565
|
+
const content = await (0, import_promises14.readFile)((0, import_node_path15.join)(toolRoot(), file.root ?? "manager", file.path));
|
|
15621
15566
|
const headers = { "content-type": file.contentType };
|
|
15622
15567
|
if (file.root !== "docs") {
|
|
15623
15568
|
headers["cache-control"] = "no-store";
|
|
@@ -15626,11 +15571,11 @@ async function serveStatic(context, url, response) {
|
|
|
15626
15571
|
response.end(content);
|
|
15627
15572
|
}
|
|
15628
15573
|
async function readTree(config, path2, uri, relativePath, options = {}) {
|
|
15629
|
-
const pathStat = await (0,
|
|
15574
|
+
const pathStat = await (0, import_promises14.stat)(path2);
|
|
15630
15575
|
const name = relativePath ? relativePath.split("/").at(-1) ?? relativePath : options.rootName ?? "memories";
|
|
15631
15576
|
const isDir = pathStat.isDirectory();
|
|
15632
15577
|
if (!isDir) {
|
|
15633
|
-
const record = options.parseMemoryDocuments === false ? void 0 : parseMemoryDocument(uri, await (0,
|
|
15578
|
+
const record = options.parseMemoryDocuments === false ? void 0 : parseMemoryDocument(uri, await (0, import_promises14.readFile)(path2, "utf8").catch(() => ""));
|
|
15634
15579
|
return {
|
|
15635
15580
|
isDir: false,
|
|
15636
15581
|
isShared: isInSharedNamespace(config, uri),
|
|
@@ -15644,13 +15589,13 @@ async function readTree(config, path2, uri, relativePath, options = {}) {
|
|
|
15644
15589
|
uri
|
|
15645
15590
|
};
|
|
15646
15591
|
}
|
|
15647
|
-
const entries = await (0,
|
|
15592
|
+
const entries = await (0, import_promises14.readdir)(path2, { withFileTypes: true });
|
|
15648
15593
|
const children = await Promise.all(
|
|
15649
15594
|
entries.sort(
|
|
15650
15595
|
(left, right) => Number(right.isDirectory()) - Number(left.isDirectory()) || left.name.localeCompare(right.name)
|
|
15651
15596
|
).map((entry) => {
|
|
15652
15597
|
const childRelative = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
15653
|
-
return readTree(config, (0,
|
|
15598
|
+
return readTree(config, (0, import_node_path15.join)(path2, entry.name), `${uri}/${entry.name}`, childRelative, options);
|
|
15654
15599
|
})
|
|
15655
15600
|
);
|
|
15656
15601
|
return {
|
|
@@ -15824,27 +15769,27 @@ async function removeManagedFolder(config, uri) {
|
|
|
15824
15769
|
if (!path2) {
|
|
15825
15770
|
throw new Error(`Manager can only remove current-user memory folders: ${uri}`);
|
|
15826
15771
|
}
|
|
15827
|
-
const pathStat = await (0,
|
|
15772
|
+
const pathStat = await (0, import_promises14.stat)(path2);
|
|
15828
15773
|
if (!pathStat.isDirectory()) {
|
|
15829
15774
|
throw new Error(`Not a folder: ${uri}`);
|
|
15830
15775
|
}
|
|
15831
|
-
const relativePath = (0,
|
|
15832
|
-
if (!relativePath || relativePath.startsWith("..") || relativePath.split(
|
|
15776
|
+
const relativePath = (0, import_node_path15.relative)(localMemoriesRoot(config), path2);
|
|
15777
|
+
if (!relativePath || relativePath.startsWith("..") || relativePath.split(import_node_path15.sep).includes("..")) {
|
|
15833
15778
|
throw new Error("Refusing to remove a folder outside the memories tree.");
|
|
15834
15779
|
}
|
|
15835
15780
|
const fileUris = await fileUrisUnderFolder(config, path2);
|
|
15836
15781
|
for (const fileUri of fileUris) {
|
|
15837
15782
|
await runForget(config, fileUri, {});
|
|
15838
15783
|
}
|
|
15839
|
-
await (0,
|
|
15784
|
+
await (0, import_promises14.rm)(path2, { force: true, recursive: true });
|
|
15840
15785
|
console.log(`Removed folder: ${uri}`);
|
|
15841
15786
|
console.log(`Forgot ${fileUris.length} file${fileUris.length === 1 ? "" : "s"}.`);
|
|
15842
15787
|
}
|
|
15843
15788
|
async function fileUrisUnderFolder(config, folderPath) {
|
|
15844
|
-
const entries = await (0,
|
|
15789
|
+
const entries = await (0, import_promises14.readdir)(folderPath, { withFileTypes: true });
|
|
15845
15790
|
const uris = [];
|
|
15846
15791
|
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
15847
|
-
const path2 = (0,
|
|
15792
|
+
const path2 = (0, import_node_path15.join)(folderPath, entry.name);
|
|
15848
15793
|
if (entry.isDirectory()) {
|
|
15849
15794
|
uris.push(...await fileUrisUnderFolder(config, path2));
|
|
15850
15795
|
} else if (entry.isFile()) {
|
|
@@ -15942,11 +15887,11 @@ async function runConsolidationAgent(agent, sources) {
|
|
|
15942
15887
|
throw new Error(`${agent} executable was not found.`);
|
|
15943
15888
|
}
|
|
15944
15889
|
const prompt = consolidationPrompt(sources);
|
|
15945
|
-
const stagingDir = await (0,
|
|
15946
|
-
const promptPath = (0,
|
|
15890
|
+
const stagingDir = await (0, import_promises14.mkdtemp)((0, import_node_path15.join)((0, import_node_os7.tmpdir)(), "threadnote-consolidate-"));
|
|
15891
|
+
const promptPath = (0, import_node_path15.join)(stagingDir, "prompt.txt");
|
|
15947
15892
|
try {
|
|
15948
|
-
await (0,
|
|
15949
|
-
await (0,
|
|
15893
|
+
await (0, import_promises14.writeFile)(promptPath, prompt, { encoding: "utf8", mode: 384 });
|
|
15894
|
+
await (0, import_promises14.chmod)(promptPath, 384);
|
|
15950
15895
|
const script = consolidationAgentScript(agent, executable);
|
|
15951
15896
|
const result = await runCommand("sh", ["-lc", script, "threadnote-consolidate", promptPath], {
|
|
15952
15897
|
allowFailure: true,
|
|
@@ -15962,7 +15907,7 @@ async function runConsolidationAgent(agent, sources) {
|
|
|
15962
15907
|
}
|
|
15963
15908
|
return draft;
|
|
15964
15909
|
} finally {
|
|
15965
|
-
await (0,
|
|
15910
|
+
await (0, import_promises14.rm)(stagingDir, { force: true, recursive: true });
|
|
15966
15911
|
}
|
|
15967
15912
|
}
|
|
15968
15913
|
function consolidationAgentScript(agent, executable) {
|
|
@@ -16100,10 +16045,10 @@ function memoryDirectoryUri2(config, kind, status, projectSegment) {
|
|
|
16100
16045
|
}
|
|
16101
16046
|
}
|
|
16102
16047
|
function localMemoriesRoot(config) {
|
|
16103
|
-
return (0,
|
|
16048
|
+
return (0, import_node_path15.join)(config.agentContextHome, "data", "viking", config.account, "user", uriSegment(config.user), "memories");
|
|
16104
16049
|
}
|
|
16105
16050
|
function localResourcesRoot(config) {
|
|
16106
|
-
return (0,
|
|
16051
|
+
return (0, import_node_path15.join)(config.agentContextHome, "data", "viking", config.account, "resources");
|
|
16107
16052
|
}
|
|
16108
16053
|
function localPathForMemoryUri(config, uri) {
|
|
16109
16054
|
const prefix = `viking://user/${uriSegment(config.user)}/memories`;
|
|
@@ -16115,17 +16060,17 @@ function localPathForMemoryUri(config, uri) {
|
|
|
16115
16060
|
if (segments.some((segment) => segment === "." || segment === "..")) {
|
|
16116
16061
|
return void 0;
|
|
16117
16062
|
}
|
|
16118
|
-
return (0,
|
|
16063
|
+
return (0, import_node_path15.join)(localMemoriesRoot(config), ...segments);
|
|
16119
16064
|
}
|
|
16120
16065
|
function isMissingPathError(err) {
|
|
16121
16066
|
return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
|
|
16122
16067
|
}
|
|
16123
16068
|
function localPathToMemoryUri(config, path2) {
|
|
16124
|
-
const relativePath = (0,
|
|
16125
|
-
if (!relativePath || relativePath.startsWith("..") || relativePath.split(
|
|
16069
|
+
const relativePath = (0, import_node_path15.relative)(localMemoriesRoot(config), path2);
|
|
16070
|
+
if (!relativePath || relativePath.startsWith("..") || relativePath.split(import_node_path15.sep).includes("..")) {
|
|
16126
16071
|
throw new Error(`Path is outside the memories tree: ${path2}`);
|
|
16127
16072
|
}
|
|
16128
|
-
return `viking://user/${uriSegment(config.user)}/memories/${relativePath.split(
|
|
16073
|
+
return `viking://user/${uriSegment(config.user)}/memories/${relativePath.split(import_node_path15.sep).join("/")}`;
|
|
16129
16074
|
}
|
|
16130
16075
|
async function ensurePersonalDirectoryChain2(config, ov, directoryUri) {
|
|
16131
16076
|
const prefix = "viking://";
|
|
@@ -16365,9 +16310,6 @@ async function main() {
|
|
|
16365
16310
|
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) => {
|
|
16366
16311
|
await runMigrateLifecycle(getRuntimeConfig(program2), options);
|
|
16367
16312
|
});
|
|
16368
|
-
program2.command("repair-semantic-queue").description("Patch the installed OpenViking to drain/avoid the semantic-queue poison loop (#2734)").option("--apply", "Apply the patch and restart the server; without this, prints a dry run").option("--dry-run", "Print what would change without patching or restarting").action(async (options) => {
|
|
16369
|
-
await runRepairSemanticQueue(getRuntimeConfig(program2), options);
|
|
16370
|
-
});
|
|
16371
16313
|
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 recall results").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) => {
|
|
16372
16314
|
await runRecall(getRuntimeConfig(program2), options);
|
|
16373
16315
|
});
|
package/docs/index.html
CHANGED
|
@@ -1147,7 +1147,7 @@
|
|
|
1147
1147
|
<span class="out">--user`, which fails on PEP 668 setups.</span>
|
|
1148
1148
|
<span class="out">Install uv now? [Y/n] </span><span class="out-strong">Y</span>
|
|
1149
1149
|
<span class="out">Installing uv via Homebrew…</span>
|
|
1150
|
-
<span class="out-strong">OK openviking 0.4.
|
|
1150
|
+
<span class="out-strong">OK openviking 0.4.5 ready · server healthy</span></pre>
|
|
1151
1151
|
</div>
|
|
1152
1152
|
<p class="muted" style="margin-top: 0.75rem; font-size: 0.9rem">
|
|
1153
1153
|
Or manually: <code>npm install -g threadnote && threadnote install</code>. On a fresh macOS / modern
|
|
@@ -1651,7 +1651,7 @@ threadnote doctor --dry-run</code></pre>
|
|
|
1651
1651
|
</div>
|
|
1652
1652
|
|
|
1653
1653
|
<p class="colophon">
|
|
1654
|
-
threadnote · AGPL-3.0-or-later · built on OpenViking 0.4.
|
|
1654
|
+
threadnote · AGPL-3.0-or-later · built on OpenViking 0.4.5 · use <span class="kbd">↑</span>
|
|
1655
1655
|
<span class="kbd">↓</span> / <span class="kbd">j</span> <span class="kbd">k</span> to navigate
|
|
1656
1656
|
</p>
|
|
1657
1657
|
</div>
|
package/docs/troubleshooting.md
CHANGED
|
@@ -102,28 +102,26 @@ Symptom: agents hang or `remember`/`handoff` get very slow, and `~/.openviking/l
|
|
|
102
102
|
RuntimeError: Failed to list memory directory viking://user/.../memories/.../<name>.md: Directory not found
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
-
A memory _file_ got enqueued for directory-level semantic processing; OpenViking
|
|
106
|
-
|
|
107
|
-
queue — a non-zero `Errors`/`Requeued` on the `Semantic` row is the signature:
|
|
105
|
+
A memory _file_ got enqueued for directory-level semantic processing; older OpenViking releases listed it as a
|
|
106
|
+
directory, failed, and re-enqueued the message forever. The entry is AGFS-persisted, so it survives a server restart.
|
|
107
|
+
Check the queue — a non-zero `Errors`/`Requeued` on the `Semantic` row is the signature:
|
|
108
108
|
|
|
109
109
|
```bash
|
|
110
110
|
ov observer queue
|
|
111
111
|
```
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
This is fixed upstream in OpenViking 0.4.5. Update Threadnote so it upgrades the pinned OpenViking install and restarts
|
|
114
|
+
the server:
|
|
114
115
|
|
|
115
116
|
```bash
|
|
116
|
-
threadnote
|
|
117
|
+
threadnote update
|
|
117
118
|
```
|
|
118
119
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
patch** — `threadnote update` also offers it as a post-update step, and it is superseded automatically once Threadnote
|
|
122
|
-
pins an OpenViking release containing the fix. To revert manually:
|
|
120
|
+
If Threadnote is already current but OpenViking is still older than 0.4.5, force a reinstall of the pinned OpenViking
|
|
121
|
+
tool:
|
|
123
122
|
|
|
124
123
|
```bash
|
|
125
|
-
|
|
126
|
-
threadnote stop && threadnote start
|
|
124
|
+
threadnote install --force
|
|
127
125
|
```
|
|
128
126
|
|
|
129
127
|
## Port Already In Use
|