threadnote 1.4.0 → 1.4.2

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.
@@ -35689,6 +35689,9 @@ function commandResultFromExecFileCallback(params) {
35689
35689
  function defaultCommandTimeoutMs() {
35690
35690
  return positiveIntegerFromEnv("THREADNOTE_COMMAND_TIMEOUT_MS") ?? 10 * 60 * 1e3;
35691
35691
  }
35692
+ function reindexWaitTimeoutMs() {
35693
+ return positiveIntegerFromEnv("THREADNOTE_REINDEX_TIMEOUT_MS") ?? 12e4;
35694
+ }
35692
35695
  function defaultCommandMaxOutputBytes() {
35693
35696
  return positiveIntegerFromEnv("THREADNOTE_COMMAND_MAX_OUTPUT_BYTES") ?? 5 * 1024 * 1024;
35694
35697
  }
@@ -35712,6 +35715,57 @@ async function sleep(ms) {
35712
35715
  setTimeout(resolvePromise, ms);
35713
35716
  });
35714
35717
  }
35718
+ function compareVersions(a, b) {
35719
+ const left = parseVersion(a);
35720
+ const right = parseVersion(b);
35721
+ for (let index = 0; index < 3; index += 1) {
35722
+ const difference = left.numbers[index] - right.numbers[index];
35723
+ if (difference !== 0) {
35724
+ return difference;
35725
+ }
35726
+ }
35727
+ const rankDelta = suffixRank(left.suffix) - suffixRank(right.suffix);
35728
+ if (rankDelta !== 0) {
35729
+ return rankDelta;
35730
+ }
35731
+ if (left.suffix === right.suffix) {
35732
+ return 0;
35733
+ }
35734
+ return (left.suffix ?? "").localeCompare(right.suffix ?? "");
35735
+ }
35736
+ function suffixRank(suffix) {
35737
+ if (suffix === void 0) {
35738
+ return 0;
35739
+ }
35740
+ return /^post/i.test(suffix) ? 1 : -1;
35741
+ }
35742
+ function parseVersion(version2) {
35743
+ const normalized = version2.trim().replace(/^v/, "").split("+", 1)[0];
35744
+ const core = normalized.match(/^\d+(?:\.\d+){0,2}/)?.[0] ?? "";
35745
+ const rawSuffix = normalized.slice(core.length).replace(/^[-_.]/, "");
35746
+ const suffix = core.length > 0 && rawSuffix.length > 0 ? rawSuffix : void 0;
35747
+ const parts = core.split(".");
35748
+ return {
35749
+ numbers: [
35750
+ safeVersionNumber(Number.parseInt(parts[0] ?? "", 10)),
35751
+ safeVersionNumber(Number.parseInt(parts[1] ?? "", 10)),
35752
+ safeVersionNumber(Number.parseInt(parts[2] ?? "", 10))
35753
+ ],
35754
+ suffix
35755
+ };
35756
+ }
35757
+ function safeVersionNumber(value) {
35758
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : 0;
35759
+ }
35760
+ function formatStaleVersionNotice(runningVersion, diskVersion) {
35761
+ if (runningVersion === void 0 || diskVersion === void 0) {
35762
+ return void 0;
35763
+ }
35764
+ if (compareVersions(diskVersion, runningVersion) <= 0) {
35765
+ return void 0;
35766
+ }
35767
+ return `threadnote ${diskVersion} is installed but this MCP server is still running ${runningVersion}. Reconnect the threadnote MCP server (e.g. /mcp) to load the update.`;
35768
+ }
35715
35769
  async function ensureDirectory(path, dryRun) {
35716
35770
  if (dryRun) {
35717
35771
  console.log(`Would create directory: ${path}`);
@@ -36215,6 +36269,20 @@ function shellQuote(value) {
36215
36269
  }
36216
36270
  return `'${value.replaceAll("'", `'"'"'`)}'`;
36217
36271
  }
36272
+ function toolRoot() {
36273
+ if ((0, import_node_fs.existsSync)((0, import_node_path.join)(__dirname, "package.json"))) {
36274
+ return __dirname;
36275
+ }
36276
+ return (0, import_node_path.resolve)(__dirname, "..");
36277
+ }
36278
+ async function currentPackageVersion() {
36279
+ const rawPackage = await (0, import_promises.readFile)((0, import_node_path.join)(toolRoot(), "package.json"), "utf8");
36280
+ const parsed = JSON.parse(rawPackage);
36281
+ if (!isJsonObject(parsed) || typeof parsed.version !== "string") {
36282
+ throw new Error("Could not read current threadnote package version.");
36283
+ }
36284
+ return parsed.version;
36285
+ }
36218
36286
  function errorMessage(err) {
36219
36287
  return err instanceof Error ? err.message : String(err);
36220
36288
  }
@@ -36290,7 +36358,7 @@ function readStringArray(object3, key) {
36290
36358
 
36291
36359
  // src/runtime.ts
36292
36360
  function withIdentity(config2, args) {
36293
- return [...args, "--account", config2.account, "--user", config2.user, "--agent-id", config2.agentId];
36361
+ return [...args, "--account", config2.account, "--user", config2.user];
36294
36362
  }
36295
36363
 
36296
36364
  // src/index_repair.ts
@@ -36334,7 +36402,11 @@ async function repairStaleRecallIndex(config2, ov, options = {}) {
36334
36402
  const result = await runCommand(
36335
36403
  ov,
36336
36404
  withIdentity(config2, ["reindex", target.uri, "--mode", "semantic_and_vectors", "--wait", "true"]),
36337
- { allowFailure: true }
36405
+ // Bound the wait: `ov reindex` has no --timeout, so a stuck/poisoned
36406
+ // semantic queue would block each target for the full 10-min command
36407
+ // timeout. A timed-out target counts as a failure and trips the
36408
+ // consecutive-failure stop instead of hanging the whole repair.
36409
+ { allowFailure: true, timeoutMs: reindexWaitTimeoutMs() }
36338
36410
  );
36339
36411
  if (result.exitCode === 0) {
36340
36412
  consecutiveFailures = 0;
@@ -38580,7 +38652,7 @@ async function refreshMemoryIndex(config2, ov, uri, options = {}) {
38580
38652
  const result = await runCommand(
38581
38653
  ov,
38582
38654
  withIdentity(config2, ["reindex", uri, "--mode", "vectors_only", "--wait", "true"]),
38583
- { allowFailure: true }
38655
+ { allowFailure: true, timeoutMs: reindexWaitTimeoutMs() }
38584
38656
  );
38585
38657
  if (result.exitCode === 0) {
38586
38658
  if (options.quiet !== true && result.stdout.trim()) {
@@ -39260,8 +39332,36 @@ function formatPlanSection(title, lines) {
39260
39332
  }
39261
39333
 
39262
39334
  // src/mcp_server.ts
39335
+ var mcpStartupVersion;
39336
+ var staleNoticeCache;
39337
+ var STALE_NOTICE_TTL_MS = 6e4;
39338
+ async function staleVersionNotice() {
39339
+ if (mcpStartupVersion === void 0) {
39340
+ return void 0;
39341
+ }
39342
+ const nowMs = Date.now();
39343
+ if (staleNoticeCache && nowMs - staleNoticeCache.checkedAtMs < STALE_NOTICE_TTL_MS) {
39344
+ return staleNoticeCache.notice;
39345
+ }
39346
+ let notice;
39347
+ try {
39348
+ notice = formatStaleVersionNotice(mcpStartupVersion, await currentPackageVersion());
39349
+ } catch {
39350
+ notice = void 0;
39351
+ }
39352
+ staleNoticeCache = { checkedAtMs: nowMs, notice };
39353
+ return notice;
39354
+ }
39355
+ async function withStaleVersionNotice(result) {
39356
+ const notice = await staleVersionNotice();
39357
+ if (notice === void 0) {
39358
+ return result;
39359
+ }
39360
+ return { ...result, content: [...result.content ?? [], { type: "text", text: `\u26A0 ${notice}` }] };
39361
+ }
39263
39362
  async function main() {
39264
39363
  const config2 = getRuntimeConfig();
39364
+ mcpStartupVersion = await currentPackageVersion().catch(() => void 0);
39265
39365
  const server = new McpServer(
39266
39366
  { name: "threadnote-local-adapter", version: "0.2.0" },
39267
39367
  {
@@ -39459,7 +39559,7 @@ function registerTools(server, config2) {
39459
39559
  description: "Check OpenViking server health through the CLI.",
39460
39560
  inputSchema: {}
39461
39561
  },
39462
- async () => runOpenVikingMcpTool(config2, "health", {})
39562
+ async () => withStaleVersionNotice(await runOpenVikingMcpTool(config2, "health", {}))
39463
39563
  );
39464
39564
  registerOpenVikingParityTools(server, config2);
39465
39565
  server.registerTool(
@@ -39952,14 +40052,16 @@ function registerSearchTool(server, config2, name, description) {
39952
40052
  if (!checkedUri.ok) {
39953
40053
  return checkedUri.error;
39954
40054
  }
39955
- return runRecallTool(config2, {
39956
- callerCwd,
39957
- query: checkedQuery.value,
39958
- pinnedUri: checkedUri.value,
39959
- nodeLimit,
39960
- includeArchived: includeArchived === true,
39961
- threshold: threshold === void 0 ? void 0 : String(threshold)
39962
- });
40055
+ return withStaleVersionNotice(
40056
+ await runRecallTool(config2, {
40057
+ callerCwd,
40058
+ query: checkedQuery.value,
40059
+ pinnedUri: checkedUri.value,
40060
+ nodeLimit,
40061
+ includeArchived: includeArchived === true,
40062
+ threshold: threshold === void 0 ? void 0 : String(threshold)
40063
+ })
40064
+ );
39963
40065
  }
39964
40066
  );
39965
40067
  }
@@ -40187,11 +40289,13 @@ function registerStoreTool(server, config2, name, description) {
40187
40289
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
40188
40290
  topic: normalizeOptionalMetadata2(topic)
40189
40291
  };
40190
- return writeDurableMemory(config2, {
40191
- bodyText: checkedText.value,
40192
- metadata,
40193
- replaceUri: checkedReplaceUri.value
40194
- });
40292
+ return withStaleVersionNotice(
40293
+ await writeDurableMemory(config2, {
40294
+ bodyText: checkedText.value,
40295
+ metadata,
40296
+ replaceUri: checkedReplaceUri.value
40297
+ })
40298
+ );
40195
40299
  }
40196
40300
  );
40197
40301
  }
@@ -40814,8 +40918,9 @@ async function runOpenVikingMcpTool(config2, toolName, args) {
40814
40918
  const transport = new StreamableHTTPClientTransport(new URL(config2.openVikingMcpUrl), {
40815
40919
  requestInit: {
40816
40920
  headers: {
40921
+ // OpenViking 0.4.x dropped agent_id as an identity input; only
40922
+ // account + user are honored (mirrors withIdentity in runtime.ts).
40817
40923
  "X-OpenViking-Account": config2.account,
40818
- "X-OpenViking-Agent": config2.agentId,
40819
40924
  "X-OpenViking-User": config2.user
40820
40925
  }
40821
40926
  }
@@ -3835,6 +3835,9 @@ function commandResultFromExecFileCallback(params) {
3835
3835
  function defaultCommandTimeoutMs() {
3836
3836
  return positiveIntegerFromEnv("THREADNOTE_COMMAND_TIMEOUT_MS") ?? 10 * 60 * 1e3;
3837
3837
  }
3838
+ function reindexWaitTimeoutMs() {
3839
+ return positiveIntegerFromEnv("THREADNOTE_REINDEX_TIMEOUT_MS") ?? 12e4;
3840
+ }
3838
3841
  function defaultCommandMaxOutputBytes() {
3839
3842
  return positiveIntegerFromEnv("THREADNOTE_COMMAND_MAX_OUTPUT_BYTES") ?? 5 * 1024 * 1024;
3840
3843
  }
@@ -4580,6 +4583,14 @@ function toolRoot() {
4580
4583
  }
4581
4584
  return (0, import_node_path2.resolve)(__dirname, "..");
4582
4585
  }
4586
+ async function currentPackageVersion() {
4587
+ const rawPackage = await (0, import_promises.readFile)((0, import_node_path2.join)(toolRoot(), "package.json"), "utf8");
4588
+ const parsed = JSON.parse(rawPackage);
4589
+ if (!isJsonObject(parsed) || typeof parsed.version !== "string") {
4590
+ throw new Error("Could not read current threadnote package version.");
4591
+ }
4592
+ return parsed.version;
4593
+ }
4583
4594
  function errorMessage(err) {
4584
4595
  return err instanceof Error ? err.message : String(err);
4585
4596
  }
@@ -7548,7 +7559,7 @@ function openVikingServerArgs(config) {
7548
7559
  return ["--config", (0, import_node_path4.join)(config.agentContextHome, "ov.conf"), "--host", config.host, "--port", String(config.port)];
7549
7560
  }
7550
7561
  function withIdentity(config, args) {
7551
- return [...args, "--account", config.account, "--user", config.user, "--agent-id", config.agentId];
7562
+ return [...args, "--account", config.account, "--user", config.user];
7552
7563
  }
7553
7564
  function renderTemplate(template, config, extras = {}) {
7554
7565
  let rendered = template.replaceAll("{{THREADNOTE_HOME}}", config.agentContextHome).replaceAll("{{OPENVIKING_ACCOUNT}}", config.account).replaceAll("{{OPENVIKING_AGENT_ID}}", config.agentId).replaceAll("{{OPENVIKING_HOST}}", config.host).replaceAll("{{OPENVIKING_PORT}}", String(config.port)).replaceAll("{{OPENVIKING_USER}}", config.user);
@@ -7602,7 +7613,11 @@ async function repairStaleRecallIndex(config, ov, options = {}) {
7602
7613
  const result = await runCommand(
7603
7614
  ov,
7604
7615
  withIdentity(config, ["reindex", target.uri, "--mode", "semantic_and_vectors", "--wait", "true"]),
7605
- { allowFailure: true }
7616
+ // Bound the wait: `ov reindex` has no --timeout, so a stuck/poisoned
7617
+ // semantic queue would block each target for the full 10-min command
7618
+ // timeout. A timed-out target counts as a failure and trips the
7619
+ // consecutive-failure stop instead of hanging the whole repair.
7620
+ { allowFailure: true, timeoutMs: reindexWaitTimeoutMs() }
7606
7621
  );
7607
7622
  if (result.exitCode === 0) {
7608
7623
  consecutiveFailures = 0;
@@ -10905,7 +10920,7 @@ async function refreshMemoryIndex(config, ov, uri, options = {}) {
10905
10920
  const result = await runCommand(
10906
10921
  ov,
10907
10922
  withIdentity(config, ["reindex", uri, "--mode", "vectors_only", "--wait", "true"]),
10908
- { allowFailure: true }
10923
+ { allowFailure: true, timeoutMs: reindexWaitTimeoutMs() }
10909
10924
  );
10910
10925
  if (result.exitCode === 0) {
10911
10926
  if (options.quiet !== true && result.stdout.trim()) {
@@ -13423,14 +13438,6 @@ async function getUpdateInfo(config, options) {
13423
13438
  registry: options.registry
13424
13439
  };
13425
13440
  }
13426
- async function currentPackageVersion() {
13427
- const rawPackage = await (0, import_promises10.readFile)((0, import_node_path13.join)(toolRoot(), "package.json"), "utf8");
13428
- const parsed = JSON.parse(rawPackage);
13429
- if (!isJsonObject(parsed) || typeof parsed.version !== "string") {
13430
- throw new Error("Could not read current threadnote package version.");
13431
- }
13432
- return parsed.version;
13433
- }
13434
13441
  async function fetchLatestVersion2(registry) {
13435
13442
  const url = new URL(`${NPM_PACKAGE_NAME}/latest`, normalizeRegistry(registry));
13436
13443
  const controller = new AbortController();
@@ -14256,22 +14263,20 @@ async function recallShapeCheck(config) {
14256
14263
  if (result.exitCode !== 0) {
14257
14264
  return { name: "recall shape", status: "warn", detail: "search failed; run threadnote repair" };
14258
14265
  }
14259
- let parsed;
14266
+ const start = result.stdout.search(/^\{/m);
14267
+ let envelope;
14260
14268
  try {
14261
- parsed = JSON.parse(result.stdout.trim());
14269
+ const parsed = start >= 0 ? JSON.parse(result.stdout.slice(start)) : void 0;
14270
+ envelope = isJsonObject(parsed) ? parsed.result : void 0;
14262
14271
  } catch {
14263
- return {
14264
- name: "recall shape",
14265
- status: "warn",
14266
- detail: "search output is not JSON; recall may silently return nothing"
14267
- };
14272
+ envelope = void 0;
14268
14273
  }
14269
14274
  const buckets = ["memories", "resources", "skills"];
14270
- if (!isJsonObject(parsed) || !buckets.some((key) => Array.isArray(parsed[key]))) {
14275
+ if (!isJsonObject(envelope) || !buckets.some((key) => Array.isArray(envelope[key]))) {
14271
14276
  return {
14272
14277
  name: "recall shape",
14273
14278
  status: "warn",
14274
- detail: `search JSON missing ${buckets.join("/")} buckets; recall parsing is out of sync with this OpenViking`
14279
+ detail: `search JSON missing the result.{${buckets.join(",")}} buckets recall parsing depends on`
14275
14280
  };
14276
14281
  }
14277
14282
  return { name: "recall shape", status: "ok", detail: "memories/resources/skills buckets present" };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "threadnote",
3
- "version": "1.4.0",
3
+ "version": "1.4.2",
4
4
  "type": "commonjs",
5
5
  "main": "dist/threadnote.cjs",
6
6
  "description": "Shared local context and handoffs for development agents",