@algosuite/vo-mcp 0.2.0-beta.0 → 0.2.0-beta.10
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 +178 -153
- package/bin/vo-mcp +44 -33
- package/dist/autostart-cli.js +79 -2
- package/dist/autostart-cli.js.map +2 -2
- package/dist/cli.js +1595 -848
- package/dist/cli.js.map +4 -4
- package/dist/index.js +519 -115
- package/dist/index.js.map +4 -4
- package/dist/install-cli.js +351 -322
- package/dist/install-cli.js.map +4 -4
- package/dist/login-cli.js +3 -3
- package/dist/login-cli.js.map +2 -2
- package/dist/pair-cli.js +214 -0
- package/dist/pair-cli.js.map +7 -0
- package/dist/runner-cli.js +7145 -837
- package/dist/runner-cli.js.map +4 -4
- package/dist/runner-supervisor.js +727 -0
- package/dist/runner-supervisor.js.map +7 -0
- package/dist/set-key-cli.js +204 -0
- package/dist/set-key-cli.js.map +7 -0
- package/dist/supervisor-credential-helper.js +227 -0
- package/dist/supervisor-credential-helper.js.map +7 -0
- package/dist/update-cli.js +62 -0
- package/dist/update-cli.js.map +7 -0
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -1122,9 +1122,15 @@ function createMemoryEventsWriter() {
|
|
|
1122
1122
|
function readVoMcpVersion() {
|
|
1123
1123
|
try {
|
|
1124
1124
|
const here = dirname3(fileURLToPath2(import.meta.url));
|
|
1125
|
-
const
|
|
1126
|
-
|
|
1127
|
-
|
|
1125
|
+
for (const rel of ["..", ["..", ".."], ["..", "..", ".."]]) {
|
|
1126
|
+
try {
|
|
1127
|
+
const segs = Array.isArray(rel) ? rel : [rel];
|
|
1128
|
+
const pkg = JSON.parse(readFileSync4(join4(here, ...segs, "package.json"), "utf8"));
|
|
1129
|
+
if (pkg.name === "@algosuite/vo-mcp" && typeof pkg.version === "string") return pkg.version;
|
|
1130
|
+
} catch {
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
return "0.0.0-unknown";
|
|
1128
1134
|
} catch {
|
|
1129
1135
|
return "0.0.0-unknown";
|
|
1130
1136
|
}
|
|
@@ -2044,6 +2050,8 @@ async function handleConsensusJudgment(deps, rawInput, signal) {
|
|
|
2044
2050
|
...engineResult.synthesized_verdict.confidence_badge !== void 0 ? { confidence_badge: engineResult.synthesized_verdict.confidence_badge } : {},
|
|
2045
2051
|
// Feature 1 (agreement-gate) — fan-out diagnostics (present iff the gate ran).
|
|
2046
2052
|
...engineResult.fan_out_diagnostics !== void 0 ? { fan_out_diagnostics: engineResult.fan_out_diagnostics } : {},
|
|
2053
|
+
// Stage A7-shadow — adaptive-vs-incumbent comparison (PII-free; present iff shadow on).
|
|
2054
|
+
...engineResult.shadow_synthesis !== void 0 ? { shadow_synthesis: engineResult.shadow_synthesis } : {},
|
|
2047
2055
|
// Source-grounded Tier-4 outputs (present iff the call was source-grounded).
|
|
2048
2056
|
...engineResult.source_grounded === true ? { source_grounded: true } : {},
|
|
2049
2057
|
...engineResult.citation_grade !== void 0 ? { citation_grade: engineResult.citation_grade } : {},
|
|
@@ -4097,7 +4105,7 @@ var inputSchema13 = {
|
|
|
4097
4105
|
properties: {},
|
|
4098
4106
|
additionalProperties: false
|
|
4099
4107
|
};
|
|
4100
|
-
var description13 = "Lists open
|
|
4108
|
+
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
4109
|
function isToolInput13(v) {
|
|
4102
4110
|
return typeof v === "object" && v !== null;
|
|
4103
4111
|
}
|
|
@@ -4133,7 +4141,7 @@ var inputSchema14 = {
|
|
|
4133
4141
|
required: ["pr_number"],
|
|
4134
4142
|
additionalProperties: false
|
|
4135
4143
|
};
|
|
4136
|
-
var description14 = "Approves + merges a single
|
|
4144
|
+
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
4145
|
function isToolInput14(v) {
|
|
4138
4146
|
if (typeof v !== "object" || v === null) return false;
|
|
4139
4147
|
const o = v;
|
|
@@ -4215,7 +4223,7 @@ var inputSchema16 = {
|
|
|
4215
4223
|
properties: {},
|
|
4216
4224
|
additionalProperties: false
|
|
4217
4225
|
};
|
|
4218
|
-
var description16 = "Iterates all open
|
|
4226
|
+
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
4227
|
function isToolInput16(v) {
|
|
4220
4228
|
return typeof v === "object" && v !== null;
|
|
4221
4229
|
}
|
|
@@ -4249,7 +4257,7 @@ var inputSchema17 = {
|
|
|
4249
4257
|
required: ["pr_number"],
|
|
4250
4258
|
additionalProperties: false
|
|
4251
4259
|
};
|
|
4252
|
-
var description17 = "Closes
|
|
4260
|
+
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
4261
|
function isToolInput17(v) {
|
|
4254
4262
|
if (typeof v !== "object" || v === null) return false;
|
|
4255
4263
|
const o = v;
|
|
@@ -4325,7 +4333,7 @@ function buildPrompt4(pr, notes) {
|
|
|
4325
4333
|
const lines = [
|
|
4326
4334
|
"You are a release gatekeeper deciding whether a pull request is safe to MERGE.",
|
|
4327
4335
|
"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
|
|
4336
|
+
"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
4337
|
"",
|
|
4330
4338
|
`PR #${pr.number}: ${pr.title}`,
|
|
4331
4339
|
`Source: ${pr.source ?? "unknown"}`,
|
|
@@ -4393,7 +4401,7 @@ async function handleReviewMerge(deps, rawInput, signal) {
|
|
|
4393
4401
|
}
|
|
4394
4402
|
if (pr === null) {
|
|
4395
4403
|
return emit(
|
|
4396
|
-
emptyPayload("hold", `PR #${prNumber} is not among open
|
|
4404
|
+
emptyPayload("hold", `PR #${prNumber} is not among open AlgoHQ PRs (already merged/closed, or not an AlgoHQ-source PR).`, null)
|
|
4397
4405
|
);
|
|
4398
4406
|
}
|
|
4399
4407
|
const hasBlocker = pr.blocker !== null && pr.blocker !== "none";
|
|
@@ -4488,6 +4496,8 @@ function suggestedHandoffPath(session_id, isoTimestamp) {
|
|
|
4488
4496
|
}
|
|
4489
4497
|
|
|
4490
4498
|
// src/tools/session/report-session-state.ts
|
|
4499
|
+
init_auth_token_source();
|
|
4500
|
+
init_credential_store();
|
|
4491
4501
|
var TOOL_NAME19 = "vo_report_session_state";
|
|
4492
4502
|
var VALID_AGENT_TYPES = ["claude-code", "codex", "cursor", "continue"];
|
|
4493
4503
|
var MAX_GOAL_CHARS = 500;
|
|
@@ -4538,7 +4548,7 @@ var inputSchema19 = {
|
|
|
4538
4548
|
required: ["operator_id", "session_id", "agent_type", "context_used_pct"],
|
|
4539
4549
|
additionalProperties: false
|
|
4540
4550
|
};
|
|
4541
|
-
var description19 = "Reports per-session context-window utilization to
|
|
4551
|
+
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
4552
|
function isStringArray2(v, maxItems) {
|
|
4543
4553
|
if (!Array.isArray(v)) return false;
|
|
4544
4554
|
if (v.length > maxItems) return false;
|
|
@@ -4563,33 +4573,93 @@ function isToolInput19(v) {
|
|
|
4563
4573
|
}
|
|
4564
4574
|
return true;
|
|
4565
4575
|
}
|
|
4566
|
-
function
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4576
|
+
async function fetchCloudIdentity(url, token, fetchFn) {
|
|
4577
|
+
try {
|
|
4578
|
+
const response = await fetchFn(`${url}/api/v1/auth/me`, {
|
|
4579
|
+
method: "GET",
|
|
4580
|
+
headers: {
|
|
4581
|
+
"Authorization": `Bearer ${token}`
|
|
4582
|
+
}
|
|
4583
|
+
});
|
|
4584
|
+
if (!response.ok) return null;
|
|
4585
|
+
const data = await response.json();
|
|
4586
|
+
if (!data.ok || !data.provisioned || !data.operator_id || !data.tenant_id) return null;
|
|
4587
|
+
return { operator_id: data.operator_id, tenant_id: data.tenant_id };
|
|
4588
|
+
} catch {
|
|
4589
|
+
return null;
|
|
4590
|
+
}
|
|
4571
4591
|
}
|
|
4572
|
-
async function
|
|
4592
|
+
async function getCloudConfig(fetchFn = fetch) {
|
|
4593
|
+
const url = process.env["VO_CONTROL_PLANE_URL"]?.trim();
|
|
4594
|
+
if (!url) return null;
|
|
4595
|
+
const tokenSource = createAuthTokenSourceFromEnv(
|
|
4596
|
+
process.env,
|
|
4597
|
+
fetchFn,
|
|
4598
|
+
() => readStoredCredential(process.env)
|
|
4599
|
+
);
|
|
4600
|
+
const token = await tokenSource?.getToken();
|
|
4601
|
+
if (!token) return null;
|
|
4602
|
+
const tenant_id = process.env["VO_TENANT_ID"]?.trim();
|
|
4603
|
+
if (tenant_id) return { url, token, tenant_id };
|
|
4604
|
+
const identity = await fetchCloudIdentity(url, token, fetchFn);
|
|
4605
|
+
if (!identity) return null;
|
|
4606
|
+
return { url, token, tenant_id: identity.tenant_id, operator_id: identity.operator_id };
|
|
4607
|
+
}
|
|
4608
|
+
async function tryCloudReportState(cloud, input, fetchFn = fetch) {
|
|
4573
4609
|
try {
|
|
4574
|
-
const
|
|
4610
|
+
const reportBody = {
|
|
4575
4611
|
context_used_pct: input.context_used_pct
|
|
4576
4612
|
};
|
|
4577
|
-
if (input.current_goal !== void 0)
|
|
4613
|
+
if (input.current_goal !== void 0) reportBody["current_goal"] = input.current_goal;
|
|
4578
4614
|
if (input.recent_files_touched !== void 0) {
|
|
4579
|
-
|
|
4615
|
+
reportBody["recent_files_touched"] = input.recent_files_touched;
|
|
4580
4616
|
}
|
|
4581
4617
|
if (input.recent_tool_uses !== void 0) {
|
|
4582
|
-
|
|
4618
|
+
reportBody["recent_tool_uses"] = input.recent_tool_uses;
|
|
4583
4619
|
}
|
|
4584
|
-
const
|
|
4585
|
-
|
|
4620
|
+
const reportUrl = `${cloud.url}/api/v1/session/${input.session_id}/report-state`;
|
|
4621
|
+
let response = await fetchFn(reportUrl, {
|
|
4586
4622
|
method: "POST",
|
|
4587
4623
|
headers: {
|
|
4588
4624
|
"Content-Type": "application/json",
|
|
4589
4625
|
"Authorization": `Bearer ${cloud.token}`
|
|
4590
4626
|
},
|
|
4591
|
-
body: JSON.stringify(
|
|
4627
|
+
body: JSON.stringify(reportBody)
|
|
4592
4628
|
});
|
|
4629
|
+
if (response.status === 404) {
|
|
4630
|
+
const allocateBody = {
|
|
4631
|
+
operator_id: cloud.operator_id ?? input.operator_id,
|
|
4632
|
+
tenant_id: cloud.tenant_id,
|
|
4633
|
+
agent_type: input.agent_type,
|
|
4634
|
+
current_goal: input.current_goal ?? "Interactive session"
|
|
4635
|
+
};
|
|
4636
|
+
if (input.context_used_pct > 0) {
|
|
4637
|
+
allocateBody["initial_context_used_pct"] = input.context_used_pct;
|
|
4638
|
+
}
|
|
4639
|
+
const allocateUrl = `${cloud.url}/api/v1/session`;
|
|
4640
|
+
const allocateResponse = await fetchFn(allocateUrl, {
|
|
4641
|
+
method: "POST",
|
|
4642
|
+
headers: {
|
|
4643
|
+
"Content-Type": "application/json",
|
|
4644
|
+
"Authorization": `Bearer ${cloud.token}`
|
|
4645
|
+
},
|
|
4646
|
+
body: JSON.stringify(allocateBody)
|
|
4647
|
+
});
|
|
4648
|
+
if (!allocateResponse.ok) {
|
|
4649
|
+
return null;
|
|
4650
|
+
}
|
|
4651
|
+
const allocateData = await allocateResponse.json();
|
|
4652
|
+
const retrySessionId = typeof allocateData.session?.session_id === "string" && allocateData.session.session_id.length > 0 ? allocateData.session.session_id : input.session_id;
|
|
4653
|
+
const retryReportUrl = `${cloud.url}/api/v1/session/${retrySessionId}/report-state`;
|
|
4654
|
+
response = await fetchFn(retryReportUrl, {
|
|
4655
|
+
method: "POST",
|
|
4656
|
+
headers: {
|
|
4657
|
+
"Content-Type": "application/json",
|
|
4658
|
+
"Authorization": `Bearer ${cloud.token}`
|
|
4659
|
+
},
|
|
4660
|
+
body: JSON.stringify(reportBody)
|
|
4661
|
+
});
|
|
4662
|
+
}
|
|
4593
4663
|
if (!response.ok) {
|
|
4594
4664
|
return null;
|
|
4595
4665
|
}
|
|
@@ -4620,7 +4690,7 @@ async function handleReportSessionState(deps, rawInput, _signal) {
|
|
|
4620
4690
|
`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
4691
|
);
|
|
4622
4692
|
}
|
|
4623
|
-
const cloud = getCloudConfig();
|
|
4693
|
+
const cloud = await getCloudConfig();
|
|
4624
4694
|
if (cloud !== null) {
|
|
4625
4695
|
const cloudPayload = await tryCloudReportState(cloud, rawInput);
|
|
4626
4696
|
if (cloudPayload !== null) {
|
|
@@ -4703,7 +4773,7 @@ var MANDATORY_READS = [
|
|
|
4703
4773
|
function buildSuccessorPrompt(handoffMarkdown, goal) {
|
|
4704
4774
|
const reads = MANDATORY_READS.map((r, i) => ` ${i + 1}. ${r}`).join("\n");
|
|
4705
4775
|
const lines = [
|
|
4706
|
-
"You are the SUCCESSOR agent for
|
|
4776
|
+
"You are the SUCCESSOR agent for an AlgoHQ lane. The previous session",
|
|
4707
4777
|
"exhausted its context and wrote the handoff below. Read it fully, verify its",
|
|
4708
4778
|
'"verification needed" items against live state (a handoff is a claim, not',
|
|
4709
4779
|
"evidence \u2014 verify via `git show origin/main:<path>`), then continue the lane.",
|
|
@@ -4714,7 +4784,7 @@ function buildSuccessorPrompt(handoffMarkdown, goal) {
|
|
|
4714
4784
|
"NON-NEGOTIABLES: multi-model consensus verification is the core; test honesty",
|
|
4715
4785
|
"(verified-answer-only, no fake green); verify-before-act + human merge approval;",
|
|
4716
4786
|
"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
|
|
4787
|
+
"branch; finish line is MERGED + DEPLOYED + LIVE-VERIFIED, and AlgoHQ changes update",
|
|
4718
4788
|
"the roadmap in the same PR.",
|
|
4719
4789
|
"",
|
|
4720
4790
|
"--- HANDOFF ---",
|
|
@@ -4864,6 +4934,27 @@ async function handleConciergeDispatch(deps, rawInput, _signal) {
|
|
|
4864
4934
|
import { homedir as homedir5 } from "node:os";
|
|
4865
4935
|
import { join as join7 } from "node:path";
|
|
4866
4936
|
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync3, readdirSync as readdirSync4 } from "node:fs";
|
|
4937
|
+
|
|
4938
|
+
// src/tools/memory/safe-memory-file.ts
|
|
4939
|
+
import { resolve, sep } from "node:path";
|
|
4940
|
+
var SAFE_MEMORY_FILE_RE = /^[A-Za-z0-9][A-Za-z0-9._ -]*\.md$/i;
|
|
4941
|
+
function isSafeMemoryFileName(fileName) {
|
|
4942
|
+
return fileName.length <= 200 && fileName.trim() === fileName && !fileName.includes("/") && !fileName.includes("\\") && !fileName.includes(":") && SAFE_MEMORY_FILE_RE.test(fileName);
|
|
4943
|
+
}
|
|
4944
|
+
function resolveMemoryFilePath(memoryDir, fileName) {
|
|
4945
|
+
if (!isSafeMemoryFileName(fileName)) {
|
|
4946
|
+
throw new Error(`unsafe memory file_name: ${fileName.slice(0, 80)}`);
|
|
4947
|
+
}
|
|
4948
|
+
const root = resolve(memoryDir);
|
|
4949
|
+
const filePath = resolve(root, fileName);
|
|
4950
|
+
const rootPrefix = root.endsWith(sep) ? root : `${root}${sep}`;
|
|
4951
|
+
if (filePath !== root && !filePath.startsWith(rootPrefix)) {
|
|
4952
|
+
throw new Error(`memory file path escapes memory directory: ${fileName.slice(0, 80)}`);
|
|
4953
|
+
}
|
|
4954
|
+
return filePath;
|
|
4955
|
+
}
|
|
4956
|
+
|
|
4957
|
+
// src/tools/memory/sync-config.ts
|
|
4867
4958
|
var TOOL_NAME22 = "vo_sync_config";
|
|
4868
4959
|
var inputSchema22 = {
|
|
4869
4960
|
type: "object",
|
|
@@ -4890,14 +4981,13 @@ function isToolInput22(v) {
|
|
|
4890
4981
|
return true;
|
|
4891
4982
|
}
|
|
4892
4983
|
function deriveProjectSlug(cwd) {
|
|
4893
|
-
|
|
4894
|
-
return normalized.replace(/^([A-Z]):/i, (_, drive) => `${drive.toUpperCase()}-`).replace(/\/$/g, "").split("/").join("--").replace(/\s+/g, "-");
|
|
4984
|
+
return cwd.replace(/\\/g, "/").replace(/\/+$/g, "").replace(/^([a-zA-Z]):/, (_m, drive) => `${drive.toUpperCase()}:`).replace(/[^a-zA-Z0-9]/g, "-");
|
|
4895
4985
|
}
|
|
4896
4986
|
function getMemoryDir(cwd) {
|
|
4897
4987
|
const slug = deriveProjectSlug(cwd);
|
|
4898
4988
|
return join7(homedir5(), ".claude", "projects", slug, "memory");
|
|
4899
4989
|
}
|
|
4900
|
-
async function pullMemory(controlPlaneUrl, token, memoryDir,
|
|
4990
|
+
async function pullMemory(controlPlaneUrl, token, memoryDir, fetchFn) {
|
|
4901
4991
|
const url = `${controlPlaneUrl}/api/v1/agent-config/memory/me`;
|
|
4902
4992
|
const response = await fetchFn(url, {
|
|
4903
4993
|
method: "GET",
|
|
@@ -4913,10 +5003,13 @@ async function pullMemory(controlPlaneUrl, token, memoryDir, sessionId, fetchFn)
|
|
|
4913
5003
|
if (!data.ok || !Array.isArray(data.entries)) {
|
|
4914
5004
|
throw new Error("GET /api/v1/agent-config/memory/me response missing ok=true or entries array");
|
|
4915
5005
|
}
|
|
5006
|
+
const writes = data.entries.map((entry) => ({
|
|
5007
|
+
entry,
|
|
5008
|
+
filePath: resolveMemoryFilePath(memoryDir, entry.file_name)
|
|
5009
|
+
}));
|
|
4916
5010
|
mkdirSync4(memoryDir, { recursive: true });
|
|
4917
5011
|
const files = [];
|
|
4918
|
-
for (const entry of
|
|
4919
|
-
const filePath = join7(memoryDir, entry.file_name);
|
|
5012
|
+
for (const { entry, filePath } of writes) {
|
|
4920
5013
|
writeFileSync3(filePath, entry.content, "utf8");
|
|
4921
5014
|
files.push(entry.file_name);
|
|
4922
5015
|
}
|
|
@@ -4928,7 +5021,7 @@ async function pushMemory(controlPlaneUrl, token, memoryDir, sessionId, fetchFn)
|
|
|
4928
5021
|
}
|
|
4929
5022
|
const localFiles = readdirSync4(memoryDir).filter((f) => f.endsWith(".md")).map((f) => ({
|
|
4930
5023
|
file_name: f,
|
|
4931
|
-
content: readFileSync7(
|
|
5024
|
+
content: readFileSync7(resolveMemoryFilePath(memoryDir, f), "utf8"),
|
|
4932
5025
|
entry_type: f === "MEMORY.md" ? "index" : "topic"
|
|
4933
5026
|
}));
|
|
4934
5027
|
if (localFiles.length === 0) {
|
|
@@ -5010,103 +5103,293 @@ async function pushMemory(controlPlaneUrl, token, memoryDir, sessionId, fetchFn)
|
|
|
5010
5103
|
}
|
|
5011
5104
|
return { pushed: localFiles.length, created, updated };
|
|
5012
5105
|
}
|
|
5013
|
-
async function
|
|
5014
|
-
if (!isToolInput22(rawInput)) {
|
|
5015
|
-
throw invalidParams(
|
|
5016
|
-
TOOL_NAME22,
|
|
5017
|
-
'invalid input. Required: { action: "pull" | "push" }. Optional: { cwd: "<path>" }.'
|
|
5018
|
-
);
|
|
5019
|
-
}
|
|
5106
|
+
async function runMemorySync(action, cwd, sessionId, fetchFn = globalThis.fetch) {
|
|
5020
5107
|
const controlPlaneUrl = process.env["VO_CONTROL_PLANE_URL"];
|
|
5021
5108
|
if (!controlPlaneUrl) {
|
|
5022
|
-
return
|
|
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
|
-
});
|
|
5109
|
+
return { synced: false, reason: "VO_CONTROL_PLANE_URL not set \u2014 cloud mode disabled" };
|
|
5030
5110
|
}
|
|
5031
5111
|
const { createAuthTokenSourceFromEnv: createAuthTokenSourceFromEnv2 } = await Promise.resolve().then(() => (init_auth_token_source(), auth_token_source_exports));
|
|
5032
5112
|
const { readStoredCredential: readStoredCredential2 } = await Promise.resolve().then(() => (init_credential_store(), credential_store_exports));
|
|
5033
5113
|
const tokenSource = createAuthTokenSourceFromEnv2(process.env, fetchFn, () => readStoredCredential2(process.env));
|
|
5034
5114
|
if (!tokenSource) {
|
|
5035
|
-
return
|
|
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
|
-
});
|
|
5115
|
+
return { synced: false, reason: "No auth configured. Run `vo-mcp login` to authenticate as an operator." };
|
|
5043
5116
|
}
|
|
5044
5117
|
const token = await tokenSource.getToken();
|
|
5045
5118
|
if (!token) {
|
|
5046
|
-
return
|
|
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
|
-
});
|
|
5119
|
+
return { synced: false, reason: "Failed to obtain auth token. Run `vo-mcp login` to re-authenticate." };
|
|
5054
5120
|
}
|
|
5055
|
-
const cwd = rawInput.cwd?.trim() || process.cwd();
|
|
5056
5121
|
const memoryDir = getMemoryDir(cwd);
|
|
5122
|
+
const baseUrl = controlPlaneUrl.replace(/\/+$/, "");
|
|
5057
5123
|
try {
|
|
5058
|
-
if (
|
|
5059
|
-
const
|
|
5060
|
-
|
|
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
|
-
});
|
|
5124
|
+
if (action === "pull") {
|
|
5125
|
+
const result2 = await pullMemory(baseUrl, token, memoryDir, fetchFn);
|
|
5126
|
+
return { synced: true, action: "pull", pulled: result2.pulled, files: result2.files, memory_dir: memoryDir };
|
|
5097
5127
|
}
|
|
5128
|
+
const result = await pushMemory(baseUrl, token, memoryDir, sessionId, fetchFn);
|
|
5129
|
+
return {
|
|
5130
|
+
synced: true,
|
|
5131
|
+
action: "push",
|
|
5132
|
+
pushed: result.pushed,
|
|
5133
|
+
created: result.created,
|
|
5134
|
+
updated: result.updated,
|
|
5135
|
+
memory_dir: memoryDir
|
|
5136
|
+
};
|
|
5098
5137
|
} catch (err) {
|
|
5099
5138
|
const message = err instanceof Error ? err.message : String(err);
|
|
5100
|
-
return
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5139
|
+
return { synced: false, reason: `Sync failed: ${message}` };
|
|
5140
|
+
}
|
|
5141
|
+
}
|
|
5142
|
+
async function handleSyncConfig(deps, rawInput, _signal, fetchFn = globalThis.fetch) {
|
|
5143
|
+
if (!isToolInput22(rawInput)) {
|
|
5144
|
+
throw invalidParams(
|
|
5145
|
+
TOOL_NAME22,
|
|
5146
|
+
'invalid input. Required: { action: "pull" | "push" }. Optional: { cwd: "<path>" }.'
|
|
5147
|
+
);
|
|
5148
|
+
}
|
|
5149
|
+
const cwd = rawInput.cwd?.trim() || process.cwd();
|
|
5150
|
+
const result = await runMemorySync(rawInput.action, cwd, deps.session.sessionId, fetchFn);
|
|
5151
|
+
return jsonContent({ tool: TOOL_NAME22, schema_version: 1, payload: result });
|
|
5152
|
+
}
|
|
5153
|
+
|
|
5154
|
+
// src/tools/memory/private-knowledge.ts
|
|
5155
|
+
var UPSERT_TOOL_NAME = "vo_private_knowledge_upsert";
|
|
5156
|
+
var CONTEXT_TOOL_NAME = "vo_private_knowledge_context";
|
|
5157
|
+
var KNOWLEDGE_CLASSES = ["memory", "skill", "doctrine", "hook", "command"];
|
|
5158
|
+
var PRECISION_CHAR_BUDGET = 12e3;
|
|
5159
|
+
var upsertInputSchema = {
|
|
5160
|
+
type: "object",
|
|
5161
|
+
properties: {
|
|
5162
|
+
knowledge_class: { type: "string", enum: KNOWLEDGE_CLASSES },
|
|
5163
|
+
source_path: { type: "string", description: "Stable private source identifier; not exposed to other users." },
|
|
5164
|
+
title: { type: "string", description: 'Descriptive, retrieval-friendly title (e.g. "AlgoTax OCR redaction architecture", not "notes") \u2014 retrieval matches on it.' },
|
|
5165
|
+
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." }
|
|
5166
|
+
},
|
|
5167
|
+
required: ["knowledge_class", "source_path", "title", "content"],
|
|
5168
|
+
additionalProperties: false
|
|
5169
|
+
};
|
|
5170
|
+
var contextInputSchema = {
|
|
5171
|
+
type: "object",
|
|
5172
|
+
properties: {
|
|
5173
|
+
query: { type: "string" },
|
|
5174
|
+
limit: { type: "number", minimum: 1, maximum: 50 },
|
|
5175
|
+
knowledge_class: { type: "string", enum: KNOWLEDGE_CLASSES }
|
|
5176
|
+
},
|
|
5177
|
+
required: ["query"],
|
|
5178
|
+
additionalProperties: false
|
|
5179
|
+
};
|
|
5180
|
+
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.";
|
|
5181
|
+
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.";
|
|
5182
|
+
function isKnowledgeClass(value) {
|
|
5183
|
+
return typeof value === "string" && KNOWLEDGE_CLASSES.includes(value);
|
|
5184
|
+
}
|
|
5185
|
+
function isUpsertInput(value) {
|
|
5186
|
+
if (typeof value !== "object" || value === null) return false;
|
|
5187
|
+
const input = value;
|
|
5188
|
+
return isKnowledgeClass(input["knowledge_class"]) && typeof input["source_path"] === "string" && typeof input["title"] === "string" && typeof input["content"] === "string";
|
|
5189
|
+
}
|
|
5190
|
+
function isContextInput(value) {
|
|
5191
|
+
if (typeof value !== "object" || value === null) return false;
|
|
5192
|
+
const input = value;
|
|
5193
|
+
if (typeof input["query"] !== "string") return false;
|
|
5194
|
+
if (input["limit"] !== void 0 && typeof input["limit"] !== "number") return false;
|
|
5195
|
+
if (input["knowledge_class"] !== void 0 && !isKnowledgeClass(input["knowledge_class"])) return false;
|
|
5196
|
+
return true;
|
|
5197
|
+
}
|
|
5198
|
+
async function getCloudAuth(fetchFn) {
|
|
5199
|
+
const controlPlaneUrl = process.env["VO_CONTROL_PLANE_URL"]?.replace(/\/+$/, "");
|
|
5200
|
+
if (!controlPlaneUrl) {
|
|
5201
|
+
return { ok: false, reason: "VO_CONTROL_PLANE_URL not set \u2014 cloud mode disabled" };
|
|
5202
|
+
}
|
|
5203
|
+
const { createAuthTokenSourceFromEnv: createAuthTokenSourceFromEnv2 } = await Promise.resolve().then(() => (init_auth_token_source(), auth_token_source_exports));
|
|
5204
|
+
const { readStoredCredential: readStoredCredential2 } = await Promise.resolve().then(() => (init_credential_store(), credential_store_exports));
|
|
5205
|
+
const tokenSource = createAuthTokenSourceFromEnv2(process.env, fetchFn, () => readStoredCredential2(process.env));
|
|
5206
|
+
if (!tokenSource) return { ok: false, reason: "No auth configured. Run `vo-mcp login`." };
|
|
5207
|
+
const token = await tokenSource.getToken();
|
|
5208
|
+
if (!token) return { ok: false, reason: "Failed to obtain auth token. Run `vo-mcp login` again." };
|
|
5209
|
+
return { ok: true, controlPlaneUrl, token };
|
|
5210
|
+
}
|
|
5211
|
+
async function callPrivateKnowledge(path3, body, fetchFn) {
|
|
5212
|
+
const auth = await getCloudAuth(fetchFn);
|
|
5213
|
+
if (!auth.ok) return { ok: false, reason: auth.reason };
|
|
5214
|
+
const response = await fetchFn(`${auth.controlPlaneUrl}${path3}`, {
|
|
5215
|
+
method: "POST",
|
|
5216
|
+
headers: {
|
|
5217
|
+
authorization: `Bearer ${auth.token}`,
|
|
5218
|
+
"content-type": "application/json"
|
|
5219
|
+
},
|
|
5220
|
+
body: JSON.stringify(body)
|
|
5221
|
+
});
|
|
5222
|
+
const text = await response.text();
|
|
5223
|
+
const parsed = text ? JSON.parse(text) : null;
|
|
5224
|
+
if (response.status < 200 || response.status >= 300) {
|
|
5225
|
+
return { ok: false, status: response.status, response: parsed ?? text };
|
|
5226
|
+
}
|
|
5227
|
+
return parsed;
|
|
5228
|
+
}
|
|
5229
|
+
async function handlePrivateKnowledgeUpsert(_deps, rawInput, _signal, fetchFn = globalThis.fetch) {
|
|
5230
|
+
if (!isUpsertInput(rawInput)) {
|
|
5231
|
+
throw invalidParams(UPSERT_TOOL_NAME, "expected { knowledge_class, source_path, title, content }.");
|
|
5232
|
+
}
|
|
5233
|
+
const payload = await callPrivateKnowledge("/api/v1/knowledge/private", rawInput, fetchFn);
|
|
5234
|
+
const envelope = {
|
|
5235
|
+
tool: UPSERT_TOOL_NAME,
|
|
5236
|
+
schema_version: 1,
|
|
5237
|
+
payload
|
|
5238
|
+
};
|
|
5239
|
+
if (rawInput.content.length > PRECISION_CHAR_BUDGET) {
|
|
5240
|
+
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}.`;
|
|
5241
|
+
}
|
|
5242
|
+
return jsonContent(envelope);
|
|
5243
|
+
}
|
|
5244
|
+
async function handlePrivateKnowledgeContext(_deps, rawInput, _signal, fetchFn = globalThis.fetch) {
|
|
5245
|
+
if (!isContextInput(rawInput)) {
|
|
5246
|
+
throw invalidParams(CONTEXT_TOOL_NAME, "expected { query, optional limit, optional knowledge_class }.");
|
|
5247
|
+
}
|
|
5248
|
+
const payload = await callPrivateKnowledge("/api/v1/knowledge/private/context", rawInput, fetchFn);
|
|
5249
|
+
return jsonContent({ tool: CONTEXT_TOOL_NAME, schema_version: 1, payload });
|
|
5250
|
+
}
|
|
5251
|
+
|
|
5252
|
+
// src/tools/hq/whiteboard.ts
|
|
5253
|
+
init_auth_token_source();
|
|
5254
|
+
init_credential_store();
|
|
5255
|
+
var POST_TOOL_NAME = "hq_whiteboard_post";
|
|
5256
|
+
var READ_TOOL_NAME = "hq_whiteboard_read";
|
|
5257
|
+
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.";
|
|
5258
|
+
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.";
|
|
5259
|
+
var postInputSchema = {
|
|
5260
|
+
type: "object",
|
|
5261
|
+
properties: {
|
|
5262
|
+
from: { type: "string", minLength: 1, maxLength: 100, description: "Agent/session display name." },
|
|
5263
|
+
type: { type: "string", minLength: 1, maxLength: 64, description: "Message kind, such as intent, worklog, blocker, or completion." },
|
|
5264
|
+
content: { type: "string", minLength: 1, maxLength: 500, description: "Short coordination note." },
|
|
5265
|
+
targetAgent: { type: "string", maxLength: 100 },
|
|
5266
|
+
tester: { type: "string", maxLength: 100 },
|
|
5267
|
+
tier: { type: "string", maxLength: 32 }
|
|
5268
|
+
},
|
|
5269
|
+
required: ["from", "type", "content"],
|
|
5270
|
+
additionalProperties: false
|
|
5271
|
+
};
|
|
5272
|
+
var readInputSchema = {
|
|
5273
|
+
type: "object",
|
|
5274
|
+
properties: {
|
|
5275
|
+
limit: { type: "integer", minimum: 1, maximum: 100, default: 25 },
|
|
5276
|
+
since: { type: "string", description: "Optional ISO-8601 lower bound." },
|
|
5277
|
+
type: { type: "string", minLength: 1, maxLength: 64 }
|
|
5278
|
+
},
|
|
5279
|
+
additionalProperties: false
|
|
5280
|
+
};
|
|
5281
|
+
function resolveTimeoutMs() {
|
|
5282
|
+
const parsed = Number(process.env["HQ_WHITEBOARD_TIMEOUT_MS"]);
|
|
5283
|
+
return Number.isFinite(parsed) && parsed >= 10 && parsed <= 12e4 ? parsed : 1e4;
|
|
5284
|
+
}
|
|
5285
|
+
function isRecord(value) {
|
|
5286
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5287
|
+
}
|
|
5288
|
+
function onlyKeys(value, allowed) {
|
|
5289
|
+
return Object.keys(value).every((key) => allowed.includes(key));
|
|
5290
|
+
}
|
|
5291
|
+
function isBoundedString(value, min, max) {
|
|
5292
|
+
return typeof value === "string" && value.trim().length >= min && value.trim().length <= max;
|
|
5293
|
+
}
|
|
5294
|
+
function parsePostInput(value) {
|
|
5295
|
+
if (!isRecord(value) || !onlyKeys(value, ["from", "type", "content", "targetAgent", "tester", "tier"])) return null;
|
|
5296
|
+
if (!isBoundedString(value["from"], 1, 100)) return null;
|
|
5297
|
+
if (!isBoundedString(value["type"], 1, 64) || !/^[a-zA-Z0-9_-]+$/.test(value["type"].trim())) return null;
|
|
5298
|
+
if (!isBoundedString(value["content"], 1, 500)) return null;
|
|
5299
|
+
for (const [key, max] of [["targetAgent", 100], ["tester", 100], ["tier", 32]]) {
|
|
5300
|
+
if (value[key] !== void 0 && !isBoundedString(value[key], 0, max)) return null;
|
|
5301
|
+
}
|
|
5302
|
+
return {
|
|
5303
|
+
from: value["from"].trim(),
|
|
5304
|
+
type: value["type"].trim(),
|
|
5305
|
+
content: value["content"].trim(),
|
|
5306
|
+
...typeof value["targetAgent"] === "string" ? { targetAgent: value["targetAgent"].trim() } : {},
|
|
5307
|
+
...typeof value["tester"] === "string" ? { tester: value["tester"].trim() } : {},
|
|
5308
|
+
...typeof value["tier"] === "string" ? { tier: value["tier"].trim() } : {}
|
|
5309
|
+
};
|
|
5310
|
+
}
|
|
5311
|
+
function parseReadInput(value) {
|
|
5312
|
+
if (!isRecord(value) || !onlyKeys(value, ["limit", "since", "type"])) return null;
|
|
5313
|
+
if (value["limit"] !== void 0 && (!Number.isInteger(value["limit"]) || Number(value["limit"]) < 1 || Number(value["limit"]) > 100)) return null;
|
|
5314
|
+
if (value["since"] !== void 0 && (typeof value["since"] !== "string" || Number.isNaN(Date.parse(value["since"])))) return null;
|
|
5315
|
+
if (value["type"] !== void 0 && !isBoundedString(value["type"], 1, 64)) return null;
|
|
5316
|
+
return {
|
|
5317
|
+
...typeof value["limit"] === "number" ? { limit: value["limit"] } : {},
|
|
5318
|
+
...typeof value["since"] === "string" ? { since: value["since"] } : {},
|
|
5319
|
+
...typeof value["type"] === "string" ? { type: value["type"].trim() } : {}
|
|
5320
|
+
};
|
|
5321
|
+
}
|
|
5322
|
+
async function resolveCloud(fetchFn) {
|
|
5323
|
+
const url = process.env["VO_CONTROL_PLANE_URL"]?.trim().replace(/\/$/, "");
|
|
5324
|
+
if (!url) return null;
|
|
5325
|
+
try {
|
|
5326
|
+
const source = createAuthTokenSourceFromEnv(process.env, fetchFn, () => readStoredCredential(process.env));
|
|
5327
|
+
const token = await source?.getToken();
|
|
5328
|
+
return token ? { url, token } : null;
|
|
5329
|
+
} catch {
|
|
5330
|
+
return null;
|
|
5331
|
+
}
|
|
5332
|
+
}
|
|
5333
|
+
async function callWhiteboard(method, bodyOrQuery, signal, fetchFn = fetch) {
|
|
5334
|
+
const cloud = await resolveCloud(fetchFn);
|
|
5335
|
+
if (!cloud) {
|
|
5336
|
+
return {
|
|
5337
|
+
ok: false,
|
|
5338
|
+
error: "hq_whiteboard_not_configured",
|
|
5339
|
+
message: "Set VO_CONTROL_PLANE_URL and run vo-mcp login to install a scoped HQ credential."
|
|
5340
|
+
};
|
|
5341
|
+
}
|
|
5342
|
+
const timeoutSignal = AbortSignal.timeout(resolveTimeoutMs());
|
|
5343
|
+
const requestSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
5344
|
+
const query = new URLSearchParams();
|
|
5345
|
+
if (method === "GET") {
|
|
5346
|
+
const input = bodyOrQuery;
|
|
5347
|
+
query.set("limit", String(input.limit ?? 25));
|
|
5348
|
+
if (input.since) query.set("since", input.since);
|
|
5349
|
+
if (input.type) query.set("type", input.type);
|
|
5350
|
+
}
|
|
5351
|
+
try {
|
|
5352
|
+
const response = await fetchFn(
|
|
5353
|
+
`${cloud.url}/api/v1/hq/whiteboard/messages${query.size ? `?${query}` : ""}`,
|
|
5354
|
+
{
|
|
5355
|
+
method,
|
|
5356
|
+
headers: {
|
|
5357
|
+
Authorization: `Bearer ${cloud.token}`,
|
|
5358
|
+
...method === "POST" ? { "Content-Type": "application/json" } : {}
|
|
5359
|
+
},
|
|
5360
|
+
...method === "POST" ? { body: JSON.stringify(bodyOrQuery) } : {},
|
|
5361
|
+
signal: requestSignal
|
|
5106
5362
|
}
|
|
5107
|
-
|
|
5363
|
+
);
|
|
5364
|
+
const text = await response.text();
|
|
5365
|
+
let payload;
|
|
5366
|
+
try {
|
|
5367
|
+
payload = JSON.parse(text);
|
|
5368
|
+
} catch {
|
|
5369
|
+
payload = { ok: false, error: "invalid_response", message: text.slice(0, 200) };
|
|
5370
|
+
}
|
|
5371
|
+
if (!response.ok) {
|
|
5372
|
+
return { ok: false, error: "hq_whiteboard_http_error", status: response.status, response: payload };
|
|
5373
|
+
}
|
|
5374
|
+
return payload;
|
|
5375
|
+
} catch (error) {
|
|
5376
|
+
return {
|
|
5377
|
+
ok: false,
|
|
5378
|
+
error: signal?.aborted ? "cancelled" : timeoutSignal.aborted ? "hq_whiteboard_timeout" : "hq_whiteboard_unreachable",
|
|
5379
|
+
message: error instanceof Error ? error.message.slice(0, 200) : String(error).slice(0, 200)
|
|
5380
|
+
};
|
|
5108
5381
|
}
|
|
5109
5382
|
}
|
|
5383
|
+
async function handleHqWhiteboardPost(_deps, rawInput, signal) {
|
|
5384
|
+
const input = parsePostInput(rawInput);
|
|
5385
|
+
if (!input) throw invalidParams(POST_TOOL_NAME, "requires from, type, and 1-500 character content; unknown fields are rejected");
|
|
5386
|
+
return jsonContent(await callWhiteboard("POST", input, signal));
|
|
5387
|
+
}
|
|
5388
|
+
async function handleHqWhiteboardRead(_deps, rawInput, signal) {
|
|
5389
|
+
const input = parseReadInput(rawInput);
|
|
5390
|
+
if (!input) throw invalidParams(READ_TOOL_NAME, "limit must be 1-100, since must be ISO-8601, and unknown fields are rejected");
|
|
5391
|
+
return jsonContent(await callWhiteboard("GET", input, signal));
|
|
5392
|
+
}
|
|
5110
5393
|
|
|
5111
5394
|
// src/server.ts
|
|
5112
5395
|
function buildToolRegistry() {
|
|
@@ -5286,6 +5569,38 @@ function buildToolRegistry() {
|
|
|
5286
5569
|
inputSchema: inputSchema22
|
|
5287
5570
|
},
|
|
5288
5571
|
handler: handleSyncConfig
|
|
5572
|
+
},
|
|
5573
|
+
[UPSERT_TOOL_NAME]: {
|
|
5574
|
+
definition: {
|
|
5575
|
+
name: UPSERT_TOOL_NAME,
|
|
5576
|
+
description: upsertDescription,
|
|
5577
|
+
inputSchema: upsertInputSchema
|
|
5578
|
+
},
|
|
5579
|
+
handler: handlePrivateKnowledgeUpsert
|
|
5580
|
+
},
|
|
5581
|
+
[CONTEXT_TOOL_NAME]: {
|
|
5582
|
+
definition: {
|
|
5583
|
+
name: CONTEXT_TOOL_NAME,
|
|
5584
|
+
description: contextDescription,
|
|
5585
|
+
inputSchema: contextInputSchema
|
|
5586
|
+
},
|
|
5587
|
+
handler: handlePrivateKnowledgeContext
|
|
5588
|
+
},
|
|
5589
|
+
[POST_TOOL_NAME]: {
|
|
5590
|
+
definition: {
|
|
5591
|
+
name: POST_TOOL_NAME,
|
|
5592
|
+
description: postDescription,
|
|
5593
|
+
inputSchema: postInputSchema
|
|
5594
|
+
},
|
|
5595
|
+
handler: handleHqWhiteboardPost
|
|
5596
|
+
},
|
|
5597
|
+
[READ_TOOL_NAME]: {
|
|
5598
|
+
definition: {
|
|
5599
|
+
name: READ_TOOL_NAME,
|
|
5600
|
+
description: readDescription,
|
|
5601
|
+
inputSchema: readInputSchema
|
|
5602
|
+
},
|
|
5603
|
+
handler: handleHqWhiteboardRead
|
|
5289
5604
|
}
|
|
5290
5605
|
};
|
|
5291
5606
|
}
|
|
@@ -5575,6 +5890,64 @@ function createNullConsensusEngineClient(reason = NULL_CLIENT_DEFAULT_REASON) {
|
|
|
5575
5890
|
// src/consensus/engine-client.ts
|
|
5576
5891
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
5577
5892
|
|
|
5893
|
+
// src/consensus/meta-model-caller.ts
|
|
5894
|
+
var META_MODEL_API_BASE_URL = "https://api.meta.ai/v1";
|
|
5895
|
+
var META_CONSENSUS_MODEL = "muse-spark-1.1";
|
|
5896
|
+
var META_MODEL_API_KEY_ENV = "MODEL_API_KEY";
|
|
5897
|
+
var META_MODEL_API_KEY_ALIAS = "META_API";
|
|
5898
|
+
function resolveMetaKey(env) {
|
|
5899
|
+
return String(env[META_MODEL_API_KEY_ENV] || env[META_MODEL_API_KEY_ALIAS] || "").trim();
|
|
5900
|
+
}
|
|
5901
|
+
function positiveMaxTokens(value) {
|
|
5902
|
+
const parsed = Math.floor(Number(value));
|
|
5903
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 2048;
|
|
5904
|
+
}
|
|
5905
|
+
function createMetaModelCaller(options = {}) {
|
|
5906
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
5907
|
+
const envSource = options.envSource ?? process.env;
|
|
5908
|
+
const reasoningEffort = options.reasoningEffort ?? "high";
|
|
5909
|
+
return async function callMetaWithMetrics2(prompt, systemPrompt, model, maxTokens, _privacyOptions, signal) {
|
|
5910
|
+
const key = resolveMetaKey(envSource);
|
|
5911
|
+
if (!key) throw new Error(`Missing ${META_MODEL_API_KEY_ENV} for Meta Model API`);
|
|
5912
|
+
const messages = [
|
|
5913
|
+
...systemPrompt ? [{ role: "system", content: systemPrompt }] : [],
|
|
5914
|
+
{ role: "user", content: prompt }
|
|
5915
|
+
];
|
|
5916
|
+
const response = await fetchImpl(`${META_MODEL_API_BASE_URL}/chat/completions`, {
|
|
5917
|
+
method: "POST",
|
|
5918
|
+
headers: {
|
|
5919
|
+
Authorization: `Bearer ${key}`,
|
|
5920
|
+
"Content-Type": "application/json"
|
|
5921
|
+
},
|
|
5922
|
+
body: JSON.stringify({
|
|
5923
|
+
model: model || META_CONSENSUS_MODEL,
|
|
5924
|
+
messages,
|
|
5925
|
+
max_tokens: positiveMaxTokens(maxTokens),
|
|
5926
|
+
reasoning_effort: reasoningEffort
|
|
5927
|
+
}),
|
|
5928
|
+
signal
|
|
5929
|
+
});
|
|
5930
|
+
const payload = await response.json();
|
|
5931
|
+
if (!response.ok) {
|
|
5932
|
+
const message = String(payload.error?.message || response.statusText || "request failed").slice(0, 500);
|
|
5933
|
+
throw Object.assign(new Error(`Meta Model API ${response.status}: ${message}`), { status: response.status });
|
|
5934
|
+
}
|
|
5935
|
+
const content = payload.choices?.[0]?.message?.content;
|
|
5936
|
+
if (typeof content !== "string" || !content.trim()) {
|
|
5937
|
+
throw new Error(`Meta Model API returned no text (finish=${payload.choices?.[0]?.finish_reason || "unknown"})`);
|
|
5938
|
+
}
|
|
5939
|
+
const inputTokens = Number(payload.usage?.prompt_tokens || 0);
|
|
5940
|
+
const outputTokens = Number(payload.usage?.completion_tokens || 0);
|
|
5941
|
+
return {
|
|
5942
|
+
content,
|
|
5943
|
+
inputTokens,
|
|
5944
|
+
outputTokens,
|
|
5945
|
+
totalTokens: Number(payload.usage?.total_tokens || inputTokens + outputTokens)
|
|
5946
|
+
};
|
|
5947
|
+
};
|
|
5948
|
+
}
|
|
5949
|
+
var callMetaWithMetrics = createMetaModelCaller();
|
|
5950
|
+
|
|
5578
5951
|
// src/consensus/engine-options.ts
|
|
5579
5952
|
var AGREEMENT_GATE_ENV_VAR = "VO_CONSENSUS_AGREEMENT_GATE";
|
|
5580
5953
|
function isTruthyFlag(raw) {
|
|
@@ -5621,6 +5994,25 @@ function mapFanOutDiagnostics(fd) {
|
|
|
5621
5994
|
refused: fd.refused
|
|
5622
5995
|
};
|
|
5623
5996
|
}
|
|
5997
|
+
var SHADOW_SYNTHESIS_ENV_VAR = "VO_CONSENSUS_SHADOW";
|
|
5998
|
+
function shadowEnabled(env) {
|
|
5999
|
+
const raw = (env ?? {})[SHADOW_SYNTHESIS_ENV_VAR];
|
|
6000
|
+
if (raw === void 0) return true;
|
|
6001
|
+
const norm = raw.trim().toLowerCase();
|
|
6002
|
+
return !(norm === "0" || norm === "false" || norm === "no" || norm === "off" || norm === "");
|
|
6003
|
+
}
|
|
6004
|
+
function mapShadowSynthesis(s) {
|
|
6005
|
+
if (s === void 0) return void 0;
|
|
6006
|
+
return {
|
|
6007
|
+
incumbent: { verdict: s.incumbent.verdict, confidence: s.incumbent.confidence, synthesizer: s.incumbent.synthesizer },
|
|
6008
|
+
adaptive: {
|
|
6009
|
+
verdict: s.adaptive.verdict,
|
|
6010
|
+
confidence: s.adaptive.confidence,
|
|
6011
|
+
...s.adaptive.calibrated_confidence !== void 0 ? { calibrated_confidence: s.adaptive.calibrated_confidence } : {}
|
|
6012
|
+
},
|
|
6013
|
+
agree: s.agree
|
|
6014
|
+
};
|
|
6015
|
+
}
|
|
5624
6016
|
function mapCitationGrade(cg) {
|
|
5625
6017
|
if (cg === void 0) return void 0;
|
|
5626
6018
|
return {
|
|
@@ -5761,7 +6153,12 @@ function createEngineConsensusClient(options) {
|
|
|
5761
6153
|
const engineOptions = {
|
|
5762
6154
|
panel,
|
|
5763
6155
|
...options.per_model_timeout_ms !== void 0 ? { per_model_timeout_ms: options.per_model_timeout_ms } : {},
|
|
5764
|
-
...agreementGate !== void 0 ? { agreement_gate: agreementGate } : {}
|
|
6156
|
+
...agreementGate !== void 0 ? { agreement_gate: agreementGate } : {},
|
|
6157
|
+
// Stage A7-shadow: run the adaptive verdict alongside the live one for grading.
|
|
6158
|
+
// Cheap (pure log-odds over already-fetched verdicts; no extra model calls),
|
|
6159
|
+
// PII-free, and never alters the live verdict. ON by default; kill with
|
|
6160
|
+
// VO_CONSENSUS_SHADOW=0. Cold-start has no skill registry → neutral priors.
|
|
6161
|
+
shadow_synthesis: { enabled: shadowEnabled(options.env) }
|
|
5765
6162
|
};
|
|
5766
6163
|
const sources = request.source_urls;
|
|
5767
6164
|
const useSourceGrounded = sources !== void 0 && sources.length > 0 && typeof engine.runSourceGroundedConsensus === "function";
|
|
@@ -5817,6 +6214,8 @@ function createEngineConsensusClient(options) {
|
|
|
5817
6214
|
...sourceExtras?.escalation_reason !== void 0 ? { escalation_reason: sourceExtras.escalation_reason } : response.escalation_reason !== void 0 ? { escalation_reason: response.escalation_reason } : {},
|
|
5818
6215
|
// Feature 1 (agreement-gate) — fan-out diagnostics (additive telemetry).
|
|
5819
6216
|
...mapFanOutDiagnostics(response.fan_out_diagnostics) !== void 0 ? { fan_out_diagnostics: mapFanOutDiagnostics(response.fan_out_diagnostics) } : {},
|
|
6217
|
+
// Stage A7-shadow — adaptive-vs-incumbent comparison (PII-free; present iff shadow on).
|
|
6218
|
+
...mapShadowSynthesis(response.shadow_synthesis) !== void 0 ? { shadow_synthesis: mapShadowSynthesis(response.shadow_synthesis) } : {},
|
|
5820
6219
|
// Source-grounded additive outputs (Tier-4 features).
|
|
5821
6220
|
...useSourceGrounded ? { source_grounded: true } : {},
|
|
5822
6221
|
...sourceExtras?.citation_grade !== void 0 ? { citation_grade: sourceExtras.citation_grade } : {},
|
|
@@ -5843,7 +6242,8 @@ var DEFAULT_MODELS = {
|
|
|
5843
6242
|
// callGeminiWithMetrics; 2.5-pro REJECTS budget 0 ("only works in thinking mode").
|
|
5844
6243
|
// Flash is also ~10x cheaper. 2026-06-02.
|
|
5845
6244
|
google: "gemini-2.5-flash",
|
|
5846
|
-
deepseek: "deepseek-chat"
|
|
6245
|
+
deepseek: "deepseek-chat",
|
|
6246
|
+
meta: META_CONSENSUS_MODEL
|
|
5847
6247
|
};
|
|
5848
6248
|
function probeProviders(env = process.env) {
|
|
5849
6249
|
const out = [];
|
|
@@ -5851,6 +6251,7 @@ function probeProviders(env = process.env) {
|
|
|
5851
6251
|
if ((env["OPENAI_API_KEY"] ?? "").trim().length > 0) out.push("openai");
|
|
5852
6252
|
if ((env["GOOGLE_API_KEY"] ?? "").trim().length > 0) out.push("google");
|
|
5853
6253
|
if ((env["DEEPSEEK_API_KEY"] ?? "").trim().length > 0) out.push("deepseek");
|
|
6254
|
+
if ((env[META_MODEL_API_KEY_ENV] ?? "").trim().length > 0 || (env[META_MODEL_API_KEY_ALIAS] ?? "").trim().length > 0) out.push("meta");
|
|
5854
6255
|
return out;
|
|
5855
6256
|
}
|
|
5856
6257
|
async function loadFactoryAndCallers(injectedEngine, injectedShared) {
|
|
@@ -5896,21 +6297,24 @@ async function tryCreateEngineConsensusClientFromEnvAsync(options = {}) {
|
|
|
5896
6297
|
anthropic: loaded.shared.callAnthropicWithMetrics,
|
|
5897
6298
|
openai: loaded.shared.callOpenAIWithMetrics,
|
|
5898
6299
|
google: loaded.shared.callGeminiWithMetrics,
|
|
5899
|
-
deepseek: loaded.shared.callDeepSeekWithMetrics
|
|
6300
|
+
deepseek: loaded.shared.callDeepSeekWithMetrics,
|
|
6301
|
+
meta: options.metaCaller ?? callMetaWithMetrics
|
|
5900
6302
|
};
|
|
5901
6303
|
const modelByProvider = {
|
|
5902
6304
|
anthropic: options.models?.anthropic ?? DEFAULT_MODELS.anthropic,
|
|
5903
6305
|
openai: options.models?.openai ?? DEFAULT_MODELS.openai,
|
|
5904
6306
|
google: options.models?.google ?? DEFAULT_MODELS.google,
|
|
5905
|
-
deepseek: options.models?.deepseek ?? DEFAULT_MODELS.deepseek
|
|
6307
|
+
deepseek: options.models?.deepseek ?? DEFAULT_MODELS.deepseek,
|
|
6308
|
+
meta: options.models?.meta ?? DEFAULT_MODELS.meta
|
|
5906
6309
|
};
|
|
6310
|
+
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
6311
|
const panel = [];
|
|
5908
6312
|
for (const p of providers) {
|
|
5909
6313
|
try {
|
|
5910
6314
|
const adapter = loaded.engine.createAdapter(p, {
|
|
5911
6315
|
model: modelByProvider[p],
|
|
5912
6316
|
caller: callerByProvider[p],
|
|
5913
|
-
envSource:
|
|
6317
|
+
envSource: adapterEnv
|
|
5914
6318
|
});
|
|
5915
6319
|
panel.push(adapter);
|
|
5916
6320
|
} catch {
|