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.
@@ -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
  }
@@ -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.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 exitCode = await runInteractive(installCommand.executable, installCommand.args);
14491
- if (exitCode !== 0) {
14492
- printOpenVikingInstallFailureHelp(installCommand);
14493
- throw new Error(`${formatShellCommand(installCommand.executable, installCommand.args)} exited with ${exitCode}.`);
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 printOpenVikingInstallFailureHelp(failedCommand) {
14498
- console.error("");
14499
- console.error("OpenViking install did not complete.");
14500
- console.error(
14501
- "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."
14502
- );
14503
- console.error("Re-run it directly to see full output without any wrapper timeout:");
14504
- console.error(` ${formatShellCommand(failedCommand.executable, failedCommand.args)}`);
14505
- console.error("Cap memory use during the compile with: CMAKE_BUILD_PARALLEL_LEVEL=2 <command above>");
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
- console.error(
14509
- "If uv could not fetch a managed CPython (offline or restricted network), drop the version pin and retry: THREADNOTE_OPENVIKING_PYTHON= threadnote install --force"
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 import_promises15 = require("node:fs/promises");
15249
- var import_node_os8 = require("node:os");
15250
- var import_node_path16 = require("node:path");
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, import_promises15.readFile)(path2, "utf8"), (0, import_promises15.stat)(path2)]);
15322
- const relativePath = (0, import_node_path16.relative)(localMemoriesRoot(config), path2).split(import_node_path16.sep).join("/");
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(import_node_path16.sep).at(-1) ?? ""),
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(import_node_path16.sep).at(-1) ?? uri,
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, import_promises15.readFile)((0, import_node_path16.join)(toolRoot(), file.root ?? "manager", file.path));
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, import_promises15.stat)(path2);
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, import_promises15.readFile)(path2, "utf8").catch(() => ""));
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, import_promises15.readdir)(path2, { withFileTypes: true });
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, import_node_path16.join)(path2, entry.name), `${uri}/${entry.name}`, childRelative, options);
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, import_promises15.stat)(path2);
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, import_node_path16.relative)(localMemoriesRoot(config), path2);
15832
- if (!relativePath || relativePath.startsWith("..") || relativePath.split(import_node_path16.sep).includes("..")) {
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, import_promises15.rm)(path2, { force: true, recursive: true });
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, import_promises15.readdir)(folderPath, { withFileTypes: true });
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, import_node_path16.join)(folderPath, entry.name);
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, import_promises15.mkdtemp)((0, import_node_path16.join)((0, import_node_os8.tmpdir)(), "threadnote-consolidate-"));
15946
- const promptPath = (0, import_node_path16.join)(stagingDir, "prompt.txt");
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, import_promises15.writeFile)(promptPath, prompt, { encoding: "utf8", mode: 384 });
15949
- await (0, import_promises15.chmod)(promptPath, 384);
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, import_promises15.rm)(stagingDir, { force: true, recursive: true });
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, import_node_path16.join)(config.agentContextHome, "data", "viking", config.account, "user", uriSegment(config.user), "memories");
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, import_node_path16.join)(config.agentContextHome, "data", "viking", config.account, "resources");
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, import_node_path16.join)(localMemoriesRoot(config), ...segments);
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, import_node_path16.relative)(localMemoriesRoot(config), path2);
16125
- if (!relativePath || relativePath.startsWith("..") || relativePath.split(import_node_path16.sep).includes("..")) {
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(import_node_path16.sep).join("/")}`;
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.4 ready · server healthy</span></pre>
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.4 · use <span class="kbd">↑</span>
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>
@@ -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's `_process_memory_directory` lists
106
- it, fails, and the message re-enqueues forever. The entry is AGFS-persisted, so it survives a server restart. Check the
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
- Fix it by patching the installed OpenViking and restarting the server:
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 repair-semantic-queue --apply
117
+ threadnote update
117
118
  ```
118
119
 
119
- It skips non-directory/missing memory URIs (OpenViking PR #2735), keeps a `.threadnote-bak`, compile-checks the patched
120
- file before writing, and is a no-op once the installed OpenViking already includes the fix. This is a **temporary local
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
- mv <printed-path>.threadnote-bak <printed-path>
126
- threadnote stop && threadnote start
124
+ threadnote install --force
127
125
  ```
128
126
 
129
127
  ## Port Already In Use
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "threadnote",
3
- "version": "1.4.3",
3
+ "version": "1.4.5",
4
4
  "type": "commonjs",
5
5
  "main": "dist/threadnote.cjs",
6
6
  "description": "Shared local context and handoffs for development agents",