@algosuite/vo-mcp 0.2.0-beta.2 → 0.2.0-beta.21
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 -38
- package/dist/autostart-cli.js +104 -14
- package/dist/autostart-cli.js.map +2 -2
- package/dist/ci/check-local-pr-overlap.js +102449 -0
- package/dist/cli.js +1880 -786
- package/dist/cli.js.map +4 -4
- package/dist/index.js +869 -129
- package/dist/index.js.map +4 -4
- package/dist/install-cli.js +365 -327
- package/dist/install-cli.js.map +4 -4
- package/dist/login-cli.js +4 -4
- package/dist/login-cli.js.map +2 -2
- package/dist/pair-cli.js +1 -1
- package/dist/pair-cli.js.map +2 -2
- package/dist/runner-cli.js +9605 -1946
- package/dist/runner-cli.js.map +4 -4
- package/dist/runner-supervisor.js +2333 -0
- package/dist/runner-supervisor.js.map +7 -0
- package/dist/set-key-cli.js +44 -4
- package/dist/set-key-cli.js.map +2 -2
- package/dist/supervisor-credential-helper.js +233 -0
- package/dist/supervisor-credential-helper.js.map +7 -0
- package/dist/update-cli.js +125 -0
- package/dist/update-cli.js.map +7 -0
- package/package.json +4 -2
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 });
|
|
@@ -287,6 +293,123 @@ var init_credential_store = __esm({
|
|
|
287
293
|
}
|
|
288
294
|
});
|
|
289
295
|
|
|
296
|
+
// src/tools/memory/safe-memory-file.ts
|
|
297
|
+
import { resolve, sep } from "node:path";
|
|
298
|
+
function isSafeMemoryFileName(fileName) {
|
|
299
|
+
return fileName.length <= 200 && fileName.trim() === fileName && !fileName.includes("/") && !fileName.includes("\\") && !fileName.includes(":") && SAFE_MEMORY_FILE_RE.test(fileName);
|
|
300
|
+
}
|
|
301
|
+
function resolveMemoryFilePath(memoryDir, fileName) {
|
|
302
|
+
if (!isSafeMemoryFileName(fileName)) {
|
|
303
|
+
throw new Error(`unsafe memory file_name: ${fileName.slice(0, 80)}`);
|
|
304
|
+
}
|
|
305
|
+
const root = resolve(memoryDir);
|
|
306
|
+
const filePath = resolve(root, fileName);
|
|
307
|
+
const rootPrefix = root.endsWith(sep) ? root : `${root}${sep}`;
|
|
308
|
+
if (filePath !== root && !filePath.startsWith(rootPrefix)) {
|
|
309
|
+
throw new Error(`memory file path escapes memory directory: ${fileName.slice(0, 80)}`);
|
|
310
|
+
}
|
|
311
|
+
return filePath;
|
|
312
|
+
}
|
|
313
|
+
var SAFE_MEMORY_FILE_RE;
|
|
314
|
+
var init_safe_memory_file = __esm({
|
|
315
|
+
"src/tools/memory/safe-memory-file.ts"() {
|
|
316
|
+
"use strict";
|
|
317
|
+
SAFE_MEMORY_FILE_RE = /^[A-Za-z0-9][A-Za-z0-9._ -]*\.md$/i;
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// src/tools/memory/memory-knowledge-bridge.ts
|
|
322
|
+
var memory_knowledge_bridge_exports = {};
|
|
323
|
+
__export(memory_knowledge_bridge_exports, {
|
|
324
|
+
extractMemoryTitle: () => extractMemoryTitle,
|
|
325
|
+
upsertMemoryFilesAsKnowledge: () => upsertMemoryFilesAsKnowledge
|
|
326
|
+
});
|
|
327
|
+
import { existsSync as existsSync5, readdirSync as readdirSync4, readFileSync as readFileSync7 } from "node:fs";
|
|
328
|
+
function extractMemoryTitle(fileName, content) {
|
|
329
|
+
const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
330
|
+
if (frontmatter) {
|
|
331
|
+
const description23 = frontmatter[1].match(/^description:\s*(.+)$/m);
|
|
332
|
+
if (description23 && description23[1].trim()) return description23[1].trim().slice(0, 200);
|
|
333
|
+
}
|
|
334
|
+
const heading = content.match(/^#\s+(.+)$/m);
|
|
335
|
+
if (heading && heading[1].trim()) return heading[1].trim().slice(0, 200);
|
|
336
|
+
return fileName;
|
|
337
|
+
}
|
|
338
|
+
async function upsertMemoryFilesAsKnowledge(options) {
|
|
339
|
+
const { controlPlaneUrl, token, memoryDir, fetchFn } = options;
|
|
340
|
+
let files;
|
|
341
|
+
try {
|
|
342
|
+
if (!existsSync5(memoryDir)) {
|
|
343
|
+
return { attempted: 0, upserted: 0, failed: 0, failures: [] };
|
|
344
|
+
}
|
|
345
|
+
files = readdirSync4(memoryDir).filter(
|
|
346
|
+
(f) => f.endsWith(".md") && f.toUpperCase() !== "MEMORY.MD"
|
|
347
|
+
);
|
|
348
|
+
} catch (err) {
|
|
349
|
+
return {
|
|
350
|
+
attempted: 0,
|
|
351
|
+
upserted: 0,
|
|
352
|
+
failed: 1,
|
|
353
|
+
failures: [`memory dir scan: ${err instanceof Error ? err.message : String(err)}`]
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
let upserted = 0;
|
|
357
|
+
const failures = [];
|
|
358
|
+
for (const fileName of files) {
|
|
359
|
+
try {
|
|
360
|
+
const content = readFileSync7(resolveMemoryFilePath(memoryDir, fileName), "utf8");
|
|
361
|
+
if (content.length > CONTENT_HARD_LIMIT) {
|
|
362
|
+
failures.push(`${fileName}: ${content.length} chars exceeds the ${CONTENT_HARD_LIMIT} server limit \u2014 split the memory file`);
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
const title = extractMemoryTitle(fileName, content);
|
|
366
|
+
const base = {
|
|
367
|
+
knowledge_class: "memory",
|
|
368
|
+
source_path: `memory/${fileName}`,
|
|
369
|
+
title,
|
|
370
|
+
content
|
|
371
|
+
};
|
|
372
|
+
const post = (body) => fetchFn(`${controlPlaneUrl}/api/v1/knowledge/private`, {
|
|
373
|
+
method: "POST",
|
|
374
|
+
headers: {
|
|
375
|
+
authorization: `Bearer ${token}`,
|
|
376
|
+
"content-type": "application/json"
|
|
377
|
+
},
|
|
378
|
+
body: JSON.stringify(body)
|
|
379
|
+
});
|
|
380
|
+
let response = await post({
|
|
381
|
+
...base,
|
|
382
|
+
provenance: { written_by: "memory-bridge", source_kind: "operator_memory" }
|
|
383
|
+
});
|
|
384
|
+
if (response.status === 400) {
|
|
385
|
+
response = await post(base);
|
|
386
|
+
}
|
|
387
|
+
if (response.status >= 200 && response.status < 300) {
|
|
388
|
+
upserted += 1;
|
|
389
|
+
} else {
|
|
390
|
+
const text = await response.text();
|
|
391
|
+
failures.push(`${fileName}: HTTP ${response.status} ${text.slice(0, 80)}`);
|
|
392
|
+
}
|
|
393
|
+
} catch (err) {
|
|
394
|
+
failures.push(`${fileName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return {
|
|
398
|
+
attempted: files.length,
|
|
399
|
+
upserted,
|
|
400
|
+
failed: failures.length,
|
|
401
|
+
failures: failures.slice(0, 5)
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
var CONTENT_HARD_LIMIT;
|
|
405
|
+
var init_memory_knowledge_bridge = __esm({
|
|
406
|
+
"src/tools/memory/memory-knowledge-bridge.ts"() {
|
|
407
|
+
"use strict";
|
|
408
|
+
init_safe_memory_file();
|
|
409
|
+
CONTENT_HARD_LIMIT = 5e5;
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
|
|
290
413
|
// src/server.ts
|
|
291
414
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
292
415
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -1122,9 +1245,15 @@ function createMemoryEventsWriter() {
|
|
|
1122
1245
|
function readVoMcpVersion() {
|
|
1123
1246
|
try {
|
|
1124
1247
|
const here = dirname3(fileURLToPath2(import.meta.url));
|
|
1125
|
-
const
|
|
1126
|
-
|
|
1127
|
-
|
|
1248
|
+
for (const rel of ["..", ["..", ".."], ["..", "..", ".."]]) {
|
|
1249
|
+
try {
|
|
1250
|
+
const segs = Array.isArray(rel) ? rel : [rel];
|
|
1251
|
+
const pkg = JSON.parse(readFileSync4(join4(here, ...segs, "package.json"), "utf8"));
|
|
1252
|
+
if (pkg.name === "@algosuite/vo-mcp" && typeof pkg.version === "string") return pkg.version;
|
|
1253
|
+
} catch {
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
return "0.0.0-unknown";
|
|
1128
1257
|
} catch {
|
|
1129
1258
|
return "0.0.0-unknown";
|
|
1130
1259
|
}
|
|
@@ -2044,13 +2173,19 @@ async function handleConsensusJudgment(deps, rawInput, signal) {
|
|
|
2044
2173
|
...engineResult.synthesized_verdict.confidence_badge !== void 0 ? { confidence_badge: engineResult.synthesized_verdict.confidence_badge } : {},
|
|
2045
2174
|
// Feature 1 (agreement-gate) — fan-out diagnostics (present iff the gate ran).
|
|
2046
2175
|
...engineResult.fan_out_diagnostics !== void 0 ? { fan_out_diagnostics: engineResult.fan_out_diagnostics } : {},
|
|
2176
|
+
// Stage A7-shadow — adaptive-vs-incumbent comparison (PII-free; present iff shadow on).
|
|
2177
|
+
...engineResult.shadow_synthesis !== void 0 ? { shadow_synthesis: engineResult.shadow_synthesis } : {},
|
|
2047
2178
|
// Source-grounded Tier-4 outputs (present iff the call was source-grounded).
|
|
2048
2179
|
...engineResult.source_grounded === true ? { source_grounded: true } : {},
|
|
2049
2180
|
...engineResult.citation_grade !== void 0 ? { citation_grade: engineResult.citation_grade } : {},
|
|
2050
2181
|
...engineResult.low_confidence_sources !== void 0 ? { low_confidence_sources: engineResult.low_confidence_sources } : {},
|
|
2051
2182
|
// Escalation (from citation grade or human-tiebreak synthesizer).
|
|
2052
2183
|
...engineResult.escalation_required !== void 0 ? { escalation_required: engineResult.escalation_required } : {},
|
|
2053
|
-
...engineResult.escalation_reason !== void 0 ? { escalation_reason: engineResult.escalation_reason } : {}
|
|
2184
|
+
...engineResult.escalation_reason !== void 0 ? { escalation_reason: engineResult.escalation_reason } : {},
|
|
2185
|
+
// Critique-uptake (2026-07-20 red-team fix) — the engine computes this
|
|
2186
|
+
// on every call; this spread closes the gap where the visibility report
|
|
2187
|
+
// was itself silently dropped at the payload boundary.
|
|
2188
|
+
...engineResult.critique_uptake !== void 0 ? { critique_uptake: engineResult.critique_uptake } : {}
|
|
2054
2189
|
};
|
|
2055
2190
|
const envelope = {
|
|
2056
2191
|
tool: TOOL_NAME4,
|
|
@@ -4097,7 +4232,7 @@ var inputSchema13 = {
|
|
|
4097
4232
|
properties: {},
|
|
4098
4233
|
additionalProperties: false
|
|
4099
4234
|
};
|
|
4100
|
-
var description13 = "Lists open
|
|
4235
|
+
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
4236
|
function isToolInput13(v) {
|
|
4102
4237
|
return typeof v === "object" && v !== null;
|
|
4103
4238
|
}
|
|
@@ -4133,7 +4268,7 @@ var inputSchema14 = {
|
|
|
4133
4268
|
required: ["pr_number"],
|
|
4134
4269
|
additionalProperties: false
|
|
4135
4270
|
};
|
|
4136
|
-
var description14 = "Approves + merges a single
|
|
4271
|
+
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
4272
|
function isToolInput14(v) {
|
|
4138
4273
|
if (typeof v !== "object" || v === null) return false;
|
|
4139
4274
|
const o = v;
|
|
@@ -4215,7 +4350,7 @@ var inputSchema16 = {
|
|
|
4215
4350
|
properties: {},
|
|
4216
4351
|
additionalProperties: false
|
|
4217
4352
|
};
|
|
4218
|
-
var description16 = "Iterates all open
|
|
4353
|
+
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
4354
|
function isToolInput16(v) {
|
|
4220
4355
|
return typeof v === "object" && v !== null;
|
|
4221
4356
|
}
|
|
@@ -4249,7 +4384,7 @@ var inputSchema17 = {
|
|
|
4249
4384
|
required: ["pr_number"],
|
|
4250
4385
|
additionalProperties: false
|
|
4251
4386
|
};
|
|
4252
|
-
var description17 = "Closes
|
|
4387
|
+
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
4388
|
function isToolInput17(v) {
|
|
4254
4389
|
if (typeof v !== "object" || v === null) return false;
|
|
4255
4390
|
const o = v;
|
|
@@ -4325,7 +4460,7 @@ function buildPrompt4(pr, notes) {
|
|
|
4325
4460
|
const lines = [
|
|
4326
4461
|
"You are a release gatekeeper deciding whether a pull request is safe to MERGE.",
|
|
4327
4462
|
"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
|
|
4463
|
+
"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
4464
|
"",
|
|
4330
4465
|
`PR #${pr.number}: ${pr.title}`,
|
|
4331
4466
|
`Source: ${pr.source ?? "unknown"}`,
|
|
@@ -4393,7 +4528,7 @@ async function handleReviewMerge(deps, rawInput, signal) {
|
|
|
4393
4528
|
}
|
|
4394
4529
|
if (pr === null) {
|
|
4395
4530
|
return emit(
|
|
4396
|
-
emptyPayload("hold", `PR #${prNumber} is not among open
|
|
4531
|
+
emptyPayload("hold", `PR #${prNumber} is not among open AlgoHQ PRs (already merged/closed, or not an AlgoHQ-source PR).`, null)
|
|
4397
4532
|
);
|
|
4398
4533
|
}
|
|
4399
4534
|
const hasBlocker = pr.blocker !== null && pr.blocker !== "none";
|
|
@@ -4488,6 +4623,8 @@ function suggestedHandoffPath(session_id, isoTimestamp) {
|
|
|
4488
4623
|
}
|
|
4489
4624
|
|
|
4490
4625
|
// src/tools/session/report-session-state.ts
|
|
4626
|
+
init_auth_token_source();
|
|
4627
|
+
init_credential_store();
|
|
4491
4628
|
var TOOL_NAME19 = "vo_report_session_state";
|
|
4492
4629
|
var VALID_AGENT_TYPES = ["claude-code", "codex", "cursor", "continue"];
|
|
4493
4630
|
var MAX_GOAL_CHARS = 500;
|
|
@@ -4538,7 +4675,7 @@ var inputSchema19 = {
|
|
|
4538
4675
|
required: ["operator_id", "session_id", "agent_type", "context_used_pct"],
|
|
4539
4676
|
additionalProperties: false
|
|
4540
4677
|
};
|
|
4541
|
-
var description19 = "Reports per-session context-window utilization to
|
|
4678
|
+
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
4679
|
function isStringArray2(v, maxItems) {
|
|
4543
4680
|
if (!Array.isArray(v)) return false;
|
|
4544
4681
|
if (v.length > maxItems) return false;
|
|
@@ -4563,33 +4700,93 @@ function isToolInput19(v) {
|
|
|
4563
4700
|
}
|
|
4564
4701
|
return true;
|
|
4565
4702
|
}
|
|
4566
|
-
function
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4703
|
+
async function fetchCloudIdentity(url, token, fetchFn) {
|
|
4704
|
+
try {
|
|
4705
|
+
const response = await fetchFn(`${url}/api/v1/auth/me`, {
|
|
4706
|
+
method: "GET",
|
|
4707
|
+
headers: {
|
|
4708
|
+
"Authorization": `Bearer ${token}`
|
|
4709
|
+
}
|
|
4710
|
+
});
|
|
4711
|
+
if (!response.ok) return null;
|
|
4712
|
+
const data = await response.json();
|
|
4713
|
+
if (!data.ok || !data.provisioned || !data.operator_id || !data.tenant_id) return null;
|
|
4714
|
+
return { operator_id: data.operator_id, tenant_id: data.tenant_id };
|
|
4715
|
+
} catch {
|
|
4716
|
+
return null;
|
|
4717
|
+
}
|
|
4571
4718
|
}
|
|
4572
|
-
async function
|
|
4719
|
+
async function getCloudConfig(fetchFn = fetch) {
|
|
4720
|
+
const url = process.env["VO_CONTROL_PLANE_URL"]?.trim();
|
|
4721
|
+
if (!url) return null;
|
|
4722
|
+
const tokenSource = createAuthTokenSourceFromEnv(
|
|
4723
|
+
process.env,
|
|
4724
|
+
fetchFn,
|
|
4725
|
+
() => readStoredCredential(process.env)
|
|
4726
|
+
);
|
|
4727
|
+
const token = await tokenSource?.getToken();
|
|
4728
|
+
if (!token) return null;
|
|
4729
|
+
const tenant_id = process.env["VO_TENANT_ID"]?.trim();
|
|
4730
|
+
if (tenant_id) return { url, token, tenant_id };
|
|
4731
|
+
const identity = await fetchCloudIdentity(url, token, fetchFn);
|
|
4732
|
+
if (!identity) return null;
|
|
4733
|
+
return { url, token, tenant_id: identity.tenant_id, operator_id: identity.operator_id };
|
|
4734
|
+
}
|
|
4735
|
+
async function tryCloudReportState(cloud, input, fetchFn = fetch) {
|
|
4573
4736
|
try {
|
|
4574
|
-
const
|
|
4737
|
+
const reportBody = {
|
|
4575
4738
|
context_used_pct: input.context_used_pct
|
|
4576
4739
|
};
|
|
4577
|
-
if (input.current_goal !== void 0)
|
|
4740
|
+
if (input.current_goal !== void 0) reportBody["current_goal"] = input.current_goal;
|
|
4578
4741
|
if (input.recent_files_touched !== void 0) {
|
|
4579
|
-
|
|
4742
|
+
reportBody["recent_files_touched"] = input.recent_files_touched;
|
|
4580
4743
|
}
|
|
4581
4744
|
if (input.recent_tool_uses !== void 0) {
|
|
4582
|
-
|
|
4745
|
+
reportBody["recent_tool_uses"] = input.recent_tool_uses;
|
|
4583
4746
|
}
|
|
4584
|
-
const
|
|
4585
|
-
|
|
4747
|
+
const reportUrl = `${cloud.url}/api/v1/session/${input.session_id}/report-state`;
|
|
4748
|
+
let response = await fetchFn(reportUrl, {
|
|
4586
4749
|
method: "POST",
|
|
4587
4750
|
headers: {
|
|
4588
4751
|
"Content-Type": "application/json",
|
|
4589
4752
|
"Authorization": `Bearer ${cloud.token}`
|
|
4590
4753
|
},
|
|
4591
|
-
body: JSON.stringify(
|
|
4754
|
+
body: JSON.stringify(reportBody)
|
|
4592
4755
|
});
|
|
4756
|
+
if (response.status === 404) {
|
|
4757
|
+
const allocateBody = {
|
|
4758
|
+
operator_id: cloud.operator_id ?? input.operator_id,
|
|
4759
|
+
tenant_id: cloud.tenant_id,
|
|
4760
|
+
agent_type: input.agent_type,
|
|
4761
|
+
current_goal: input.current_goal ?? "Interactive session"
|
|
4762
|
+
};
|
|
4763
|
+
if (input.context_used_pct > 0) {
|
|
4764
|
+
allocateBody["initial_context_used_pct"] = input.context_used_pct;
|
|
4765
|
+
}
|
|
4766
|
+
const allocateUrl = `${cloud.url}/api/v1/session`;
|
|
4767
|
+
const allocateResponse = await fetchFn(allocateUrl, {
|
|
4768
|
+
method: "POST",
|
|
4769
|
+
headers: {
|
|
4770
|
+
"Content-Type": "application/json",
|
|
4771
|
+
"Authorization": `Bearer ${cloud.token}`
|
|
4772
|
+
},
|
|
4773
|
+
body: JSON.stringify(allocateBody)
|
|
4774
|
+
});
|
|
4775
|
+
if (!allocateResponse.ok) {
|
|
4776
|
+
return null;
|
|
4777
|
+
}
|
|
4778
|
+
const allocateData = await allocateResponse.json();
|
|
4779
|
+
const retrySessionId = typeof allocateData.session?.session_id === "string" && allocateData.session.session_id.length > 0 ? allocateData.session.session_id : input.session_id;
|
|
4780
|
+
const retryReportUrl = `${cloud.url}/api/v1/session/${retrySessionId}/report-state`;
|
|
4781
|
+
response = await fetchFn(retryReportUrl, {
|
|
4782
|
+
method: "POST",
|
|
4783
|
+
headers: {
|
|
4784
|
+
"Content-Type": "application/json",
|
|
4785
|
+
"Authorization": `Bearer ${cloud.token}`
|
|
4786
|
+
},
|
|
4787
|
+
body: JSON.stringify(reportBody)
|
|
4788
|
+
});
|
|
4789
|
+
}
|
|
4593
4790
|
if (!response.ok) {
|
|
4594
4791
|
return null;
|
|
4595
4792
|
}
|
|
@@ -4620,7 +4817,7 @@ async function handleReportSessionState(deps, rawInput, _signal) {
|
|
|
4620
4817
|
`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
4818
|
);
|
|
4622
4819
|
}
|
|
4623
|
-
const cloud = getCloudConfig();
|
|
4820
|
+
const cloud = await getCloudConfig();
|
|
4624
4821
|
if (cloud !== null) {
|
|
4625
4822
|
const cloudPayload = await tryCloudReportState(cloud, rawInput);
|
|
4626
4823
|
if (cloudPayload !== null) {
|
|
@@ -4703,7 +4900,7 @@ var MANDATORY_READS = [
|
|
|
4703
4900
|
function buildSuccessorPrompt(handoffMarkdown, goal) {
|
|
4704
4901
|
const reads = MANDATORY_READS.map((r, i) => ` ${i + 1}. ${r}`).join("\n");
|
|
4705
4902
|
const lines = [
|
|
4706
|
-
"You are the SUCCESSOR agent for
|
|
4903
|
+
"You are the SUCCESSOR agent for an AlgoHQ lane. The previous session",
|
|
4707
4904
|
"exhausted its context and wrote the handoff below. Read it fully, verify its",
|
|
4708
4905
|
'"verification needed" items against live state (a handoff is a claim, not',
|
|
4709
4906
|
"evidence \u2014 verify via `git show origin/main:<path>`), then continue the lane.",
|
|
@@ -4714,7 +4911,7 @@ function buildSuccessorPrompt(handoffMarkdown, goal) {
|
|
|
4714
4911
|
"NON-NEGOTIABLES: multi-model consensus verification is the core; test honesty",
|
|
4715
4912
|
"(verified-answer-only, no fake green); verify-before-act + human merge approval;",
|
|
4716
4913
|
"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
|
|
4914
|
+
"branch; finish line is MERGED + DEPLOYED + LIVE-VERIFIED, and AlgoHQ changes update",
|
|
4718
4915
|
"the roadmap in the same PR.",
|
|
4719
4916
|
"",
|
|
4720
4917
|
"--- HANDOFF ---",
|
|
@@ -4863,7 +5060,8 @@ async function handleConciergeDispatch(deps, rawInput, _signal) {
|
|
|
4863
5060
|
// src/tools/memory/sync-config.ts
|
|
4864
5061
|
import { homedir as homedir5 } from "node:os";
|
|
4865
5062
|
import { join as join7 } from "node:path";
|
|
4866
|
-
import { existsSync as
|
|
5063
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync3, readdirSync as readdirSync5 } from "node:fs";
|
|
5064
|
+
init_safe_memory_file();
|
|
4867
5065
|
var TOOL_NAME22 = "vo_sync_config";
|
|
4868
5066
|
var inputSchema22 = {
|
|
4869
5067
|
type: "object",
|
|
@@ -4890,14 +5088,13 @@ function isToolInput22(v) {
|
|
|
4890
5088
|
return true;
|
|
4891
5089
|
}
|
|
4892
5090
|
function deriveProjectSlug(cwd) {
|
|
4893
|
-
|
|
4894
|
-
return normalized.replace(/^([A-Z]):/i, (_, drive) => `${drive.toUpperCase()}-`).replace(/\/$/g, "").split("/").join("--").replace(/\s+/g, "-");
|
|
5091
|
+
return cwd.replace(/([^:\\/])[\\/]+$/, "$1").replace(/\\/g, "/").replace(/^([a-zA-Z]):/, (_m, drive) => `${drive.toUpperCase()}:`).replace(/[^a-zA-Z0-9]/g, "-");
|
|
4895
5092
|
}
|
|
4896
5093
|
function getMemoryDir(cwd) {
|
|
4897
5094
|
const slug = deriveProjectSlug(cwd);
|
|
4898
5095
|
return join7(homedir5(), ".claude", "projects", slug, "memory");
|
|
4899
5096
|
}
|
|
4900
|
-
async function pullMemory(controlPlaneUrl, token, memoryDir,
|
|
5097
|
+
async function pullMemory(controlPlaneUrl, token, memoryDir, fetchFn) {
|
|
4901
5098
|
const url = `${controlPlaneUrl}/api/v1/agent-config/memory/me`;
|
|
4902
5099
|
const response = await fetchFn(url, {
|
|
4903
5100
|
method: "GET",
|
|
@@ -4913,22 +5110,25 @@ async function pullMemory(controlPlaneUrl, token, memoryDir, sessionId, fetchFn)
|
|
|
4913
5110
|
if (!data.ok || !Array.isArray(data.entries)) {
|
|
4914
5111
|
throw new Error("GET /api/v1/agent-config/memory/me response missing ok=true or entries array");
|
|
4915
5112
|
}
|
|
5113
|
+
const writes = data.entries.map((entry) => ({
|
|
5114
|
+
entry,
|
|
5115
|
+
filePath: resolveMemoryFilePath(memoryDir, entry.file_name)
|
|
5116
|
+
}));
|
|
4916
5117
|
mkdirSync4(memoryDir, { recursive: true });
|
|
4917
5118
|
const files = [];
|
|
4918
|
-
for (const entry of
|
|
4919
|
-
const filePath = join7(memoryDir, entry.file_name);
|
|
5119
|
+
for (const { entry, filePath } of writes) {
|
|
4920
5120
|
writeFileSync3(filePath, entry.content, "utf8");
|
|
4921
5121
|
files.push(entry.file_name);
|
|
4922
5122
|
}
|
|
4923
5123
|
return { pulled: data.entries.length, files };
|
|
4924
5124
|
}
|
|
4925
5125
|
async function pushMemory(controlPlaneUrl, token, memoryDir, sessionId, fetchFn) {
|
|
4926
|
-
if (!
|
|
5126
|
+
if (!existsSync6(memoryDir)) {
|
|
4927
5127
|
return { pushed: 0, created: 0, updated: 0 };
|
|
4928
5128
|
}
|
|
4929
|
-
const localFiles =
|
|
5129
|
+
const localFiles = readdirSync5(memoryDir).filter((f) => f.endsWith(".md")).map((f) => ({
|
|
4930
5130
|
file_name: f,
|
|
4931
|
-
content:
|
|
5131
|
+
content: readFileSync8(resolveMemoryFilePath(memoryDir, f), "utf8"),
|
|
4932
5132
|
entry_type: f === "MEMORY.md" ? "index" : "topic"
|
|
4933
5133
|
}));
|
|
4934
5134
|
if (localFiles.length === 0) {
|
|
@@ -5010,102 +5210,528 @@ async function pushMemory(controlPlaneUrl, token, memoryDir, sessionId, fetchFn)
|
|
|
5010
5210
|
}
|
|
5011
5211
|
return { pushed: localFiles.length, created, updated };
|
|
5012
5212
|
}
|
|
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
|
-
}
|
|
5213
|
+
async function runMemorySync(action, cwd, sessionId, fetchFn = globalThis.fetch) {
|
|
5020
5214
|
const controlPlaneUrl = process.env["VO_CONTROL_PLANE_URL"];
|
|
5021
5215
|
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
|
-
});
|
|
5216
|
+
return { synced: false, reason: "VO_CONTROL_PLANE_URL not set \u2014 cloud mode disabled" };
|
|
5030
5217
|
}
|
|
5031
5218
|
const { createAuthTokenSourceFromEnv: createAuthTokenSourceFromEnv2 } = await Promise.resolve().then(() => (init_auth_token_source(), auth_token_source_exports));
|
|
5032
5219
|
const { readStoredCredential: readStoredCredential2 } = await Promise.resolve().then(() => (init_credential_store(), credential_store_exports));
|
|
5033
5220
|
const tokenSource = createAuthTokenSourceFromEnv2(process.env, fetchFn, () => readStoredCredential2(process.env));
|
|
5034
5221
|
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
|
-
});
|
|
5222
|
+
return { synced: false, reason: "No auth configured. Run `vo-mcp login` to authenticate as an operator." };
|
|
5043
5223
|
}
|
|
5044
5224
|
const token = await tokenSource.getToken();
|
|
5045
5225
|
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
|
-
});
|
|
5226
|
+
return { synced: false, reason: "Failed to obtain auth token. Run `vo-mcp login` to re-authenticate." };
|
|
5054
5227
|
}
|
|
5055
|
-
const cwd = rawInput.cwd?.trim() || process.cwd();
|
|
5056
5228
|
const memoryDir = getMemoryDir(cwd);
|
|
5229
|
+
const baseUrl = controlPlaneUrl.replace(/\/+$/, "");
|
|
5057
5230
|
try {
|
|
5058
|
-
if (
|
|
5059
|
-
const
|
|
5060
|
-
|
|
5231
|
+
if (action === "pull") {
|
|
5232
|
+
const result2 = await pullMemory(baseUrl, token, memoryDir, fetchFn);
|
|
5233
|
+
return { synced: true, action: "pull", pulled: result2.pulled, files: result2.files, memory_dir: memoryDir };
|
|
5234
|
+
}
|
|
5235
|
+
const result = await pushMemory(baseUrl, token, memoryDir, sessionId, fetchFn);
|
|
5236
|
+
let bridge = { upserted: 0, failed: 0, failures: [] };
|
|
5237
|
+
try {
|
|
5238
|
+
const { upsertMemoryFilesAsKnowledge: upsertMemoryFilesAsKnowledge2 } = await Promise.resolve().then(() => (init_memory_knowledge_bridge(), memory_knowledge_bridge_exports));
|
|
5239
|
+
bridge = await upsertMemoryFilesAsKnowledge2({
|
|
5240
|
+
controlPlaneUrl: baseUrl,
|
|
5061
5241
|
token,
|
|
5062
5242
|
memoryDir,
|
|
5063
|
-
deps.sessionId,
|
|
5064
5243
|
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
5244
|
});
|
|
5245
|
+
} catch (err) {
|
|
5246
|
+
bridge = {
|
|
5247
|
+
upserted: 0,
|
|
5248
|
+
failed: 1,
|
|
5249
|
+
failures: [`bridge unavailable: ${err instanceof Error ? err.message : String(err)}`]
|
|
5250
|
+
};
|
|
5097
5251
|
}
|
|
5252
|
+
return {
|
|
5253
|
+
synced: true,
|
|
5254
|
+
action: "push",
|
|
5255
|
+
pushed: result.pushed,
|
|
5256
|
+
created: result.created,
|
|
5257
|
+
updated: result.updated,
|
|
5258
|
+
memory_dir: memoryDir,
|
|
5259
|
+
knowledge_upserted: bridge.upserted,
|
|
5260
|
+
knowledge_failed: bridge.failed,
|
|
5261
|
+
...bridge.failed > 0 ? { knowledge_failures: bridge.failures } : {}
|
|
5262
|
+
};
|
|
5098
5263
|
} catch (err) {
|
|
5099
5264
|
const message = err instanceof Error ? err.message : String(err);
|
|
5100
|
-
return
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5265
|
+
return { synced: false, reason: `Sync failed: ${message}` };
|
|
5266
|
+
}
|
|
5267
|
+
}
|
|
5268
|
+
async function handleSyncConfig(deps, rawInput, _signal, fetchFn = globalThis.fetch) {
|
|
5269
|
+
if (!isToolInput22(rawInput)) {
|
|
5270
|
+
throw invalidParams(
|
|
5271
|
+
TOOL_NAME22,
|
|
5272
|
+
'invalid input. Required: { action: "pull" | "push" }. Optional: { cwd: "<path>" }.'
|
|
5273
|
+
);
|
|
5274
|
+
}
|
|
5275
|
+
const cwd = rawInput.cwd?.trim() || process.cwd();
|
|
5276
|
+
const result = await runMemorySync(rawInput.action, cwd, deps.session.sessionId, fetchFn);
|
|
5277
|
+
return jsonContent({ tool: TOOL_NAME22, schema_version: 1, payload: result });
|
|
5278
|
+
}
|
|
5279
|
+
|
|
5280
|
+
// src/tools/memory/private-knowledge.ts
|
|
5281
|
+
var UPSERT_TOOL_NAME = "vo_private_knowledge_upsert";
|
|
5282
|
+
var CONTEXT_TOOL_NAME = "vo_private_knowledge_context";
|
|
5283
|
+
var INVALIDATE_TOOL_NAME = "vo_private_knowledge_invalidate";
|
|
5284
|
+
var KNOWLEDGE_CLASSES = ["memory", "skill", "doctrine", "hook", "command"];
|
|
5285
|
+
var PRECISION_CHAR_BUDGET = 12e3;
|
|
5286
|
+
var upsertInputSchema = {
|
|
5287
|
+
type: "object",
|
|
5288
|
+
properties: {
|
|
5289
|
+
knowledge_class: { type: "string", enum: KNOWLEDGE_CLASSES },
|
|
5290
|
+
source_path: { type: "string", description: "Stable private source identifier; not exposed to other users." },
|
|
5291
|
+
title: { type: "string", description: 'Descriptive, retrieval-friendly title (e.g. "AlgoTax OCR redaction architecture", not "notes") \u2014 retrieval matches on it.' },
|
|
5292
|
+
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." }
|
|
5293
|
+
},
|
|
5294
|
+
required: ["knowledge_class", "source_path", "title", "content"],
|
|
5295
|
+
additionalProperties: false
|
|
5296
|
+
};
|
|
5297
|
+
var contextInputSchema = {
|
|
5298
|
+
type: "object",
|
|
5299
|
+
properties: {
|
|
5300
|
+
query: { type: "string" },
|
|
5301
|
+
limit: { type: "number", minimum: 1, maximum: 50 },
|
|
5302
|
+
knowledge_class: { type: "string", enum: KNOWLEDGE_CLASSES }
|
|
5303
|
+
},
|
|
5304
|
+
required: ["query"],
|
|
5305
|
+
additionalProperties: false
|
|
5306
|
+
};
|
|
5307
|
+
var invalidateInputSchema = {
|
|
5308
|
+
type: "object",
|
|
5309
|
+
properties: {
|
|
5310
|
+
knowledge_class: { type: "string", enum: KNOWLEDGE_CLASSES },
|
|
5311
|
+
source_path: { type: "string", minLength: 1, maxLength: 400, description: "Stable private source identifier of the entry to invalidate \u2014 must match the source_path used at upsert." }
|
|
5312
|
+
},
|
|
5313
|
+
required: ["knowledge_class", "source_path"],
|
|
5314
|
+
additionalProperties: false
|
|
5315
|
+
};
|
|
5316
|
+
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.";
|
|
5317
|
+
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.";
|
|
5318
|
+
var invalidateDescription = `Soft-deletes one private-knowledge entry for the authenticated operator: closes the live entry\u2019s validity window (bi-temporal) so it stops surfacing in retrieval. Never destroys data \u2014 invalidated versions remain queryable server-side via include_invalidated. Identify the entry by the same { knowledge_class, source_path } used at upsert; a not_found response means no live entry matches. After invalidating, self-test via ${CONTEXT_TOOL_NAME} to confirm the entry no longer surfaces.`;
|
|
5319
|
+
function isKnowledgeClass(value) {
|
|
5320
|
+
return typeof value === "string" && KNOWLEDGE_CLASSES.includes(value);
|
|
5321
|
+
}
|
|
5322
|
+
function isUpsertInput(value) {
|
|
5323
|
+
if (typeof value !== "object" || value === null) return false;
|
|
5324
|
+
const input = value;
|
|
5325
|
+
return isKnowledgeClass(input["knowledge_class"]) && typeof input["source_path"] === "string" && typeof input["title"] === "string" && typeof input["content"] === "string";
|
|
5326
|
+
}
|
|
5327
|
+
function isInvalidateInput(value) {
|
|
5328
|
+
if (typeof value !== "object" || value === null) return false;
|
|
5329
|
+
const input = value;
|
|
5330
|
+
return isKnowledgeClass(input["knowledge_class"]) && typeof input["source_path"] === "string";
|
|
5331
|
+
}
|
|
5332
|
+
function isContextInput(value) {
|
|
5333
|
+
if (typeof value !== "object" || value === null) return false;
|
|
5334
|
+
const input = value;
|
|
5335
|
+
if (typeof input["query"] !== "string") return false;
|
|
5336
|
+
if (input["limit"] !== void 0 && typeof input["limit"] !== "number") return false;
|
|
5337
|
+
if (input["knowledge_class"] !== void 0 && !isKnowledgeClass(input["knowledge_class"])) return false;
|
|
5338
|
+
return true;
|
|
5339
|
+
}
|
|
5340
|
+
async function getCloudAuth(fetchFn) {
|
|
5341
|
+
const controlPlaneUrl = process.env["VO_CONTROL_PLANE_URL"]?.replace(/\/+$/, "");
|
|
5342
|
+
if (!controlPlaneUrl) {
|
|
5343
|
+
return { ok: false, reason: "VO_CONTROL_PLANE_URL not set \u2014 cloud mode disabled" };
|
|
5344
|
+
}
|
|
5345
|
+
const { createAuthTokenSourceFromEnv: createAuthTokenSourceFromEnv2 } = await Promise.resolve().then(() => (init_auth_token_source(), auth_token_source_exports));
|
|
5346
|
+
const { readStoredCredential: readStoredCredential2 } = await Promise.resolve().then(() => (init_credential_store(), credential_store_exports));
|
|
5347
|
+
const tokenSource = createAuthTokenSourceFromEnv2(process.env, fetchFn, () => readStoredCredential2(process.env));
|
|
5348
|
+
if (!tokenSource) return { ok: false, reason: "No auth configured. Run `vo-mcp login`." };
|
|
5349
|
+
const token = await tokenSource.getToken();
|
|
5350
|
+
if (!token) return { ok: false, reason: "Failed to obtain auth token. Run `vo-mcp login` again." };
|
|
5351
|
+
return { ok: true, controlPlaneUrl, token };
|
|
5352
|
+
}
|
|
5353
|
+
async function callPrivateKnowledge(path3, body, fetchFn) {
|
|
5354
|
+
const auth = await getCloudAuth(fetchFn);
|
|
5355
|
+
if (!auth.ok) return { ok: false, reason: auth.reason };
|
|
5356
|
+
const response = await fetchFn(`${auth.controlPlaneUrl}${path3}`, {
|
|
5357
|
+
method: "POST",
|
|
5358
|
+
headers: {
|
|
5359
|
+
authorization: `Bearer ${auth.token}`,
|
|
5360
|
+
"content-type": "application/json"
|
|
5361
|
+
},
|
|
5362
|
+
body: JSON.stringify(body)
|
|
5363
|
+
});
|
|
5364
|
+
const text = await response.text();
|
|
5365
|
+
let parsed;
|
|
5366
|
+
try {
|
|
5367
|
+
parsed = text ? JSON.parse(text) : null;
|
|
5368
|
+
} catch {
|
|
5369
|
+
parsed = null;
|
|
5370
|
+
}
|
|
5371
|
+
if (response.status < 200 || response.status >= 300) {
|
|
5372
|
+
return { ok: false, status: response.status, response: parsed ?? text };
|
|
5373
|
+
}
|
|
5374
|
+
return parsed;
|
|
5375
|
+
}
|
|
5376
|
+
async function handlePrivateKnowledgeUpsert(_deps, rawInput, _signal, fetchFn = globalThis.fetch) {
|
|
5377
|
+
if (!isUpsertInput(rawInput)) {
|
|
5378
|
+
throw invalidParams(UPSERT_TOOL_NAME, "expected { knowledge_class, source_path, title, content }.");
|
|
5379
|
+
}
|
|
5380
|
+
const payload = await callPrivateKnowledge("/api/v1/knowledge/private", rawInput, fetchFn);
|
|
5381
|
+
const envelope = {
|
|
5382
|
+
tool: UPSERT_TOOL_NAME,
|
|
5383
|
+
schema_version: 1,
|
|
5384
|
+
payload
|
|
5385
|
+
};
|
|
5386
|
+
if (rawInput.content.length > PRECISION_CHAR_BUDGET) {
|
|
5387
|
+
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}.`;
|
|
5388
|
+
}
|
|
5389
|
+
return jsonContent(envelope);
|
|
5390
|
+
}
|
|
5391
|
+
async function handlePrivateKnowledgeInvalidate(_deps, rawInput, _signal, fetchFn = globalThis.fetch) {
|
|
5392
|
+
if (!isInvalidateInput(rawInput)) {
|
|
5393
|
+
throw invalidParams(INVALIDATE_TOOL_NAME, "expected { knowledge_class, source_path }.");
|
|
5394
|
+
}
|
|
5395
|
+
const payload = await callPrivateKnowledge("/api/v1/knowledge/private/invalidate", rawInput, fetchFn);
|
|
5396
|
+
return jsonContent({ tool: INVALIDATE_TOOL_NAME, schema_version: 1, payload });
|
|
5397
|
+
}
|
|
5398
|
+
async function handlePrivateKnowledgeContext(_deps, rawInput, _signal, fetchFn = globalThis.fetch) {
|
|
5399
|
+
if (!isContextInput(rawInput)) {
|
|
5400
|
+
throw invalidParams(CONTEXT_TOOL_NAME, "expected { query, optional limit, optional knowledge_class }.");
|
|
5401
|
+
}
|
|
5402
|
+
const payload = await callPrivateKnowledge("/api/v1/knowledge/private/context", rawInput, fetchFn);
|
|
5403
|
+
return jsonContent({ tool: CONTEXT_TOOL_NAME, schema_version: 1, payload });
|
|
5404
|
+
}
|
|
5405
|
+
|
|
5406
|
+
// src/tools/hq/whiteboard.ts
|
|
5407
|
+
init_auth_token_source();
|
|
5408
|
+
init_credential_store();
|
|
5409
|
+
var POST_TOOL_NAME = "hq_whiteboard_post";
|
|
5410
|
+
var READ_TOOL_NAME = "hq_whiteboard_read";
|
|
5411
|
+
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.";
|
|
5412
|
+
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.";
|
|
5413
|
+
var postInputSchema = {
|
|
5414
|
+
type: "object",
|
|
5415
|
+
properties: {
|
|
5416
|
+
from: { type: "string", minLength: 1, maxLength: 100, description: "Agent/session display name." },
|
|
5417
|
+
type: { type: "string", minLength: 1, maxLength: 64, description: "Message kind, such as intent, worklog, blocker, or completion." },
|
|
5418
|
+
content: { type: "string", minLength: 1, maxLength: 500, description: "Short coordination note." },
|
|
5419
|
+
targetAgent: { type: "string", maxLength: 100 },
|
|
5420
|
+
tester: { type: "string", maxLength: 100 },
|
|
5421
|
+
tier: { type: "string", maxLength: 32 }
|
|
5422
|
+
},
|
|
5423
|
+
required: ["from", "type", "content"],
|
|
5424
|
+
additionalProperties: false
|
|
5425
|
+
};
|
|
5426
|
+
var readInputSchema = {
|
|
5427
|
+
type: "object",
|
|
5428
|
+
properties: {
|
|
5429
|
+
limit: { type: "integer", minimum: 1, maximum: 100, default: 25 },
|
|
5430
|
+
since: { type: "string", description: "Optional ISO-8601 lower bound." },
|
|
5431
|
+
type: { type: "string", minLength: 1, maxLength: 64 }
|
|
5432
|
+
},
|
|
5433
|
+
additionalProperties: false
|
|
5434
|
+
};
|
|
5435
|
+
function resolveTimeoutMs() {
|
|
5436
|
+
const parsed = Number(process.env["HQ_WHITEBOARD_TIMEOUT_MS"]);
|
|
5437
|
+
return Number.isFinite(parsed) && parsed >= 10 && parsed <= 12e4 ? parsed : 1e4;
|
|
5438
|
+
}
|
|
5439
|
+
function isRecord(value) {
|
|
5440
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5441
|
+
}
|
|
5442
|
+
function onlyKeys(value, allowed) {
|
|
5443
|
+
return Object.keys(value).every((key) => allowed.includes(key));
|
|
5444
|
+
}
|
|
5445
|
+
function isBoundedString(value, min, max) {
|
|
5446
|
+
return typeof value === "string" && value.trim().length >= min && value.trim().length <= max;
|
|
5447
|
+
}
|
|
5448
|
+
function parsePostInput(value) {
|
|
5449
|
+
if (!isRecord(value) || !onlyKeys(value, ["from", "type", "content", "targetAgent", "tester", "tier"])) return null;
|
|
5450
|
+
if (!isBoundedString(value["from"], 1, 100)) return null;
|
|
5451
|
+
if (!isBoundedString(value["type"], 1, 64) || !/^[a-zA-Z0-9_-]+$/.test(value["type"].trim())) return null;
|
|
5452
|
+
if (!isBoundedString(value["content"], 1, 500)) return null;
|
|
5453
|
+
for (const [key, max] of [["targetAgent", 100], ["tester", 100], ["tier", 32]]) {
|
|
5454
|
+
if (value[key] !== void 0 && !isBoundedString(value[key], 0, max)) return null;
|
|
5455
|
+
}
|
|
5456
|
+
return {
|
|
5457
|
+
from: value["from"].trim(),
|
|
5458
|
+
type: value["type"].trim(),
|
|
5459
|
+
content: value["content"].trim(),
|
|
5460
|
+
...typeof value["targetAgent"] === "string" ? { targetAgent: value["targetAgent"].trim() } : {},
|
|
5461
|
+
...typeof value["tester"] === "string" ? { tester: value["tester"].trim() } : {},
|
|
5462
|
+
...typeof value["tier"] === "string" ? { tier: value["tier"].trim() } : {}
|
|
5463
|
+
};
|
|
5464
|
+
}
|
|
5465
|
+
function parseReadInput(value) {
|
|
5466
|
+
if (!isRecord(value) || !onlyKeys(value, ["limit", "since", "type"])) return null;
|
|
5467
|
+
if (value["limit"] !== void 0 && (!Number.isInteger(value["limit"]) || Number(value["limit"]) < 1 || Number(value["limit"]) > 100)) return null;
|
|
5468
|
+
if (value["since"] !== void 0 && (typeof value["since"] !== "string" || Number.isNaN(Date.parse(value["since"])))) return null;
|
|
5469
|
+
if (value["type"] !== void 0 && !isBoundedString(value["type"], 1, 64)) return null;
|
|
5470
|
+
return {
|
|
5471
|
+
...typeof value["limit"] === "number" ? { limit: value["limit"] } : {},
|
|
5472
|
+
...typeof value["since"] === "string" ? { since: value["since"] } : {},
|
|
5473
|
+
...typeof value["type"] === "string" ? { type: value["type"].trim() } : {}
|
|
5474
|
+
};
|
|
5475
|
+
}
|
|
5476
|
+
async function resolveCloud(fetchFn) {
|
|
5477
|
+
const url = process.env["VO_CONTROL_PLANE_URL"]?.trim().replace(/\/$/, "");
|
|
5478
|
+
if (!url) return null;
|
|
5479
|
+
try {
|
|
5480
|
+
const source = createAuthTokenSourceFromEnv(process.env, fetchFn, () => readStoredCredential(process.env));
|
|
5481
|
+
const token = await source?.getToken();
|
|
5482
|
+
return token ? { url, token } : null;
|
|
5483
|
+
} catch {
|
|
5484
|
+
return null;
|
|
5485
|
+
}
|
|
5486
|
+
}
|
|
5487
|
+
async function callWhiteboard(method, bodyOrQuery, signal, fetchFn = fetch) {
|
|
5488
|
+
const cloud = await resolveCloud(fetchFn);
|
|
5489
|
+
if (!cloud) {
|
|
5490
|
+
return {
|
|
5491
|
+
ok: false,
|
|
5492
|
+
error: "hq_whiteboard_not_configured",
|
|
5493
|
+
message: "Set VO_CONTROL_PLANE_URL and run vo-mcp login to install a scoped HQ credential."
|
|
5494
|
+
};
|
|
5495
|
+
}
|
|
5496
|
+
const timeoutSignal = AbortSignal.timeout(resolveTimeoutMs());
|
|
5497
|
+
const requestSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
5498
|
+
const query = new URLSearchParams();
|
|
5499
|
+
if (method === "GET") {
|
|
5500
|
+
const input = bodyOrQuery;
|
|
5501
|
+
query.set("limit", String(input.limit ?? 25));
|
|
5502
|
+
if (input.since) query.set("since", input.since);
|
|
5503
|
+
if (input.type) query.set("type", input.type);
|
|
5504
|
+
}
|
|
5505
|
+
try {
|
|
5506
|
+
const response = await fetchFn(
|
|
5507
|
+
`${cloud.url}/api/v1/hq/whiteboard/messages${query.size ? `?${query}` : ""}`,
|
|
5508
|
+
{
|
|
5509
|
+
method,
|
|
5510
|
+
headers: {
|
|
5511
|
+
Authorization: `Bearer ${cloud.token}`,
|
|
5512
|
+
...method === "POST" ? { "Content-Type": "application/json" } : {}
|
|
5513
|
+
},
|
|
5514
|
+
...method === "POST" ? { body: JSON.stringify(bodyOrQuery) } : {},
|
|
5515
|
+
signal: requestSignal
|
|
5106
5516
|
}
|
|
5517
|
+
);
|
|
5518
|
+
const text = await response.text();
|
|
5519
|
+
let payload;
|
|
5520
|
+
try {
|
|
5521
|
+
payload = JSON.parse(text);
|
|
5522
|
+
} catch {
|
|
5523
|
+
payload = { ok: false, error: "invalid_response", message: text.slice(0, 200) };
|
|
5524
|
+
}
|
|
5525
|
+
if (!response.ok) {
|
|
5526
|
+
return { ok: false, error: "hq_whiteboard_http_error", status: response.status, response: payload };
|
|
5527
|
+
}
|
|
5528
|
+
return payload;
|
|
5529
|
+
} catch (error) {
|
|
5530
|
+
return {
|
|
5531
|
+
ok: false,
|
|
5532
|
+
error: signal?.aborted ? "cancelled" : timeoutSignal.aborted ? "hq_whiteboard_timeout" : "hq_whiteboard_unreachable",
|
|
5533
|
+
message: error instanceof Error ? error.message.slice(0, 200) : String(error).slice(0, 200)
|
|
5534
|
+
};
|
|
5535
|
+
}
|
|
5536
|
+
}
|
|
5537
|
+
async function handleHqWhiteboardPost(_deps, rawInput, signal) {
|
|
5538
|
+
const input = parsePostInput(rawInput);
|
|
5539
|
+
if (!input) throw invalidParams(POST_TOOL_NAME, "requires from, type, and 1-500 character content; unknown fields are rejected");
|
|
5540
|
+
return jsonContent(await callWhiteboard("POST", input, signal));
|
|
5541
|
+
}
|
|
5542
|
+
async function handleHqWhiteboardRead(_deps, rawInput, signal) {
|
|
5543
|
+
const input = parseReadInput(rawInput);
|
|
5544
|
+
if (!input) throw invalidParams(READ_TOOL_NAME, "limit must be 1-100, since must be ISO-8601, and unknown fields are rejected");
|
|
5545
|
+
return jsonContent(await callWhiteboard("GET", input, signal));
|
|
5546
|
+
}
|
|
5547
|
+
|
|
5548
|
+
// src/tools/skills/skill-corpus.ts
|
|
5549
|
+
import { existsSync as existsSync7, statSync as statSync5 } from "node:fs";
|
|
5550
|
+
import { dirname as dirname5, isAbsolute, join as join9, resolve as resolve2 } from "node:path";
|
|
5551
|
+
|
|
5552
|
+
// ../skill-registry/src/loader.ts
|
|
5553
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync9, statSync as statSync4 } from "node:fs";
|
|
5554
|
+
import { join as join8 } from "node:path";
|
|
5555
|
+
var InvalidSkillFrontmatterError = class extends Error {
|
|
5556
|
+
constructor(skillFile, reason) {
|
|
5557
|
+
super(`Invalid frontmatter in ${skillFile}: ${reason}`);
|
|
5558
|
+
this.skillFile = skillFile;
|
|
5559
|
+
this.reason = reason;
|
|
5560
|
+
}
|
|
5561
|
+
skillFile;
|
|
5562
|
+
reason;
|
|
5563
|
+
name = "InvalidSkillFrontmatterError";
|
|
5564
|
+
};
|
|
5565
|
+
var FRONTMATTER_DELIMITER = "---";
|
|
5566
|
+
function parseFrontmatter(rawInput, sourcePath) {
|
|
5567
|
+
const raw = rawInput.replace(/\r\n/g, "\n");
|
|
5568
|
+
if (!raw.startsWith(`${FRONTMATTER_DELIMITER}
|
|
5569
|
+
`)) {
|
|
5570
|
+
throw new InvalidSkillFrontmatterError(sourcePath, 'file does not start with frontmatter delimiter "---"');
|
|
5571
|
+
}
|
|
5572
|
+
const afterFirst = raw.slice(FRONTMATTER_DELIMITER.length + 1);
|
|
5573
|
+
const closingIdx = afterFirst.indexOf(`
|
|
5574
|
+
${FRONTMATTER_DELIMITER}
|
|
5575
|
+
`);
|
|
5576
|
+
if (closingIdx === -1) {
|
|
5577
|
+
throw new InvalidSkillFrontmatterError(sourcePath, 'missing closing frontmatter delimiter "---"');
|
|
5578
|
+
}
|
|
5579
|
+
const frontmatterText = afterFirst.slice(0, closingIdx);
|
|
5580
|
+
const body = afterFirst.slice(closingIdx + `
|
|
5581
|
+
${FRONTMATTER_DELIMITER}
|
|
5582
|
+
`.length);
|
|
5583
|
+
let name = "";
|
|
5584
|
+
let description23 = "";
|
|
5585
|
+
for (const line of frontmatterText.split("\n")) {
|
|
5586
|
+
const trimmed = line.trim();
|
|
5587
|
+
if (trimmed.length === 0) continue;
|
|
5588
|
+
const colonIdx = trimmed.indexOf(":");
|
|
5589
|
+
if (colonIdx === -1) continue;
|
|
5590
|
+
const key = trimmed.slice(0, colonIdx).trim();
|
|
5591
|
+
const value = trimmed.slice(colonIdx + 1).trim();
|
|
5592
|
+
if (key === "name") name = value;
|
|
5593
|
+
else if (key === "description") description23 = value;
|
|
5594
|
+
}
|
|
5595
|
+
if (name.length === 0) {
|
|
5596
|
+
throw new InvalidSkillFrontmatterError(sourcePath, 'missing required field "name"');
|
|
5597
|
+
}
|
|
5598
|
+
if (description23.length === 0) {
|
|
5599
|
+
throw new InvalidSkillFrontmatterError(sourcePath, 'missing required field "description"');
|
|
5600
|
+
}
|
|
5601
|
+
return { name, description: description23, body };
|
|
5602
|
+
}
|
|
5603
|
+
function loadSkillsFromDir(skillsDir) {
|
|
5604
|
+
const entries = readdirSync6(skillsDir);
|
|
5605
|
+
const skills = [];
|
|
5606
|
+
for (const entry of entries) {
|
|
5607
|
+
const entryPath = join8(skillsDir, entry);
|
|
5608
|
+
let stat;
|
|
5609
|
+
try {
|
|
5610
|
+
stat = statSync4(entryPath);
|
|
5611
|
+
} catch {
|
|
5612
|
+
continue;
|
|
5613
|
+
}
|
|
5614
|
+
if (!stat.isDirectory()) continue;
|
|
5615
|
+
const skillFile = join8(entryPath, "SKILL.md");
|
|
5616
|
+
let raw;
|
|
5617
|
+
try {
|
|
5618
|
+
raw = readFileSync9(skillFile, "utf8");
|
|
5619
|
+
} catch {
|
|
5620
|
+
continue;
|
|
5621
|
+
}
|
|
5622
|
+
const { name, description: description23, body } = parseFrontmatter(raw, skillFile);
|
|
5623
|
+
skills.push({ name, description: description23, body, sourcePath: skillFile });
|
|
5624
|
+
}
|
|
5625
|
+
return [...skills].sort((a, b) => a.name.localeCompare(b.name));
|
|
5626
|
+
}
|
|
5627
|
+
|
|
5628
|
+
// src/tools/skills/skill-corpus.ts
|
|
5629
|
+
var LIST_TOOL_NAME = "vo_skill_list";
|
|
5630
|
+
var GET_TOOL_NAME = "vo_skill_get";
|
|
5631
|
+
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.";
|
|
5632
|
+
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.";
|
|
5633
|
+
var listInputSchema = {
|
|
5634
|
+
type: "object",
|
|
5635
|
+
properties: {
|
|
5636
|
+
refresh: {
|
|
5637
|
+
type: "boolean",
|
|
5638
|
+
description: "Re-scan the skills directory instead of using the cached corpus."
|
|
5639
|
+
}
|
|
5640
|
+
},
|
|
5641
|
+
required: []
|
|
5642
|
+
};
|
|
5643
|
+
var getInputSchema = {
|
|
5644
|
+
type: "object",
|
|
5645
|
+
properties: {
|
|
5646
|
+
name: {
|
|
5647
|
+
type: "string",
|
|
5648
|
+
description: "Skill name exactly as returned by vo_skill_list."
|
|
5649
|
+
}
|
|
5650
|
+
},
|
|
5651
|
+
required: ["name"]
|
|
5652
|
+
};
|
|
5653
|
+
var MAX_WALK_UP_LEVELS = 8;
|
|
5654
|
+
var cachedCorpus = null;
|
|
5655
|
+
function resolveSkillsDir(env = process.env, startDir = process.cwd()) {
|
|
5656
|
+
const override = env.VO_SKILLS_DIR;
|
|
5657
|
+
if (typeof override === "string" && override.length > 0) {
|
|
5658
|
+
const abs = isAbsolute(override) ? override : resolve2(startDir, override);
|
|
5659
|
+
return existsSync7(abs) && statSync5(abs).isDirectory() ? abs : null;
|
|
5660
|
+
}
|
|
5661
|
+
let dir = resolve2(startDir);
|
|
5662
|
+
for (let i = 0; i < MAX_WALK_UP_LEVELS; i += 1) {
|
|
5663
|
+
const candidate = join9(dir, ".claude", "skills");
|
|
5664
|
+
if (existsSync7(candidate) && statSync5(candidate).isDirectory()) return candidate;
|
|
5665
|
+
const parent = dirname5(dir);
|
|
5666
|
+
if (parent === dir) break;
|
|
5667
|
+
dir = parent;
|
|
5668
|
+
}
|
|
5669
|
+
return null;
|
|
5670
|
+
}
|
|
5671
|
+
function loadCorpus() {
|
|
5672
|
+
const skillsDir = resolveSkillsDir();
|
|
5673
|
+
if (skillsDir === null) {
|
|
5674
|
+
return {
|
|
5675
|
+
skills: [],
|
|
5676
|
+
skillsDir: null,
|
|
5677
|
+
unavailableReason: "No skills directory found. Set VO_SKILLS_DIR or run inside a repo with .claude/skills."
|
|
5678
|
+
};
|
|
5679
|
+
}
|
|
5680
|
+
try {
|
|
5681
|
+
return { skills: loadSkillsFromDir(skillsDir), skillsDir, unavailableReason: null };
|
|
5682
|
+
} catch (err) {
|
|
5683
|
+
const message = err instanceof Error ? `${err.name}: ${err.message}` : String(err);
|
|
5684
|
+
return { skills: [], skillsDir, unavailableReason: message };
|
|
5685
|
+
}
|
|
5686
|
+
}
|
|
5687
|
+
function getCorpus(refresh) {
|
|
5688
|
+
if (refresh || cachedCorpus === null) {
|
|
5689
|
+
cachedCorpus = loadCorpus();
|
|
5690
|
+
}
|
|
5691
|
+
return cachedCorpus;
|
|
5692
|
+
}
|
|
5693
|
+
async function handleSkillList(_deps, rawInput) {
|
|
5694
|
+
const input = rawInput ?? {};
|
|
5695
|
+
const refresh = input.refresh === true;
|
|
5696
|
+
const corpus = getCorpus(refresh);
|
|
5697
|
+
return jsonContent({
|
|
5698
|
+
corpus_available: corpus.unavailableReason === null,
|
|
5699
|
+
skills_dir: corpus.skillsDir,
|
|
5700
|
+
unavailable_reason: corpus.unavailableReason,
|
|
5701
|
+
skill_count: corpus.skills.length,
|
|
5702
|
+
skills: corpus.skills.map((s) => ({ name: s.name, description: s.description }))
|
|
5703
|
+
});
|
|
5704
|
+
}
|
|
5705
|
+
async function handleSkillGet(_deps, rawInput) {
|
|
5706
|
+
const input = rawInput ?? {};
|
|
5707
|
+
if (typeof input.name !== "string" || input.name.trim().length === 0) {
|
|
5708
|
+
throw invalidParams(GET_TOOL_NAME, 'input field "name" (non-empty string) is required');
|
|
5709
|
+
}
|
|
5710
|
+
const requested = input.name.trim();
|
|
5711
|
+
const corpus = getCorpus(false);
|
|
5712
|
+
if (corpus.unavailableReason !== null) {
|
|
5713
|
+
return jsonContent({
|
|
5714
|
+
corpus_available: false,
|
|
5715
|
+
unavailable_reason: corpus.unavailableReason,
|
|
5716
|
+
skill: null
|
|
5107
5717
|
});
|
|
5108
5718
|
}
|
|
5719
|
+
const skill = corpus.skills.find((s) => s.name === requested);
|
|
5720
|
+
if (skill === void 0) {
|
|
5721
|
+
throw invalidParams(
|
|
5722
|
+
GET_TOOL_NAME,
|
|
5723
|
+
`unknown skill "${requested}". Known skills: ${corpus.skills.map((s) => s.name).join(", ")}`
|
|
5724
|
+
);
|
|
5725
|
+
}
|
|
5726
|
+
return jsonContent({
|
|
5727
|
+
corpus_available: true,
|
|
5728
|
+
skill: {
|
|
5729
|
+
name: skill.name,
|
|
5730
|
+
description: skill.description,
|
|
5731
|
+
instructions: skill.body,
|
|
5732
|
+
source_path: skill.sourcePath
|
|
5733
|
+
}
|
|
5734
|
+
});
|
|
5109
5735
|
}
|
|
5110
5736
|
|
|
5111
5737
|
// src/server.ts
|
|
@@ -5286,6 +5912,62 @@ function buildToolRegistry() {
|
|
|
5286
5912
|
inputSchema: inputSchema22
|
|
5287
5913
|
},
|
|
5288
5914
|
handler: handleSyncConfig
|
|
5915
|
+
},
|
|
5916
|
+
[UPSERT_TOOL_NAME]: {
|
|
5917
|
+
definition: {
|
|
5918
|
+
name: UPSERT_TOOL_NAME,
|
|
5919
|
+
description: upsertDescription,
|
|
5920
|
+
inputSchema: upsertInputSchema
|
|
5921
|
+
},
|
|
5922
|
+
handler: handlePrivateKnowledgeUpsert
|
|
5923
|
+
},
|
|
5924
|
+
[CONTEXT_TOOL_NAME]: {
|
|
5925
|
+
definition: {
|
|
5926
|
+
name: CONTEXT_TOOL_NAME,
|
|
5927
|
+
description: contextDescription,
|
|
5928
|
+
inputSchema: contextInputSchema
|
|
5929
|
+
},
|
|
5930
|
+
handler: handlePrivateKnowledgeContext
|
|
5931
|
+
},
|
|
5932
|
+
[INVALIDATE_TOOL_NAME]: {
|
|
5933
|
+
definition: {
|
|
5934
|
+
name: INVALIDATE_TOOL_NAME,
|
|
5935
|
+
description: invalidateDescription,
|
|
5936
|
+
inputSchema: invalidateInputSchema
|
|
5937
|
+
},
|
|
5938
|
+
handler: handlePrivateKnowledgeInvalidate
|
|
5939
|
+
},
|
|
5940
|
+
[POST_TOOL_NAME]: {
|
|
5941
|
+
definition: {
|
|
5942
|
+
name: POST_TOOL_NAME,
|
|
5943
|
+
description: postDescription,
|
|
5944
|
+
inputSchema: postInputSchema
|
|
5945
|
+
},
|
|
5946
|
+
handler: handleHqWhiteboardPost
|
|
5947
|
+
},
|
|
5948
|
+
[READ_TOOL_NAME]: {
|
|
5949
|
+
definition: {
|
|
5950
|
+
name: READ_TOOL_NAME,
|
|
5951
|
+
description: readDescription,
|
|
5952
|
+
inputSchema: readInputSchema
|
|
5953
|
+
},
|
|
5954
|
+
handler: handleHqWhiteboardRead
|
|
5955
|
+
},
|
|
5956
|
+
[LIST_TOOL_NAME]: {
|
|
5957
|
+
definition: {
|
|
5958
|
+
name: LIST_TOOL_NAME,
|
|
5959
|
+
description: listDescription,
|
|
5960
|
+
inputSchema: listInputSchema
|
|
5961
|
+
},
|
|
5962
|
+
handler: handleSkillList
|
|
5963
|
+
},
|
|
5964
|
+
[GET_TOOL_NAME]: {
|
|
5965
|
+
definition: {
|
|
5966
|
+
name: GET_TOOL_NAME,
|
|
5967
|
+
description: getDescription,
|
|
5968
|
+
inputSchema: getInputSchema
|
|
5969
|
+
},
|
|
5970
|
+
handler: handleSkillGet
|
|
5289
5971
|
}
|
|
5290
5972
|
};
|
|
5291
5973
|
}
|
|
@@ -5344,7 +6026,7 @@ function listToolNames() {
|
|
|
5344
6026
|
// src/cache/sqlite-cache.ts
|
|
5345
6027
|
import { createHash as createHash3 } from "node:crypto";
|
|
5346
6028
|
import { chmodSync as chmodSync3, mkdirSync as mkdirSync5 } from "node:fs";
|
|
5347
|
-
import { dirname as
|
|
6029
|
+
import { dirname as dirname6 } from "node:path";
|
|
5348
6030
|
import { DatabaseSync } from "node:sqlite";
|
|
5349
6031
|
|
|
5350
6032
|
// src/cache/canonicalize.ts
|
|
@@ -5389,7 +6071,7 @@ function normalizeString(s) {
|
|
|
5389
6071
|
function createSqliteCache(options) {
|
|
5390
6072
|
const fileBacked = options.dbPath !== ":memory:";
|
|
5391
6073
|
if (fileBacked) {
|
|
5392
|
-
mkdirSync5(
|
|
6074
|
+
mkdirSync5(dirname6(options.dbPath), { recursive: true, mode: 448 });
|
|
5393
6075
|
}
|
|
5394
6076
|
const versionNamespace = options.cacheVersionNamespace ?? "";
|
|
5395
6077
|
const db = new DatabaseSync(options.dbPath);
|
|
@@ -5575,6 +6257,44 @@ function createNullConsensusEngineClient(reason = NULL_CLIENT_DEFAULT_REASON) {
|
|
|
5575
6257
|
// src/consensus/engine-client.ts
|
|
5576
6258
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
5577
6259
|
|
|
6260
|
+
// src/consensus/meta-model-caller.ts
|
|
6261
|
+
var META_CONSENSUS_MODEL = "muse-spark-1.1";
|
|
6262
|
+
var META_MODEL_API_KEY_ENV = "MODEL_API_KEY";
|
|
6263
|
+
var META_MODEL_API_KEY_ALIAS = "META_API";
|
|
6264
|
+
function createMetaModelCaller(options = {}) {
|
|
6265
|
+
void options;
|
|
6266
|
+
return async function callMetaWithMetrics2() {
|
|
6267
|
+
throw new Error(
|
|
6268
|
+
"Muse Spark direct consensus is disabled. Use an explicit sanitized task capsule through the AlgoSuite Model Firewall."
|
|
6269
|
+
);
|
|
6270
|
+
};
|
|
6271
|
+
}
|
|
6272
|
+
var callMetaWithMetrics = createMetaModelCaller();
|
|
6273
|
+
|
|
6274
|
+
// src/consensus/consensus-panel.ts
|
|
6275
|
+
var VO_MCP_CONSENSUS_PANEL = {
|
|
6276
|
+
anthropic: "claude-opus-4-7",
|
|
6277
|
+
openai: "gpt-5",
|
|
6278
|
+
// gemini-2.5-FLASH (not -pro): flash accepts the default thinkingBudget=0 from
|
|
6279
|
+
// callGeminiWithMetrics; 2.5-pro REJECTS budget 0 ("only works in thinking mode").
|
|
6280
|
+
// Flash is also ~10x cheaper. 2026-06-02.
|
|
6281
|
+
google: "gemini-2.5-flash",
|
|
6282
|
+
deepseek: "deepseek-chat",
|
|
6283
|
+
// Muse Spark identity is owned by meta-model-caller.ts (single source of
|
|
6284
|
+
// truth for the meta slot); re-exported here so the panel stays complete.
|
|
6285
|
+
meta: META_CONSENSUS_MODEL
|
|
6286
|
+
};
|
|
6287
|
+
function getVoMcpConsensusPanel(panel = VO_MCP_CONSENSUS_PANEL) {
|
|
6288
|
+
for (const [provider, modelId] of Object.entries(panel)) {
|
|
6289
|
+
if (typeof modelId !== "string" || modelId.trim().length === 0) {
|
|
6290
|
+
throw new Error(
|
|
6291
|
+
`getVoMcpConsensusPanel: panel slot "${provider}" has a missing or blank model ID`
|
|
6292
|
+
);
|
|
6293
|
+
}
|
|
6294
|
+
}
|
|
6295
|
+
return panel;
|
|
6296
|
+
}
|
|
6297
|
+
|
|
5578
6298
|
// src/consensus/engine-options.ts
|
|
5579
6299
|
var AGREEMENT_GATE_ENV_VAR = "VO_CONSENSUS_AGREEMENT_GATE";
|
|
5580
6300
|
function isTruthyFlag(raw) {
|
|
@@ -5621,6 +6341,25 @@ function mapFanOutDiagnostics(fd) {
|
|
|
5621
6341
|
refused: fd.refused
|
|
5622
6342
|
};
|
|
5623
6343
|
}
|
|
6344
|
+
var SHADOW_SYNTHESIS_ENV_VAR = "VO_CONSENSUS_SHADOW";
|
|
6345
|
+
function shadowEnabled(env) {
|
|
6346
|
+
const raw = (env ?? {})[SHADOW_SYNTHESIS_ENV_VAR];
|
|
6347
|
+
if (raw === void 0) return true;
|
|
6348
|
+
const norm = raw.trim().toLowerCase();
|
|
6349
|
+
return !(norm === "0" || norm === "false" || norm === "no" || norm === "off" || norm === "");
|
|
6350
|
+
}
|
|
6351
|
+
function mapShadowSynthesis(s) {
|
|
6352
|
+
if (s === void 0) return void 0;
|
|
6353
|
+
return {
|
|
6354
|
+
incumbent: { verdict: s.incumbent.verdict, confidence: s.incumbent.confidence, synthesizer: s.incumbent.synthesizer },
|
|
6355
|
+
adaptive: {
|
|
6356
|
+
verdict: s.adaptive.verdict,
|
|
6357
|
+
confidence: s.adaptive.confidence,
|
|
6358
|
+
...s.adaptive.calibrated_confidence !== void 0 ? { calibrated_confidence: s.adaptive.calibrated_confidence } : {}
|
|
6359
|
+
},
|
|
6360
|
+
agree: s.agree
|
|
6361
|
+
};
|
|
6362
|
+
}
|
|
5624
6363
|
function mapCitationGrade(cg) {
|
|
5625
6364
|
if (cg === void 0) return void 0;
|
|
5626
6365
|
return {
|
|
@@ -5761,7 +6500,12 @@ function createEngineConsensusClient(options) {
|
|
|
5761
6500
|
const engineOptions = {
|
|
5762
6501
|
panel,
|
|
5763
6502
|
...options.per_model_timeout_ms !== void 0 ? { per_model_timeout_ms: options.per_model_timeout_ms } : {},
|
|
5764
|
-
...agreementGate !== void 0 ? { agreement_gate: agreementGate } : {}
|
|
6503
|
+
...agreementGate !== void 0 ? { agreement_gate: agreementGate } : {},
|
|
6504
|
+
// Stage A7-shadow: run the adaptive verdict alongside the live one for grading.
|
|
6505
|
+
// Cheap (pure log-odds over already-fetched verdicts; no extra model calls),
|
|
6506
|
+
// PII-free, and never alters the live verdict. ON by default; kill with
|
|
6507
|
+
// VO_CONSENSUS_SHADOW=0. Cold-start has no skill registry → neutral priors.
|
|
6508
|
+
shadow_synthesis: { enabled: shadowEnabled(options.env) }
|
|
5765
6509
|
};
|
|
5766
6510
|
const sources = request.source_urls;
|
|
5767
6511
|
const useSourceGrounded = sources !== void 0 && sources.length > 0 && typeof engine.runSourceGroundedConsensus === "function";
|
|
@@ -5817,6 +6561,12 @@ function createEngineConsensusClient(options) {
|
|
|
5817
6561
|
...sourceExtras?.escalation_reason !== void 0 ? { escalation_reason: sourceExtras.escalation_reason } : response.escalation_reason !== void 0 ? { escalation_reason: response.escalation_reason } : {},
|
|
5818
6562
|
// Feature 1 (agreement-gate) — fan-out diagnostics (additive telemetry).
|
|
5819
6563
|
...mapFanOutDiagnostics(response.fan_out_diagnostics) !== void 0 ? { fan_out_diagnostics: mapFanOutDiagnostics(response.fan_out_diagnostics) } : {},
|
|
6564
|
+
// Stage A7-shadow — adaptive-vs-incumbent comparison (PII-free; present iff shadow on).
|
|
6565
|
+
...mapShadowSynthesis(response.shadow_synthesis) !== void 0 ? { shadow_synthesis: mapShadowSynthesis(response.shadow_synthesis) } : {},
|
|
6566
|
+
// Critique-uptake (2026-07-20 red-team fix) — verifier-critique
|
|
6567
|
+
// visibility report; previously computed by the engine on every
|
|
6568
|
+
// call but dropped at this boundary.
|
|
6569
|
+
...response.critique_uptake !== void 0 ? { critique_uptake: response.critique_uptake } : {},
|
|
5820
6570
|
// Source-grounded additive outputs (Tier-4 features).
|
|
5821
6571
|
...useSourceGrounded ? { source_grounded: true } : {},
|
|
5822
6572
|
...sourceExtras?.citation_grade !== void 0 ? { citation_grade: sourceExtras.citation_grade } : {},
|
|
@@ -5832,25 +6582,12 @@ function createEngineConsensusClient(options) {
|
|
|
5832
6582
|
}
|
|
5833
6583
|
};
|
|
5834
6584
|
}
|
|
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
|
-
};
|
|
6585
|
+
var DEFAULT_MODELS = getVoMcpConsensusPanel();
|
|
5848
6586
|
function probeProviders(env = process.env) {
|
|
5849
6587
|
const out = [];
|
|
5850
6588
|
if ((env["ANTHROPIC_API_KEY"] ?? "").trim().length > 0) out.push("anthropic");
|
|
5851
6589
|
if ((env["OPENAI_API_KEY"] ?? "").trim().length > 0) out.push("openai");
|
|
5852
6590
|
if ((env["GOOGLE_API_KEY"] ?? "").trim().length > 0) out.push("google");
|
|
5853
|
-
if ((env["DEEPSEEK_API_KEY"] ?? "").trim().length > 0) out.push("deepseek");
|
|
5854
6591
|
return out;
|
|
5855
6592
|
}
|
|
5856
6593
|
async function loadFactoryAndCallers(injectedEngine, injectedShared) {
|
|
@@ -5896,21 +6633,24 @@ async function tryCreateEngineConsensusClientFromEnvAsync(options = {}) {
|
|
|
5896
6633
|
anthropic: loaded.shared.callAnthropicWithMetrics,
|
|
5897
6634
|
openai: loaded.shared.callOpenAIWithMetrics,
|
|
5898
6635
|
google: loaded.shared.callGeminiWithMetrics,
|
|
5899
|
-
deepseek: loaded.shared.callDeepSeekWithMetrics
|
|
6636
|
+
deepseek: loaded.shared.callDeepSeekWithMetrics,
|
|
6637
|
+
meta: options.metaCaller ?? callMetaWithMetrics
|
|
5900
6638
|
};
|
|
5901
6639
|
const modelByProvider = {
|
|
5902
6640
|
anthropic: options.models?.anthropic ?? DEFAULT_MODELS.anthropic,
|
|
5903
6641
|
openai: options.models?.openai ?? DEFAULT_MODELS.openai,
|
|
5904
6642
|
google: options.models?.google ?? DEFAULT_MODELS.google,
|
|
5905
|
-
deepseek: options.models?.deepseek ?? DEFAULT_MODELS.deepseek
|
|
6643
|
+
deepseek: options.models?.deepseek ?? DEFAULT_MODELS.deepseek,
|
|
6644
|
+
meta: options.models?.meta ?? DEFAULT_MODELS.meta
|
|
5906
6645
|
};
|
|
6646
|
+
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
6647
|
const panel = [];
|
|
5908
6648
|
for (const p of providers) {
|
|
5909
6649
|
try {
|
|
5910
6650
|
const adapter = loaded.engine.createAdapter(p, {
|
|
5911
6651
|
model: modelByProvider[p],
|
|
5912
6652
|
caller: callerByProvider[p],
|
|
5913
|
-
envSource:
|
|
6653
|
+
envSource: adapterEnv
|
|
5914
6654
|
});
|
|
5915
6655
|
panel.push(adapter);
|
|
5916
6656
|
} catch {
|