threadnote 0.7.11 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -422
- package/dist/mcp_server.cjs +346 -76
- package/dist/threadnote.cjs +726 -288
- package/docs/index.html +74 -11
- package/docs/migration.md +1 -1
- package/docs/troubleshooting.md +6 -5
- package/docs/web-manager.png +0 -0
- package/manager/app.css +42 -1
- package/manager/app.js +144 -59
- package/package.json +1 -1
package/dist/mcp_server.cjs
CHANGED
|
@@ -3645,49 +3645,49 @@ var require_fast_uri = __commonJS({
|
|
|
3645
3645
|
schemelessOptions.skipEscape = true;
|
|
3646
3646
|
return serialize(resolved, schemelessOptions);
|
|
3647
3647
|
}
|
|
3648
|
-
function resolveComponent(base,
|
|
3648
|
+
function resolveComponent(base, relative3, options, skipNormalization) {
|
|
3649
3649
|
const target = {};
|
|
3650
3650
|
if (!skipNormalization) {
|
|
3651
3651
|
base = parse3(serialize(base, options), options);
|
|
3652
|
-
|
|
3652
|
+
relative3 = parse3(serialize(relative3, options), options);
|
|
3653
3653
|
}
|
|
3654
3654
|
options = options || {};
|
|
3655
|
-
if (!options.tolerant &&
|
|
3656
|
-
target.scheme =
|
|
3657
|
-
target.userinfo =
|
|
3658
|
-
target.host =
|
|
3659
|
-
target.port =
|
|
3660
|
-
target.path = removeDotSegments(
|
|
3661
|
-
target.query =
|
|
3655
|
+
if (!options.tolerant && relative3.scheme) {
|
|
3656
|
+
target.scheme = relative3.scheme;
|
|
3657
|
+
target.userinfo = relative3.userinfo;
|
|
3658
|
+
target.host = relative3.host;
|
|
3659
|
+
target.port = relative3.port;
|
|
3660
|
+
target.path = removeDotSegments(relative3.path || "");
|
|
3661
|
+
target.query = relative3.query;
|
|
3662
3662
|
} else {
|
|
3663
|
-
if (
|
|
3664
|
-
target.userinfo =
|
|
3665
|
-
target.host =
|
|
3666
|
-
target.port =
|
|
3667
|
-
target.path = removeDotSegments(
|
|
3668
|
-
target.query =
|
|
3663
|
+
if (relative3.userinfo !== void 0 || relative3.host !== void 0 || relative3.port !== void 0) {
|
|
3664
|
+
target.userinfo = relative3.userinfo;
|
|
3665
|
+
target.host = relative3.host;
|
|
3666
|
+
target.port = relative3.port;
|
|
3667
|
+
target.path = removeDotSegments(relative3.path || "");
|
|
3668
|
+
target.query = relative3.query;
|
|
3669
3669
|
} else {
|
|
3670
|
-
if (!
|
|
3670
|
+
if (!relative3.path) {
|
|
3671
3671
|
target.path = base.path;
|
|
3672
|
-
if (
|
|
3673
|
-
target.query =
|
|
3672
|
+
if (relative3.query !== void 0) {
|
|
3673
|
+
target.query = relative3.query;
|
|
3674
3674
|
} else {
|
|
3675
3675
|
target.query = base.query;
|
|
3676
3676
|
}
|
|
3677
3677
|
} else {
|
|
3678
|
-
if (
|
|
3679
|
-
target.path = removeDotSegments(
|
|
3678
|
+
if (relative3.path[0] === "/") {
|
|
3679
|
+
target.path = removeDotSegments(relative3.path);
|
|
3680
3680
|
} else {
|
|
3681
3681
|
if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
|
|
3682
|
-
target.path = "/" +
|
|
3682
|
+
target.path = "/" + relative3.path;
|
|
3683
3683
|
} else if (!base.path) {
|
|
3684
|
-
target.path =
|
|
3684
|
+
target.path = relative3.path;
|
|
3685
3685
|
} else {
|
|
3686
|
-
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) +
|
|
3686
|
+
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative3.path;
|
|
3687
3687
|
}
|
|
3688
3688
|
target.path = removeDotSegments(target.path);
|
|
3689
3689
|
}
|
|
3690
|
-
target.query =
|
|
3690
|
+
target.query = relative3.query;
|
|
3691
3691
|
}
|
|
3692
3692
|
target.userinfo = base.userinfo;
|
|
3693
3693
|
target.host = base.host;
|
|
@@ -3695,7 +3695,7 @@ var require_fast_uri = __commonJS({
|
|
|
3695
3695
|
}
|
|
3696
3696
|
target.scheme = base.scheme;
|
|
3697
3697
|
}
|
|
3698
|
-
target.fragment =
|
|
3698
|
+
target.fragment = relative3.fragment;
|
|
3699
3699
|
return target;
|
|
3700
3700
|
}
|
|
3701
3701
|
function equal(uriA, uriB, options) {
|
|
@@ -30950,14 +30950,18 @@ var StdioServerTransport = class {
|
|
|
30950
30950
|
};
|
|
30951
30951
|
|
|
30952
30952
|
// src/mcp_server.ts
|
|
30953
|
-
var
|
|
30953
|
+
var import_promises5 = require("node:fs/promises");
|
|
30954
30954
|
var import_node_os3 = require("node:os");
|
|
30955
|
-
var
|
|
30955
|
+
var import_node_path5 = require("node:path");
|
|
30956
30956
|
|
|
30957
30957
|
// src/constants.ts
|
|
30958
30958
|
var DEFAULT_ACCOUNT = "local";
|
|
30959
30959
|
var DEFAULT_AGENT_ID = "threadnote";
|
|
30960
30960
|
|
|
30961
|
+
// src/index_repair.ts
|
|
30962
|
+
var import_promises3 = require("node:fs/promises");
|
|
30963
|
+
var import_node_path2 = require("node:path");
|
|
30964
|
+
|
|
30961
30965
|
// src/manifest.ts
|
|
30962
30966
|
var import_promises2 = require("node:fs/promises");
|
|
30963
30967
|
|
|
@@ -33808,6 +33812,13 @@ async function sleep(ms) {
|
|
|
33808
33812
|
setTimeout(resolvePromise, ms);
|
|
33809
33813
|
});
|
|
33810
33814
|
}
|
|
33815
|
+
async function ensureDirectory(path, dryRun) {
|
|
33816
|
+
if (dryRun) {
|
|
33817
|
+
console.log(`Would create directory: ${path}`);
|
|
33818
|
+
return;
|
|
33819
|
+
}
|
|
33820
|
+
await (0, import_promises.mkdir)(path, { recursive: true });
|
|
33821
|
+
}
|
|
33811
33822
|
async function exists(path) {
|
|
33812
33823
|
try {
|
|
33813
33824
|
await (0, import_promises.access)(path);
|
|
@@ -33893,6 +33904,9 @@ function uniqueUsefulWorkspaceTerms(values) {
|
|
|
33893
33904
|
}
|
|
33894
33905
|
return terms;
|
|
33895
33906
|
}
|
|
33907
|
+
function toPosixPath(path) {
|
|
33908
|
+
return path.split(import_node_path.sep).join("/");
|
|
33909
|
+
}
|
|
33896
33910
|
function trimTrailingSlash(value) {
|
|
33897
33911
|
return value.endsWith("/") ? value.slice(0, -1) : value;
|
|
33898
33912
|
}
|
|
@@ -34043,8 +34057,236 @@ function readStringArray(object3, key) {
|
|
|
34043
34057
|
return value;
|
|
34044
34058
|
}
|
|
34045
34059
|
|
|
34060
|
+
// src/runtime.ts
|
|
34061
|
+
function withIdentity(config2, args) {
|
|
34062
|
+
return [...args, "--account", config2.account, "--user", config2.user, "--agent-id", config2.agentId];
|
|
34063
|
+
}
|
|
34064
|
+
|
|
34065
|
+
// src/index_repair.ts
|
|
34066
|
+
var AUTO_REPAIR_STATE_FILE = "index-auto-repair.json";
|
|
34067
|
+
var AUTO_REPAIR_TTL_MS = 6 * 60 * 60 * 1e3;
|
|
34068
|
+
var MAX_SCAN_DEPTH = 5;
|
|
34069
|
+
var MAX_REPAIR_TARGETS = 4;
|
|
34070
|
+
async function repairStaleRecallIndex(config2, ov, options = {}) {
|
|
34071
|
+
const targets = await findStaleRecallIndexTargets(config2, options);
|
|
34072
|
+
if (targets.length === 0) {
|
|
34073
|
+
return { repairedUris: [], skippedRecentUris: [], warnings: [] };
|
|
34074
|
+
}
|
|
34075
|
+
const state = options.dryRun === true ? { entries: {}, version: 1 } : await readAutoRepairState(config2);
|
|
34076
|
+
const now = Date.now();
|
|
34077
|
+
const repairedUris = [];
|
|
34078
|
+
const skippedRecentUris = [];
|
|
34079
|
+
const warnings = [];
|
|
34080
|
+
for (const target of targets.slice(0, options.maxTargets ?? MAX_REPAIR_TARGETS)) {
|
|
34081
|
+
const previous = state.entries[target.uri];
|
|
34082
|
+
if (previous?.signature === target.signature && now - Date.parse(previous.repairedAt) < AUTO_REPAIR_TTL_MS && options.dryRun !== true && options.ignoreBackoff !== true) {
|
|
34083
|
+
skippedRecentUris.push(target.uri);
|
|
34084
|
+
continue;
|
|
34085
|
+
}
|
|
34086
|
+
if (options.dryRun === true) {
|
|
34087
|
+
repairedUris.push(target.uri);
|
|
34088
|
+
continue;
|
|
34089
|
+
}
|
|
34090
|
+
const result = await runCommand(
|
|
34091
|
+
ov,
|
|
34092
|
+
withIdentity(config2, ["reindex", target.uri, "--mode", "semantic_and_vectors", "--wait", "true"]),
|
|
34093
|
+
{ allowFailure: true }
|
|
34094
|
+
);
|
|
34095
|
+
if (result.exitCode === 0) {
|
|
34096
|
+
repairedUris.push(target.uri);
|
|
34097
|
+
state.entries[target.uri] = { repairedAt: new Date(now).toISOString(), signature: target.signature };
|
|
34098
|
+
} else {
|
|
34099
|
+
warnings.push(indexRepairWarning(target.uri, result));
|
|
34100
|
+
}
|
|
34101
|
+
}
|
|
34102
|
+
if (options.dryRun !== true && repairedUris.length > 0) {
|
|
34103
|
+
await writeAutoRepairState(config2, state);
|
|
34104
|
+
}
|
|
34105
|
+
return { repairedUris, skippedRecentUris, warnings };
|
|
34106
|
+
}
|
|
34107
|
+
async function findStaleRecallIndexTargets(config2, options = {}) {
|
|
34108
|
+
const roots = await scanRoots(config2, options);
|
|
34109
|
+
const byUri = /* @__PURE__ */ new Map();
|
|
34110
|
+
for (const root of roots) {
|
|
34111
|
+
if (!await exists(root.path)) {
|
|
34112
|
+
continue;
|
|
34113
|
+
}
|
|
34114
|
+
const sidecars = await staleSidecars(root.path, root.uri);
|
|
34115
|
+
if (options.collapseToRoots === true) {
|
|
34116
|
+
if (sidecars.length === 0) {
|
|
34117
|
+
continue;
|
|
34118
|
+
}
|
|
34119
|
+
const parts = sidecars.map((sidecar) => `${sidecar.relativePath}
|
|
34120
|
+
${sidecar.content}`);
|
|
34121
|
+
byUri.set(root.uri, {
|
|
34122
|
+
parts,
|
|
34123
|
+
staleCount: sidecars.length,
|
|
34124
|
+
uri: root.uri
|
|
34125
|
+
});
|
|
34126
|
+
continue;
|
|
34127
|
+
}
|
|
34128
|
+
for (const sidecar of sidecars) {
|
|
34129
|
+
const current = byUri.get(sidecar.uri) ?? { parts: [], staleCount: 0, uri: sidecar.uri };
|
|
34130
|
+
current.parts.push(`${sidecar.relativePath}
|
|
34131
|
+
${sidecar.content}`);
|
|
34132
|
+
current.staleCount += 1;
|
|
34133
|
+
byUri.set(sidecar.uri, current);
|
|
34134
|
+
}
|
|
34135
|
+
}
|
|
34136
|
+
return [...byUri.values()].map((target) => ({
|
|
34137
|
+
signature: sha256([target.uri, ...target.parts.sort()].join("\n---\n")),
|
|
34138
|
+
staleCount: target.staleCount,
|
|
34139
|
+
uri: target.uri
|
|
34140
|
+
}));
|
|
34141
|
+
}
|
|
34142
|
+
function formatRecallIndexRepairMessages(result, options = {}) {
|
|
34143
|
+
const messages = [];
|
|
34144
|
+
for (const uri of result.repairedUris) {
|
|
34145
|
+
messages.push(
|
|
34146
|
+
`${options.dryRun === true ? "Would auto-reindex stale recall scope" : "Auto-reindexed stale recall scope"}: ${uri}`
|
|
34147
|
+
);
|
|
34148
|
+
}
|
|
34149
|
+
for (const warning2 of result.warnings) {
|
|
34150
|
+
messages.push(`Auto-index repair warning: ${warning2}`);
|
|
34151
|
+
}
|
|
34152
|
+
return messages;
|
|
34153
|
+
}
|
|
34154
|
+
function filterStaleRecallSummaryRows(output) {
|
|
34155
|
+
return output.split(/\r?\n/).filter((line) => !isStaleRecallSummaryRow(line)).join("\n").trim();
|
|
34156
|
+
}
|
|
34157
|
+
function isStaleRecallSummaryRow(line) {
|
|
34158
|
+
return /viking:\/\/\S+\/\.(?:abstract|overview)\.md\b/.test(line) && isStaleSummary(line);
|
|
34159
|
+
}
|
|
34160
|
+
async function scanRoots(config2, options) {
|
|
34161
|
+
const accountRoot = (0, import_node_path2.join)(config2.agentContextHome, "data", "viking", config2.account);
|
|
34162
|
+
const roots = [
|
|
34163
|
+
{
|
|
34164
|
+
path: (0, import_node_path2.join)(accountRoot, "user", uriSegment(config2.user), "memories"),
|
|
34165
|
+
uri: `viking://user/${uriSegment(config2.user)}/memories`
|
|
34166
|
+
}
|
|
34167
|
+
];
|
|
34168
|
+
const query = options.query;
|
|
34169
|
+
if (query) {
|
|
34170
|
+
const project = await inferProjectFromQuery(config2.manifestPath, query);
|
|
34171
|
+
const projectPath = project?.uri.startsWith("viking://resources/") ? (0, import_node_path2.join)(accountRoot, "resources", ...project.uri.slice("viking://resources/".length).split("/")) : void 0;
|
|
34172
|
+
if (project && projectPath) {
|
|
34173
|
+
roots.push({ path: projectPath, uri: project.uri });
|
|
34174
|
+
}
|
|
34175
|
+
}
|
|
34176
|
+
if (options.includeManifestResources === true) {
|
|
34177
|
+
roots.push(...await manifestResourceRoots(config2, accountRoot));
|
|
34178
|
+
}
|
|
34179
|
+
const scanAgentSkills = options.includeAgentSkills === true || (query ? /\bskills?\b/.test(query.toLowerCase()) : false);
|
|
34180
|
+
if (scanAgentSkills) {
|
|
34181
|
+
roots.push({ path: (0, import_node_path2.join)(accountRoot, "resources", "agent-skills"), uri: "viking://resources/agent-skills" });
|
|
34182
|
+
}
|
|
34183
|
+
return dedupeRoots(roots);
|
|
34184
|
+
}
|
|
34185
|
+
async function manifestResourceRoots(config2, accountRoot) {
|
|
34186
|
+
try {
|
|
34187
|
+
const manifest = await readSeedManifest(config2.manifestPath);
|
|
34188
|
+
return manifest.projects.filter((project) => project.uri.startsWith("viking://resources/")).map((project) => ({
|
|
34189
|
+
path: (0, import_node_path2.join)(accountRoot, "resources", ...project.uri.slice("viking://resources/".length).split("/")),
|
|
34190
|
+
uri: project.uri
|
|
34191
|
+
}));
|
|
34192
|
+
} catch (_err) {
|
|
34193
|
+
return [];
|
|
34194
|
+
}
|
|
34195
|
+
}
|
|
34196
|
+
function dedupeRoots(roots) {
|
|
34197
|
+
const seen = /* @__PURE__ */ new Set();
|
|
34198
|
+
const deduped = [];
|
|
34199
|
+
for (const root of roots) {
|
|
34200
|
+
if (seen.has(root.uri)) {
|
|
34201
|
+
continue;
|
|
34202
|
+
}
|
|
34203
|
+
seen.add(root.uri);
|
|
34204
|
+
deduped.push(root);
|
|
34205
|
+
}
|
|
34206
|
+
return deduped;
|
|
34207
|
+
}
|
|
34208
|
+
async function staleSidecars(rootPath, rootUri) {
|
|
34209
|
+
const results = [];
|
|
34210
|
+
async function visit(path, depth) {
|
|
34211
|
+
let entries;
|
|
34212
|
+
try {
|
|
34213
|
+
entries = await (0, import_promises3.readdir)(path, { withFileTypes: true });
|
|
34214
|
+
} catch (_err) {
|
|
34215
|
+
return;
|
|
34216
|
+
}
|
|
34217
|
+
for (const entry of entries) {
|
|
34218
|
+
const childPath = (0, import_node_path2.join)(path, entry.name);
|
|
34219
|
+
if (entry.isFile() && isSummarySidecar(entry.name)) {
|
|
34220
|
+
let content;
|
|
34221
|
+
try {
|
|
34222
|
+
content = await (0, import_promises3.readFile)(childPath, "utf8");
|
|
34223
|
+
} catch (_err) {
|
|
34224
|
+
continue;
|
|
34225
|
+
}
|
|
34226
|
+
if (!isStaleSummary(content)) {
|
|
34227
|
+
continue;
|
|
34228
|
+
}
|
|
34229
|
+
const parentPath = (0, import_node_path2.dirname)(childPath);
|
|
34230
|
+
const parentRelative = toPosixPath((0, import_node_path2.relative)(rootPath, parentPath));
|
|
34231
|
+
const relativePath = toPosixPath((0, import_node_path2.relative)(rootPath, childPath));
|
|
34232
|
+
results.push({
|
|
34233
|
+
content: content.trim(),
|
|
34234
|
+
relativePath,
|
|
34235
|
+
uri: parentRelative ? `${trimLocalRootUri(rootUri)}/${parentRelative}` : trimLocalRootUri(rootUri)
|
|
34236
|
+
});
|
|
34237
|
+
} else if (entry.isDirectory() && depth < MAX_SCAN_DEPTH) {
|
|
34238
|
+
await visit(childPath, depth + 1);
|
|
34239
|
+
}
|
|
34240
|
+
}
|
|
34241
|
+
}
|
|
34242
|
+
await visit(rootPath, 0);
|
|
34243
|
+
return results;
|
|
34244
|
+
}
|
|
34245
|
+
function isSummarySidecar(name) {
|
|
34246
|
+
return name === ".abstract.md" || name === ".overview.md";
|
|
34247
|
+
}
|
|
34248
|
+
function isStaleSummary(content) {
|
|
34249
|
+
return content.includes("[Directory overview is not ready]") || content.includes("[Directory abstract is not ready]");
|
|
34250
|
+
}
|
|
34251
|
+
function trimLocalRootUri(uri) {
|
|
34252
|
+
return uri.endsWith("/") ? uri.slice(0, -1) : uri;
|
|
34253
|
+
}
|
|
34254
|
+
async function readAutoRepairState(config2) {
|
|
34255
|
+
const raw = await readFileIfExists(autoRepairStatePath(config2));
|
|
34256
|
+
if (!raw) {
|
|
34257
|
+
return { entries: {}, version: 1 };
|
|
34258
|
+
}
|
|
34259
|
+
try {
|
|
34260
|
+
const parsed = JSON.parse(raw);
|
|
34261
|
+
if (!isJsonObject(parsed) || parsed.version !== 1 || !isJsonObject(parsed.entries)) {
|
|
34262
|
+
return { entries: {}, version: 1 };
|
|
34263
|
+
}
|
|
34264
|
+
const entries = {};
|
|
34265
|
+
for (const [uri, entry] of Object.entries(parsed.entries)) {
|
|
34266
|
+
if (isJsonObject(entry) && typeof entry.signature === "string" && typeof entry.repairedAt === "string") {
|
|
34267
|
+
entries[uri] = { repairedAt: entry.repairedAt, signature: entry.signature };
|
|
34268
|
+
}
|
|
34269
|
+
}
|
|
34270
|
+
return { entries, version: 1 };
|
|
34271
|
+
} catch (_err) {
|
|
34272
|
+
return { entries: {}, version: 1 };
|
|
34273
|
+
}
|
|
34274
|
+
}
|
|
34275
|
+
async function writeAutoRepairState(config2, state) {
|
|
34276
|
+
const path = autoRepairStatePath(config2);
|
|
34277
|
+
await ensureDirectory((0, import_node_path2.dirname)(path), false);
|
|
34278
|
+
await (0, import_promises3.writeFile)(path, `${JSON.stringify(state, void 0, 2)}
|
|
34279
|
+
`, { encoding: "utf8", mode: 384 });
|
|
34280
|
+
}
|
|
34281
|
+
function autoRepairStatePath(config2) {
|
|
34282
|
+
return (0, import_node_path2.join)(config2.agentContextHome, AUTO_REPAIR_STATE_FILE);
|
|
34283
|
+
}
|
|
34284
|
+
function indexRepairWarning(uri, result) {
|
|
34285
|
+
return `${uri}: ${result.stderr.trim() || result.stdout.trim() || "reindex failed"}`;
|
|
34286
|
+
}
|
|
34287
|
+
|
|
34046
34288
|
// src/memory_hygiene.ts
|
|
34047
|
-
var
|
|
34289
|
+
var import_node_path3 = require("node:path");
|
|
34048
34290
|
var HYGIENE_SOURCES_HEADING = "## Threadnote Hygiene Sources";
|
|
34049
34291
|
var STALE_HANDOFF_AGE_MS = 14 * 24 * 60 * 60 * 1e3;
|
|
34050
34292
|
function parseMemoryDocument(uri, content) {
|
|
@@ -34327,7 +34569,7 @@ function branchFromBody(body) {
|
|
|
34327
34569
|
return branch?.split(/\s+/)[0]?.replace(/[.,;:]+$/g, "");
|
|
34328
34570
|
}
|
|
34329
34571
|
function topicFromUri(uri) {
|
|
34330
|
-
const name = (0,
|
|
34572
|
+
const name = (0, import_node_path3.basename)(uri).replace(/\.md$/, "");
|
|
34331
34573
|
return name.startsWith("threadnote-") ? void 0 : name;
|
|
34332
34574
|
}
|
|
34333
34575
|
function parseProjectFromUri(uri) {
|
|
@@ -34357,7 +34599,7 @@ function preferredKeepRecord(records, topic) {
|
|
|
34357
34599
|
}
|
|
34358
34600
|
function isStableRecord(record2, topic) {
|
|
34359
34601
|
const recordTopic = topic ?? topicForRecord(record2);
|
|
34360
|
-
return recordTopic !== void 0 && (0,
|
|
34602
|
+
return recordTopic !== void 0 && (0, import_node_path3.basename)(record2.uri) === `${uriSegment(recordTopic)}.md`;
|
|
34361
34603
|
}
|
|
34362
34604
|
function sortedNewestFirst(records) {
|
|
34363
34605
|
return [...records].sort((left, right) => {
|
|
@@ -34469,16 +34711,9 @@ function formatPlanSection(title, lines) {
|
|
|
34469
34711
|
}
|
|
34470
34712
|
|
|
34471
34713
|
// src/share.ts
|
|
34472
|
-
var
|
|
34714
|
+
var import_promises4 = require("node:fs/promises");
|
|
34473
34715
|
var import_node_os2 = require("node:os");
|
|
34474
|
-
var
|
|
34475
|
-
|
|
34476
|
-
// src/runtime.ts
|
|
34477
|
-
function withIdentity(config2, args) {
|
|
34478
|
-
return [...args, "--account", config2.account, "--user", config2.user, "--agent-id", config2.agentId];
|
|
34479
|
-
}
|
|
34480
|
-
|
|
34481
|
-
// src/share.ts
|
|
34716
|
+
var import_node_path4 = require("node:path");
|
|
34482
34717
|
var TEAMS_FILE_VERSION = 1;
|
|
34483
34718
|
var SHARED_SEGMENT = "shared";
|
|
34484
34719
|
var SHAREABLE_MEMORY_KIND_DIRS = ["durable"];
|
|
@@ -34585,7 +34820,7 @@ function autoShareState(config2) {
|
|
|
34585
34820
|
return state;
|
|
34586
34821
|
}
|
|
34587
34822
|
function pendingReindexesPath(config2) {
|
|
34588
|
-
return (0,
|
|
34823
|
+
return (0, import_node_path4.join)(config2.agentContextHome, "share", "auto-sync-pending-reindexes.json");
|
|
34589
34824
|
}
|
|
34590
34825
|
async function loadPendingReindexes(config2, state) {
|
|
34591
34826
|
const raw = await readFileIfExists(pendingReindexesPath(config2));
|
|
@@ -34622,18 +34857,18 @@ async function loadPendingReindexes(config2, state) {
|
|
|
34622
34857
|
async function writePendingReindexes(config2, state) {
|
|
34623
34858
|
const path = pendingReindexesPath(config2);
|
|
34624
34859
|
if (state.pendingReindexes.size === 0) {
|
|
34625
|
-
await (0,
|
|
34860
|
+
await (0, import_promises4.rm)(path, { force: true });
|
|
34626
34861
|
return;
|
|
34627
34862
|
}
|
|
34628
34863
|
const contents = {
|
|
34629
34864
|
teams: Object.fromEntries(state.pendingReindexes),
|
|
34630
34865
|
version: 1
|
|
34631
34866
|
};
|
|
34632
|
-
await (0,
|
|
34867
|
+
await (0, import_promises4.mkdir)((0, import_node_path4.dirname)(path), { recursive: true });
|
|
34633
34868
|
const tempPath = `${path}.${process.pid}.tmp`;
|
|
34634
|
-
await (0,
|
|
34869
|
+
await (0, import_promises4.writeFile)(tempPath, `${JSON.stringify(contents, void 0, 2)}
|
|
34635
34870
|
`, { encoding: "utf8", mode: 384 });
|
|
34636
|
-
await (0,
|
|
34871
|
+
await (0, import_promises4.rename)(tempPath, path);
|
|
34637
34872
|
}
|
|
34638
34873
|
async function refreshShareUpdateState(config2, options) {
|
|
34639
34874
|
const state = autoShareState(config2);
|
|
@@ -34729,7 +34964,7 @@ async function runShareSyncQuiet(config2, state, options) {
|
|
|
34729
34964
|
const beforeRev = await gitOutput(worktree, ["rev-parse", "HEAD"], false);
|
|
34730
34965
|
const pullResult = await runCommand(git, ["-C", worktree, "rebase", "@{u}"], { allowFailure: true });
|
|
34731
34966
|
if (pullResult.exitCode !== 0) {
|
|
34732
|
-
if (await exists((0,
|
|
34967
|
+
if (await exists((0, import_node_path4.join)(team.config.gitdir, "rebase-merge")) || await exists((0, import_node_path4.join)(team.config.gitdir, "rebase-apply"))) {
|
|
34733
34968
|
throw new Error(
|
|
34734
34969
|
`Automatic share sync hit git conflicts in ${worktree}. Resolve them in-place, run \`git -C ${worktree} rebase --continue\` (or --abort), then rerun recall/read.`
|
|
34735
34970
|
);
|
|
@@ -34814,10 +35049,10 @@ function normalizeTeamName(input) {
|
|
|
34814
35049
|
return candidate;
|
|
34815
35050
|
}
|
|
34816
35051
|
function teamsFilePath(config2) {
|
|
34817
|
-
return (0,
|
|
35052
|
+
return (0, import_node_path4.join)(config2.agentContextHome, "share", "teams.json");
|
|
34818
35053
|
}
|
|
34819
35054
|
function teamWorktreePath(config2, team) {
|
|
34820
|
-
return (0,
|
|
35055
|
+
return (0, import_node_path4.join)(
|
|
34821
35056
|
config2.agentContextHome,
|
|
34822
35057
|
"data",
|
|
34823
35058
|
"viking",
|
|
@@ -34830,7 +35065,7 @@ function teamWorktreePath(config2, team) {
|
|
|
34830
35065
|
);
|
|
34831
35066
|
}
|
|
34832
35067
|
function teamGitdirPath(config2, team) {
|
|
34833
|
-
return (0,
|
|
35068
|
+
return (0, import_node_path4.join)(config2.agentContextHome, "share", "teams", `${team}.gitdir`);
|
|
34834
35069
|
}
|
|
34835
35070
|
async function readTeamsFile(config2) {
|
|
34836
35071
|
const path = teamsFilePath(config2);
|
|
@@ -34886,7 +35121,7 @@ async function resolveTeam(config2, requested) {
|
|
|
34886
35121
|
return { config: found, name: wantName };
|
|
34887
35122
|
}
|
|
34888
35123
|
function workfileToVikingUri(config2, team, filePath) {
|
|
34889
|
-
const rel = (0,
|
|
35124
|
+
const rel = (0, import_node_path4.relative)(team.worktree, filePath).split(import_node_path4.sep).join("/");
|
|
34890
35125
|
return `viking://user/${uriSegment(config2.user)}/memories/${SHARED_SEGMENT}/${team.name}/${rel}`;
|
|
34891
35126
|
}
|
|
34892
35127
|
function isInSharedNamespace(config2, uri) {
|
|
@@ -35011,13 +35246,14 @@ async function writeMemoryFile(config2, ov, uri, content, initialMode, dryRun, o
|
|
|
35011
35246
|
}
|
|
35012
35247
|
return;
|
|
35013
35248
|
}
|
|
35014
|
-
const stagingDir = await (0,
|
|
35015
|
-
const tempPath = (0,
|
|
35249
|
+
const stagingDir = await (0, import_promises4.mkdtemp)((0, import_node_path4.join)((0, import_node_os2.tmpdir)(), "threadnote-share-"));
|
|
35250
|
+
const tempPath = (0, import_node_path4.join)(stagingDir, "body.txt");
|
|
35016
35251
|
try {
|
|
35017
|
-
await (0,
|
|
35252
|
+
await (0, import_promises4.writeFile)(tempPath, content, { encoding: "utf8", mode: 384 });
|
|
35018
35253
|
await writeOvFileWithRetry(config2, ov, uri, tempPath, initialMode, options);
|
|
35254
|
+
await refreshMemoryIndex(config2, ov, uri, options);
|
|
35019
35255
|
} finally {
|
|
35020
|
-
await (0,
|
|
35256
|
+
await (0, import_promises4.rm)(stagingDir, { force: true, recursive: true });
|
|
35021
35257
|
}
|
|
35022
35258
|
}
|
|
35023
35259
|
var BUSY_RETRY_BACKOFF_MS = [2e3, 5e3, 1e4, 2e4, 3e4];
|
|
@@ -35072,6 +35308,27 @@ async function writeOvFileWithRetry(config2, ov, uri, fromFile, initialMode, opt
|
|
|
35072
35308
|
}
|
|
35073
35309
|
}
|
|
35074
35310
|
}
|
|
35311
|
+
async function refreshMemoryIndex(config2, ov, uri, options = {}) {
|
|
35312
|
+
const result = await runCommand(
|
|
35313
|
+
ov,
|
|
35314
|
+
withIdentity(config2, ["reindex", uri, "--mode", "semantic_and_vectors", "--wait", "true"]),
|
|
35315
|
+
{ allowFailure: true }
|
|
35316
|
+
);
|
|
35317
|
+
if (result.exitCode === 0) {
|
|
35318
|
+
if (options.quiet !== true && result.stdout.trim()) {
|
|
35319
|
+
console.log(result.stdout.trim());
|
|
35320
|
+
}
|
|
35321
|
+
if (options.quiet !== true && result.stderr.trim()) {
|
|
35322
|
+
console.error(result.stderr.trim());
|
|
35323
|
+
}
|
|
35324
|
+
return;
|
|
35325
|
+
}
|
|
35326
|
+
if (options.quiet !== true) {
|
|
35327
|
+
console.error(
|
|
35328
|
+
`Memory stored, but index refresh failed for ${uri}: ${result.stderr.trim() || result.stdout.trim()}`
|
|
35329
|
+
);
|
|
35330
|
+
}
|
|
35331
|
+
}
|
|
35075
35332
|
async function waitForOvQueue(ov, config2, options = {}) {
|
|
35076
35333
|
const result = await runCommand(ov, withIdentity(config2, ["wait", "--timeout", "120"]), { allowFailure: true });
|
|
35077
35334
|
if (options.quiet !== true && result.stdout.trim()) {
|
|
@@ -35092,7 +35349,7 @@ ${stdout2}`.toLowerCase();
|
|
|
35092
35349
|
return output.includes("resource is busy") || output.includes("resource is being processed");
|
|
35093
35350
|
}
|
|
35094
35351
|
async function ingestSingleFile(ov, config2, uri, filePath, initialMode, options = {}) {
|
|
35095
|
-
const content = await (0,
|
|
35352
|
+
const content = await (0, import_promises4.readFile)(filePath, "utf8");
|
|
35096
35353
|
await writeMemoryFile(config2, ov, uri, content, initialMode, false, options);
|
|
35097
35354
|
}
|
|
35098
35355
|
async function removeMemoryUri(config2, ov, uri, dryRun, options = {}) {
|
|
@@ -35157,8 +35414,8 @@ async function listChangedFiles(worktree, beforeRev, afterRev) {
|
|
|
35157
35414
|
const oldRel = entries[index + 1];
|
|
35158
35415
|
const newRel = entries[index + 2];
|
|
35159
35416
|
if (oldRel && newRel) {
|
|
35160
|
-
changes.push({ path: (0,
|
|
35161
|
-
changes.push({ path: (0,
|
|
35417
|
+
changes.push({ path: (0, import_node_path4.join)(worktree, oldRel), relativePath: oldRel, status: "removed" });
|
|
35418
|
+
changes.push({ path: (0, import_node_path4.join)(worktree, newRel), relativePath: newRel, status: "added" });
|
|
35162
35419
|
}
|
|
35163
35420
|
index += 3;
|
|
35164
35421
|
continue;
|
|
@@ -35166,7 +35423,7 @@ async function listChangedFiles(worktree, beforeRev, afterRev) {
|
|
|
35166
35423
|
const rel = entries[index + 1];
|
|
35167
35424
|
if (rel) {
|
|
35168
35425
|
const status = head === "A" ? "added" : head === "D" ? "removed" : "modified";
|
|
35169
|
-
changes.push({ path: (0,
|
|
35426
|
+
changes.push({ path: (0, import_node_path4.join)(worktree, rel), relativePath: rel, status });
|
|
35170
35427
|
}
|
|
35171
35428
|
index += 2;
|
|
35172
35429
|
}
|
|
@@ -35516,6 +35773,14 @@ async function runRecallTool(config2, params) {
|
|
|
35516
35773
|
cwd: params.callerCwd,
|
|
35517
35774
|
includeProcessCwd: false
|
|
35518
35775
|
});
|
|
35776
|
+
let indexRepairMessages;
|
|
35777
|
+
try {
|
|
35778
|
+
const ov = await requiredOpenVikingCli2();
|
|
35779
|
+
const indexRepair = await repairStaleRecallIndex(config2, ov, { query: projectQuery });
|
|
35780
|
+
indexRepairMessages = formatRecallIndexRepairMessages(indexRepair);
|
|
35781
|
+
} catch (err) {
|
|
35782
|
+
indexRepairMessages = [`Auto-index repair warning: ${errorMessage(err)}`];
|
|
35783
|
+
}
|
|
35519
35784
|
const contextualParams = { ...params, query };
|
|
35520
35785
|
const baseArgs = [
|
|
35521
35786
|
"search",
|
|
@@ -35532,8 +35797,13 @@ async function runRecallTool(config2, params) {
|
|
|
35532
35797
|
return semanticResult;
|
|
35533
35798
|
}
|
|
35534
35799
|
const sections = [];
|
|
35535
|
-
|
|
35536
|
-
|
|
35800
|
+
const semanticText = filterStaleRecallSummaryRows(firstContent.text);
|
|
35801
|
+
const filteredSemanticText = semanticText !== firstContent.text.trim();
|
|
35802
|
+
if (semanticText) {
|
|
35803
|
+
sections.push(semanticText);
|
|
35804
|
+
}
|
|
35805
|
+
if (indexRepairMessages.length > 0) {
|
|
35806
|
+
sections.push(indexRepairMessages.join("\n"));
|
|
35537
35807
|
}
|
|
35538
35808
|
const seededSection = await seededResourcesSection(config2, contextualParams, projectQuery);
|
|
35539
35809
|
if (seededSection) {
|
|
@@ -35554,7 +35824,7 @@ ${exactMatches}`);
|
|
|
35554
35824
|
for (const warning2 of syncWarnings) {
|
|
35555
35825
|
sections.push(`Auto-sync warning: ${warning2}`);
|
|
35556
35826
|
}
|
|
35557
|
-
if (sections.length <= 1) {
|
|
35827
|
+
if (sections.length <= 1 && !filteredSemanticText) {
|
|
35558
35828
|
return semanticResult;
|
|
35559
35829
|
}
|
|
35560
35830
|
return { ...semanticResult, content: [{ type: "text", text: sections.join("\n\n") }] };
|
|
@@ -35592,7 +35862,7 @@ async function seededResourcesSection(config2, params, projectQuery) {
|
|
|
35592
35862
|
if (result.exitCode !== 0) {
|
|
35593
35863
|
return void 0;
|
|
35594
35864
|
}
|
|
35595
|
-
const body = [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join("\n");
|
|
35865
|
+
const body = filterStaleRecallSummaryRows([result.stdout.trim(), result.stderr.trim()].filter(Boolean).join("\n"));
|
|
35596
35866
|
if (!body) {
|
|
35597
35867
|
return void 0;
|
|
35598
35868
|
}
|
|
@@ -35914,7 +36184,7 @@ async function scopedCompactRecords(config2, options) {
|
|
|
35914
36184
|
const uriDirectory = memoryUriDirectoryForCompact(config2, kind, options.project);
|
|
35915
36185
|
let entries;
|
|
35916
36186
|
try {
|
|
35917
|
-
entries = await (0,
|
|
36187
|
+
entries = await (0, import_promises5.readdir)(directory, { withFileTypes: true });
|
|
35918
36188
|
} catch (_err) {
|
|
35919
36189
|
continue;
|
|
35920
36190
|
}
|
|
@@ -35922,7 +36192,7 @@ async function scopedCompactRecords(config2, options) {
|
|
|
35922
36192
|
if (!entry.isFile() || entry.name.startsWith(".") || !entry.name.endsWith(".md")) {
|
|
35923
36193
|
continue;
|
|
35924
36194
|
}
|
|
35925
|
-
const content = await readTextIfExists((0,
|
|
36195
|
+
const content = await readTextIfExists((0, import_node_path5.join)(directory, entry.name));
|
|
35926
36196
|
if (!content) {
|
|
35927
36197
|
continue;
|
|
35928
36198
|
}
|
|
@@ -35957,11 +36227,11 @@ function localMemoryDirectoryForCompact(config2, kind, project) {
|
|
|
35957
36227
|
const projectSegment = uriSegment2(project);
|
|
35958
36228
|
switch (kind) {
|
|
35959
36229
|
case "durable":
|
|
35960
|
-
return (0,
|
|
36230
|
+
return (0, import_node_path5.join)(root, "durable", "projects", projectSegment);
|
|
35961
36231
|
case "handoff":
|
|
35962
|
-
return (0,
|
|
36232
|
+
return (0, import_node_path5.join)(root, "handoffs", "active", projectSegment);
|
|
35963
36233
|
case "incident":
|
|
35964
|
-
return (0,
|
|
36234
|
+
return (0, import_node_path5.join)(root, "incidents", "active", projectSegment);
|
|
35965
36235
|
}
|
|
35966
36236
|
}
|
|
35967
36237
|
function memoryUriDirectoryForCompact(config2, kind, project) {
|
|
@@ -35981,18 +36251,18 @@ function localMemoryPathForUri(config2, uri) {
|
|
|
35981
36251
|
if (!uri.startsWith(prefix) || uri.includes("/shared/")) {
|
|
35982
36252
|
return void 0;
|
|
35983
36253
|
}
|
|
35984
|
-
const
|
|
35985
|
-
if (
|
|
36254
|
+
const relative3 = uri.slice(prefix.length);
|
|
36255
|
+
if (relative3.includes("..") || relative3.startsWith("/")) {
|
|
35986
36256
|
return void 0;
|
|
35987
36257
|
}
|
|
35988
|
-
return (0,
|
|
36258
|
+
return (0, import_node_path5.join)(localUserMemoriesRoot(config2), ...relative3.split("/"));
|
|
35989
36259
|
}
|
|
35990
36260
|
function localUserMemoriesRoot(config2) {
|
|
35991
|
-
return (0,
|
|
36261
|
+
return (0, import_node_path5.join)(config2.agentContextHome, "data", "viking", config2.account, "user", uriSegment2(config2.user), "memories");
|
|
35992
36262
|
}
|
|
35993
36263
|
async function readTextIfExists(path) {
|
|
35994
36264
|
try {
|
|
35995
|
-
return await (0,
|
|
36265
|
+
return await (0, import_promises5.readFile)(path, "utf8");
|
|
35996
36266
|
} catch (_err) {
|
|
35997
36267
|
return void 0;
|
|
35998
36268
|
}
|
|
@@ -36285,8 +36555,8 @@ async function runSharePublishTool(config2, sourceUri, options) {
|
|
|
36285
36555
|
`Refusing to publish: ${targetUri} already exists in the shared namespace. Inspect it via threadnote read; if it should be replaced, forget the existing shared copy first.`
|
|
36286
36556
|
);
|
|
36287
36557
|
}
|
|
36288
|
-
await ensureSharedDirectoryChain(config2, ov, targetUri, false);
|
|
36289
|
-
await writeMemoryFile(config2, ov, targetUri, content, "create", false);
|
|
36558
|
+
await ensureSharedDirectoryChain(config2, ov, targetUri, false, { quiet: true });
|
|
36559
|
+
await writeMemoryFile(config2, ov, targetUri, content, "create", false, { quiet: true });
|
|
36290
36560
|
const messages = [`Published ${sourceUri} -> ${targetUri}`];
|
|
36291
36561
|
for (const redaction of scrub.redactions) {
|
|
36292
36562
|
messages.push(`Redacted ${redaction.count}\xD7 ${redaction.name} before publish.`);
|
|
@@ -36327,7 +36597,7 @@ function withIdentity2(config2, args) {
|
|
|
36327
36597
|
return [...args, "--account", config2.account, "--user", config2.user, "--agent-id", config2.agentId];
|
|
36328
36598
|
}
|
|
36329
36599
|
async function requiredOpenVikingCli2() {
|
|
36330
|
-
const command2 = process.env.THREADNOTE_OV ?? await findExecutable(["ov", "openviking"]) ?? await firstExistingPath([(0,
|
|
36600
|
+
const command2 = process.env.THREADNOTE_OV ?? await findExecutable(["ov", "openviking"]) ?? await firstExistingPath([(0, import_node_path5.join)((0, import_node_os3.homedir)(), ".local", "bin", "ov"), (0, import_node_path5.join)((0, import_node_os3.homedir)(), ".local", "bin", "openviking")]);
|
|
36331
36601
|
if (!command2) {
|
|
36332
36602
|
throw new Error("Neither ov nor openviking was found. Run threadnote install first.");
|
|
36333
36603
|
}
|
|
@@ -36336,8 +36606,8 @@ async function requiredOpenVikingCli2() {
|
|
|
36336
36606
|
async function firstExistingPath(paths) {
|
|
36337
36607
|
for (const path of paths) {
|
|
36338
36608
|
try {
|
|
36339
|
-
await (0,
|
|
36340
|
-
return await (0,
|
|
36609
|
+
await (0, import_promises5.access)(path);
|
|
36610
|
+
return await (0, import_promises5.realpath)(path);
|
|
36341
36611
|
} catch (_err) {
|
|
36342
36612
|
continue;
|
|
36343
36613
|
}
|