threadnote 1.4.2 → 1.4.3
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 +15 -0
- package/dist/threadnote.cjs +225 -29
- package/docs/troubleshooting.md +32 -0
- package/package.json +1 -1
|
@@ -18,6 +18,21 @@
|
|
|
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
|
+
]
|
|
21
36
|
}
|
|
22
37
|
]
|
|
23
38
|
}
|
package/dist/threadnote.cjs
CHANGED
|
@@ -15009,6 +15009,199 @@ function parsePackageManager(value) {
|
|
|
15009
15009
|
throw new Error(`Invalid package manager: ${value}`);
|
|
15010
15010
|
}
|
|
15011
15011
|
|
|
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
|
+
|
|
15012
15205
|
// src/version_command.ts
|
|
15013
15206
|
async function runVersion(config, options) {
|
|
15014
15207
|
const currentVersion = await currentPackageVersion();
|
|
@@ -15052,9 +15245,9 @@ function printWhatsNew(whatsNew) {
|
|
|
15052
15245
|
// src/manager.ts
|
|
15053
15246
|
var import_node_http2 = require("node:http");
|
|
15054
15247
|
var import_node_crypto2 = require("node:crypto");
|
|
15055
|
-
var
|
|
15056
|
-
var
|
|
15057
|
-
var
|
|
15248
|
+
var import_promises15 = require("node:fs/promises");
|
|
15249
|
+
var import_node_os8 = require("node:os");
|
|
15250
|
+
var import_node_path16 = require("node:path");
|
|
15058
15251
|
var STATIC_FILES = {
|
|
15059
15252
|
"/": { contentType: "text/html; charset=utf-8", path: "index.html" },
|
|
15060
15253
|
"/index.html": { contentType: "text/html; charset=utf-8", path: "index.html" },
|
|
@@ -15125,18 +15318,18 @@ async function readManagedMemory(config, uri) {
|
|
|
15125
15318
|
if (!path2) {
|
|
15126
15319
|
throw new Error(`Manager can only read current-user memory URIs: ${uri}`);
|
|
15127
15320
|
}
|
|
15128
|
-
const [content, pathStat] = await Promise.all([(0,
|
|
15129
|
-
const relativePath = (0,
|
|
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("/");
|
|
15130
15323
|
const record = parseMemoryDocument(uri, content);
|
|
15131
15324
|
return {
|
|
15132
15325
|
content,
|
|
15133
15326
|
node: {
|
|
15134
15327
|
isDir: false,
|
|
15135
15328
|
isShared: isInSharedNamespace(config, uri),
|
|
15136
|
-
isSystem: isSystemMemoryName(path2.split(
|
|
15329
|
+
isSystem: isSystemMemoryName(path2.split(import_node_path16.sep).at(-1) ?? ""),
|
|
15137
15330
|
metadata: record?.metadata,
|
|
15138
15331
|
modTime: pathStat.mtime.toISOString(),
|
|
15139
|
-
name: path2.split(
|
|
15332
|
+
name: path2.split(import_node_path16.sep).at(-1) ?? uri,
|
|
15140
15333
|
relativePath,
|
|
15141
15334
|
sharedTeam: sharedTeamNameForUri(config, uri),
|
|
15142
15335
|
size: pathStat.size,
|
|
@@ -15424,7 +15617,7 @@ async function handleRequest(context, request, response) {
|
|
|
15424
15617
|
}
|
|
15425
15618
|
async function serveStatic(context, url, response) {
|
|
15426
15619
|
const file = STATIC_FILES[url.pathname] ?? STATIC_FILES["/"];
|
|
15427
|
-
const content = await (0,
|
|
15620
|
+
const content = await (0, import_promises15.readFile)((0, import_node_path16.join)(toolRoot(), file.root ?? "manager", file.path));
|
|
15428
15621
|
const headers = { "content-type": file.contentType };
|
|
15429
15622
|
if (file.root !== "docs") {
|
|
15430
15623
|
headers["cache-control"] = "no-store";
|
|
@@ -15433,11 +15626,11 @@ async function serveStatic(context, url, response) {
|
|
|
15433
15626
|
response.end(content);
|
|
15434
15627
|
}
|
|
15435
15628
|
async function readTree(config, path2, uri, relativePath, options = {}) {
|
|
15436
|
-
const pathStat = await (0,
|
|
15629
|
+
const pathStat = await (0, import_promises15.stat)(path2);
|
|
15437
15630
|
const name = relativePath ? relativePath.split("/").at(-1) ?? relativePath : options.rootName ?? "memories";
|
|
15438
15631
|
const isDir = pathStat.isDirectory();
|
|
15439
15632
|
if (!isDir) {
|
|
15440
|
-
const record = options.parseMemoryDocuments === false ? void 0 : parseMemoryDocument(uri, await (0,
|
|
15633
|
+
const record = options.parseMemoryDocuments === false ? void 0 : parseMemoryDocument(uri, await (0, import_promises15.readFile)(path2, "utf8").catch(() => ""));
|
|
15441
15634
|
return {
|
|
15442
15635
|
isDir: false,
|
|
15443
15636
|
isShared: isInSharedNamespace(config, uri),
|
|
@@ -15451,13 +15644,13 @@ async function readTree(config, path2, uri, relativePath, options = {}) {
|
|
|
15451
15644
|
uri
|
|
15452
15645
|
};
|
|
15453
15646
|
}
|
|
15454
|
-
const entries = await (0,
|
|
15647
|
+
const entries = await (0, import_promises15.readdir)(path2, { withFileTypes: true });
|
|
15455
15648
|
const children = await Promise.all(
|
|
15456
15649
|
entries.sort(
|
|
15457
15650
|
(left, right) => Number(right.isDirectory()) - Number(left.isDirectory()) || left.name.localeCompare(right.name)
|
|
15458
15651
|
).map((entry) => {
|
|
15459
15652
|
const childRelative = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
15460
|
-
return readTree(config, (0,
|
|
15653
|
+
return readTree(config, (0, import_node_path16.join)(path2, entry.name), `${uri}/${entry.name}`, childRelative, options);
|
|
15461
15654
|
})
|
|
15462
15655
|
);
|
|
15463
15656
|
return {
|
|
@@ -15631,27 +15824,27 @@ async function removeManagedFolder(config, uri) {
|
|
|
15631
15824
|
if (!path2) {
|
|
15632
15825
|
throw new Error(`Manager can only remove current-user memory folders: ${uri}`);
|
|
15633
15826
|
}
|
|
15634
|
-
const pathStat = await (0,
|
|
15827
|
+
const pathStat = await (0, import_promises15.stat)(path2);
|
|
15635
15828
|
if (!pathStat.isDirectory()) {
|
|
15636
15829
|
throw new Error(`Not a folder: ${uri}`);
|
|
15637
15830
|
}
|
|
15638
|
-
const relativePath = (0,
|
|
15639
|
-
if (!relativePath || relativePath.startsWith("..") || relativePath.split(
|
|
15831
|
+
const relativePath = (0, import_node_path16.relative)(localMemoriesRoot(config), path2);
|
|
15832
|
+
if (!relativePath || relativePath.startsWith("..") || relativePath.split(import_node_path16.sep).includes("..")) {
|
|
15640
15833
|
throw new Error("Refusing to remove a folder outside the memories tree.");
|
|
15641
15834
|
}
|
|
15642
15835
|
const fileUris = await fileUrisUnderFolder(config, path2);
|
|
15643
15836
|
for (const fileUri of fileUris) {
|
|
15644
15837
|
await runForget(config, fileUri, {});
|
|
15645
15838
|
}
|
|
15646
|
-
await (0,
|
|
15839
|
+
await (0, import_promises15.rm)(path2, { force: true, recursive: true });
|
|
15647
15840
|
console.log(`Removed folder: ${uri}`);
|
|
15648
15841
|
console.log(`Forgot ${fileUris.length} file${fileUris.length === 1 ? "" : "s"}.`);
|
|
15649
15842
|
}
|
|
15650
15843
|
async function fileUrisUnderFolder(config, folderPath) {
|
|
15651
|
-
const entries = await (0,
|
|
15844
|
+
const entries = await (0, import_promises15.readdir)(folderPath, { withFileTypes: true });
|
|
15652
15845
|
const uris = [];
|
|
15653
15846
|
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
15654
|
-
const path2 = (0,
|
|
15847
|
+
const path2 = (0, import_node_path16.join)(folderPath, entry.name);
|
|
15655
15848
|
if (entry.isDirectory()) {
|
|
15656
15849
|
uris.push(...await fileUrisUnderFolder(config, path2));
|
|
15657
15850
|
} else if (entry.isFile()) {
|
|
@@ -15749,11 +15942,11 @@ async function runConsolidationAgent(agent, sources) {
|
|
|
15749
15942
|
throw new Error(`${agent} executable was not found.`);
|
|
15750
15943
|
}
|
|
15751
15944
|
const prompt = consolidationPrompt(sources);
|
|
15752
|
-
const stagingDir = await (0,
|
|
15753
|
-
const promptPath = (0,
|
|
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");
|
|
15754
15947
|
try {
|
|
15755
|
-
await (0,
|
|
15756
|
-
await (0,
|
|
15948
|
+
await (0, import_promises15.writeFile)(promptPath, prompt, { encoding: "utf8", mode: 384 });
|
|
15949
|
+
await (0, import_promises15.chmod)(promptPath, 384);
|
|
15757
15950
|
const script = consolidationAgentScript(agent, executable);
|
|
15758
15951
|
const result = await runCommand("sh", ["-lc", script, "threadnote-consolidate", promptPath], {
|
|
15759
15952
|
allowFailure: true,
|
|
@@ -15769,7 +15962,7 @@ async function runConsolidationAgent(agent, sources) {
|
|
|
15769
15962
|
}
|
|
15770
15963
|
return draft;
|
|
15771
15964
|
} finally {
|
|
15772
|
-
await (0,
|
|
15965
|
+
await (0, import_promises15.rm)(stagingDir, { force: true, recursive: true });
|
|
15773
15966
|
}
|
|
15774
15967
|
}
|
|
15775
15968
|
function consolidationAgentScript(agent, executable) {
|
|
@@ -15907,10 +16100,10 @@ function memoryDirectoryUri2(config, kind, status, projectSegment) {
|
|
|
15907
16100
|
}
|
|
15908
16101
|
}
|
|
15909
16102
|
function localMemoriesRoot(config) {
|
|
15910
|
-
return (0,
|
|
16103
|
+
return (0, import_node_path16.join)(config.agentContextHome, "data", "viking", config.account, "user", uriSegment(config.user), "memories");
|
|
15911
16104
|
}
|
|
15912
16105
|
function localResourcesRoot(config) {
|
|
15913
|
-
return (0,
|
|
16106
|
+
return (0, import_node_path16.join)(config.agentContextHome, "data", "viking", config.account, "resources");
|
|
15914
16107
|
}
|
|
15915
16108
|
function localPathForMemoryUri(config, uri) {
|
|
15916
16109
|
const prefix = `viking://user/${uriSegment(config.user)}/memories`;
|
|
@@ -15922,17 +16115,17 @@ function localPathForMemoryUri(config, uri) {
|
|
|
15922
16115
|
if (segments.some((segment) => segment === "." || segment === "..")) {
|
|
15923
16116
|
return void 0;
|
|
15924
16117
|
}
|
|
15925
|
-
return (0,
|
|
16118
|
+
return (0, import_node_path16.join)(localMemoriesRoot(config), ...segments);
|
|
15926
16119
|
}
|
|
15927
16120
|
function isMissingPathError(err) {
|
|
15928
16121
|
return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
|
|
15929
16122
|
}
|
|
15930
16123
|
function localPathToMemoryUri(config, path2) {
|
|
15931
|
-
const relativePath = (0,
|
|
15932
|
-
if (!relativePath || relativePath.startsWith("..") || relativePath.split(
|
|
16124
|
+
const relativePath = (0, import_node_path16.relative)(localMemoriesRoot(config), path2);
|
|
16125
|
+
if (!relativePath || relativePath.startsWith("..") || relativePath.split(import_node_path16.sep).includes("..")) {
|
|
15933
16126
|
throw new Error(`Path is outside the memories tree: ${path2}`);
|
|
15934
16127
|
}
|
|
15935
|
-
return `viking://user/${uriSegment(config.user)}/memories/${relativePath.split(
|
|
16128
|
+
return `viking://user/${uriSegment(config.user)}/memories/${relativePath.split(import_node_path16.sep).join("/")}`;
|
|
15936
16129
|
}
|
|
15937
16130
|
async function ensurePersonalDirectoryChain2(config, ov, directoryUri) {
|
|
15938
16131
|
const prefix = "viking://";
|
|
@@ -16172,6 +16365,9 @@ async function main() {
|
|
|
16172
16365
|
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) => {
|
|
16173
16366
|
await runMigrateLifecycle(getRuntimeConfig(program2), options);
|
|
16174
16367
|
});
|
|
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
|
+
});
|
|
16175
16371
|
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) => {
|
|
16176
16372
|
await runRecall(getRuntimeConfig(program2), options);
|
|
16177
16373
|
});
|
package/docs/troubleshooting.md
CHANGED
|
@@ -94,6 +94,38 @@ threadnote doctor --dry-run
|
|
|
94
94
|
If it still is not healthy, open that log. Certificate failures during the first embedding model download are covered
|
|
95
95
|
above.
|
|
96
96
|
|
|
97
|
+
## Semantic Queue Stuck / Memory Writes Hang
|
|
98
|
+
|
|
99
|
+
Symptom: agents hang or `remember`/`handoff` get very slow, and `~/.openviking/logs/server.log` repeats:
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
RuntimeError: Failed to list memory directory viking://user/.../memories/.../<name>.md: Directory not found
|
|
103
|
+
```
|
|
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:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
ov observer queue
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Fix it by patching the installed OpenViking and restarting the server:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
threadnote repair-semantic-queue --apply
|
|
117
|
+
```
|
|
118
|
+
|
|
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:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
mv <printed-path>.threadnote-bak <printed-path>
|
|
126
|
+
threadnote stop && threadnote start
|
|
127
|
+
```
|
|
128
|
+
|
|
97
129
|
## Port Already In Use
|
|
98
130
|
|
|
99
131
|
The default bind address is `127.0.0.1:1933`. This does not conflict with projects serving `localhost:80`,
|