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

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/index.js CHANGED
@@ -180,6 +180,7 @@ __export(credential_store_exports, {
180
180
  KEYCHAIN_LOCATION: () => KEYCHAIN_LOCATION,
181
181
  credentialPath: () => credentialPath,
182
182
  readStoredCredential: () => readStoredCredential,
183
+ readStoredCredentialKeychainOnly: () => readStoredCredentialKeychainOnly,
183
184
  writeStoredCredential: () => writeStoredCredential
184
185
  });
185
186
  import { homedir as homedir3 } from "node:os";
@@ -238,6 +239,11 @@ function readStoredCredential(env = process.env, keychain = realKeychain) {
238
239
  }
239
240
  return readFromFile(env);
240
241
  }
242
+ function readStoredCredentialKeychainOnly(env = process.env, keychain = realKeychain) {
243
+ if (!keychainEnabled(env, keychain)) return null;
244
+ const raw = keychain.get();
245
+ return raw ? deserialize(raw) : null;
246
+ }
241
247
  function deleteFile(env) {
242
248
  try {
243
249
  rmSync(credentialPath(env), { force: true });
@@ -1122,9 +1128,15 @@ function createMemoryEventsWriter() {
1122
1128
  function readVoMcpVersion() {
1123
1129
  try {
1124
1130
  const here = dirname3(fileURLToPath2(import.meta.url));
1125
- const pkgPath = join4(here, "..", "..", "package.json");
1126
- const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
1127
- return typeof pkg.version === "string" ? pkg.version : "0.0.0-unknown";
1131
+ for (const rel of ["..", ["..", ".."], ["..", "..", ".."]]) {
1132
+ try {
1133
+ const segs = Array.isArray(rel) ? rel : [rel];
1134
+ const pkg = JSON.parse(readFileSync4(join4(here, ...segs, "package.json"), "utf8"));
1135
+ if (pkg.name === "@algosuite/vo-mcp" && typeof pkg.version === "string") return pkg.version;
1136
+ } catch {
1137
+ }
1138
+ }
1139
+ return "0.0.0-unknown";
1128
1140
  } catch {
1129
1141
  return "0.0.0-unknown";
1130
1142
  }
@@ -2044,13 +2056,19 @@ async function handleConsensusJudgment(deps, rawInput, signal) {
2044
2056
  ...engineResult.synthesized_verdict.confidence_badge !== void 0 ? { confidence_badge: engineResult.synthesized_verdict.confidence_badge } : {},
2045
2057
  // Feature 1 (agreement-gate) — fan-out diagnostics (present iff the gate ran).
2046
2058
  ...engineResult.fan_out_diagnostics !== void 0 ? { fan_out_diagnostics: engineResult.fan_out_diagnostics } : {},
2059
+ // Stage A7-shadow — adaptive-vs-incumbent comparison (PII-free; present iff shadow on).
2060
+ ...engineResult.shadow_synthesis !== void 0 ? { shadow_synthesis: engineResult.shadow_synthesis } : {},
2047
2061
  // Source-grounded Tier-4 outputs (present iff the call was source-grounded).
2048
2062
  ...engineResult.source_grounded === true ? { source_grounded: true } : {},
2049
2063
  ...engineResult.citation_grade !== void 0 ? { citation_grade: engineResult.citation_grade } : {},
2050
2064
  ...engineResult.low_confidence_sources !== void 0 ? { low_confidence_sources: engineResult.low_confidence_sources } : {},
2051
2065
  // Escalation (from citation grade or human-tiebreak synthesizer).
2052
2066
  ...engineResult.escalation_required !== void 0 ? { escalation_required: engineResult.escalation_required } : {},
2053
- ...engineResult.escalation_reason !== void 0 ? { escalation_reason: engineResult.escalation_reason } : {}
2067
+ ...engineResult.escalation_reason !== void 0 ? { escalation_reason: engineResult.escalation_reason } : {},
2068
+ // Critique-uptake (2026-07-20 red-team fix) — the engine computes this
2069
+ // on every call; this spread closes the gap where the visibility report
2070
+ // was itself silently dropped at the payload boundary.
2071
+ ...engineResult.critique_uptake !== void 0 ? { critique_uptake: engineResult.critique_uptake } : {}
2054
2072
  };
2055
2073
  const envelope = {
2056
2074
  tool: TOOL_NAME4,
@@ -4097,7 +4115,7 @@ var inputSchema13 = {
4097
4115
  properties: {},
4098
4116
  additionalProperties: false
4099
4117
  };
4100
- var description13 = "Lists open VO-source pull requests with blocker / source / tester / specialist-context metadata. Read-only diagnostic for Command Center reads. Wraps `voListPendingPRs` admin Cloud Function. V1 stub: returns `verdict: 'unimplemented'` \u2014 cloud-mode wiring pending.";
4118
+ var description13 = "Lists open AlgoHQ-source pull requests with blocker / source / tester / specialist-context metadata. Read-only diagnostic for Command Center reads. Wraps `voListPendingPRs` admin Cloud Function. V1 stub: returns `verdict: 'unimplemented'` \u2014 cloud-mode wiring pending.";
4101
4119
  function isToolInput13(v) {
4102
4120
  return typeof v === "object" && v !== null;
4103
4121
  }
@@ -4133,7 +4151,7 @@ var inputSchema14 = {
4133
4151
  required: ["pr_number"],
4134
4152
  additionalProperties: false
4135
4153
  };
4136
- var description14 = "Approves + merges a single VO-source pull request by number. Wraps `voMergePR` admin Cloud Function (server-side refuses non-VO PRs with permission-denied). V1 stub: returns `verdict: 'unimplemented'` \u2014 cloud-mode wiring pending.";
4154
+ var description14 = "Approves + merges a single AlgoHQ-source pull request by number. Wraps `voMergePR` admin Cloud Function (server-side refuses non-AlgoHQ PRs with permission-denied). V1 stub: returns `verdict: 'unimplemented'` \u2014 cloud-mode wiring pending.";
4137
4155
  function isToolInput14(v) {
4138
4156
  if (typeof v !== "object" || v === null) return false;
4139
4157
  const o = v;
@@ -4215,7 +4233,7 @@ var inputSchema16 = {
4215
4233
  properties: {},
4216
4234
  additionalProperties: false
4217
4235
  };
4218
- var description16 = "Iterates all open VO-source pull requests and merges (or arms auto-merge) on each. Returns counts of merged / accepted / total plus per-PR results. Wraps `voApproveAllFixes` admin Cloud Function. V1 stub: returns `verdict: 'unimplemented'` \u2014 cloud-mode wiring pending.";
4236
+ var description16 = "Iterates all open AlgoHQ-source pull requests and merges (or arms auto-merge) on each. Returns counts of merged / accepted / total plus per-PR results. Wraps `voApproveAllFixes` admin Cloud Function. V1 stub: returns `verdict: 'unimplemented'` \u2014 cloud-mode wiring pending.";
4219
4237
  function isToolInput16(v) {
4220
4238
  return typeof v === "object" && v !== null;
4221
4239
  }
@@ -4249,7 +4267,7 @@ var inputSchema17 = {
4249
4267
  required: ["pr_number"],
4250
4268
  additionalProperties: false
4251
4269
  };
4252
- var description17 = "Closes a VO pull request and dispatches a self-heal pass to retry the same focus page. Cloud callable refuses non-VO PRs and respects the self-heal kill switch + per-PR retry block. Wraps `voRejectAndRetry` admin Cloud Function. V1 stub: returns `verdict: 'unimplemented'` \u2014 cloud-mode wiring pending.";
4270
+ var description17 = "Closes an AlgoHQ pull request and dispatches a self-heal pass to retry the same focus page. Cloud callable refuses non-AlgoHQ PRs and respects the self-heal kill switch + per-PR retry block. Wraps `voRejectAndRetry` admin Cloud Function. V1 stub: returns `verdict: 'unimplemented'` \u2014 cloud-mode wiring pending.";
4253
4271
  function isToolInput17(v) {
4254
4272
  if (typeof v !== "object" || v === null) return false;
4255
4273
  const o = v;
@@ -4325,7 +4343,7 @@ function buildPrompt4(pr, notes) {
4325
4343
  const lines = [
4326
4344
  "You are a release gatekeeper deciding whether a pull request is safe to MERGE.",
4327
4345
  "Recommend exactly one of: merge / hold / reject. Be conservative \u2014 this is a high-stakes irreversible action.",
4328
- "Rules: HOLD if CI is failing/blocked, there is a merge conflict, or the change is a draft. REJECT if the PR is not a legitimate VO-source change or has no clear purpose. MERGE only if it looks complete, scoped, and unblocked.",
4346
+ "Rules: HOLD if CI is failing/blocked, there is a merge conflict, or the change is a draft. REJECT if the PR is not a legitimate AlgoHQ-source change or has no clear purpose. MERGE only if it looks complete, scoped, and unblocked.",
4329
4347
  "",
4330
4348
  `PR #${pr.number}: ${pr.title}`,
4331
4349
  `Source: ${pr.source ?? "unknown"}`,
@@ -4393,7 +4411,7 @@ async function handleReviewMerge(deps, rawInput, signal) {
4393
4411
  }
4394
4412
  if (pr === null) {
4395
4413
  return emit(
4396
- emptyPayload("hold", `PR #${prNumber} is not among open VO PRs (already merged/closed, or not a VO-source PR).`, null)
4414
+ emptyPayload("hold", `PR #${prNumber} is not among open AlgoHQ PRs (already merged/closed, or not an AlgoHQ-source PR).`, null)
4397
4415
  );
4398
4416
  }
4399
4417
  const hasBlocker = pr.blocker !== null && pr.blocker !== "none";
@@ -4488,6 +4506,8 @@ function suggestedHandoffPath(session_id, isoTimestamp) {
4488
4506
  }
4489
4507
 
4490
4508
  // src/tools/session/report-session-state.ts
4509
+ init_auth_token_source();
4510
+ init_credential_store();
4491
4511
  var TOOL_NAME19 = "vo_report_session_state";
4492
4512
  var VALID_AGENT_TYPES = ["claude-code", "codex", "cursor", "continue"];
4493
4513
  var MAX_GOAL_CHARS = 500;
@@ -4538,7 +4558,7 @@ var inputSchema19 = {
4538
4558
  required: ["operator_id", "session_id", "agent_type", "context_used_pct"],
4539
4559
  additionalProperties: false
4540
4560
  };
4541
- var description19 = "Reports per-session context-window utilization to VO and returns a directive: 'continue' (under 70%), 'prepare_handoff' (70-84%), or 'execute_handoff_now' (\u226585%). Implements V1 launch gate #9 (fleet context lifecycle management) per the official VO roadmap. V1 backend is stub-local \u2014 computes the directive purely from `context_used_pct` against the documented thresholds without a network call. Phase 3 wires this to the deployed vo-control-plane HTTP API; the response shape stays stable across the cutover (`backend_mode` field in the payload tells the caller which mode produced the verdict).";
4561
+ var description19 = "Reports per-session context-window utilization to AlgoHQ and returns a directive: 'continue' (under 70%), 'prepare_handoff' (70-84%), or 'execute_handoff_now' (\u226585%). Implements V1 launch gate #9 (fleet context lifecycle management) per the official AlgoHQ roadmap. Cloud-control-plane mode when VO_CONTROL_PLANE_URL plus a user/scoped HQ credential (or legacy admin token) is available; auto-allocates the session on first report so interactive agents (Claude Code, Cursor, Codex, Continue) appear on the live fleet whiteboard. Stub-local fallback when cloud config is absent or fails. The response shape stays stable across modes (`backend_mode` field in the payload tells the caller which mode produced the verdict).";
4542
4562
  function isStringArray2(v, maxItems) {
4543
4563
  if (!Array.isArray(v)) return false;
4544
4564
  if (v.length > maxItems) return false;
@@ -4563,33 +4583,93 @@ function isToolInput19(v) {
4563
4583
  }
4564
4584
  return true;
4565
4585
  }
4566
- function getCloudConfig() {
4567
- const url = process.env["VO_CONTROL_PLANE_URL"];
4568
- const token = process.env["VO_CONTROL_PLANE_ADMIN_TOKEN"];
4569
- if (!url || !token) return null;
4570
- return { url, token };
4586
+ async function fetchCloudIdentity(url, token, fetchFn) {
4587
+ try {
4588
+ const response = await fetchFn(`${url}/api/v1/auth/me`, {
4589
+ method: "GET",
4590
+ headers: {
4591
+ "Authorization": `Bearer ${token}`
4592
+ }
4593
+ });
4594
+ if (!response.ok) return null;
4595
+ const data = await response.json();
4596
+ if (!data.ok || !data.provisioned || !data.operator_id || !data.tenant_id) return null;
4597
+ return { operator_id: data.operator_id, tenant_id: data.tenant_id };
4598
+ } catch {
4599
+ return null;
4600
+ }
4571
4601
  }
4572
- async function tryCloudReportState(cloud, input) {
4602
+ async function getCloudConfig(fetchFn = fetch) {
4603
+ const url = process.env["VO_CONTROL_PLANE_URL"]?.trim();
4604
+ if (!url) return null;
4605
+ const tokenSource = createAuthTokenSourceFromEnv(
4606
+ process.env,
4607
+ fetchFn,
4608
+ () => readStoredCredential(process.env)
4609
+ );
4610
+ const token = await tokenSource?.getToken();
4611
+ if (!token) return null;
4612
+ const tenant_id = process.env["VO_TENANT_ID"]?.trim();
4613
+ if (tenant_id) return { url, token, tenant_id };
4614
+ const identity = await fetchCloudIdentity(url, token, fetchFn);
4615
+ if (!identity) return null;
4616
+ return { url, token, tenant_id: identity.tenant_id, operator_id: identity.operator_id };
4617
+ }
4618
+ async function tryCloudReportState(cloud, input, fetchFn = fetch) {
4573
4619
  try {
4574
- const body = {
4620
+ const reportBody = {
4575
4621
  context_used_pct: input.context_used_pct
4576
4622
  };
4577
- if (input.current_goal !== void 0) body["current_goal"] = input.current_goal;
4623
+ if (input.current_goal !== void 0) reportBody["current_goal"] = input.current_goal;
4578
4624
  if (input.recent_files_touched !== void 0) {
4579
- body["recent_files_touched"] = input.recent_files_touched;
4625
+ reportBody["recent_files_touched"] = input.recent_files_touched;
4580
4626
  }
4581
4627
  if (input.recent_tool_uses !== void 0) {
4582
- body["recent_tool_uses"] = input.recent_tool_uses;
4628
+ reportBody["recent_tool_uses"] = input.recent_tool_uses;
4583
4629
  }
4584
- const url = `${cloud.url}/api/v1/session/${input.session_id}/report-state`;
4585
- const response = await fetch(url, {
4630
+ const reportUrl = `${cloud.url}/api/v1/session/${input.session_id}/report-state`;
4631
+ let response = await fetchFn(reportUrl, {
4586
4632
  method: "POST",
4587
4633
  headers: {
4588
4634
  "Content-Type": "application/json",
4589
4635
  "Authorization": `Bearer ${cloud.token}`
4590
4636
  },
4591
- body: JSON.stringify(body)
4637
+ body: JSON.stringify(reportBody)
4592
4638
  });
4639
+ if (response.status === 404) {
4640
+ const allocateBody = {
4641
+ operator_id: cloud.operator_id ?? input.operator_id,
4642
+ tenant_id: cloud.tenant_id,
4643
+ agent_type: input.agent_type,
4644
+ current_goal: input.current_goal ?? "Interactive session"
4645
+ };
4646
+ if (input.context_used_pct > 0) {
4647
+ allocateBody["initial_context_used_pct"] = input.context_used_pct;
4648
+ }
4649
+ const allocateUrl = `${cloud.url}/api/v1/session`;
4650
+ const allocateResponse = await fetchFn(allocateUrl, {
4651
+ method: "POST",
4652
+ headers: {
4653
+ "Content-Type": "application/json",
4654
+ "Authorization": `Bearer ${cloud.token}`
4655
+ },
4656
+ body: JSON.stringify(allocateBody)
4657
+ });
4658
+ if (!allocateResponse.ok) {
4659
+ return null;
4660
+ }
4661
+ const allocateData = await allocateResponse.json();
4662
+ const retrySessionId = typeof allocateData.session?.session_id === "string" && allocateData.session.session_id.length > 0 ? allocateData.session.session_id : input.session_id;
4663
+ const retryReportUrl = `${cloud.url}/api/v1/session/${retrySessionId}/report-state`;
4664
+ response = await fetchFn(retryReportUrl, {
4665
+ method: "POST",
4666
+ headers: {
4667
+ "Content-Type": "application/json",
4668
+ "Authorization": `Bearer ${cloud.token}`
4669
+ },
4670
+ body: JSON.stringify(reportBody)
4671
+ });
4672
+ }
4593
4673
  if (!response.ok) {
4594
4674
  return null;
4595
4675
  }
@@ -4620,7 +4700,7 @@ async function handleReportSessionState(deps, rawInput, _signal) {
4620
4700
  `invalid input. Required fields: operator_id (non-empty string), session_id (non-empty string), agent_type (one of: ${VALID_AGENT_TYPES.join(" | ")}), context_used_pct (number 0-100). Optional: current_goal (string \u2264${MAX_GOAL_CHARS} chars), recent_files_touched (string[] \u2264${MAX_RECENT_FILES}), recent_tool_uses (string[] \u2264${MAX_RECENT_TOOLS}).`
4621
4701
  );
4622
4702
  }
4623
- const cloud = getCloudConfig();
4703
+ const cloud = await getCloudConfig();
4624
4704
  if (cloud !== null) {
4625
4705
  const cloudPayload = await tryCloudReportState(cloud, rawInput);
4626
4706
  if (cloudPayload !== null) {
@@ -4703,7 +4783,7 @@ var MANDATORY_READS = [
4703
4783
  function buildSuccessorPrompt(handoffMarkdown, goal) {
4704
4784
  const reads = MANDATORY_READS.map((r, i) => ` ${i + 1}. ${r}`).join("\n");
4705
4785
  const lines = [
4706
- "You are the SUCCESSOR agent for a Virtual Office lane. The previous session",
4786
+ "You are the SUCCESSOR agent for an AlgoHQ lane. The previous session",
4707
4787
  "exhausted its context and wrote the handoff below. Read it fully, verify its",
4708
4788
  '"verification needed" items against live state (a handoff is a claim, not',
4709
4789
  "evidence \u2014 verify via `git show origin/main:<path>`), then continue the lane.",
@@ -4714,7 +4794,7 @@ function buildSuccessorPrompt(handoffMarkdown, goal) {
4714
4794
  "NON-NEGOTIABLES: multi-model consensus verification is the core; test honesty",
4715
4795
  "(verified-answer-only, no fake green); verify-before-act + human merge approval;",
4716
4796
  "never a full functions-shared deploy; Gen2 only; work in a worktree on your own",
4717
- "branch; finish line is MERGED + DEPLOYED + LIVE-VERIFIED, and VO changes update",
4797
+ "branch; finish line is MERGED + DEPLOYED + LIVE-VERIFIED, and AlgoHQ changes update",
4718
4798
  "the roadmap in the same PR.",
4719
4799
  "",
4720
4800
  "--- HANDOFF ---",
@@ -4864,6 +4944,27 @@ async function handleConciergeDispatch(deps, rawInput, _signal) {
4864
4944
  import { homedir as homedir5 } from "node:os";
4865
4945
  import { join as join7 } from "node:path";
4866
4946
  import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync3, readdirSync as readdirSync4 } from "node:fs";
4947
+
4948
+ // src/tools/memory/safe-memory-file.ts
4949
+ import { resolve, sep } from "node:path";
4950
+ var SAFE_MEMORY_FILE_RE = /^[A-Za-z0-9][A-Za-z0-9._ -]*\.md$/i;
4951
+ function isSafeMemoryFileName(fileName) {
4952
+ return fileName.length <= 200 && fileName.trim() === fileName && !fileName.includes("/") && !fileName.includes("\\") && !fileName.includes(":") && SAFE_MEMORY_FILE_RE.test(fileName);
4953
+ }
4954
+ function resolveMemoryFilePath(memoryDir, fileName) {
4955
+ if (!isSafeMemoryFileName(fileName)) {
4956
+ throw new Error(`unsafe memory file_name: ${fileName.slice(0, 80)}`);
4957
+ }
4958
+ const root = resolve(memoryDir);
4959
+ const filePath = resolve(root, fileName);
4960
+ const rootPrefix = root.endsWith(sep) ? root : `${root}${sep}`;
4961
+ if (filePath !== root && !filePath.startsWith(rootPrefix)) {
4962
+ throw new Error(`memory file path escapes memory directory: ${fileName.slice(0, 80)}`);
4963
+ }
4964
+ return filePath;
4965
+ }
4966
+
4967
+ // src/tools/memory/sync-config.ts
4867
4968
  var TOOL_NAME22 = "vo_sync_config";
4868
4969
  var inputSchema22 = {
4869
4970
  type: "object",
@@ -4890,14 +4991,13 @@ function isToolInput22(v) {
4890
4991
  return true;
4891
4992
  }
4892
4993
  function deriveProjectSlug(cwd) {
4893
- const normalized = cwd.replace(/\\/g, "/");
4894
- return normalized.replace(/^([A-Z]):/i, (_, drive) => `${drive.toUpperCase()}-`).replace(/\/$/g, "").split("/").join("--").replace(/\s+/g, "-");
4994
+ return cwd.replace(/\\/g, "/").replace(/\/+$/g, "").replace(/^([a-zA-Z]):/, (_m, drive) => `${drive.toUpperCase()}:`).replace(/[^a-zA-Z0-9]/g, "-");
4895
4995
  }
4896
4996
  function getMemoryDir(cwd) {
4897
4997
  const slug = deriveProjectSlug(cwd);
4898
4998
  return join7(homedir5(), ".claude", "projects", slug, "memory");
4899
4999
  }
4900
- async function pullMemory(controlPlaneUrl, token, memoryDir, sessionId, fetchFn) {
5000
+ async function pullMemory(controlPlaneUrl, token, memoryDir, fetchFn) {
4901
5001
  const url = `${controlPlaneUrl}/api/v1/agent-config/memory/me`;
4902
5002
  const response = await fetchFn(url, {
4903
5003
  method: "GET",
@@ -4913,10 +5013,13 @@ async function pullMemory(controlPlaneUrl, token, memoryDir, sessionId, fetchFn)
4913
5013
  if (!data.ok || !Array.isArray(data.entries)) {
4914
5014
  throw new Error("GET /api/v1/agent-config/memory/me response missing ok=true or entries array");
4915
5015
  }
5016
+ const writes = data.entries.map((entry) => ({
5017
+ entry,
5018
+ filePath: resolveMemoryFilePath(memoryDir, entry.file_name)
5019
+ }));
4916
5020
  mkdirSync4(memoryDir, { recursive: true });
4917
5021
  const files = [];
4918
- for (const entry of data.entries) {
4919
- const filePath = join7(memoryDir, entry.file_name);
5022
+ for (const { entry, filePath } of writes) {
4920
5023
  writeFileSync3(filePath, entry.content, "utf8");
4921
5024
  files.push(entry.file_name);
4922
5025
  }
@@ -4928,7 +5031,7 @@ async function pushMemory(controlPlaneUrl, token, memoryDir, sessionId, fetchFn)
4928
5031
  }
4929
5032
  const localFiles = readdirSync4(memoryDir).filter((f) => f.endsWith(".md")).map((f) => ({
4930
5033
  file_name: f,
4931
- content: readFileSync7(join7(memoryDir, f), "utf8"),
5034
+ content: readFileSync7(resolveMemoryFilePath(memoryDir, f), "utf8"),
4932
5035
  entry_type: f === "MEMORY.md" ? "index" : "topic"
4933
5036
  }));
4934
5037
  if (localFiles.length === 0) {
@@ -5010,6 +5113,42 @@ async function pushMemory(controlPlaneUrl, token, memoryDir, sessionId, fetchFn)
5010
5113
  }
5011
5114
  return { pushed: localFiles.length, created, updated };
5012
5115
  }
5116
+ async function runMemorySync(action, cwd, sessionId, fetchFn = globalThis.fetch) {
5117
+ const controlPlaneUrl = process.env["VO_CONTROL_PLANE_URL"];
5118
+ if (!controlPlaneUrl) {
5119
+ return { synced: false, reason: "VO_CONTROL_PLANE_URL not set \u2014 cloud mode disabled" };
5120
+ }
5121
+ const { createAuthTokenSourceFromEnv: createAuthTokenSourceFromEnv2 } = await Promise.resolve().then(() => (init_auth_token_source(), auth_token_source_exports));
5122
+ const { readStoredCredential: readStoredCredential2 } = await Promise.resolve().then(() => (init_credential_store(), credential_store_exports));
5123
+ const tokenSource = createAuthTokenSourceFromEnv2(process.env, fetchFn, () => readStoredCredential2(process.env));
5124
+ if (!tokenSource) {
5125
+ return { synced: false, reason: "No auth configured. Run `vo-mcp login` to authenticate as an operator." };
5126
+ }
5127
+ const token = await tokenSource.getToken();
5128
+ if (!token) {
5129
+ return { synced: false, reason: "Failed to obtain auth token. Run `vo-mcp login` to re-authenticate." };
5130
+ }
5131
+ const memoryDir = getMemoryDir(cwd);
5132
+ const baseUrl = controlPlaneUrl.replace(/\/+$/, "");
5133
+ try {
5134
+ if (action === "pull") {
5135
+ const result2 = await pullMemory(baseUrl, token, memoryDir, fetchFn);
5136
+ return { synced: true, action: "pull", pulled: result2.pulled, files: result2.files, memory_dir: memoryDir };
5137
+ }
5138
+ const result = await pushMemory(baseUrl, token, memoryDir, sessionId, fetchFn);
5139
+ return {
5140
+ synced: true,
5141
+ action: "push",
5142
+ pushed: result.pushed,
5143
+ created: result.created,
5144
+ updated: result.updated,
5145
+ memory_dir: memoryDir
5146
+ };
5147
+ } catch (err) {
5148
+ const message = err instanceof Error ? err.message : String(err);
5149
+ return { synced: false, reason: `Sync failed: ${message}` };
5150
+ }
5151
+ }
5013
5152
  async function handleSyncConfig(deps, rawInput, _signal, fetchFn = globalThis.fetch) {
5014
5153
  if (!isToolInput22(rawInput)) {
5015
5154
  throw invalidParams(
@@ -5017,95 +5156,438 @@ async function handleSyncConfig(deps, rawInput, _signal, fetchFn = globalThis.fe
5017
5156
  'invalid input. Required: { action: "pull" | "push" }. Optional: { cwd: "<path>" }.'
5018
5157
  );
5019
5158
  }
5020
- const controlPlaneUrl = process.env["VO_CONTROL_PLANE_URL"];
5159
+ const cwd = rawInput.cwd?.trim() || process.cwd();
5160
+ const result = await runMemorySync(rawInput.action, cwd, deps.session.sessionId, fetchFn);
5161
+ return jsonContent({ tool: TOOL_NAME22, schema_version: 1, payload: result });
5162
+ }
5163
+
5164
+ // src/tools/memory/private-knowledge.ts
5165
+ var UPSERT_TOOL_NAME = "vo_private_knowledge_upsert";
5166
+ var CONTEXT_TOOL_NAME = "vo_private_knowledge_context";
5167
+ var KNOWLEDGE_CLASSES = ["memory", "skill", "doctrine", "hook", "command"];
5168
+ var PRECISION_CHAR_BUDGET = 12e3;
5169
+ var upsertInputSchema = {
5170
+ type: "object",
5171
+ properties: {
5172
+ knowledge_class: { type: "string", enum: KNOWLEDGE_CLASSES },
5173
+ source_path: { type: "string", description: "Stable private source identifier; not exposed to other users." },
5174
+ title: { type: "string", description: 'Descriptive, retrieval-friendly title (e.g. "AlgoTax OCR redaction architecture", not "notes") \u2014 retrieval matches on it.' },
5175
+ content: { type: "string", description: "Private knowledge text to store server-side. Keep each entry tight and focused (~1-3 pages, under ~12k chars); split larger corpora into separate entries." }
5176
+ },
5177
+ required: ["knowledge_class", "source_path", "title", "content"],
5178
+ additionalProperties: false
5179
+ };
5180
+ var contextInputSchema = {
5181
+ type: "object",
5182
+ properties: {
5183
+ query: { type: "string" },
5184
+ limit: { type: "number", minimum: 1, maximum: 50 },
5185
+ knowledge_class: { type: "string", enum: KNOWLEDGE_CLASSES }
5186
+ },
5187
+ required: ["query"],
5188
+ additionalProperties: false
5189
+ };
5190
+ 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.";
5191
+ 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.";
5192
+ function isKnowledgeClass(value) {
5193
+ return typeof value === "string" && KNOWLEDGE_CLASSES.includes(value);
5194
+ }
5195
+ function isUpsertInput(value) {
5196
+ if (typeof value !== "object" || value === null) return false;
5197
+ const input = value;
5198
+ return isKnowledgeClass(input["knowledge_class"]) && typeof input["source_path"] === "string" && typeof input["title"] === "string" && typeof input["content"] === "string";
5199
+ }
5200
+ function isContextInput(value) {
5201
+ if (typeof value !== "object" || value === null) return false;
5202
+ const input = value;
5203
+ if (typeof input["query"] !== "string") return false;
5204
+ if (input["limit"] !== void 0 && typeof input["limit"] !== "number") return false;
5205
+ if (input["knowledge_class"] !== void 0 && !isKnowledgeClass(input["knowledge_class"])) return false;
5206
+ return true;
5207
+ }
5208
+ async function getCloudAuth(fetchFn) {
5209
+ const controlPlaneUrl = process.env["VO_CONTROL_PLANE_URL"]?.replace(/\/+$/, "");
5021
5210
  if (!controlPlaneUrl) {
5022
- return jsonContent({
5023
- tool: TOOL_NAME22,
5024
- schema_version: 1,
5025
- payload: {
5026
- synced: false,
5027
- reason: "VO_CONTROL_PLANE_URL not set \u2014 cloud mode disabled"
5028
- }
5029
- });
5211
+ return { ok: false, reason: "VO_CONTROL_PLANE_URL not set \u2014 cloud mode disabled" };
5030
5212
  }
5031
5213
  const { createAuthTokenSourceFromEnv: createAuthTokenSourceFromEnv2 } = await Promise.resolve().then(() => (init_auth_token_source(), auth_token_source_exports));
5032
5214
  const { readStoredCredential: readStoredCredential2 } = await Promise.resolve().then(() => (init_credential_store(), credential_store_exports));
5033
5215
  const tokenSource = createAuthTokenSourceFromEnv2(process.env, fetchFn, () => readStoredCredential2(process.env));
5034
- if (!tokenSource) {
5035
- return jsonContent({
5036
- tool: TOOL_NAME22,
5037
- schema_version: 1,
5038
- payload: {
5039
- synced: false,
5040
- reason: "No auth configured. Run `vo-mcp login` to authenticate as an operator."
5041
- }
5042
- });
5043
- }
5216
+ if (!tokenSource) return { ok: false, reason: "No auth configured. Run `vo-mcp login`." };
5044
5217
  const token = await tokenSource.getToken();
5045
- if (!token) {
5046
- return jsonContent({
5047
- tool: TOOL_NAME22,
5048
- schema_version: 1,
5049
- payload: {
5050
- synced: false,
5051
- reason: "Failed to obtain auth token. Run `vo-mcp login` to re-authenticate."
5052
- }
5053
- });
5218
+ if (!token) return { ok: false, reason: "Failed to obtain auth token. Run `vo-mcp login` again." };
5219
+ return { ok: true, controlPlaneUrl, token };
5220
+ }
5221
+ async function callPrivateKnowledge(path3, body, fetchFn) {
5222
+ const auth = await getCloudAuth(fetchFn);
5223
+ if (!auth.ok) return { ok: false, reason: auth.reason };
5224
+ const response = await fetchFn(`${auth.controlPlaneUrl}${path3}`, {
5225
+ method: "POST",
5226
+ headers: {
5227
+ authorization: `Bearer ${auth.token}`,
5228
+ "content-type": "application/json"
5229
+ },
5230
+ body: JSON.stringify(body)
5231
+ });
5232
+ const text = await response.text();
5233
+ const parsed = text ? JSON.parse(text) : null;
5234
+ if (response.status < 200 || response.status >= 300) {
5235
+ return { ok: false, status: response.status, response: parsed ?? text };
5054
5236
  }
5055
- const cwd = rawInput.cwd?.trim() || process.cwd();
5056
- const memoryDir = getMemoryDir(cwd);
5237
+ return parsed;
5238
+ }
5239
+ async function handlePrivateKnowledgeUpsert(_deps, rawInput, _signal, fetchFn = globalThis.fetch) {
5240
+ if (!isUpsertInput(rawInput)) {
5241
+ throw invalidParams(UPSERT_TOOL_NAME, "expected { knowledge_class, source_path, title, content }.");
5242
+ }
5243
+ const payload = await callPrivateKnowledge("/api/v1/knowledge/private", rawInput, fetchFn);
5244
+ const envelope = {
5245
+ tool: UPSERT_TOOL_NAME,
5246
+ schema_version: 1,
5247
+ payload
5248
+ };
5249
+ if (rawInput.content.length > PRECISION_CHAR_BUDGET) {
5250
+ envelope.precision_note = `content is ${rawInput.content.length} chars (> ${PRECISION_CHAR_BUDGET}). Tight 1-3 page entries retrieve better \u2014 consider splitting into focused entries, then re-test retrieval via ${CONTEXT_TOOL_NAME}.`;
5251
+ }
5252
+ return jsonContent(envelope);
5253
+ }
5254
+ async function handlePrivateKnowledgeContext(_deps, rawInput, _signal, fetchFn = globalThis.fetch) {
5255
+ if (!isContextInput(rawInput)) {
5256
+ throw invalidParams(CONTEXT_TOOL_NAME, "expected { query, optional limit, optional knowledge_class }.");
5257
+ }
5258
+ const payload = await callPrivateKnowledge("/api/v1/knowledge/private/context", rawInput, fetchFn);
5259
+ return jsonContent({ tool: CONTEXT_TOOL_NAME, schema_version: 1, payload });
5260
+ }
5261
+
5262
+ // src/tools/hq/whiteboard.ts
5263
+ init_auth_token_source();
5264
+ init_credential_store();
5265
+ var POST_TOOL_NAME = "hq_whiteboard_post";
5266
+ var READ_TOOL_NAME = "hq_whiteboard_read";
5267
+ var postDescription = "Post an append-only coordination note to the live AlgoHQ whiteboard. Uses the scoped credential from vo-mcp login; operator and tenant ownership are derived by the server.";
5268
+ var readDescription = "Read recent coordination notes from the caller's live AlgoHQ whiteboard. Uses the scoped credential from vo-mcp login and cannot widen tenant scope.";
5269
+ var postInputSchema = {
5270
+ type: "object",
5271
+ properties: {
5272
+ from: { type: "string", minLength: 1, maxLength: 100, description: "Agent/session display name." },
5273
+ type: { type: "string", minLength: 1, maxLength: 64, description: "Message kind, such as intent, worklog, blocker, or completion." },
5274
+ content: { type: "string", minLength: 1, maxLength: 500, description: "Short coordination note." },
5275
+ targetAgent: { type: "string", maxLength: 100 },
5276
+ tester: { type: "string", maxLength: 100 },
5277
+ tier: { type: "string", maxLength: 32 }
5278
+ },
5279
+ required: ["from", "type", "content"],
5280
+ additionalProperties: false
5281
+ };
5282
+ var readInputSchema = {
5283
+ type: "object",
5284
+ properties: {
5285
+ limit: { type: "integer", minimum: 1, maximum: 100, default: 25 },
5286
+ since: { type: "string", description: "Optional ISO-8601 lower bound." },
5287
+ type: { type: "string", minLength: 1, maxLength: 64 }
5288
+ },
5289
+ additionalProperties: false
5290
+ };
5291
+ function resolveTimeoutMs() {
5292
+ const parsed = Number(process.env["HQ_WHITEBOARD_TIMEOUT_MS"]);
5293
+ return Number.isFinite(parsed) && parsed >= 10 && parsed <= 12e4 ? parsed : 1e4;
5294
+ }
5295
+ function isRecord(value) {
5296
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5297
+ }
5298
+ function onlyKeys(value, allowed) {
5299
+ return Object.keys(value).every((key) => allowed.includes(key));
5300
+ }
5301
+ function isBoundedString(value, min, max) {
5302
+ return typeof value === "string" && value.trim().length >= min && value.trim().length <= max;
5303
+ }
5304
+ function parsePostInput(value) {
5305
+ if (!isRecord(value) || !onlyKeys(value, ["from", "type", "content", "targetAgent", "tester", "tier"])) return null;
5306
+ if (!isBoundedString(value["from"], 1, 100)) return null;
5307
+ if (!isBoundedString(value["type"], 1, 64) || !/^[a-zA-Z0-9_-]+$/.test(value["type"].trim())) return null;
5308
+ if (!isBoundedString(value["content"], 1, 500)) return null;
5309
+ for (const [key, max] of [["targetAgent", 100], ["tester", 100], ["tier", 32]]) {
5310
+ if (value[key] !== void 0 && !isBoundedString(value[key], 0, max)) return null;
5311
+ }
5312
+ return {
5313
+ from: value["from"].trim(),
5314
+ type: value["type"].trim(),
5315
+ content: value["content"].trim(),
5316
+ ...typeof value["targetAgent"] === "string" ? { targetAgent: value["targetAgent"].trim() } : {},
5317
+ ...typeof value["tester"] === "string" ? { tester: value["tester"].trim() } : {},
5318
+ ...typeof value["tier"] === "string" ? { tier: value["tier"].trim() } : {}
5319
+ };
5320
+ }
5321
+ function parseReadInput(value) {
5322
+ if (!isRecord(value) || !onlyKeys(value, ["limit", "since", "type"])) return null;
5323
+ if (value["limit"] !== void 0 && (!Number.isInteger(value["limit"]) || Number(value["limit"]) < 1 || Number(value["limit"]) > 100)) return null;
5324
+ if (value["since"] !== void 0 && (typeof value["since"] !== "string" || Number.isNaN(Date.parse(value["since"])))) return null;
5325
+ if (value["type"] !== void 0 && !isBoundedString(value["type"], 1, 64)) return null;
5326
+ return {
5327
+ ...typeof value["limit"] === "number" ? { limit: value["limit"] } : {},
5328
+ ...typeof value["since"] === "string" ? { since: value["since"] } : {},
5329
+ ...typeof value["type"] === "string" ? { type: value["type"].trim() } : {}
5330
+ };
5331
+ }
5332
+ async function resolveCloud(fetchFn) {
5333
+ const url = process.env["VO_CONTROL_PLANE_URL"]?.trim().replace(/\/$/, "");
5334
+ if (!url) return null;
5057
5335
  try {
5058
- if (rawInput.action === "pull") {
5059
- const result = await pullMemory(
5060
- controlPlaneUrl.replace(/\/+$/, ""),
5061
- token,
5062
- memoryDir,
5063
- deps.sessionId,
5064
- fetchFn
5065
- );
5066
- return jsonContent({
5067
- tool: TOOL_NAME22,
5068
- schema_version: 1,
5069
- payload: {
5070
- synced: true,
5071
- action: "pull",
5072
- pulled: result.pulled,
5073
- files: result.files,
5074
- memory_dir: memoryDir
5075
- }
5076
- });
5077
- } else {
5078
- const result = await pushMemory(
5079
- controlPlaneUrl.replace(/\/+$/, ""),
5080
- token,
5081
- memoryDir,
5082
- deps.sessionId,
5083
- fetchFn
5084
- );
5085
- return jsonContent({
5086
- tool: TOOL_NAME22,
5087
- schema_version: 1,
5088
- payload: {
5089
- synced: true,
5090
- action: "push",
5091
- pushed: result.pushed,
5092
- created: result.created,
5093
- updated: result.updated,
5094
- memory_dir: memoryDir
5095
- }
5096
- });
5336
+ const source = createAuthTokenSourceFromEnv(process.env, fetchFn, () => readStoredCredential(process.env));
5337
+ const token = await source?.getToken();
5338
+ return token ? { url, token } : null;
5339
+ } catch {
5340
+ return null;
5341
+ }
5342
+ }
5343
+ async function callWhiteboard(method, bodyOrQuery, signal, fetchFn = fetch) {
5344
+ const cloud = await resolveCloud(fetchFn);
5345
+ if (!cloud) {
5346
+ return {
5347
+ ok: false,
5348
+ error: "hq_whiteboard_not_configured",
5349
+ message: "Set VO_CONTROL_PLANE_URL and run vo-mcp login to install a scoped HQ credential."
5350
+ };
5351
+ }
5352
+ const timeoutSignal = AbortSignal.timeout(resolveTimeoutMs());
5353
+ const requestSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
5354
+ const query = new URLSearchParams();
5355
+ if (method === "GET") {
5356
+ const input = bodyOrQuery;
5357
+ query.set("limit", String(input.limit ?? 25));
5358
+ if (input.since) query.set("since", input.since);
5359
+ if (input.type) query.set("type", input.type);
5360
+ }
5361
+ try {
5362
+ const response = await fetchFn(
5363
+ `${cloud.url}/api/v1/hq/whiteboard/messages${query.size ? `?${query}` : ""}`,
5364
+ {
5365
+ method,
5366
+ headers: {
5367
+ Authorization: `Bearer ${cloud.token}`,
5368
+ ...method === "POST" ? { "Content-Type": "application/json" } : {}
5369
+ },
5370
+ ...method === "POST" ? { body: JSON.stringify(bodyOrQuery) } : {},
5371
+ signal: requestSignal
5372
+ }
5373
+ );
5374
+ const text = await response.text();
5375
+ let payload;
5376
+ try {
5377
+ payload = JSON.parse(text);
5378
+ } catch {
5379
+ payload = { ok: false, error: "invalid_response", message: text.slice(0, 200) };
5380
+ }
5381
+ if (!response.ok) {
5382
+ return { ok: false, error: "hq_whiteboard_http_error", status: response.status, response: payload };
5383
+ }
5384
+ return payload;
5385
+ } catch (error) {
5386
+ return {
5387
+ ok: false,
5388
+ error: signal?.aborted ? "cancelled" : timeoutSignal.aborted ? "hq_whiteboard_timeout" : "hq_whiteboard_unreachable",
5389
+ message: error instanceof Error ? error.message.slice(0, 200) : String(error).slice(0, 200)
5390
+ };
5391
+ }
5392
+ }
5393
+ async function handleHqWhiteboardPost(_deps, rawInput, signal) {
5394
+ const input = parsePostInput(rawInput);
5395
+ if (!input) throw invalidParams(POST_TOOL_NAME, "requires from, type, and 1-500 character content; unknown fields are rejected");
5396
+ return jsonContent(await callWhiteboard("POST", input, signal));
5397
+ }
5398
+ async function handleHqWhiteboardRead(_deps, rawInput, signal) {
5399
+ const input = parseReadInput(rawInput);
5400
+ if (!input) throw invalidParams(READ_TOOL_NAME, "limit must be 1-100, since must be ISO-8601, and unknown fields are rejected");
5401
+ return jsonContent(await callWhiteboard("GET", input, signal));
5402
+ }
5403
+
5404
+ // src/tools/skills/skill-corpus.ts
5405
+ import { existsSync as existsSync6, statSync as statSync5 } from "node:fs";
5406
+ import { dirname as dirname5, isAbsolute, join as join9, resolve as resolve2 } from "node:path";
5407
+
5408
+ // ../skill-registry/src/loader.ts
5409
+ import { readdirSync as readdirSync5, readFileSync as readFileSync8, statSync as statSync4 } from "node:fs";
5410
+ import { join as join8 } from "node:path";
5411
+ var InvalidSkillFrontmatterError = class extends Error {
5412
+ constructor(skillFile, reason) {
5413
+ super(`Invalid frontmatter in ${skillFile}: ${reason}`);
5414
+ this.skillFile = skillFile;
5415
+ this.reason = reason;
5416
+ }
5417
+ skillFile;
5418
+ reason;
5419
+ name = "InvalidSkillFrontmatterError";
5420
+ };
5421
+ var FRONTMATTER_DELIMITER = "---";
5422
+ function parseFrontmatter(rawInput, sourcePath) {
5423
+ const raw = rawInput.replace(/\r\n/g, "\n");
5424
+ if (!raw.startsWith(`${FRONTMATTER_DELIMITER}
5425
+ `)) {
5426
+ throw new InvalidSkillFrontmatterError(sourcePath, 'file does not start with frontmatter delimiter "---"');
5427
+ }
5428
+ const afterFirst = raw.slice(FRONTMATTER_DELIMITER.length + 1);
5429
+ const closingIdx = afterFirst.indexOf(`
5430
+ ${FRONTMATTER_DELIMITER}
5431
+ `);
5432
+ if (closingIdx === -1) {
5433
+ throw new InvalidSkillFrontmatterError(sourcePath, 'missing closing frontmatter delimiter "---"');
5434
+ }
5435
+ const frontmatterText = afterFirst.slice(0, closingIdx);
5436
+ const body = afterFirst.slice(closingIdx + `
5437
+ ${FRONTMATTER_DELIMITER}
5438
+ `.length);
5439
+ let name = "";
5440
+ let description23 = "";
5441
+ for (const line of frontmatterText.split("\n")) {
5442
+ const trimmed = line.trim();
5443
+ if (trimmed.length === 0) continue;
5444
+ const colonIdx = trimmed.indexOf(":");
5445
+ if (colonIdx === -1) continue;
5446
+ const key = trimmed.slice(0, colonIdx).trim();
5447
+ const value = trimmed.slice(colonIdx + 1).trim();
5448
+ if (key === "name") name = value;
5449
+ else if (key === "description") description23 = value;
5450
+ }
5451
+ if (name.length === 0) {
5452
+ throw new InvalidSkillFrontmatterError(sourcePath, 'missing required field "name"');
5453
+ }
5454
+ if (description23.length === 0) {
5455
+ throw new InvalidSkillFrontmatterError(sourcePath, 'missing required field "description"');
5456
+ }
5457
+ return { name, description: description23, body };
5458
+ }
5459
+ function loadSkillsFromDir(skillsDir) {
5460
+ const entries = readdirSync5(skillsDir);
5461
+ const skills = [];
5462
+ for (const entry of entries) {
5463
+ const entryPath = join8(skillsDir, entry);
5464
+ let stat;
5465
+ try {
5466
+ stat = statSync4(entryPath);
5467
+ } catch {
5468
+ continue;
5097
5469
  }
5470
+ if (!stat.isDirectory()) continue;
5471
+ const skillFile = join8(entryPath, "SKILL.md");
5472
+ let raw;
5473
+ try {
5474
+ raw = readFileSync8(skillFile, "utf8");
5475
+ } catch {
5476
+ continue;
5477
+ }
5478
+ const { name, description: description23, body } = parseFrontmatter(raw, skillFile);
5479
+ skills.push({ name, description: description23, body, sourcePath: skillFile });
5480
+ }
5481
+ return [...skills].sort((a, b) => a.name.localeCompare(b.name));
5482
+ }
5483
+
5484
+ // src/tools/skills/skill-corpus.ts
5485
+ var LIST_TOOL_NAME = "vo_skill_list";
5486
+ var GET_TOOL_NAME = "vo_skill_get";
5487
+ var listDescription = "List the Algosuite skill corpus (name + trigger description for every skill). Call once near session start to learn which skills exist; then fetch the full instructions for a relevant skill with vo_skill_get. This is the same corpus Claude Code loads natively from .claude/skills \u2014 served over MCP so every vendor works from identical playbooks. Pass refresh:true to re-scan from disk.";
5488
+ var getDescription = "Fetch the full markdown instructions of one Algosuite skill by name. Follow the returned instructions for the current task the same way a native skill invocation would. Use vo_skill_list to discover skill names.";
5489
+ var listInputSchema = {
5490
+ type: "object",
5491
+ properties: {
5492
+ refresh: {
5493
+ type: "boolean",
5494
+ description: "Re-scan the skills directory instead of using the cached corpus."
5495
+ }
5496
+ },
5497
+ required: []
5498
+ };
5499
+ var getInputSchema = {
5500
+ type: "object",
5501
+ properties: {
5502
+ name: {
5503
+ type: "string",
5504
+ description: "Skill name exactly as returned by vo_skill_list."
5505
+ }
5506
+ },
5507
+ required: ["name"]
5508
+ };
5509
+ var MAX_WALK_UP_LEVELS = 8;
5510
+ var cachedCorpus = null;
5511
+ function resolveSkillsDir(env = process.env, startDir = process.cwd()) {
5512
+ const override = env.VO_SKILLS_DIR;
5513
+ if (typeof override === "string" && override.length > 0) {
5514
+ const abs = isAbsolute(override) ? override : resolve2(startDir, override);
5515
+ return existsSync6(abs) && statSync5(abs).isDirectory() ? abs : null;
5516
+ }
5517
+ let dir = resolve2(startDir);
5518
+ for (let i = 0; i < MAX_WALK_UP_LEVELS; i += 1) {
5519
+ const candidate = join9(dir, ".claude", "skills");
5520
+ if (existsSync6(candidate) && statSync5(candidate).isDirectory()) return candidate;
5521
+ const parent = dirname5(dir);
5522
+ if (parent === dir) break;
5523
+ dir = parent;
5524
+ }
5525
+ return null;
5526
+ }
5527
+ function loadCorpus() {
5528
+ const skillsDir = resolveSkillsDir();
5529
+ if (skillsDir === null) {
5530
+ return {
5531
+ skills: [],
5532
+ skillsDir: null,
5533
+ unavailableReason: "No skills directory found. Set VO_SKILLS_DIR or run inside a repo with .claude/skills."
5534
+ };
5535
+ }
5536
+ try {
5537
+ return { skills: loadSkillsFromDir(skillsDir), skillsDir, unavailableReason: null };
5098
5538
  } catch (err) {
5099
- const message = err instanceof Error ? err.message : String(err);
5539
+ const message = err instanceof Error ? `${err.name}: ${err.message}` : String(err);
5540
+ return { skills: [], skillsDir, unavailableReason: message };
5541
+ }
5542
+ }
5543
+ function getCorpus(refresh) {
5544
+ if (refresh || cachedCorpus === null) {
5545
+ cachedCorpus = loadCorpus();
5546
+ }
5547
+ return cachedCorpus;
5548
+ }
5549
+ async function handleSkillList(_deps, rawInput) {
5550
+ const input = rawInput ?? {};
5551
+ const refresh = input.refresh === true;
5552
+ const corpus = getCorpus(refresh);
5553
+ return jsonContent({
5554
+ corpus_available: corpus.unavailableReason === null,
5555
+ skills_dir: corpus.skillsDir,
5556
+ unavailable_reason: corpus.unavailableReason,
5557
+ skill_count: corpus.skills.length,
5558
+ skills: corpus.skills.map((s) => ({ name: s.name, description: s.description }))
5559
+ });
5560
+ }
5561
+ async function handleSkillGet(_deps, rawInput) {
5562
+ const input = rawInput ?? {};
5563
+ if (typeof input.name !== "string" || input.name.trim().length === 0) {
5564
+ throw invalidParams(GET_TOOL_NAME, 'input field "name" (non-empty string) is required');
5565
+ }
5566
+ const requested = input.name.trim();
5567
+ const corpus = getCorpus(false);
5568
+ if (corpus.unavailableReason !== null) {
5100
5569
  return jsonContent({
5101
- tool: TOOL_NAME22,
5102
- schema_version: 1,
5103
- payload: {
5104
- synced: false,
5105
- reason: `Sync failed: ${message}`
5106
- }
5570
+ corpus_available: false,
5571
+ unavailable_reason: corpus.unavailableReason,
5572
+ skill: null
5107
5573
  });
5108
5574
  }
5575
+ const skill = corpus.skills.find((s) => s.name === requested);
5576
+ if (skill === void 0) {
5577
+ throw invalidParams(
5578
+ GET_TOOL_NAME,
5579
+ `unknown skill "${requested}". Known skills: ${corpus.skills.map((s) => s.name).join(", ")}`
5580
+ );
5581
+ }
5582
+ return jsonContent({
5583
+ corpus_available: true,
5584
+ skill: {
5585
+ name: skill.name,
5586
+ description: skill.description,
5587
+ instructions: skill.body,
5588
+ source_path: skill.sourcePath
5589
+ }
5590
+ });
5109
5591
  }
5110
5592
 
5111
5593
  // src/server.ts
@@ -5286,6 +5768,54 @@ function buildToolRegistry() {
5286
5768
  inputSchema: inputSchema22
5287
5769
  },
5288
5770
  handler: handleSyncConfig
5771
+ },
5772
+ [UPSERT_TOOL_NAME]: {
5773
+ definition: {
5774
+ name: UPSERT_TOOL_NAME,
5775
+ description: upsertDescription,
5776
+ inputSchema: upsertInputSchema
5777
+ },
5778
+ handler: handlePrivateKnowledgeUpsert
5779
+ },
5780
+ [CONTEXT_TOOL_NAME]: {
5781
+ definition: {
5782
+ name: CONTEXT_TOOL_NAME,
5783
+ description: contextDescription,
5784
+ inputSchema: contextInputSchema
5785
+ },
5786
+ handler: handlePrivateKnowledgeContext
5787
+ },
5788
+ [POST_TOOL_NAME]: {
5789
+ definition: {
5790
+ name: POST_TOOL_NAME,
5791
+ description: postDescription,
5792
+ inputSchema: postInputSchema
5793
+ },
5794
+ handler: handleHqWhiteboardPost
5795
+ },
5796
+ [READ_TOOL_NAME]: {
5797
+ definition: {
5798
+ name: READ_TOOL_NAME,
5799
+ description: readDescription,
5800
+ inputSchema: readInputSchema
5801
+ },
5802
+ handler: handleHqWhiteboardRead
5803
+ },
5804
+ [LIST_TOOL_NAME]: {
5805
+ definition: {
5806
+ name: LIST_TOOL_NAME,
5807
+ description: listDescription,
5808
+ inputSchema: listInputSchema
5809
+ },
5810
+ handler: handleSkillList
5811
+ },
5812
+ [GET_TOOL_NAME]: {
5813
+ definition: {
5814
+ name: GET_TOOL_NAME,
5815
+ description: getDescription,
5816
+ inputSchema: getInputSchema
5817
+ },
5818
+ handler: handleSkillGet
5289
5819
  }
5290
5820
  };
5291
5821
  }
@@ -5344,7 +5874,7 @@ function listToolNames() {
5344
5874
  // src/cache/sqlite-cache.ts
5345
5875
  import { createHash as createHash3 } from "node:crypto";
5346
5876
  import { chmodSync as chmodSync3, mkdirSync as mkdirSync5 } from "node:fs";
5347
- import { dirname as dirname5 } from "node:path";
5877
+ import { dirname as dirname6 } from "node:path";
5348
5878
  import { DatabaseSync } from "node:sqlite";
5349
5879
 
5350
5880
  // src/cache/canonicalize.ts
@@ -5389,7 +5919,7 @@ function normalizeString(s) {
5389
5919
  function createSqliteCache(options) {
5390
5920
  const fileBacked = options.dbPath !== ":memory:";
5391
5921
  if (fileBacked) {
5392
- mkdirSync5(dirname5(options.dbPath), { recursive: true, mode: 448 });
5922
+ mkdirSync5(dirname6(options.dbPath), { recursive: true, mode: 448 });
5393
5923
  }
5394
5924
  const versionNamespace = options.cacheVersionNamespace ?? "";
5395
5925
  const db = new DatabaseSync(options.dbPath);
@@ -5575,6 +6105,44 @@ function createNullConsensusEngineClient(reason = NULL_CLIENT_DEFAULT_REASON) {
5575
6105
  // src/consensus/engine-client.ts
5576
6106
  import { randomUUID as randomUUID3 } from "node:crypto";
5577
6107
 
6108
+ // src/consensus/meta-model-caller.ts
6109
+ var META_CONSENSUS_MODEL = "muse-spark-1.1";
6110
+ var META_MODEL_API_KEY_ENV = "MODEL_API_KEY";
6111
+ var META_MODEL_API_KEY_ALIAS = "META_API";
6112
+ function createMetaModelCaller(options = {}) {
6113
+ void options;
6114
+ return async function callMetaWithMetrics2() {
6115
+ throw new Error(
6116
+ "Muse Spark direct consensus is disabled. Use an explicit sanitized task capsule through the AlgoSuite Model Firewall."
6117
+ );
6118
+ };
6119
+ }
6120
+ var callMetaWithMetrics = createMetaModelCaller();
6121
+
6122
+ // src/consensus/consensus-panel.ts
6123
+ var VO_MCP_CONSENSUS_PANEL = {
6124
+ anthropic: "claude-opus-4-7",
6125
+ openai: "gpt-5",
6126
+ // gemini-2.5-FLASH (not -pro): flash accepts the default thinkingBudget=0 from
6127
+ // callGeminiWithMetrics; 2.5-pro REJECTS budget 0 ("only works in thinking mode").
6128
+ // Flash is also ~10x cheaper. 2026-06-02.
6129
+ google: "gemini-2.5-flash",
6130
+ deepseek: "deepseek-chat",
6131
+ // Muse Spark identity is owned by meta-model-caller.ts (single source of
6132
+ // truth for the meta slot); re-exported here so the panel stays complete.
6133
+ meta: META_CONSENSUS_MODEL
6134
+ };
6135
+ function getVoMcpConsensusPanel(panel = VO_MCP_CONSENSUS_PANEL) {
6136
+ for (const [provider, modelId] of Object.entries(panel)) {
6137
+ if (typeof modelId !== "string" || modelId.trim().length === 0) {
6138
+ throw new Error(
6139
+ `getVoMcpConsensusPanel: panel slot "${provider}" has a missing or blank model ID`
6140
+ );
6141
+ }
6142
+ }
6143
+ return panel;
6144
+ }
6145
+
5578
6146
  // src/consensus/engine-options.ts
5579
6147
  var AGREEMENT_GATE_ENV_VAR = "VO_CONSENSUS_AGREEMENT_GATE";
5580
6148
  function isTruthyFlag(raw) {
@@ -5621,6 +6189,25 @@ function mapFanOutDiagnostics(fd) {
5621
6189
  refused: fd.refused
5622
6190
  };
5623
6191
  }
6192
+ var SHADOW_SYNTHESIS_ENV_VAR = "VO_CONSENSUS_SHADOW";
6193
+ function shadowEnabled(env) {
6194
+ const raw = (env ?? {})[SHADOW_SYNTHESIS_ENV_VAR];
6195
+ if (raw === void 0) return true;
6196
+ const norm = raw.trim().toLowerCase();
6197
+ return !(norm === "0" || norm === "false" || norm === "no" || norm === "off" || norm === "");
6198
+ }
6199
+ function mapShadowSynthesis(s) {
6200
+ if (s === void 0) return void 0;
6201
+ return {
6202
+ incumbent: { verdict: s.incumbent.verdict, confidence: s.incumbent.confidence, synthesizer: s.incumbent.synthesizer },
6203
+ adaptive: {
6204
+ verdict: s.adaptive.verdict,
6205
+ confidence: s.adaptive.confidence,
6206
+ ...s.adaptive.calibrated_confidence !== void 0 ? { calibrated_confidence: s.adaptive.calibrated_confidence } : {}
6207
+ },
6208
+ agree: s.agree
6209
+ };
6210
+ }
5624
6211
  function mapCitationGrade(cg) {
5625
6212
  if (cg === void 0) return void 0;
5626
6213
  return {
@@ -5761,7 +6348,12 @@ function createEngineConsensusClient(options) {
5761
6348
  const engineOptions = {
5762
6349
  panel,
5763
6350
  ...options.per_model_timeout_ms !== void 0 ? { per_model_timeout_ms: options.per_model_timeout_ms } : {},
5764
- ...agreementGate !== void 0 ? { agreement_gate: agreementGate } : {}
6351
+ ...agreementGate !== void 0 ? { agreement_gate: agreementGate } : {},
6352
+ // Stage A7-shadow: run the adaptive verdict alongside the live one for grading.
6353
+ // Cheap (pure log-odds over already-fetched verdicts; no extra model calls),
6354
+ // PII-free, and never alters the live verdict. ON by default; kill with
6355
+ // VO_CONSENSUS_SHADOW=0. Cold-start has no skill registry → neutral priors.
6356
+ shadow_synthesis: { enabled: shadowEnabled(options.env) }
5765
6357
  };
5766
6358
  const sources = request.source_urls;
5767
6359
  const useSourceGrounded = sources !== void 0 && sources.length > 0 && typeof engine.runSourceGroundedConsensus === "function";
@@ -5817,6 +6409,12 @@ function createEngineConsensusClient(options) {
5817
6409
  ...sourceExtras?.escalation_reason !== void 0 ? { escalation_reason: sourceExtras.escalation_reason } : response.escalation_reason !== void 0 ? { escalation_reason: response.escalation_reason } : {},
5818
6410
  // Feature 1 (agreement-gate) — fan-out diagnostics (additive telemetry).
5819
6411
  ...mapFanOutDiagnostics(response.fan_out_diagnostics) !== void 0 ? { fan_out_diagnostics: mapFanOutDiagnostics(response.fan_out_diagnostics) } : {},
6412
+ // Stage A7-shadow — adaptive-vs-incumbent comparison (PII-free; present iff shadow on).
6413
+ ...mapShadowSynthesis(response.shadow_synthesis) !== void 0 ? { shadow_synthesis: mapShadowSynthesis(response.shadow_synthesis) } : {},
6414
+ // Critique-uptake (2026-07-20 red-team fix) — verifier-critique
6415
+ // visibility report; previously computed by the engine on every
6416
+ // call but dropped at this boundary.
6417
+ ...response.critique_uptake !== void 0 ? { critique_uptake: response.critique_uptake } : {},
5820
6418
  // Source-grounded additive outputs (Tier-4 features).
5821
6419
  ...useSourceGrounded ? { source_grounded: true } : {},
5822
6420
  ...sourceExtras?.citation_grade !== void 0 ? { citation_grade: sourceExtras.citation_grade } : {},
@@ -5832,25 +6430,12 @@ function createEngineConsensusClient(options) {
5832
6430
  }
5833
6431
  };
5834
6432
  }
5835
- var DEFAULT_MODELS = {
5836
- // These ids match the strategic-roadmap §4 `newsStandard` / `newsDeep` panel
5837
- // intent — current production model ids. Per handoff §C-3 these MUST come
5838
- // from `CONSENSUS_PANELS` in `functions-shared/shared-model-resolvers.ts`
5839
- // for V1; placeholder defaults here keep Phase 2 Lane A non-blocking.
5840
- anthropic: "claude-opus-4-7",
5841
- openai: "gpt-5",
5842
- // gemini-2.5-FLASH (not -pro): flash accepts the default thinkingBudget=0 from
5843
- // callGeminiWithMetrics; 2.5-pro REJECTS budget 0 ("only works in thinking mode").
5844
- // Flash is also ~10x cheaper. 2026-06-02.
5845
- google: "gemini-2.5-flash",
5846
- deepseek: "deepseek-chat"
5847
- };
6433
+ var DEFAULT_MODELS = getVoMcpConsensusPanel();
5848
6434
  function probeProviders(env = process.env) {
5849
6435
  const out = [];
5850
6436
  if ((env["ANTHROPIC_API_KEY"] ?? "").trim().length > 0) out.push("anthropic");
5851
6437
  if ((env["OPENAI_API_KEY"] ?? "").trim().length > 0) out.push("openai");
5852
6438
  if ((env["GOOGLE_API_KEY"] ?? "").trim().length > 0) out.push("google");
5853
- if ((env["DEEPSEEK_API_KEY"] ?? "").trim().length > 0) out.push("deepseek");
5854
6439
  return out;
5855
6440
  }
5856
6441
  async function loadFactoryAndCallers(injectedEngine, injectedShared) {
@@ -5896,21 +6481,24 @@ async function tryCreateEngineConsensusClientFromEnvAsync(options = {}) {
5896
6481
  anthropic: loaded.shared.callAnthropicWithMetrics,
5897
6482
  openai: loaded.shared.callOpenAIWithMetrics,
5898
6483
  google: loaded.shared.callGeminiWithMetrics,
5899
- deepseek: loaded.shared.callDeepSeekWithMetrics
6484
+ deepseek: loaded.shared.callDeepSeekWithMetrics,
6485
+ meta: options.metaCaller ?? callMetaWithMetrics
5900
6486
  };
5901
6487
  const modelByProvider = {
5902
6488
  anthropic: options.models?.anthropic ?? DEFAULT_MODELS.anthropic,
5903
6489
  openai: options.models?.openai ?? DEFAULT_MODELS.openai,
5904
6490
  google: options.models?.google ?? DEFAULT_MODELS.google,
5905
- deepseek: options.models?.deepseek ?? DEFAULT_MODELS.deepseek
6491
+ deepseek: options.models?.deepseek ?? DEFAULT_MODELS.deepseek,
6492
+ meta: options.models?.meta ?? DEFAULT_MODELS.meta
5906
6493
  };
6494
+ const adapterEnv = !(env[META_MODEL_API_KEY_ENV] ?? "").trim() && (env[META_MODEL_API_KEY_ALIAS] ?? "").trim() ? { ...env, [META_MODEL_API_KEY_ENV]: env[META_MODEL_API_KEY_ALIAS] } : env;
5907
6495
  const panel = [];
5908
6496
  for (const p of providers) {
5909
6497
  try {
5910
6498
  const adapter = loaded.engine.createAdapter(p, {
5911
6499
  model: modelByProvider[p],
5912
6500
  caller: callerByProvider[p],
5913
- envSource: env
6501
+ envSource: adapterEnv
5914
6502
  });
5915
6503
  panel.push(adapter);
5916
6504
  } catch {