@algosuite/vo-mcp 0.2.0-beta.20 → 0.2.0-beta.22

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/dist/cli.js CHANGED
@@ -1399,6 +1399,98 @@ var init_safe_memory_file = __esm({
1399
1399
  }
1400
1400
  });
1401
1401
 
1402
+ // src/tools/memory/memory-knowledge-bridge.ts
1403
+ var memory_knowledge_bridge_exports = {};
1404
+ __export(memory_knowledge_bridge_exports, {
1405
+ extractMemoryTitle: () => extractMemoryTitle,
1406
+ upsertMemoryFilesAsKnowledge: () => upsertMemoryFilesAsKnowledge
1407
+ });
1408
+ import { existsSync as existsSync5, readdirSync as readdirSync4, readFileSync as readFileSync7 } from "node:fs";
1409
+ function extractMemoryTitle(fileName, content) {
1410
+ const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
1411
+ if (frontmatter) {
1412
+ const description23 = frontmatter[1].match(/^description:\s*(.+)$/m);
1413
+ if (description23 && description23[1].trim()) return description23[1].trim().slice(0, 200);
1414
+ }
1415
+ const heading = content.match(/^#\s+(.+)$/m);
1416
+ if (heading && heading[1].trim()) return heading[1].trim().slice(0, 200);
1417
+ return fileName;
1418
+ }
1419
+ async function upsertMemoryFilesAsKnowledge(options) {
1420
+ const { controlPlaneUrl, token, memoryDir, fetchFn } = options;
1421
+ let files;
1422
+ try {
1423
+ if (!existsSync5(memoryDir)) {
1424
+ return { attempted: 0, upserted: 0, failed: 0, failures: [] };
1425
+ }
1426
+ files = readdirSync4(memoryDir).filter(
1427
+ (f) => f.endsWith(".md") && f.toUpperCase() !== "MEMORY.MD"
1428
+ );
1429
+ } catch (err) {
1430
+ return {
1431
+ attempted: 0,
1432
+ upserted: 0,
1433
+ failed: 1,
1434
+ failures: [`memory dir scan: ${err instanceof Error ? err.message : String(err)}`]
1435
+ };
1436
+ }
1437
+ let upserted = 0;
1438
+ const failures = [];
1439
+ for (const fileName of files) {
1440
+ try {
1441
+ const content = readFileSync7(resolveMemoryFilePath(memoryDir, fileName), "utf8");
1442
+ if (content.length > CONTENT_HARD_LIMIT) {
1443
+ failures.push(`${fileName}: ${content.length} chars exceeds the ${CONTENT_HARD_LIMIT} server limit \u2014 split the memory file`);
1444
+ continue;
1445
+ }
1446
+ const title = extractMemoryTitle(fileName, content);
1447
+ const base = {
1448
+ knowledge_class: "memory",
1449
+ source_path: `memory/${fileName}`,
1450
+ title,
1451
+ content
1452
+ };
1453
+ const post = (body) => fetchFn(`${controlPlaneUrl}/api/v1/knowledge/private`, {
1454
+ method: "POST",
1455
+ headers: {
1456
+ authorization: `Bearer ${token}`,
1457
+ "content-type": "application/json"
1458
+ },
1459
+ body: JSON.stringify(body)
1460
+ });
1461
+ let response = await post({
1462
+ ...base,
1463
+ provenance: { written_by: "memory-bridge", source_kind: "operator_memory" }
1464
+ });
1465
+ if (response.status === 400) {
1466
+ response = await post(base);
1467
+ }
1468
+ if (response.status >= 200 && response.status < 300) {
1469
+ upserted += 1;
1470
+ } else {
1471
+ const text = await response.text();
1472
+ failures.push(`${fileName}: HTTP ${response.status} ${text.slice(0, 80)}`);
1473
+ }
1474
+ } catch (err) {
1475
+ failures.push(`${fileName}: ${err instanceof Error ? err.message : String(err)}`);
1476
+ }
1477
+ }
1478
+ return {
1479
+ attempted: files.length,
1480
+ upserted,
1481
+ failed: failures.length,
1482
+ failures: failures.slice(0, 5)
1483
+ };
1484
+ }
1485
+ var CONTENT_HARD_LIMIT;
1486
+ var init_memory_knowledge_bridge = __esm({
1487
+ "src/tools/memory/memory-knowledge-bridge.ts"() {
1488
+ "use strict";
1489
+ init_safe_memory_file();
1490
+ CONTENT_HARD_LIMIT = 5e5;
1491
+ }
1492
+ });
1493
+
1402
1494
  // src/tools/memory/sync-config.ts
1403
1495
  var sync_config_exports = {};
1404
1496
  __export(sync_config_exports, {
@@ -1413,7 +1505,7 @@ __export(sync_config_exports, {
1413
1505
  });
1414
1506
  import { homedir as homedir5 } from "node:os";
1415
1507
  import { join as join7 } from "node:path";
1416
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync3, readdirSync as readdirSync4 } from "node:fs";
1508
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync3, readdirSync as readdirSync5 } from "node:fs";
1417
1509
  function isToolInput22(v) {
1418
1510
  if (typeof v !== "object" || v === null) return false;
1419
1511
  const o = v;
@@ -1422,7 +1514,7 @@ function isToolInput22(v) {
1422
1514
  return true;
1423
1515
  }
1424
1516
  function deriveProjectSlug(cwd) {
1425
- return cwd.replace(/\\/g, "/").replace(/\/+$/g, "").replace(/^([a-zA-Z]):/, (_m, drive) => `${drive.toUpperCase()}:`).replace(/[^a-zA-Z0-9]/g, "-");
1517
+ return cwd.replace(/([^:\\/])[\\/]+$/, "$1").replace(/\\/g, "/").replace(/^([a-zA-Z]):/, (_m, drive) => `${drive.toUpperCase()}:`).replace(/[^a-zA-Z0-9]/g, "-");
1426
1518
  }
1427
1519
  function getMemoryDir(cwd) {
1428
1520
  const slug = deriveProjectSlug(cwd);
@@ -1457,12 +1549,12 @@ async function pullMemory(controlPlaneUrl, token, memoryDir, fetchFn) {
1457
1549
  return { pulled: data.entries.length, files };
1458
1550
  }
1459
1551
  async function pushMemory(controlPlaneUrl, token, memoryDir, sessionId, fetchFn) {
1460
- if (!existsSync5(memoryDir)) {
1552
+ if (!existsSync6(memoryDir)) {
1461
1553
  return { pushed: 0, created: 0, updated: 0 };
1462
1554
  }
1463
- const localFiles = readdirSync4(memoryDir).filter((f) => f.endsWith(".md")).map((f) => ({
1555
+ const localFiles = readdirSync5(memoryDir).filter((f) => f.endsWith(".md")).map((f) => ({
1464
1556
  file_name: f,
1465
- content: readFileSync7(resolveMemoryFilePath(memoryDir, f), "utf8"),
1557
+ content: readFileSync8(resolveMemoryFilePath(memoryDir, f), "utf8"),
1466
1558
  entry_type: f === "MEMORY.md" ? "index" : "topic"
1467
1559
  }));
1468
1560
  if (localFiles.length === 0) {
@@ -1571,13 +1663,32 @@ async function runMemorySync(action, cwd, sessionId, fetchFn = globalThis.fetch)
1571
1663
  return { synced: true, action: "pull", pulled: result2.pulled, files: result2.files, memory_dir: memoryDir };
1572
1664
  }
1573
1665
  const result = await pushMemory(baseUrl, token, memoryDir, sessionId, fetchFn);
1666
+ let bridge = { upserted: 0, failed: 0, failures: [] };
1667
+ try {
1668
+ const { upsertMemoryFilesAsKnowledge: upsertMemoryFilesAsKnowledge2 } = await Promise.resolve().then(() => (init_memory_knowledge_bridge(), memory_knowledge_bridge_exports));
1669
+ bridge = await upsertMemoryFilesAsKnowledge2({
1670
+ controlPlaneUrl: baseUrl,
1671
+ token,
1672
+ memoryDir,
1673
+ fetchFn
1674
+ });
1675
+ } catch (err) {
1676
+ bridge = {
1677
+ upserted: 0,
1678
+ failed: 1,
1679
+ failures: [`bridge unavailable: ${err instanceof Error ? err.message : String(err)}`]
1680
+ };
1681
+ }
1574
1682
  return {
1575
1683
  synced: true,
1576
1684
  action: "push",
1577
1685
  pushed: result.pushed,
1578
1686
  created: result.created,
1579
1687
  updated: result.updated,
1580
- memory_dir: memoryDir
1688
+ memory_dir: memoryDir,
1689
+ knowledge_upserted: bridge.upserted,
1690
+ knowledge_failed: bridge.failed,
1691
+ ...bridge.failed > 0 ? { knowledge_failures: bridge.failures } : {}
1581
1692
  };
1582
1693
  } catch (err) {
1583
1694
  const message = err instanceof Error ? err.message : String(err);
@@ -5507,6 +5618,7 @@ init_sync_config();
5507
5618
  init_common();
5508
5619
  var UPSERT_TOOL_NAME = "vo_private_knowledge_upsert";
5509
5620
  var CONTEXT_TOOL_NAME = "vo_private_knowledge_context";
5621
+ var INVALIDATE_TOOL_NAME = "vo_private_knowledge_invalidate";
5510
5622
  var KNOWLEDGE_CLASSES = ["memory", "skill", "doctrine", "hook", "command"];
5511
5623
  var PRECISION_CHAR_BUDGET = 12e3;
5512
5624
  var upsertInputSchema = {
@@ -5530,8 +5642,18 @@ var contextInputSchema = {
5530
5642
  required: ["query"],
5531
5643
  additionalProperties: false
5532
5644
  };
5645
+ var invalidateInputSchema = {
5646
+ type: "object",
5647
+ properties: {
5648
+ knowledge_class: { type: "string", enum: KNOWLEDGE_CLASSES },
5649
+ source_path: { type: "string", minLength: 1, maxLength: 400, description: "Stable private source identifier of the entry to invalidate \u2014 must match the source_path used at upsert." }
5650
+ },
5651
+ required: ["knowledge_class", "source_path"],
5652
+ additionalProperties: false
5653
+ };
5533
5654
  var upsertDescription = "Uploads or refreshes the authenticated operator\u2019s private cloud knowledge. Works for Claude, Codex, Cursor, and cowork clients via the same vo-mcp login credential. Returns metadata only, not raw stored content. PRECISION DISCIPLINE: keep each entry tight and focused (~1-3 pages) with a descriptive retrieval-friendly title \u2014 retrieval surfaces whole entries, so small dense entries beat bulk dumps. Split large corpora into focused entries, then run a retrieval self-test via vo_private_knowledge_context before relying on the knowledge.";
5534
5655
  var contextDescription = "Retrieves prompt-ready private knowledge context for the authenticated operator. Returns snippets/context only; no raw corpus download. Also the retrieval self-test surface: after upserting critical knowledge, query for it here and confirm the entry surfaces before trusting it in downstream work.";
5656
+ var invalidateDescription = `Soft-deletes one private-knowledge entry for the authenticated operator: closes the live entry\u2019s validity window (bi-temporal) so it stops surfacing in retrieval. Never destroys data \u2014 invalidated versions remain queryable server-side via include_invalidated. Identify the entry by the same { knowledge_class, source_path } used at upsert; a not_found response means no live entry matches. After invalidating, self-test via ${CONTEXT_TOOL_NAME} to confirm the entry no longer surfaces.`;
5535
5657
  function isKnowledgeClass(value) {
5536
5658
  return typeof value === "string" && KNOWLEDGE_CLASSES.includes(value);
5537
5659
  }
@@ -5540,6 +5662,11 @@ function isUpsertInput(value) {
5540
5662
  const input = value;
5541
5663
  return isKnowledgeClass(input["knowledge_class"]) && typeof input["source_path"] === "string" && typeof input["title"] === "string" && typeof input["content"] === "string";
5542
5664
  }
5665
+ function isInvalidateInput(value) {
5666
+ if (typeof value !== "object" || value === null) return false;
5667
+ const input = value;
5668
+ return isKnowledgeClass(input["knowledge_class"]) && typeof input["source_path"] === "string";
5669
+ }
5543
5670
  function isContextInput(value) {
5544
5671
  if (typeof value !== "object" || value === null) return false;
5545
5672
  const input = value;
@@ -5573,7 +5700,12 @@ async function callPrivateKnowledge(path3, body, fetchFn) {
5573
5700
  body: JSON.stringify(body)
5574
5701
  });
5575
5702
  const text = await response.text();
5576
- const parsed = text ? JSON.parse(text) : null;
5703
+ let parsed;
5704
+ try {
5705
+ parsed = text ? JSON.parse(text) : null;
5706
+ } catch {
5707
+ parsed = null;
5708
+ }
5577
5709
  if (response.status < 200 || response.status >= 300) {
5578
5710
  return { ok: false, status: response.status, response: parsed ?? text };
5579
5711
  }
@@ -5594,6 +5726,13 @@ async function handlePrivateKnowledgeUpsert(_deps, rawInput, _signal, fetchFn =
5594
5726
  }
5595
5727
  return jsonContent(envelope);
5596
5728
  }
5729
+ async function handlePrivateKnowledgeInvalidate(_deps, rawInput, _signal, fetchFn = globalThis.fetch) {
5730
+ if (!isInvalidateInput(rawInput)) {
5731
+ throw invalidParams(INVALIDATE_TOOL_NAME, "expected { knowledge_class, source_path }.");
5732
+ }
5733
+ const payload = await callPrivateKnowledge("/api/v1/knowledge/private/invalidate", rawInput, fetchFn);
5734
+ return jsonContent({ tool: INVALIDATE_TOOL_NAME, schema_version: 1, payload });
5735
+ }
5597
5736
  async function handlePrivateKnowledgeContext(_deps, rawInput, _signal, fetchFn = globalThis.fetch) {
5598
5737
  if (!isContextInput(rawInput)) {
5599
5738
  throw invalidParams(CONTEXT_TOOL_NAME, "expected { query, optional limit, optional knowledge_class }.");
@@ -5746,11 +5885,11 @@ async function handleHqWhiteboardRead(_deps, rawInput, signal) {
5746
5885
  }
5747
5886
 
5748
5887
  // src/tools/skills/skill-corpus.ts
5749
- import { existsSync as existsSync6, statSync as statSync5 } from "node:fs";
5888
+ import { existsSync as existsSync7, statSync as statSync5 } from "node:fs";
5750
5889
  import { dirname as dirname5, isAbsolute, join as join9, resolve as resolve2 } from "node:path";
5751
5890
 
5752
5891
  // ../skill-registry/src/loader.ts
5753
- import { readdirSync as readdirSync5, readFileSync as readFileSync8, statSync as statSync4 } from "node:fs";
5892
+ import { readdirSync as readdirSync6, readFileSync as readFileSync9, statSync as statSync4 } from "node:fs";
5754
5893
  import { join as join8 } from "node:path";
5755
5894
  var InvalidSkillFrontmatterError = class extends Error {
5756
5895
  constructor(skillFile, reason) {
@@ -5801,7 +5940,7 @@ ${FRONTMATTER_DELIMITER}
5801
5940
  return { name, description: description23, body };
5802
5941
  }
5803
5942
  function loadSkillsFromDir(skillsDir) {
5804
- const entries = readdirSync5(skillsDir);
5943
+ const entries = readdirSync6(skillsDir);
5805
5944
  const skills = [];
5806
5945
  for (const entry of entries) {
5807
5946
  const entryPath = join8(skillsDir, entry);
@@ -5815,7 +5954,7 @@ function loadSkillsFromDir(skillsDir) {
5815
5954
  const skillFile = join8(entryPath, "SKILL.md");
5816
5955
  let raw;
5817
5956
  try {
5818
- raw = readFileSync8(skillFile, "utf8");
5957
+ raw = readFileSync9(skillFile, "utf8");
5819
5958
  } catch {
5820
5959
  continue;
5821
5960
  }
@@ -5857,12 +5996,12 @@ function resolveSkillsDir(env = process.env, startDir = process.cwd()) {
5857
5996
  const override = env.VO_SKILLS_DIR;
5858
5997
  if (typeof override === "string" && override.length > 0) {
5859
5998
  const abs = isAbsolute(override) ? override : resolve2(startDir, override);
5860
- return existsSync6(abs) && statSync5(abs).isDirectory() ? abs : null;
5999
+ return existsSync7(abs) && statSync5(abs).isDirectory() ? abs : null;
5861
6000
  }
5862
6001
  let dir = resolve2(startDir);
5863
6002
  for (let i = 0; i < MAX_WALK_UP_LEVELS; i += 1) {
5864
6003
  const candidate = join9(dir, ".claude", "skills");
5865
- if (existsSync6(candidate) && statSync5(candidate).isDirectory()) return candidate;
6004
+ if (existsSync7(candidate) && statSync5(candidate).isDirectory()) return candidate;
5866
6005
  const parent = dirname5(dir);
5867
6006
  if (parent === dir) break;
5868
6007
  dir = parent;
@@ -6130,6 +6269,14 @@ function buildToolRegistry() {
6130
6269
  },
6131
6270
  handler: handlePrivateKnowledgeContext
6132
6271
  },
6272
+ [INVALIDATE_TOOL_NAME]: {
6273
+ definition: {
6274
+ name: INVALIDATE_TOOL_NAME,
6275
+ description: invalidateDescription,
6276
+ inputSchema: invalidateInputSchema
6277
+ },
6278
+ handler: handlePrivateKnowledgeInvalidate
6279
+ },
6133
6280
  [POST_TOOL_NAME]: {
6134
6281
  definition: {
6135
6282
  name: POST_TOOL_NAME,
@@ -7401,18 +7548,36 @@ if (process.argv[2] === "login") {
7401
7548
  const cwdFlag = process.argv.indexOf("--cwd");
7402
7549
  const cwd = cwdFlag >= 0 && typeof process.argv[cwdFlag + 1] === "string" ? process.argv[cwdFlag + 1] : process.cwd();
7403
7550
  const sessionId = randomUUID5();
7551
+ const appendSyncLog = async (line) => {
7552
+ try {
7553
+ const { appendFileSync: appendFileSync2, mkdirSync: mkdirSync6 } = await import("node:fs");
7554
+ const { join: join11 } = await import("node:path");
7555
+ const { homedir: homedir7 } = await import("node:os");
7556
+ const dir = join11(homedir7(), ".claude");
7557
+ mkdirSync6(dir, { recursive: true });
7558
+ appendFileSync2(join11(dir, "vo-mcp-sync.log"), `${line}
7559
+ `, "utf8");
7560
+ } catch {
7561
+ }
7562
+ };
7404
7563
  Promise.resolve().then(() => (init_sync_config(), sync_config_exports)).then(async ({ runMemorySync: runMemorySync2, isNoopSyncReason: isNoopSyncReason2 }) => {
7405
7564
  const r = await runMemorySync2(action, cwd, sessionId);
7565
+ const stamp = `${sessionId} ${action}`;
7406
7566
  if (r.synced) {
7407
7567
  console.error(`[vo-mcp] sync ${action} ok: ${JSON.stringify(r)}`);
7568
+ await appendSyncLog(`ok ${stamp} ${JSON.stringify(r)}`);
7408
7569
  } else if (isNoopSyncReason2(r.reason)) {
7409
7570
  console.error(`[vo-mcp] sync ${action} skipped: ${r.reason}`);
7571
+ await appendSyncLog(`skip ${stamp} ${r.reason ?? ""}`);
7410
7572
  } else {
7411
7573
  console.error(`[vo-mcp] sync ${action} failed: ${r.reason}`);
7574
+ await appendSyncLog(`FAIL ${stamp} ${r.reason ?? ""}`);
7412
7575
  process.exitCode = 1;
7413
7576
  }
7414
- }).catch((err) => {
7415
- console.error("[vo-mcp] sync fatal:", err instanceof Error ? err.message : String(err));
7577
+ }).catch(async (err) => {
7578
+ const message = err instanceof Error ? err.message : String(err);
7579
+ console.error("[vo-mcp] sync fatal:", message);
7580
+ await appendSyncLog(`FATAL ${sessionId} ${action} ${message}`);
7416
7581
  process.exitCode = 1;
7417
7582
  });
7418
7583
  } else {