@algosuite/vo-mcp 0.2.0-beta.2 → 0.2.0-beta.7
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 +154 -153
- package/bin/vo-mcp +41 -38
- package/dist/autostart-cli.js +79 -2
- package/dist/autostart-cli.js.map +2 -2
- package/dist/cli.js +1389 -875
- package/dist/cli.js.map +4 -4
- package/dist/index.js +354 -108
- package/dist/index.js.map +4 -4
- package/dist/install-cli.js +174 -287
- package/dist/install-cli.js.map +4 -4
- package/dist/login-cli.js +0 -0
- package/dist/login-cli.js.map +1 -1
- package/dist/pair-cli.js +0 -0
- package/dist/pair-cli.js.map +1 -1
- package/dist/runner-cli.js +5937 -875
- package/dist/runner-cli.js.map +4 -4
- package/dist/set-key-cli.js +13 -3
- package/dist/set-key-cli.js.map +2 -2
- package/dist/update-cli.js +62 -0
- package/dist/update-cli.js.map +7 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -15,500 +15,222 @@ var __export = (target, all) => {
|
|
|
15
15
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
// src/
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
// ../vo-arch-defaults/src/schema/rule-v1.ts
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
function parseRule(input) {
|
|
21
|
+
return ArchitecturalDefaultRuleSchema.parse(input);
|
|
22
|
+
}
|
|
23
|
+
var EvidenceMatcherKind, EvidenceMatcher, ChangeType, AppliesWhen, Reference, IsoDate, RuleId, ArchitecturalDefaultRuleSchema;
|
|
24
|
+
var init_rule_v1 = __esm({
|
|
25
|
+
"../vo-arch-defaults/src/schema/rule-v1.ts"() {
|
|
26
|
+
"use strict";
|
|
27
|
+
EvidenceMatcherKind = z.enum([
|
|
28
|
+
"regex",
|
|
29
|
+
"import-detector",
|
|
30
|
+
"package-json-field",
|
|
31
|
+
"file-size",
|
|
32
|
+
"ast-pattern",
|
|
33
|
+
"custom"
|
|
34
|
+
]);
|
|
35
|
+
EvidenceMatcher = z.object({
|
|
36
|
+
kind: EvidenceMatcherKind,
|
|
37
|
+
pattern: z.string().optional(),
|
|
38
|
+
config: z.record(z.string(), z.unknown()).optional(),
|
|
39
|
+
description: z.string().min(1)
|
|
40
|
+
}).strict().refine(
|
|
41
|
+
(m) => m.kind !== "regex" || typeof m.pattern === "string" && m.pattern.length > 0,
|
|
42
|
+
{ message: "regex matcher requires non-empty pattern" }
|
|
43
|
+
);
|
|
44
|
+
ChangeType = z.enum(["new-file", "edit", "delete", "rename", "any"]);
|
|
45
|
+
AppliesWhen = z.object({
|
|
46
|
+
stack: z.array(z.string().min(1)).optional(),
|
|
47
|
+
change_types: z.array(ChangeType).optional(),
|
|
48
|
+
file_globs: z.array(z.string().min(1)).optional(),
|
|
49
|
+
not_file_globs: z.array(z.string().min(1)).optional()
|
|
50
|
+
}).strict();
|
|
51
|
+
Reference = z.object({
|
|
52
|
+
type: z.enum(["framework-doc", "internal-doc", "pr", "incident", "external"]),
|
|
53
|
+
url: z.string().min(1),
|
|
54
|
+
description: z.string().min(1)
|
|
55
|
+
}).strict();
|
|
56
|
+
IsoDate = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "last_verified must be ISO date YYYY-MM-DD").refine((s) => {
|
|
57
|
+
const d = /* @__PURE__ */ new Date(s + "T00:00:00Z");
|
|
58
|
+
return !Number.isNaN(d.getTime()) && d.toISOString().startsWith(s);
|
|
59
|
+
}, "last_verified must be a real calendar date");
|
|
60
|
+
RuleId = z.string().regex(/^[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*$/, {
|
|
61
|
+
message: "rule_id must be `<category>/<kebab-name>`"
|
|
62
|
+
});
|
|
63
|
+
ArchitecturalDefaultRuleSchema = z.object({
|
|
64
|
+
schema_version: z.literal(1),
|
|
65
|
+
rule_id: RuleId,
|
|
66
|
+
rule_version: z.number().int().positive(),
|
|
67
|
+
category: z.string().min(1),
|
|
68
|
+
severity: z.enum(["blocker", "warning", "info"]),
|
|
69
|
+
title: z.string().min(1),
|
|
70
|
+
rationale: z.string().min(1),
|
|
71
|
+
applies_when: AppliesWhen,
|
|
72
|
+
evidence_of_violation: z.array(EvidenceMatcher).min(1, {
|
|
73
|
+
message: "rule must declare at least one evidence matcher"
|
|
74
|
+
}),
|
|
75
|
+
remediation: z.string().min(1),
|
|
76
|
+
references: z.array(Reference),
|
|
77
|
+
last_verified: IsoDate,
|
|
78
|
+
tags: z.array(z.string().min(1))
|
|
79
|
+
}).strict();
|
|
80
|
+
}
|
|
26
81
|
});
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
82
|
+
|
|
83
|
+
// ../vo-arch-defaults/src/schema/override-v1.ts
|
|
84
|
+
import { z as z2 } from "zod";
|
|
85
|
+
function parseOverride(input) {
|
|
86
|
+
return TenantOverrideSchema.parse(input);
|
|
30
87
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const expiresInSec = Number(parsed.expires_in);
|
|
61
|
-
const ttlMs = Number.isFinite(expiresInSec) && expiresInSec > 0 ? expiresInSec * 1e3 : 36e5;
|
|
62
|
-
cachedToken = idToken;
|
|
63
|
-
expiresAtMs = now() + ttlMs;
|
|
64
|
-
return idToken;
|
|
65
|
-
} catch {
|
|
66
|
-
cachedToken = null;
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
88
|
+
var PartialRuleSchema, TenantOverrideSchema;
|
|
89
|
+
var init_override_v1 = __esm({
|
|
90
|
+
"../vo-arch-defaults/src/schema/override-v1.ts"() {
|
|
91
|
+
"use strict";
|
|
92
|
+
init_rule_v1();
|
|
93
|
+
PartialRuleSchema = ArchitecturalDefaultRuleSchema.partial();
|
|
94
|
+
TenantOverrideSchema = z2.object({
|
|
95
|
+
schema_version: z2.literal(1),
|
|
96
|
+
suppressed_rule_ids: z2.array(z2.string().min(1)),
|
|
97
|
+
modified_rules: z2.record(z2.string().min(1), PartialRuleSchema),
|
|
98
|
+
added_rules: z2.array(ArchitecturalDefaultRuleSchema),
|
|
99
|
+
/**
|
|
100
|
+
* Per-tenant stack override (added 2026-05-24 — audit HIGH
|
|
101
|
+
* "DEFAULT_STACK hardcoded for Nexus repo"). When set, downstream
|
|
102
|
+
* consumers (vo-mcp KB pre-filter, vo-arch-check CLI) use this list
|
|
103
|
+
* instead of their built-in default. Lets external tenants whose
|
|
104
|
+
* repo isn't `firebase+react+pnpm` get useful KB rule matches by
|
|
105
|
+
* dropping a `~/.claude/vo-arch-defaults.local.json` with their own
|
|
106
|
+
* stack identifiers — no code change required.
|
|
107
|
+
*
|
|
108
|
+
* Open string union — values are not constrained beyond non-empty
|
|
109
|
+
* strings so out-of-tree tenants can declare their own
|
|
110
|
+
* (e.g. `vercel-edge`, `next-15`, `drizzle-postgres`).
|
|
111
|
+
*
|
|
112
|
+
* Optional. Undefined / omitted preserves pre-2026-05-24 behavior
|
|
113
|
+
* (consumer falls back to its hardcoded default stack).
|
|
114
|
+
*/
|
|
115
|
+
tenant_stack: z2.array(z2.string().min(1)).optional()
|
|
116
|
+
}).strict();
|
|
69
117
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// ../vo-arch-defaults/src/schema/index.ts
|
|
121
|
+
var init_schema = __esm({
|
|
122
|
+
"../vo-arch-defaults/src/schema/index.ts"() {
|
|
123
|
+
"use strict";
|
|
124
|
+
init_rule_v1();
|
|
125
|
+
init_override_v1();
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// ../vo-arch-defaults/src/storage/load-bundled.ts
|
|
130
|
+
import { readdirSync, readFileSync, statSync, existsSync } from "node:fs";
|
|
131
|
+
import { dirname, join } from "node:path";
|
|
132
|
+
import { fileURLToPath } from "node:url";
|
|
133
|
+
function resolveBundledCorpusDir() {
|
|
134
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
135
|
+
const candidates = [
|
|
136
|
+
join(here, "..", "corpus"),
|
|
137
|
+
// dist/corpus next to dist/storage
|
|
138
|
+
join(here, "..", "..", "corpus")
|
|
139
|
+
// src/corpus next to src/storage (under vitest)
|
|
140
|
+
];
|
|
141
|
+
for (const c of candidates) {
|
|
142
|
+
if (existsSync(c) && statSync(c).isDirectory()) return c;
|
|
143
|
+
}
|
|
144
|
+
throw new Error(
|
|
145
|
+
`vo-arch-defaults: could not locate bundled corpus directory. Tried: ${candidates.join(", ")}`
|
|
146
|
+
);
|
|
82
147
|
}
|
|
83
|
-
function
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (
|
|
90
|
-
|
|
91
|
-
"Per-user refresh auth requires BOTH VO_USER_REFRESH_TOKEN and VO_FIREBASE_API_KEY"
|
|
92
|
-
);
|
|
148
|
+
function walkJson(dir, out) {
|
|
149
|
+
for (const entry of readdirSync(dir)) {
|
|
150
|
+
const full = join(dir, entry);
|
|
151
|
+
const st = statSync(full);
|
|
152
|
+
if (st.isDirectory()) {
|
|
153
|
+
walkJson(full, out);
|
|
154
|
+
} else if (entry.endsWith(".json")) {
|
|
155
|
+
out.push(full);
|
|
93
156
|
}
|
|
94
|
-
return createFirebaseRefreshTokenSource({
|
|
95
|
-
refreshToken,
|
|
96
|
-
apiKey,
|
|
97
|
-
...fetchFn ? { fetchFn } : {}
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
if (idToken) return createStaticTokenSource(idToken, "firebase-id-token");
|
|
101
|
-
const stored = readStoredCred();
|
|
102
|
-
if (stored?.vo_credential && stored.vo_credential.trim()) {
|
|
103
|
-
return createStaticTokenSource(stored.vo_credential.trim(), "vo-credential");
|
|
104
157
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
158
|
+
}
|
|
159
|
+
function loadBundledCorpus(opts = {}) {
|
|
160
|
+
const dir = opts.corpusDir ?? resolveBundledCorpusDir();
|
|
161
|
+
const files = [];
|
|
162
|
+
walkJson(dir, files);
|
|
163
|
+
files.sort();
|
|
164
|
+
const rules = [];
|
|
165
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
166
|
+
for (const file of files) {
|
|
167
|
+
const raw = readFileSync(file, "utf8");
|
|
168
|
+
let parsed;
|
|
169
|
+
try {
|
|
170
|
+
parsed = JSON.parse(raw);
|
|
171
|
+
} catch (err) {
|
|
172
|
+
const m = err instanceof Error ? err.message : String(err);
|
|
173
|
+
throw new Error(`vo-arch-defaults: invalid JSON in ${file}: ${m}`, { cause: err });
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
const rule = parseRule(parsed);
|
|
177
|
+
if (seenIds.has(rule.rule_id)) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
`vo-arch-defaults: duplicate rule_id '${rule.rule_id}' (second occurrence in ${file})`
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
seenIds.add(rule.rule_id);
|
|
183
|
+
rules.push(rule);
|
|
184
|
+
} catch (err) {
|
|
185
|
+
const m = err instanceof Error ? err.message : String(err);
|
|
186
|
+
throw new Error(`vo-arch-defaults: schema validation failed for ${file}: ${m}`, { cause: err });
|
|
187
|
+
}
|
|
111
188
|
}
|
|
112
|
-
|
|
113
|
-
return null;
|
|
189
|
+
return { rules, source_paths: files };
|
|
114
190
|
}
|
|
115
|
-
var
|
|
116
|
-
|
|
117
|
-
"src/cloud/auth-token-source.ts"() {
|
|
191
|
+
var init_load_bundled = __esm({
|
|
192
|
+
"../vo-arch-defaults/src/storage/load-bundled.ts"() {
|
|
118
193
|
"use strict";
|
|
119
|
-
|
|
120
|
-
FIREBASE_TOKEN_REFERER = "https://algosuite.ai/";
|
|
121
|
-
REFRESH_SKEW_MS = 6e4;
|
|
194
|
+
init_schema();
|
|
122
195
|
}
|
|
123
196
|
});
|
|
124
197
|
|
|
125
|
-
// src/
|
|
126
|
-
import {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const mod = req("@napi-rs/keyring");
|
|
132
|
-
cached = mod && typeof mod.Entry === "function" ? mod : null;
|
|
133
|
-
} catch {
|
|
134
|
-
cached = null;
|
|
135
|
-
}
|
|
136
|
-
return cached;
|
|
137
|
-
}
|
|
138
|
-
function keychainAvailable() {
|
|
139
|
-
return loadKeyring() !== null;
|
|
198
|
+
// ../vo-arch-defaults/src/storage/load-override.ts
|
|
199
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
200
|
+
import { homedir } from "node:os";
|
|
201
|
+
import { join as join2 } from "node:path";
|
|
202
|
+
function defaultOverridePath() {
|
|
203
|
+
return join2(homedir(), ".claude", "vo-arch-defaults.local.json");
|
|
140
204
|
}
|
|
141
|
-
function
|
|
142
|
-
const
|
|
143
|
-
if (!
|
|
144
|
-
|
|
145
|
-
return new k.Entry(SERVICE, ACCOUNT).getPassword();
|
|
146
|
-
} catch {
|
|
147
|
-
return null;
|
|
205
|
+
function loadTenantOverride(opts = {}) {
|
|
206
|
+
const path3 = opts.path ?? defaultOverridePath();
|
|
207
|
+
if (!existsSync2(path3)) {
|
|
208
|
+
return { override: null, source_path: null };
|
|
148
209
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const k = loadKeyring();
|
|
152
|
-
if (!k) return false;
|
|
210
|
+
const raw = readFileSync2(path3, "utf8");
|
|
211
|
+
let parsed;
|
|
153
212
|
try {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
213
|
+
parsed = JSON.parse(raw);
|
|
214
|
+
} catch (err) {
|
|
215
|
+
const m = err instanceof Error ? err.message : String(err);
|
|
216
|
+
throw new Error(`vo-arch-defaults: invalid JSON in override ${path3}: ${m}`, { cause: err });
|
|
158
217
|
}
|
|
159
|
-
}
|
|
160
|
-
function keychainDelete() {
|
|
161
|
-
const k = loadKeyring();
|
|
162
|
-
if (!k) return false;
|
|
163
218
|
try {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
219
|
+
const override = parseOverride(parsed);
|
|
220
|
+
return { override, source_path: path3 };
|
|
221
|
+
} catch (err) {
|
|
222
|
+
const m = err instanceof Error ? err.message : String(err);
|
|
223
|
+
throw new Error(`vo-arch-defaults: override schema validation failed for ${path3}: ${m}`, { cause: err });
|
|
167
224
|
}
|
|
168
225
|
}
|
|
169
|
-
var
|
|
170
|
-
|
|
171
|
-
"src/cloud/keychain.ts"() {
|
|
226
|
+
var init_load_override = __esm({
|
|
227
|
+
"../vo-arch-defaults/src/storage/load-override.ts"() {
|
|
172
228
|
"use strict";
|
|
173
|
-
|
|
174
|
-
ACCOUNT = "refresh-credential";
|
|
229
|
+
init_schema();
|
|
175
230
|
}
|
|
176
231
|
});
|
|
177
232
|
|
|
178
|
-
// src/cloud/credential-store.ts
|
|
179
|
-
var credential_store_exports = {};
|
|
180
|
-
__export(credential_store_exports, {
|
|
181
|
-
KEYCHAIN_LOCATION: () => KEYCHAIN_LOCATION,
|
|
182
|
-
credentialPath: () => credentialPath,
|
|
183
|
-
readStoredCredential: () => readStoredCredential,
|
|
184
|
-
writeStoredCredential: () => writeStoredCredential
|
|
185
|
-
});
|
|
186
|
-
import { homedir as homedir3 } from "node:os";
|
|
187
|
-
import { join as join5, dirname as dirname4 } from "node:path";
|
|
188
|
-
import {
|
|
189
|
-
existsSync as existsSync3,
|
|
190
|
-
mkdirSync as mkdirSync2,
|
|
191
|
-
readFileSync as readFileSync5,
|
|
192
|
-
writeFileSync as writeFileSync2,
|
|
193
|
-
chmodSync as chmodSync2,
|
|
194
|
-
rmSync
|
|
195
|
-
} from "node:fs";
|
|
196
|
-
function credentialPath(env = process.env) {
|
|
197
|
-
const override = env["VO_MCP_CREDENTIALS_PATH"]?.trim();
|
|
198
|
-
if (override) return override;
|
|
199
|
-
return join5(homedir3(), ".config", "vo-mcp", "credentials.json");
|
|
200
|
-
}
|
|
201
|
-
function keychainEnabled(env, keychain) {
|
|
202
|
-
const disabled = (env["VO_MCP_DISABLE_KEYCHAIN"] ?? "").trim().toLowerCase();
|
|
203
|
-
if (disabled === "1" || disabled === "true" || disabled === "yes") return false;
|
|
204
|
-
return keychain.available();
|
|
205
|
-
}
|
|
206
|
-
function deserialize(raw) {
|
|
207
|
-
try {
|
|
208
|
-
const parsed = JSON.parse(raw);
|
|
209
|
-
const refresh = typeof parsed.refresh_token === "string" ? parsed.refresh_token.trim() : "";
|
|
210
|
-
const apiKey = typeof parsed.api_key === "string" ? parsed.api_key.trim() : "";
|
|
211
|
-
const voCred = typeof parsed.vo_credential === "string" ? parsed.vo_credential.trim() : "";
|
|
212
|
-
if (!voCred && (!refresh || !apiKey)) return null;
|
|
213
|
-
return {
|
|
214
|
-
...refresh ? { refresh_token: refresh } : {},
|
|
215
|
-
...apiKey ? { api_key: apiKey } : {},
|
|
216
|
-
...voCred ? { vo_credential: voCred } : {},
|
|
217
|
-
...typeof parsed.vo_credential_expires_at === "string" ? { vo_credential_expires_at: parsed.vo_credential_expires_at } : {},
|
|
218
|
-
...typeof parsed.email === "string" ? { email: parsed.email } : {},
|
|
219
|
-
...typeof parsed.stored_at === "string" ? { stored_at: parsed.stored_at } : {}
|
|
220
|
-
};
|
|
221
|
-
} catch {
|
|
222
|
-
return null;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
function readFromFile(env) {
|
|
226
|
-
try {
|
|
227
|
-
const p = credentialPath(env);
|
|
228
|
-
if (!existsSync3(p)) return null;
|
|
229
|
-
return deserialize(readFileSync5(p, "utf8"));
|
|
230
|
-
} catch {
|
|
231
|
-
return null;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
function readStoredCredential(env = process.env, keychain = realKeychain) {
|
|
235
|
-
if (keychainEnabled(env, keychain)) {
|
|
236
|
-
const raw = keychain.get();
|
|
237
|
-
const fromKeychain = raw ? deserialize(raw) : null;
|
|
238
|
-
if (fromKeychain) return fromKeychain;
|
|
239
|
-
}
|
|
240
|
-
return readFromFile(env);
|
|
241
|
-
}
|
|
242
|
-
function deleteFile(env) {
|
|
243
|
-
try {
|
|
244
|
-
rmSync(credentialPath(env), { force: true });
|
|
245
|
-
} catch {
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
function writeToFile(payload, env) {
|
|
249
|
-
const p = credentialPath(env);
|
|
250
|
-
mkdirSync2(dirname4(p), { recursive: true });
|
|
251
|
-
writeFileSync2(p, `${JSON.stringify(payload, null, 2)}
|
|
252
|
-
`, { mode: 384 });
|
|
253
|
-
try {
|
|
254
|
-
chmodSync2(p, 384);
|
|
255
|
-
} catch {
|
|
256
|
-
}
|
|
257
|
-
return p;
|
|
258
|
-
}
|
|
259
|
-
function writeStoredCredential(cred, storedAt, env = process.env, keychain = realKeychain) {
|
|
260
|
-
const payload = {
|
|
261
|
-
...cred.refresh_token ? { refresh_token: cred.refresh_token } : {},
|
|
262
|
-
...cred.api_key ? { api_key: cred.api_key } : {},
|
|
263
|
-
...cred.vo_credential ? { vo_credential: cred.vo_credential } : {},
|
|
264
|
-
...cred.vo_credential_expires_at ? { vo_credential_expires_at: cred.vo_credential_expires_at } : {},
|
|
265
|
-
...cred.email ? { email: cred.email } : {},
|
|
266
|
-
stored_at: cred.stored_at ?? storedAt
|
|
267
|
-
};
|
|
268
|
-
if (keychainEnabled(env, keychain) && keychain.set(JSON.stringify(payload))) {
|
|
269
|
-
deleteFile(env);
|
|
270
|
-
return KEYCHAIN_LOCATION;
|
|
271
|
-
}
|
|
272
|
-
const p = writeToFile(payload, env);
|
|
273
|
-
if (keychainEnabled(env, keychain)) keychain.delete();
|
|
274
|
-
return p;
|
|
275
|
-
}
|
|
276
|
-
var realKeychain, KEYCHAIN_LOCATION;
|
|
277
|
-
var init_credential_store = __esm({
|
|
278
|
-
"src/cloud/credential-store.ts"() {
|
|
279
|
-
"use strict";
|
|
280
|
-
init_keychain();
|
|
281
|
-
realKeychain = {
|
|
282
|
-
available: keychainAvailable,
|
|
283
|
-
get: keychainGet,
|
|
284
|
-
set: keychainSet,
|
|
285
|
-
delete: keychainDelete
|
|
286
|
-
};
|
|
287
|
-
KEYCHAIN_LOCATION = 'OS keychain (service "vo-mcp")';
|
|
288
|
-
}
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
// src/cli.ts
|
|
292
|
-
import { homedir as homedir6, hostname } from "node:os";
|
|
293
|
-
import { join as join8 } from "node:path";
|
|
294
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
295
|
-
|
|
296
|
-
// src/server.ts
|
|
297
|
-
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
298
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
299
|
-
import {
|
|
300
|
-
CallToolRequestSchema,
|
|
301
|
-
ListToolsRequestSchema
|
|
302
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
303
|
-
|
|
304
|
-
// src/tools/common.ts
|
|
305
|
-
import { createHash as createHash2, randomUUID } from "node:crypto";
|
|
306
|
-
import { readFileSync as readFileSync4 } from "node:fs";
|
|
307
|
-
import { dirname as dirname3, join as join4 } from "node:path";
|
|
308
|
-
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
309
|
-
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
|
|
310
|
-
|
|
311
|
-
// src/logging/events-writer.ts
|
|
312
|
-
import {
|
|
313
|
-
appendFileSync,
|
|
314
|
-
chmodSync,
|
|
315
|
-
mkdirSync,
|
|
316
|
-
readdirSync as readdirSync2,
|
|
317
|
-
readFileSync as readFileSync3,
|
|
318
|
-
statSync as statSync2,
|
|
319
|
-
unlinkSync,
|
|
320
|
-
writeFileSync
|
|
321
|
-
} from "node:fs";
|
|
322
|
-
import { homedir as homedir2 } from "node:os";
|
|
323
|
-
import { basename, dirname as dirname2, join as join3 } from "node:path";
|
|
324
|
-
import { gzipSync } from "node:zlib";
|
|
325
|
-
|
|
326
|
-
// ../vo-arch-defaults/src/schema/rule-v1.ts
|
|
327
|
-
import { z } from "zod";
|
|
328
|
-
var EvidenceMatcherKind = z.enum([
|
|
329
|
-
"regex",
|
|
330
|
-
"import-detector",
|
|
331
|
-
"package-json-field",
|
|
332
|
-
"file-size",
|
|
333
|
-
"ast-pattern",
|
|
334
|
-
"custom"
|
|
335
|
-
]);
|
|
336
|
-
var EvidenceMatcher = z.object({
|
|
337
|
-
kind: EvidenceMatcherKind,
|
|
338
|
-
pattern: z.string().optional(),
|
|
339
|
-
config: z.record(z.string(), z.unknown()).optional(),
|
|
340
|
-
description: z.string().min(1)
|
|
341
|
-
}).strict().refine(
|
|
342
|
-
(m) => m.kind !== "regex" || typeof m.pattern === "string" && m.pattern.length > 0,
|
|
343
|
-
{ message: "regex matcher requires non-empty pattern" }
|
|
344
|
-
);
|
|
345
|
-
var ChangeType = z.enum(["new-file", "edit", "delete", "rename", "any"]);
|
|
346
|
-
var AppliesWhen = z.object({
|
|
347
|
-
stack: z.array(z.string().min(1)).optional(),
|
|
348
|
-
change_types: z.array(ChangeType).optional(),
|
|
349
|
-
file_globs: z.array(z.string().min(1)).optional(),
|
|
350
|
-
not_file_globs: z.array(z.string().min(1)).optional()
|
|
351
|
-
}).strict();
|
|
352
|
-
var Reference = z.object({
|
|
353
|
-
type: z.enum(["framework-doc", "internal-doc", "pr", "incident", "external"]),
|
|
354
|
-
url: z.string().min(1),
|
|
355
|
-
description: z.string().min(1)
|
|
356
|
-
}).strict();
|
|
357
|
-
var IsoDate = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "last_verified must be ISO date YYYY-MM-DD").refine((s) => {
|
|
358
|
-
const d = /* @__PURE__ */ new Date(s + "T00:00:00Z");
|
|
359
|
-
return !Number.isNaN(d.getTime()) && d.toISOString().startsWith(s);
|
|
360
|
-
}, "last_verified must be a real calendar date");
|
|
361
|
-
var RuleId = z.string().regex(/^[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*$/, {
|
|
362
|
-
message: "rule_id must be `<category>/<kebab-name>`"
|
|
363
|
-
});
|
|
364
|
-
var ArchitecturalDefaultRuleSchema = z.object({
|
|
365
|
-
schema_version: z.literal(1),
|
|
366
|
-
rule_id: RuleId,
|
|
367
|
-
rule_version: z.number().int().positive(),
|
|
368
|
-
category: z.string().min(1),
|
|
369
|
-
severity: z.enum(["blocker", "warning", "info"]),
|
|
370
|
-
title: z.string().min(1),
|
|
371
|
-
rationale: z.string().min(1),
|
|
372
|
-
applies_when: AppliesWhen,
|
|
373
|
-
evidence_of_violation: z.array(EvidenceMatcher).min(1, {
|
|
374
|
-
message: "rule must declare at least one evidence matcher"
|
|
375
|
-
}),
|
|
376
|
-
remediation: z.string().min(1),
|
|
377
|
-
references: z.array(Reference),
|
|
378
|
-
last_verified: IsoDate,
|
|
379
|
-
tags: z.array(z.string().min(1))
|
|
380
|
-
}).strict();
|
|
381
|
-
function parseRule(input) {
|
|
382
|
-
return ArchitecturalDefaultRuleSchema.parse(input);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// ../vo-arch-defaults/src/schema/override-v1.ts
|
|
386
|
-
import { z as z2 } from "zod";
|
|
387
|
-
var PartialRuleSchema = ArchitecturalDefaultRuleSchema.partial();
|
|
388
|
-
var TenantOverrideSchema = z2.object({
|
|
389
|
-
schema_version: z2.literal(1),
|
|
390
|
-
suppressed_rule_ids: z2.array(z2.string().min(1)),
|
|
391
|
-
modified_rules: z2.record(z2.string().min(1), PartialRuleSchema),
|
|
392
|
-
added_rules: z2.array(ArchitecturalDefaultRuleSchema),
|
|
393
|
-
/**
|
|
394
|
-
* Per-tenant stack override (added 2026-05-24 — audit HIGH
|
|
395
|
-
* "DEFAULT_STACK hardcoded for Nexus repo"). When set, downstream
|
|
396
|
-
* consumers (vo-mcp KB pre-filter, vo-arch-check CLI) use this list
|
|
397
|
-
* instead of their built-in default. Lets external tenants whose
|
|
398
|
-
* repo isn't `firebase+react+pnpm` get useful KB rule matches by
|
|
399
|
-
* dropping a `~/.claude/vo-arch-defaults.local.json` with their own
|
|
400
|
-
* stack identifiers — no code change required.
|
|
401
|
-
*
|
|
402
|
-
* Open string union — values are not constrained beyond non-empty
|
|
403
|
-
* strings so out-of-tree tenants can declare their own
|
|
404
|
-
* (e.g. `vercel-edge`, `next-15`, `drizzle-postgres`).
|
|
405
|
-
*
|
|
406
|
-
* Optional. Undefined / omitted preserves pre-2026-05-24 behavior
|
|
407
|
-
* (consumer falls back to its hardcoded default stack).
|
|
408
|
-
*/
|
|
409
|
-
tenant_stack: z2.array(z2.string().min(1)).optional()
|
|
410
|
-
}).strict();
|
|
411
|
-
function parseOverride(input) {
|
|
412
|
-
return TenantOverrideSchema.parse(input);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// ../vo-arch-defaults/src/storage/load-bundled.ts
|
|
416
|
-
import { readdirSync, readFileSync, statSync, existsSync } from "node:fs";
|
|
417
|
-
import { dirname, join } from "node:path";
|
|
418
|
-
import { fileURLToPath } from "node:url";
|
|
419
|
-
function resolveBundledCorpusDir() {
|
|
420
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
421
|
-
const candidates = [
|
|
422
|
-
join(here, "..", "corpus"),
|
|
423
|
-
// dist/corpus next to dist/storage
|
|
424
|
-
join(here, "..", "..", "corpus")
|
|
425
|
-
// src/corpus next to src/storage (under vitest)
|
|
426
|
-
];
|
|
427
|
-
for (const c of candidates) {
|
|
428
|
-
if (existsSync(c) && statSync(c).isDirectory()) return c;
|
|
429
|
-
}
|
|
430
|
-
throw new Error(
|
|
431
|
-
`vo-arch-defaults: could not locate bundled corpus directory. Tried: ${candidates.join(", ")}`
|
|
432
|
-
);
|
|
433
|
-
}
|
|
434
|
-
function walkJson(dir, out) {
|
|
435
|
-
for (const entry of readdirSync(dir)) {
|
|
436
|
-
const full = join(dir, entry);
|
|
437
|
-
const st = statSync(full);
|
|
438
|
-
if (st.isDirectory()) {
|
|
439
|
-
walkJson(full, out);
|
|
440
|
-
} else if (entry.endsWith(".json")) {
|
|
441
|
-
out.push(full);
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
function loadBundledCorpus(opts = {}) {
|
|
446
|
-
const dir = opts.corpusDir ?? resolveBundledCorpusDir();
|
|
447
|
-
const files = [];
|
|
448
|
-
walkJson(dir, files);
|
|
449
|
-
files.sort();
|
|
450
|
-
const rules = [];
|
|
451
|
-
const seenIds = /* @__PURE__ */ new Set();
|
|
452
|
-
for (const file of files) {
|
|
453
|
-
const raw = readFileSync(file, "utf8");
|
|
454
|
-
let parsed;
|
|
455
|
-
try {
|
|
456
|
-
parsed = JSON.parse(raw);
|
|
457
|
-
} catch (err) {
|
|
458
|
-
const m = err instanceof Error ? err.message : String(err);
|
|
459
|
-
throw new Error(`vo-arch-defaults: invalid JSON in ${file}: ${m}`, { cause: err });
|
|
460
|
-
}
|
|
461
|
-
try {
|
|
462
|
-
const rule = parseRule(parsed);
|
|
463
|
-
if (seenIds.has(rule.rule_id)) {
|
|
464
|
-
throw new Error(
|
|
465
|
-
`vo-arch-defaults: duplicate rule_id '${rule.rule_id}' (second occurrence in ${file})`
|
|
466
|
-
);
|
|
467
|
-
}
|
|
468
|
-
seenIds.add(rule.rule_id);
|
|
469
|
-
rules.push(rule);
|
|
470
|
-
} catch (err) {
|
|
471
|
-
const m = err instanceof Error ? err.message : String(err);
|
|
472
|
-
throw new Error(`vo-arch-defaults: schema validation failed for ${file}: ${m}`, { cause: err });
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
return { rules, source_paths: files };
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// ../vo-arch-defaults/src/storage/load-override.ts
|
|
479
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
480
|
-
import { homedir } from "node:os";
|
|
481
|
-
import { join as join2 } from "node:path";
|
|
482
|
-
function defaultOverridePath() {
|
|
483
|
-
return join2(homedir(), ".claude", "vo-arch-defaults.local.json");
|
|
484
|
-
}
|
|
485
|
-
function loadTenantOverride(opts = {}) {
|
|
486
|
-
const path3 = opts.path ?? defaultOverridePath();
|
|
487
|
-
if (!existsSync2(path3)) {
|
|
488
|
-
return { override: null, source_path: null };
|
|
489
|
-
}
|
|
490
|
-
const raw = readFileSync2(path3, "utf8");
|
|
491
|
-
let parsed;
|
|
492
|
-
try {
|
|
493
|
-
parsed = JSON.parse(raw);
|
|
494
|
-
} catch (err) {
|
|
495
|
-
const m = err instanceof Error ? err.message : String(err);
|
|
496
|
-
throw new Error(`vo-arch-defaults: invalid JSON in override ${path3}: ${m}`, { cause: err });
|
|
497
|
-
}
|
|
498
|
-
try {
|
|
499
|
-
const override = parseOverride(parsed);
|
|
500
|
-
return { override, source_path: path3 };
|
|
501
|
-
} catch (err) {
|
|
502
|
-
const m = err instanceof Error ? err.message : String(err);
|
|
503
|
-
throw new Error(`vo-arch-defaults: override schema validation failed for ${path3}: ${m}`, { cause: err });
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
|
|
507
233
|
// ../vo-arch-defaults/src/storage/merge.ts
|
|
508
|
-
var IMMUTABLE_FIELDS = [
|
|
509
|
-
"schema_version",
|
|
510
|
-
"rule_id"
|
|
511
|
-
];
|
|
512
234
|
function applyModification(base, patch) {
|
|
513
235
|
const cleanPatch = { ...patch };
|
|
514
236
|
for (const k of IMMUTABLE_FIELDS) {
|
|
@@ -560,6 +282,16 @@ function mergeCorpusWithOverride(bundled, override) {
|
|
|
560
282
|
added_count: addedCount
|
|
561
283
|
};
|
|
562
284
|
}
|
|
285
|
+
var IMMUTABLE_FIELDS;
|
|
286
|
+
var init_merge = __esm({
|
|
287
|
+
"../vo-arch-defaults/src/storage/merge.ts"() {
|
|
288
|
+
"use strict";
|
|
289
|
+
IMMUTABLE_FIELDS = [
|
|
290
|
+
"schema_version",
|
|
291
|
+
"rule_id"
|
|
292
|
+
];
|
|
293
|
+
}
|
|
294
|
+
});
|
|
563
295
|
|
|
564
296
|
// ../vo-arch-defaults/src/query/applies-to-stack.ts
|
|
565
297
|
function appliesToStack(rule, stacks) {
|
|
@@ -572,6 +304,11 @@ function appliesToStack(rule, stacks) {
|
|
|
572
304
|
}
|
|
573
305
|
return false;
|
|
574
306
|
}
|
|
307
|
+
var init_applies_to_stack = __esm({
|
|
308
|
+
"../vo-arch-defaults/src/query/applies-to-stack.ts"() {
|
|
309
|
+
"use strict";
|
|
310
|
+
}
|
|
311
|
+
});
|
|
575
312
|
|
|
576
313
|
// ../vo-arch-defaults/src/query/applies-to-change.ts
|
|
577
314
|
function appliesToChangeType(rule, changeType) {
|
|
@@ -581,9 +318,13 @@ function appliesToChangeType(rule, changeType) {
|
|
|
581
318
|
if (changeType === "any") return true;
|
|
582
319
|
return required.includes(changeType);
|
|
583
320
|
}
|
|
321
|
+
var init_applies_to_change = __esm({
|
|
322
|
+
"../vo-arch-defaults/src/query/applies-to-change.ts"() {
|
|
323
|
+
"use strict";
|
|
324
|
+
}
|
|
325
|
+
});
|
|
584
326
|
|
|
585
327
|
// ../vo-arch-defaults/src/query/glob.ts
|
|
586
|
-
var REGEX_META = /[.+^${}()|[\]\\]/g;
|
|
587
328
|
function globToRegExp(glob) {
|
|
588
329
|
let out = "";
|
|
589
330
|
let i = 0;
|
|
@@ -636,6 +377,13 @@ function matchesAnyGlob(path3, globs) {
|
|
|
636
377
|
}
|
|
637
378
|
return false;
|
|
638
379
|
}
|
|
380
|
+
var REGEX_META;
|
|
381
|
+
var init_glob = __esm({
|
|
382
|
+
"../vo-arch-defaults/src/query/glob.ts"() {
|
|
383
|
+
"use strict";
|
|
384
|
+
REGEX_META = /[.+^${}()|[\]\\]/g;
|
|
385
|
+
}
|
|
386
|
+
});
|
|
639
387
|
|
|
640
388
|
// ../vo-arch-defaults/src/query/applies-to-files.ts
|
|
641
389
|
function appliesToFiles(rule, filePaths) {
|
|
@@ -656,8 +404,14 @@ function appliesToFiles(rule, filePaths) {
|
|
|
656
404
|
}
|
|
657
405
|
return false;
|
|
658
406
|
}
|
|
659
|
-
|
|
660
|
-
|
|
407
|
+
var init_applies_to_files = __esm({
|
|
408
|
+
"../vo-arch-defaults/src/query/applies-to-files.ts"() {
|
|
409
|
+
"use strict";
|
|
410
|
+
init_glob();
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// ../vo-arch-defaults/src/analyze/diff-parser.ts
|
|
661
415
|
function stripPathPrefix(p) {
|
|
662
416
|
if (p.startsWith("a/") || p.startsWith("b/")) return p.slice(2);
|
|
663
417
|
return p;
|
|
@@ -727,9 +481,13 @@ function parseUnifiedDiff(text) {
|
|
|
727
481
|
}
|
|
728
482
|
return { files };
|
|
729
483
|
}
|
|
484
|
+
var init_diff_parser = __esm({
|
|
485
|
+
"../vo-arch-defaults/src/analyze/diff-parser.ts"() {
|
|
486
|
+
"use strict";
|
|
487
|
+
}
|
|
488
|
+
});
|
|
730
489
|
|
|
731
490
|
// ../vo-arch-defaults/src/analyze/evidence-matchers/regex-matcher.ts
|
|
732
|
-
var MAX_EXCERPT = 200;
|
|
733
491
|
function sanitizeLine(s) {
|
|
734
492
|
let out = s.replace(/^\s+/, "");
|
|
735
493
|
if (out.length > MAX_EXCERPT) out = out.slice(0, MAX_EXCERPT) + "\u2026";
|
|
@@ -757,12 +515,15 @@ function runRegexMatcher(matcher, file) {
|
|
|
757
515
|
}
|
|
758
516
|
return hits;
|
|
759
517
|
}
|
|
518
|
+
var MAX_EXCERPT;
|
|
519
|
+
var init_regex_matcher = __esm({
|
|
520
|
+
"../vo-arch-defaults/src/analyze/evidence-matchers/regex-matcher.ts"() {
|
|
521
|
+
"use strict";
|
|
522
|
+
MAX_EXCERPT = 200;
|
|
523
|
+
}
|
|
524
|
+
});
|
|
760
525
|
|
|
761
526
|
// ../vo-arch-defaults/src/analyze/evidence-matchers/import-detector.ts
|
|
762
|
-
var STATIC_IMPORT = /import\s+(?:[^'"\n]{0,200}?from\s+)?(['"])([^'"]+)\1/;
|
|
763
|
-
var DYNAMIC_IMPORT = /import\(\s*(['"])([^'"]+)\1\s*\)/;
|
|
764
|
-
var REQUIRE_CALL = /require\(\s*(['"])([^'"]+)\1\s*\)/;
|
|
765
|
-
var MAX_EXCERPT2 = 200;
|
|
766
527
|
function sanitize(s) {
|
|
767
528
|
let out = s.replace(/^\s+/, "");
|
|
768
529
|
if (out.length > MAX_EXCERPT2) out = out.slice(0, MAX_EXCERPT2) + "\u2026";
|
|
@@ -807,6 +568,16 @@ function runImportDetector(matcher, file) {
|
|
|
807
568
|
}
|
|
808
569
|
return hits;
|
|
809
570
|
}
|
|
571
|
+
var STATIC_IMPORT, DYNAMIC_IMPORT, REQUIRE_CALL, MAX_EXCERPT2;
|
|
572
|
+
var init_import_detector = __esm({
|
|
573
|
+
"../vo-arch-defaults/src/analyze/evidence-matchers/import-detector.ts"() {
|
|
574
|
+
"use strict";
|
|
575
|
+
STATIC_IMPORT = /import\s+(?:[^'"\n]{0,200}?from\s+)?(['"])([^'"]+)\1/;
|
|
576
|
+
DYNAMIC_IMPORT = /import\(\s*(['"])([^'"]+)\1\s*\)/;
|
|
577
|
+
REQUIRE_CALL = /require\(\s*(['"])([^'"]+)\1\s*\)/;
|
|
578
|
+
MAX_EXCERPT2 = 200;
|
|
579
|
+
}
|
|
580
|
+
});
|
|
810
581
|
|
|
811
582
|
// ../vo-arch-defaults/src/analyze/evidence-matchers/file-size.ts
|
|
812
583
|
function runFileSizeMatcher(matcher, file) {
|
|
@@ -827,6 +598,11 @@ function runFileSizeMatcher(matcher, file) {
|
|
|
827
598
|
}
|
|
828
599
|
];
|
|
829
600
|
}
|
|
601
|
+
var init_file_size = __esm({
|
|
602
|
+
"../vo-arch-defaults/src/analyze/evidence-matchers/file-size.ts"() {
|
|
603
|
+
"use strict";
|
|
604
|
+
}
|
|
605
|
+
});
|
|
830
606
|
|
|
831
607
|
// ../vo-arch-defaults/src/analyze/evidence-matchers/package-json.ts
|
|
832
608
|
function valueMatches(value, cfg) {
|
|
@@ -867,6 +643,11 @@ function runPackageJsonMatcher(matcher, file) {
|
|
|
867
643
|
}
|
|
868
644
|
return hits;
|
|
869
645
|
}
|
|
646
|
+
var init_package_json = __esm({
|
|
647
|
+
"../vo-arch-defaults/src/analyze/evidence-matchers/package-json.ts"() {
|
|
648
|
+
"use strict";
|
|
649
|
+
}
|
|
650
|
+
});
|
|
870
651
|
|
|
871
652
|
// ../vo-arch-defaults/src/analyze/evidence-matchers/index.ts
|
|
872
653
|
function runEvidenceMatcher(matcher, file) {
|
|
@@ -886,9 +667,17 @@ function runEvidenceMatcher(matcher, file) {
|
|
|
886
667
|
return [];
|
|
887
668
|
}
|
|
888
669
|
}
|
|
670
|
+
var init_evidence_matchers = __esm({
|
|
671
|
+
"../vo-arch-defaults/src/analyze/evidence-matchers/index.ts"() {
|
|
672
|
+
"use strict";
|
|
673
|
+
init_regex_matcher();
|
|
674
|
+
init_import_detector();
|
|
675
|
+
init_file_size();
|
|
676
|
+
init_package_json();
|
|
677
|
+
}
|
|
678
|
+
});
|
|
889
679
|
|
|
890
680
|
// ../vo-arch-defaults/src/query/staleness.ts
|
|
891
|
-
var DEFAULT_STALENESS_THRESHOLD_DAYS = 365;
|
|
892
681
|
function computeStaleRules(rules, opts = {}) {
|
|
893
682
|
const threshold = opts.thresholdDays ?? DEFAULT_STALENESS_THRESHOLD_DAYS;
|
|
894
683
|
if (!Number.isFinite(threshold)) return [];
|
|
@@ -912,6 +701,13 @@ function computeStaleRules(rules, opts = {}) {
|
|
|
912
701
|
out.sort((a, b) => b.days_stale - a.days_stale);
|
|
913
702
|
return out;
|
|
914
703
|
}
|
|
704
|
+
var DEFAULT_STALENESS_THRESHOLD_DAYS;
|
|
705
|
+
var init_staleness = __esm({
|
|
706
|
+
"../vo-arch-defaults/src/query/staleness.ts"() {
|
|
707
|
+
"use strict";
|
|
708
|
+
DEFAULT_STALENESS_THRESHOLD_DAYS = 365;
|
|
709
|
+
}
|
|
710
|
+
});
|
|
915
711
|
|
|
916
712
|
// ../vo-arch-defaults/src/query/run-query.ts
|
|
917
713
|
function findApplicableRules(input, opts = {}) {
|
|
@@ -970,9 +766,28 @@ function findApplicableRules(input, opts = {}) {
|
|
|
970
766
|
stale_rules
|
|
971
767
|
};
|
|
972
768
|
}
|
|
769
|
+
var init_run_query = __esm({
|
|
770
|
+
"../vo-arch-defaults/src/query/run-query.ts"() {
|
|
771
|
+
"use strict";
|
|
772
|
+
init_load_bundled();
|
|
773
|
+
init_load_override();
|
|
774
|
+
init_merge();
|
|
775
|
+
init_applies_to_stack();
|
|
776
|
+
init_applies_to_change();
|
|
777
|
+
init_applies_to_files();
|
|
778
|
+
init_diff_parser();
|
|
779
|
+
init_evidence_matchers();
|
|
780
|
+
init_staleness();
|
|
781
|
+
}
|
|
782
|
+
});
|
|
973
783
|
|
|
974
784
|
// ../vo-arch-defaults/src/query/corpus-version.ts
|
|
975
785
|
import { createHash } from "node:crypto";
|
|
786
|
+
var init_corpus_version = __esm({
|
|
787
|
+
"../vo-arch-defaults/src/query/corpus-version.ts"() {
|
|
788
|
+
"use strict";
|
|
789
|
+
}
|
|
790
|
+
});
|
|
976
791
|
|
|
977
792
|
// ../vo-arch-defaults/src/query/resolve-stack.ts
|
|
978
793
|
function resolveStack(override, fallback) {
|
|
@@ -981,9 +796,13 @@ function resolveStack(override, fallback) {
|
|
|
981
796
|
}
|
|
982
797
|
return fallback;
|
|
983
798
|
}
|
|
799
|
+
var init_resolve_stack = __esm({
|
|
800
|
+
"../vo-arch-defaults/src/query/resolve-stack.ts"() {
|
|
801
|
+
"use strict";
|
|
802
|
+
}
|
|
803
|
+
});
|
|
984
804
|
|
|
985
805
|
// ../vo-arch-defaults/src/pii-sanitize.ts
|
|
986
|
-
var SANITIZE_DEFAULT_MAX_LEN = 200;
|
|
987
806
|
function sanitizeExcerpt(raw, maxLen = SANITIZE_DEFAULT_MAX_LEN) {
|
|
988
807
|
let s = raw;
|
|
989
808
|
s = s.replace(/eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{5,}/g, "[REDACTED_JWT]");
|
|
@@ -999,10 +818,49 @@ function sanitizeExcerpt(raw, maxLen = SANITIZE_DEFAULT_MAX_LEN) {
|
|
|
999
818
|
if (s.length > maxLen) s = s.slice(0, maxLen) + "\u2026";
|
|
1000
819
|
return s;
|
|
1001
820
|
}
|
|
821
|
+
var SANITIZE_DEFAULT_MAX_LEN;
|
|
822
|
+
var init_pii_sanitize = __esm({
|
|
823
|
+
"../vo-arch-defaults/src/pii-sanitize.ts"() {
|
|
824
|
+
"use strict";
|
|
825
|
+
SANITIZE_DEFAULT_MAX_LEN = 200;
|
|
826
|
+
}
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
// ../vo-arch-defaults/src/index.ts
|
|
830
|
+
var init_src = __esm({
|
|
831
|
+
"../vo-arch-defaults/src/index.ts"() {
|
|
832
|
+
init_schema();
|
|
833
|
+
init_load_bundled();
|
|
834
|
+
init_load_override();
|
|
835
|
+
init_merge();
|
|
836
|
+
init_applies_to_stack();
|
|
837
|
+
init_applies_to_change();
|
|
838
|
+
init_applies_to_files();
|
|
839
|
+
init_run_query();
|
|
840
|
+
init_glob();
|
|
841
|
+
init_corpus_version();
|
|
842
|
+
init_resolve_stack();
|
|
843
|
+
init_staleness();
|
|
844
|
+
init_diff_parser();
|
|
845
|
+
init_evidence_matchers();
|
|
846
|
+
init_pii_sanitize();
|
|
847
|
+
}
|
|
848
|
+
});
|
|
1002
849
|
|
|
1003
850
|
// src/logging/events-writer.ts
|
|
1004
|
-
|
|
1005
|
-
|
|
851
|
+
import {
|
|
852
|
+
appendFileSync,
|
|
853
|
+
chmodSync,
|
|
854
|
+
mkdirSync,
|
|
855
|
+
readdirSync as readdirSync2,
|
|
856
|
+
readFileSync as readFileSync3,
|
|
857
|
+
statSync as statSync2,
|
|
858
|
+
unlinkSync,
|
|
859
|
+
writeFileSync
|
|
860
|
+
} from "node:fs";
|
|
861
|
+
import { homedir as homedir2 } from "node:os";
|
|
862
|
+
import { basename, dirname as dirname2, join as join3 } from "node:path";
|
|
863
|
+
import { gzipSync } from "node:zlib";
|
|
1006
864
|
function defaultEventsPath() {
|
|
1007
865
|
const envPath = process.env["VO_MCP_EVENTS_PATH"];
|
|
1008
866
|
if (envPath && envPath.length > 0) return envPath;
|
|
@@ -1055,160 +913,723 @@ function pruneRotated(filePath, keep) {
|
|
|
1055
913
|
} catch {
|
|
1056
914
|
}
|
|
1057
915
|
}
|
|
1058
|
-
} catch {
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
function createFileEventsWriter(opts = {}) {
|
|
1062
|
-
const filePath = opts.path ?? defaultEventsPath();
|
|
1063
|
-
const maxBytes = opts.maxBytes ?? envPositiveInt("VO_MCP_EVENTS_MAX_BYTES") ?? DEFAULT_EVENTS_MAX_BYTES;
|
|
1064
|
-
const keepRotated = opts.keepRotated ?? envPositiveInt("VO_MCP_EVENTS_KEEP_ROTATED") ?? DEFAULT_EVENTS_KEEP_ROTATED;
|
|
1065
|
-
mkdirSync(dirname2(filePath), { recursive: true, mode: 448 });
|
|
1066
|
-
let permsApplied = false;
|
|
1067
|
-
let currentBytes = 0;
|
|
1068
|
-
try {
|
|
1069
|
-
currentBytes = statSync2(filePath).size;
|
|
1070
|
-
} catch {
|
|
916
|
+
} catch {
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
function createFileEventsWriter(opts = {}) {
|
|
920
|
+
const filePath = opts.path ?? defaultEventsPath();
|
|
921
|
+
const maxBytes = opts.maxBytes ?? envPositiveInt("VO_MCP_EVENTS_MAX_BYTES") ?? DEFAULT_EVENTS_MAX_BYTES;
|
|
922
|
+
const keepRotated = opts.keepRotated ?? envPositiveInt("VO_MCP_EVENTS_KEEP_ROTATED") ?? DEFAULT_EVENTS_KEEP_ROTATED;
|
|
923
|
+
mkdirSync(dirname2(filePath), { recursive: true, mode: 448 });
|
|
924
|
+
let permsApplied = false;
|
|
925
|
+
let currentBytes = 0;
|
|
926
|
+
try {
|
|
927
|
+
currentBytes = statSync2(filePath).size;
|
|
928
|
+
} catch {
|
|
929
|
+
}
|
|
930
|
+
return {
|
|
931
|
+
append(event) {
|
|
932
|
+
const line = JSON.stringify(event) + "\n";
|
|
933
|
+
const lineBytes = Buffer.byteLength(line, "utf8");
|
|
934
|
+
if (currentBytes > 0 && currentBytes + lineBytes > maxBytes) {
|
|
935
|
+
if (rotateNow(filePath)) {
|
|
936
|
+
pruneRotated(filePath, keepRotated);
|
|
937
|
+
currentBytes = 0;
|
|
938
|
+
permsApplied = false;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
try {
|
|
942
|
+
appendFileSync(filePath, line, "utf8");
|
|
943
|
+
currentBytes += lineBytes;
|
|
944
|
+
} catch (err) {
|
|
945
|
+
const code = err.code;
|
|
946
|
+
if (code === "ENOSPC") {
|
|
947
|
+
process.stderr.write(
|
|
948
|
+
`[vo-mcp] events log write failed (disk full): event dropped
|
|
949
|
+
`
|
|
950
|
+
);
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
throw err;
|
|
954
|
+
}
|
|
955
|
+
if (!permsApplied) {
|
|
956
|
+
try {
|
|
957
|
+
chmodSync(filePath, 384);
|
|
958
|
+
} catch {
|
|
959
|
+
}
|
|
960
|
+
permsApplied = true;
|
|
961
|
+
}
|
|
962
|
+
},
|
|
963
|
+
path() {
|
|
964
|
+
return filePath;
|
|
965
|
+
},
|
|
966
|
+
close() {
|
|
967
|
+
}
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
var DEFAULT_EVENTS_MAX_BYTES, DEFAULT_EVENTS_KEEP_ROTATED;
|
|
971
|
+
var init_events_writer = __esm({
|
|
972
|
+
"src/logging/events-writer.ts"() {
|
|
973
|
+
"use strict";
|
|
974
|
+
init_src();
|
|
975
|
+
DEFAULT_EVENTS_MAX_BYTES = 50 * 1024 * 1024;
|
|
976
|
+
DEFAULT_EVENTS_KEEP_ROTATED = 10;
|
|
977
|
+
}
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
// src/tools/common.ts
|
|
981
|
+
import { createHash as createHash2, randomUUID } from "node:crypto";
|
|
982
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
983
|
+
import { dirname as dirname3, join as join4 } from "node:path";
|
|
984
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
985
|
+
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
|
|
986
|
+
function readVoMcpVersion() {
|
|
987
|
+
try {
|
|
988
|
+
const here = dirname3(fileURLToPath2(import.meta.url));
|
|
989
|
+
for (const rel of ["..", ["..", ".."], ["..", "..", ".."]]) {
|
|
990
|
+
try {
|
|
991
|
+
const segs = Array.isArray(rel) ? rel : [rel];
|
|
992
|
+
const pkg = JSON.parse(readFileSync4(join4(here, ...segs, "package.json"), "utf8"));
|
|
993
|
+
if (pkg.name === "@algosuite/vo-mcp" && typeof pkg.version === "string") return pkg.version;
|
|
994
|
+
} catch {
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
return "0.0.0-unknown";
|
|
998
|
+
} catch {
|
|
999
|
+
return "0.0.0-unknown";
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
function bytesOf(s) {
|
|
1003
|
+
return Buffer.byteLength(s, "utf8");
|
|
1004
|
+
}
|
|
1005
|
+
function sha256Hex(s) {
|
|
1006
|
+
return createHash2("sha256").update(s, "utf8").digest("hex");
|
|
1007
|
+
}
|
|
1008
|
+
function invalidParams(toolName, message) {
|
|
1009
|
+
return new McpError(ErrorCode.InvalidParams, `${toolName}: ${message}`);
|
|
1010
|
+
}
|
|
1011
|
+
function methodNotFound(toolName, knownTools) {
|
|
1012
|
+
return new McpError(
|
|
1013
|
+
ErrorCode.MethodNotFound,
|
|
1014
|
+
`Unknown tool: ${toolName}. Registered tools: ${knownTools.join(", ")}`
|
|
1015
|
+
);
|
|
1016
|
+
}
|
|
1017
|
+
function assertWithinByteCap(toolName, fieldName, value, maxBytes) {
|
|
1018
|
+
const bytes = Buffer.byteLength(value, "utf8");
|
|
1019
|
+
if (bytes > maxBytes) {
|
|
1020
|
+
throw invalidParams(
|
|
1021
|
+
toolName,
|
|
1022
|
+
`input field '${fieldName}' exceeds ${maxBytes}-byte cap (got ${bytes} bytes). Trim the input or split across multiple calls.`
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
function buildBaseEvent(args) {
|
|
1027
|
+
return {
|
|
1028
|
+
schema_version: 1,
|
|
1029
|
+
event_id: args.eventId ?? randomUUID(),
|
|
1030
|
+
ts: args.now.toISOString(),
|
|
1031
|
+
tenant_id: args.session.tenantId,
|
|
1032
|
+
operator_id: args.session.operatorId,
|
|
1033
|
+
session_id: args.session.sessionId,
|
|
1034
|
+
client_id: args.session.clientId,
|
|
1035
|
+
mode: args.session.mode,
|
|
1036
|
+
tool: args.tool,
|
|
1037
|
+
gate_type: args.gateType,
|
|
1038
|
+
input_hash: args.inputHash,
|
|
1039
|
+
input_excerpt: sanitizeExcerpt(args.inputExcerpt),
|
|
1040
|
+
input_size_bytes: args.inputSizeBytes,
|
|
1041
|
+
per_model_verdicts: [],
|
|
1042
|
+
synthesized_verdict: null,
|
|
1043
|
+
consensus_confidence: null,
|
|
1044
|
+
per_model_tokens_in: null,
|
|
1045
|
+
per_model_tokens_out: null,
|
|
1046
|
+
total_cost_usd: null,
|
|
1047
|
+
duration_ms: null,
|
|
1048
|
+
dev_override: null,
|
|
1049
|
+
downstream_outcome: null,
|
|
1050
|
+
vo_mcp_version: VO_MCP_VERSION,
|
|
1051
|
+
consensus_engine_version: null,
|
|
1052
|
+
cache_hit: false
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
function jsonContent(value) {
|
|
1056
|
+
return {
|
|
1057
|
+
content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
function toEventPerModelVerdicts(src) {
|
|
1061
|
+
return src.map((v) => {
|
|
1062
|
+
const sanitizedExcerpt = sanitizeExcerpt(v.raw_response_excerpt);
|
|
1063
|
+
return {
|
|
1064
|
+
model: v.model,
|
|
1065
|
+
model_id: v.model,
|
|
1066
|
+
provider: v.provider,
|
|
1067
|
+
verdict: v.verdict,
|
|
1068
|
+
confidence: v.confidence,
|
|
1069
|
+
duration_ms: v.duration_ms,
|
|
1070
|
+
raw_response_excerpt: sanitizedExcerpt,
|
|
1071
|
+
// Hash over the sanitized excerpt — engine doesn't yet thread the full
|
|
1072
|
+
// raw response. Documented limitation on `raw_response_hash` in types.ts.
|
|
1073
|
+
raw_response_hash: sha256Hex(v.raw_response_excerpt),
|
|
1074
|
+
reasoning_summary: typeof v.reasoning_excerpt === "string" && v.reasoning_excerpt.length > 0 ? sanitizeExcerpt(v.reasoning_excerpt, 500) : null,
|
|
1075
|
+
// Engine doesn't yet emit cited_sources; ship empty array so cloud
|
|
1076
|
+
// ingestion doesn't NPE on `Array.isArray()` checks.
|
|
1077
|
+
cited_sources: [],
|
|
1078
|
+
error: v.error ?? null
|
|
1079
|
+
};
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
function toEventSynthesizedVerdict(src) {
|
|
1083
|
+
return {
|
|
1084
|
+
verdict: src.verdict,
|
|
1085
|
+
confidence: src.confidence,
|
|
1086
|
+
reasoning_excerpt: sanitizeExcerpt(src.reasoning_excerpt)
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
var VO_MCP_VERSION;
|
|
1090
|
+
var init_common = __esm({
|
|
1091
|
+
"src/tools/common.ts"() {
|
|
1092
|
+
"use strict";
|
|
1093
|
+
init_events_writer();
|
|
1094
|
+
VO_MCP_VERSION = readVoMcpVersion();
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
|
|
1098
|
+
// src/cloud/auth-token-source.ts
|
|
1099
|
+
var auth_token_source_exports = {};
|
|
1100
|
+
__export(auth_token_source_exports, {
|
|
1101
|
+
FIREBASE_SECURETOKEN_URL: () => FIREBASE_SECURETOKEN_URL,
|
|
1102
|
+
FIREBASE_TOKEN_REFERER: () => FIREBASE_TOKEN_REFERER,
|
|
1103
|
+
createAuthTokenSourceFromEnv: () => createAuthTokenSourceFromEnv,
|
|
1104
|
+
createFirebaseRefreshTokenSource: () => createFirebaseRefreshTokenSource,
|
|
1105
|
+
createStaticTokenSource: () => createStaticTokenSource
|
|
1106
|
+
});
|
|
1107
|
+
function createStaticTokenSource(token, kind = "admin-token") {
|
|
1108
|
+
const value = token.trim();
|
|
1109
|
+
return { kind, getToken: async () => value.length > 0 ? value : null };
|
|
1110
|
+
}
|
|
1111
|
+
function createFirebaseRefreshTokenSource(opts) {
|
|
1112
|
+
const refreshToken = opts.refreshToken.trim();
|
|
1113
|
+
const apiKey = opts.apiKey.trim();
|
|
1114
|
+
const now = opts.now ?? (() => Date.now());
|
|
1115
|
+
const fetchFn = opts.fetchFn ?? globalThis.fetch;
|
|
1116
|
+
let cachedToken = null;
|
|
1117
|
+
let expiresAtMs = 0;
|
|
1118
|
+
let inFlight = null;
|
|
1119
|
+
async function refresh() {
|
|
1120
|
+
try {
|
|
1121
|
+
const res = await fetchFn(`${FIREBASE_SECURETOKEN_URL}?key=${encodeURIComponent(apiKey)}`, {
|
|
1122
|
+
method: "POST",
|
|
1123
|
+
headers: {
|
|
1124
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
1125
|
+
referer: FIREBASE_TOKEN_REFERER
|
|
1126
|
+
},
|
|
1127
|
+
body: `grant_type=refresh_token&refresh_token=${encodeURIComponent(refreshToken)}`
|
|
1128
|
+
});
|
|
1129
|
+
const text = await res.text();
|
|
1130
|
+
if (res.status < 200 || res.status >= 300) {
|
|
1131
|
+
cachedToken = null;
|
|
1132
|
+
return null;
|
|
1133
|
+
}
|
|
1134
|
+
const parsed = JSON.parse(text);
|
|
1135
|
+
const idToken = typeof parsed.id_token === "string" ? parsed.id_token : "";
|
|
1136
|
+
if (!idToken) {
|
|
1137
|
+
cachedToken = null;
|
|
1138
|
+
return null;
|
|
1139
|
+
}
|
|
1140
|
+
const expiresInSec = Number(parsed.expires_in);
|
|
1141
|
+
const ttlMs = Number.isFinite(expiresInSec) && expiresInSec > 0 ? expiresInSec * 1e3 : 36e5;
|
|
1142
|
+
cachedToken = idToken;
|
|
1143
|
+
expiresAtMs = now() + ttlMs;
|
|
1144
|
+
return idToken;
|
|
1145
|
+
} catch {
|
|
1146
|
+
cachedToken = null;
|
|
1147
|
+
return null;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
return {
|
|
1151
|
+
kind: "firebase-refresh",
|
|
1152
|
+
async getToken() {
|
|
1153
|
+
if (cachedToken && now() < expiresAtMs - REFRESH_SKEW_MS) return cachedToken;
|
|
1154
|
+
if (!inFlight) {
|
|
1155
|
+
inFlight = refresh().finally(() => {
|
|
1156
|
+
inFlight = null;
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
return inFlight;
|
|
1160
|
+
}
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
function createAuthTokenSourceFromEnv(env = process.env, fetchFn, readStoredCred = () => null) {
|
|
1164
|
+
const refreshToken = env["VO_USER_REFRESH_TOKEN"]?.trim();
|
|
1165
|
+
const apiKey = env["VO_FIREBASE_API_KEY"]?.trim();
|
|
1166
|
+
const idToken = env["VO_USER_ID_TOKEN"]?.trim();
|
|
1167
|
+
const adminToken = env["VO_CONTROL_PLANE_ADMIN_TOKEN"]?.trim();
|
|
1168
|
+
if (refreshToken || apiKey) {
|
|
1169
|
+
if (!refreshToken || !apiKey) {
|
|
1170
|
+
throw new Error(
|
|
1171
|
+
"Per-user refresh auth requires BOTH VO_USER_REFRESH_TOKEN and VO_FIREBASE_API_KEY"
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
return createFirebaseRefreshTokenSource({
|
|
1175
|
+
refreshToken,
|
|
1176
|
+
apiKey,
|
|
1177
|
+
...fetchFn ? { fetchFn } : {}
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
if (idToken) return createStaticTokenSource(idToken, "firebase-id-token");
|
|
1181
|
+
const stored = readStoredCred();
|
|
1182
|
+
if (stored?.vo_credential && stored.vo_credential.trim()) {
|
|
1183
|
+
return createStaticTokenSource(stored.vo_credential.trim(), "vo-credential");
|
|
1184
|
+
}
|
|
1185
|
+
if (stored && stored.refresh_token?.trim() && stored.api_key?.trim()) {
|
|
1186
|
+
return createFirebaseRefreshTokenSource({
|
|
1187
|
+
refreshToken: stored.refresh_token.trim(),
|
|
1188
|
+
apiKey: stored.api_key.trim(),
|
|
1189
|
+
...fetchFn ? { fetchFn } : {}
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
if (adminToken) return createStaticTokenSource(adminToken, "admin-token");
|
|
1193
|
+
return null;
|
|
1194
|
+
}
|
|
1195
|
+
var FIREBASE_SECURETOKEN_URL, FIREBASE_TOKEN_REFERER, REFRESH_SKEW_MS;
|
|
1196
|
+
var init_auth_token_source = __esm({
|
|
1197
|
+
"src/cloud/auth-token-source.ts"() {
|
|
1198
|
+
"use strict";
|
|
1199
|
+
FIREBASE_SECURETOKEN_URL = "https://securetoken.googleapis.com/v1/token";
|
|
1200
|
+
FIREBASE_TOKEN_REFERER = "https://algosuite.ai/";
|
|
1201
|
+
REFRESH_SKEW_MS = 6e4;
|
|
1202
|
+
}
|
|
1203
|
+
});
|
|
1204
|
+
|
|
1205
|
+
// src/cloud/keychain.ts
|
|
1206
|
+
import { createRequire } from "node:module";
|
|
1207
|
+
function loadKeyring() {
|
|
1208
|
+
if (cached !== void 0) return cached;
|
|
1209
|
+
try {
|
|
1210
|
+
const req = createRequire(import.meta.url);
|
|
1211
|
+
const mod = req("@napi-rs/keyring");
|
|
1212
|
+
cached = mod && typeof mod.Entry === "function" ? mod : null;
|
|
1213
|
+
} catch {
|
|
1214
|
+
cached = null;
|
|
1215
|
+
}
|
|
1216
|
+
return cached;
|
|
1217
|
+
}
|
|
1218
|
+
function keychainAvailable() {
|
|
1219
|
+
return loadKeyring() !== null;
|
|
1220
|
+
}
|
|
1221
|
+
function keychainGet() {
|
|
1222
|
+
const k = loadKeyring();
|
|
1223
|
+
if (!k) return null;
|
|
1224
|
+
try {
|
|
1225
|
+
return new k.Entry(SERVICE, ACCOUNT).getPassword();
|
|
1226
|
+
} catch {
|
|
1227
|
+
return null;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
function keychainSet(secret) {
|
|
1231
|
+
const k = loadKeyring();
|
|
1232
|
+
if (!k) return false;
|
|
1233
|
+
try {
|
|
1234
|
+
new k.Entry(SERVICE, ACCOUNT).setPassword(secret);
|
|
1235
|
+
return true;
|
|
1236
|
+
} catch {
|
|
1237
|
+
return false;
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
function keychainDelete() {
|
|
1241
|
+
const k = loadKeyring();
|
|
1242
|
+
if (!k) return false;
|
|
1243
|
+
try {
|
|
1244
|
+
return new k.Entry(SERVICE, ACCOUNT).deletePassword();
|
|
1245
|
+
} catch {
|
|
1246
|
+
return false;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
var SERVICE, ACCOUNT, cached;
|
|
1250
|
+
var init_keychain = __esm({
|
|
1251
|
+
"src/cloud/keychain.ts"() {
|
|
1252
|
+
"use strict";
|
|
1253
|
+
SERVICE = "vo-mcp";
|
|
1254
|
+
ACCOUNT = "refresh-credential";
|
|
1255
|
+
}
|
|
1256
|
+
});
|
|
1257
|
+
|
|
1258
|
+
// src/cloud/credential-store.ts
|
|
1259
|
+
var credential_store_exports = {};
|
|
1260
|
+
__export(credential_store_exports, {
|
|
1261
|
+
KEYCHAIN_LOCATION: () => KEYCHAIN_LOCATION,
|
|
1262
|
+
credentialPath: () => credentialPath,
|
|
1263
|
+
readStoredCredential: () => readStoredCredential,
|
|
1264
|
+
writeStoredCredential: () => writeStoredCredential
|
|
1265
|
+
});
|
|
1266
|
+
import { homedir as homedir3 } from "node:os";
|
|
1267
|
+
import { join as join5, dirname as dirname4 } from "node:path";
|
|
1268
|
+
import {
|
|
1269
|
+
existsSync as existsSync3,
|
|
1270
|
+
mkdirSync as mkdirSync2,
|
|
1271
|
+
readFileSync as readFileSync5,
|
|
1272
|
+
writeFileSync as writeFileSync2,
|
|
1273
|
+
chmodSync as chmodSync2,
|
|
1274
|
+
rmSync
|
|
1275
|
+
} from "node:fs";
|
|
1276
|
+
function credentialPath(env = process.env) {
|
|
1277
|
+
const override = env["VO_MCP_CREDENTIALS_PATH"]?.trim();
|
|
1278
|
+
if (override) return override;
|
|
1279
|
+
return join5(homedir3(), ".config", "vo-mcp", "credentials.json");
|
|
1280
|
+
}
|
|
1281
|
+
function keychainEnabled(env, keychain) {
|
|
1282
|
+
const disabled = (env["VO_MCP_DISABLE_KEYCHAIN"] ?? "").trim().toLowerCase();
|
|
1283
|
+
if (disabled === "1" || disabled === "true" || disabled === "yes") return false;
|
|
1284
|
+
return keychain.available();
|
|
1285
|
+
}
|
|
1286
|
+
function deserialize(raw) {
|
|
1287
|
+
try {
|
|
1288
|
+
const parsed = JSON.parse(raw);
|
|
1289
|
+
const refresh = typeof parsed.refresh_token === "string" ? parsed.refresh_token.trim() : "";
|
|
1290
|
+
const apiKey = typeof parsed.api_key === "string" ? parsed.api_key.trim() : "";
|
|
1291
|
+
const voCred = typeof parsed.vo_credential === "string" ? parsed.vo_credential.trim() : "";
|
|
1292
|
+
if (!voCred && (!refresh || !apiKey)) return null;
|
|
1293
|
+
return {
|
|
1294
|
+
...refresh ? { refresh_token: refresh } : {},
|
|
1295
|
+
...apiKey ? { api_key: apiKey } : {},
|
|
1296
|
+
...voCred ? { vo_credential: voCred } : {},
|
|
1297
|
+
...typeof parsed.vo_credential_expires_at === "string" ? { vo_credential_expires_at: parsed.vo_credential_expires_at } : {},
|
|
1298
|
+
...typeof parsed.email === "string" ? { email: parsed.email } : {},
|
|
1299
|
+
...typeof parsed.stored_at === "string" ? { stored_at: parsed.stored_at } : {}
|
|
1300
|
+
};
|
|
1301
|
+
} catch {
|
|
1302
|
+
return null;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
function readFromFile(env) {
|
|
1306
|
+
try {
|
|
1307
|
+
const p = credentialPath(env);
|
|
1308
|
+
if (!existsSync3(p)) return null;
|
|
1309
|
+
return deserialize(readFileSync5(p, "utf8"));
|
|
1310
|
+
} catch {
|
|
1311
|
+
return null;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
function readStoredCredential(env = process.env, keychain = realKeychain) {
|
|
1315
|
+
if (keychainEnabled(env, keychain)) {
|
|
1316
|
+
const raw = keychain.get();
|
|
1317
|
+
const fromKeychain = raw ? deserialize(raw) : null;
|
|
1318
|
+
if (fromKeychain) return fromKeychain;
|
|
1319
|
+
}
|
|
1320
|
+
return readFromFile(env);
|
|
1321
|
+
}
|
|
1322
|
+
function deleteFile(env) {
|
|
1323
|
+
try {
|
|
1324
|
+
rmSync(credentialPath(env), { force: true });
|
|
1325
|
+
} catch {
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
function writeToFile(payload, env) {
|
|
1329
|
+
const p = credentialPath(env);
|
|
1330
|
+
mkdirSync2(dirname4(p), { recursive: true });
|
|
1331
|
+
writeFileSync2(p, `${JSON.stringify(payload, null, 2)}
|
|
1332
|
+
`, { mode: 384 });
|
|
1333
|
+
try {
|
|
1334
|
+
chmodSync2(p, 384);
|
|
1335
|
+
} catch {
|
|
1336
|
+
}
|
|
1337
|
+
return p;
|
|
1338
|
+
}
|
|
1339
|
+
function writeStoredCredential(cred, storedAt, env = process.env, keychain = realKeychain) {
|
|
1340
|
+
const payload = {
|
|
1341
|
+
...cred.refresh_token ? { refresh_token: cred.refresh_token } : {},
|
|
1342
|
+
...cred.api_key ? { api_key: cred.api_key } : {},
|
|
1343
|
+
...cred.vo_credential ? { vo_credential: cred.vo_credential } : {},
|
|
1344
|
+
...cred.vo_credential_expires_at ? { vo_credential_expires_at: cred.vo_credential_expires_at } : {},
|
|
1345
|
+
...cred.email ? { email: cred.email } : {},
|
|
1346
|
+
stored_at: cred.stored_at ?? storedAt
|
|
1347
|
+
};
|
|
1348
|
+
if (keychainEnabled(env, keychain) && keychain.set(JSON.stringify(payload))) {
|
|
1349
|
+
deleteFile(env);
|
|
1350
|
+
return KEYCHAIN_LOCATION;
|
|
1351
|
+
}
|
|
1352
|
+
const p = writeToFile(payload, env);
|
|
1353
|
+
if (keychainEnabled(env, keychain)) keychain.delete();
|
|
1354
|
+
return p;
|
|
1355
|
+
}
|
|
1356
|
+
var realKeychain, KEYCHAIN_LOCATION;
|
|
1357
|
+
var init_credential_store = __esm({
|
|
1358
|
+
"src/cloud/credential-store.ts"() {
|
|
1359
|
+
"use strict";
|
|
1360
|
+
init_keychain();
|
|
1361
|
+
realKeychain = {
|
|
1362
|
+
available: keychainAvailable,
|
|
1363
|
+
get: keychainGet,
|
|
1364
|
+
set: keychainSet,
|
|
1365
|
+
delete: keychainDelete
|
|
1366
|
+
};
|
|
1367
|
+
KEYCHAIN_LOCATION = 'OS keychain (service "vo-mcp")';
|
|
1368
|
+
}
|
|
1369
|
+
});
|
|
1370
|
+
|
|
1371
|
+
// src/tools/memory/safe-memory-file.ts
|
|
1372
|
+
import { resolve, sep } from "node:path";
|
|
1373
|
+
function isSafeMemoryFileName(fileName) {
|
|
1374
|
+
return fileName.length <= 200 && fileName.trim() === fileName && !fileName.includes("/") && !fileName.includes("\\") && !fileName.includes(":") && SAFE_MEMORY_FILE_RE.test(fileName);
|
|
1375
|
+
}
|
|
1376
|
+
function resolveMemoryFilePath(memoryDir, fileName) {
|
|
1377
|
+
if (!isSafeMemoryFileName(fileName)) {
|
|
1378
|
+
throw new Error(`unsafe memory file_name: ${fileName.slice(0, 80)}`);
|
|
1379
|
+
}
|
|
1380
|
+
const root = resolve(memoryDir);
|
|
1381
|
+
const filePath = resolve(root, fileName);
|
|
1382
|
+
const rootPrefix = root.endsWith(sep) ? root : `${root}${sep}`;
|
|
1383
|
+
if (filePath !== root && !filePath.startsWith(rootPrefix)) {
|
|
1384
|
+
throw new Error(`memory file path escapes memory directory: ${fileName.slice(0, 80)}`);
|
|
1385
|
+
}
|
|
1386
|
+
return filePath;
|
|
1387
|
+
}
|
|
1388
|
+
var SAFE_MEMORY_FILE_RE;
|
|
1389
|
+
var init_safe_memory_file = __esm({
|
|
1390
|
+
"src/tools/memory/safe-memory-file.ts"() {
|
|
1391
|
+
"use strict";
|
|
1392
|
+
SAFE_MEMORY_FILE_RE = /^[A-Za-z0-9][A-Za-z0-9._ -]*\.md$/i;
|
|
1393
|
+
}
|
|
1394
|
+
});
|
|
1395
|
+
|
|
1396
|
+
// src/tools/memory/sync-config.ts
|
|
1397
|
+
var sync_config_exports = {};
|
|
1398
|
+
__export(sync_config_exports, {
|
|
1399
|
+
TOOL_NAME: () => TOOL_NAME22,
|
|
1400
|
+
deriveProjectSlug: () => deriveProjectSlug,
|
|
1401
|
+
description: () => description22,
|
|
1402
|
+
getMemoryDir: () => getMemoryDir,
|
|
1403
|
+
handleSyncConfig: () => handleSyncConfig,
|
|
1404
|
+
inputSchema: () => inputSchema22,
|
|
1405
|
+
isNoopSyncReason: () => isNoopSyncReason,
|
|
1406
|
+
runMemorySync: () => runMemorySync
|
|
1407
|
+
});
|
|
1408
|
+
import { homedir as homedir5 } from "node:os";
|
|
1409
|
+
import { join as join7 } from "node:path";
|
|
1410
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync3, readdirSync as readdirSync4 } from "node:fs";
|
|
1411
|
+
function isToolInput22(v) {
|
|
1412
|
+
if (typeof v !== "object" || v === null) return false;
|
|
1413
|
+
const o = v;
|
|
1414
|
+
if (o["action"] !== "pull" && o["action"] !== "push") return false;
|
|
1415
|
+
if (o["cwd"] !== void 0 && typeof o["cwd"] !== "string") return false;
|
|
1416
|
+
return true;
|
|
1417
|
+
}
|
|
1418
|
+
function deriveProjectSlug(cwd) {
|
|
1419
|
+
return cwd.replace(/\\/g, "/").replace(/\/+$/g, "").replace(/^([a-zA-Z]):/, (_m, drive) => `${drive.toUpperCase()}:`).replace(/[^a-zA-Z0-9]/g, "-");
|
|
1420
|
+
}
|
|
1421
|
+
function getMemoryDir(cwd) {
|
|
1422
|
+
const slug = deriveProjectSlug(cwd);
|
|
1423
|
+
return join7(homedir5(), ".claude", "projects", slug, "memory");
|
|
1424
|
+
}
|
|
1425
|
+
async function pullMemory(controlPlaneUrl, token, memoryDir, fetchFn) {
|
|
1426
|
+
const url = `${controlPlaneUrl}/api/v1/agent-config/memory/me`;
|
|
1427
|
+
const response = await fetchFn(url, {
|
|
1428
|
+
method: "GET",
|
|
1429
|
+
headers: {
|
|
1430
|
+
authorization: `Bearer ${token}`
|
|
1431
|
+
}
|
|
1432
|
+
});
|
|
1433
|
+
if (response.status !== 200) {
|
|
1434
|
+
const text = await response.text();
|
|
1435
|
+
throw new Error(`GET /api/v1/agent-config/memory/me returned HTTP ${response.status}: ${text.slice(0, 200)}`);
|
|
1436
|
+
}
|
|
1437
|
+
const data = JSON.parse(await response.text());
|
|
1438
|
+
if (!data.ok || !Array.isArray(data.entries)) {
|
|
1439
|
+
throw new Error("GET /api/v1/agent-config/memory/me response missing ok=true or entries array");
|
|
1440
|
+
}
|
|
1441
|
+
const writes = data.entries.map((entry) => ({
|
|
1442
|
+
entry,
|
|
1443
|
+
filePath: resolveMemoryFilePath(memoryDir, entry.file_name)
|
|
1444
|
+
}));
|
|
1445
|
+
mkdirSync4(memoryDir, { recursive: true });
|
|
1446
|
+
const files = [];
|
|
1447
|
+
for (const { entry, filePath } of writes) {
|
|
1448
|
+
writeFileSync3(filePath, entry.content, "utf8");
|
|
1449
|
+
files.push(entry.file_name);
|
|
1450
|
+
}
|
|
1451
|
+
return { pulled: data.entries.length, files };
|
|
1452
|
+
}
|
|
1453
|
+
async function pushMemory(controlPlaneUrl, token, memoryDir, sessionId, fetchFn) {
|
|
1454
|
+
if (!existsSync5(memoryDir)) {
|
|
1455
|
+
return { pushed: 0, created: 0, updated: 0 };
|
|
1456
|
+
}
|
|
1457
|
+
const localFiles = readdirSync4(memoryDir).filter((f) => f.endsWith(".md")).map((f) => ({
|
|
1458
|
+
file_name: f,
|
|
1459
|
+
content: readFileSync7(resolveMemoryFilePath(memoryDir, f), "utf8"),
|
|
1460
|
+
entry_type: f === "MEMORY.md" ? "index" : "topic"
|
|
1461
|
+
}));
|
|
1462
|
+
if (localFiles.length === 0) {
|
|
1463
|
+
return { pushed: 0, created: 0, updated: 0 };
|
|
1464
|
+
}
|
|
1465
|
+
const getUrl = `${controlPlaneUrl}/api/v1/agent-config/memory/me`;
|
|
1466
|
+
const getResponse = await fetchFn(getUrl, {
|
|
1467
|
+
method: "GET",
|
|
1468
|
+
headers: {
|
|
1469
|
+
authorization: `Bearer ${token}`
|
|
1470
|
+
}
|
|
1471
|
+
});
|
|
1472
|
+
const existingMap = /* @__PURE__ */ new Map();
|
|
1473
|
+
if (getResponse.status === 200) {
|
|
1474
|
+
const getData = JSON.parse(await getResponse.text());
|
|
1475
|
+
if (getData.ok && Array.isArray(getData.entries)) {
|
|
1476
|
+
for (const entry of getData.entries) {
|
|
1477
|
+
existingMap.set(entry.file_name, entry.memory_id);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1071
1480
|
}
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1481
|
+
let created = 0;
|
|
1482
|
+
let updated = 0;
|
|
1483
|
+
for (const localFile of localFiles) {
|
|
1484
|
+
const memoryId = existingMap.get(localFile.file_name);
|
|
1485
|
+
if (memoryId) {
|
|
1486
|
+
const updateUrl = `${controlPlaneUrl}/api/v1/agent-config/memory/${memoryId}`;
|
|
1487
|
+
const updateBody = {
|
|
1488
|
+
content: localFile.content,
|
|
1489
|
+
session_id: sessionId
|
|
1490
|
+
};
|
|
1491
|
+
const updateResponse = await fetchFn(updateUrl, {
|
|
1492
|
+
method: "PUT",
|
|
1493
|
+
headers: {
|
|
1494
|
+
authorization: `Bearer ${token}`,
|
|
1495
|
+
"content-type": "application/json"
|
|
1496
|
+
},
|
|
1497
|
+
body: JSON.stringify(updateBody)
|
|
1498
|
+
});
|
|
1499
|
+
if (updateResponse.status !== 200) {
|
|
1500
|
+
const text = await updateResponse.text();
|
|
1501
|
+
throw new Error(
|
|
1502
|
+
`PUT /api/v1/agent-config/memory/${memoryId} returned HTTP ${updateResponse.status}: ${text.slice(0, 200)}`
|
|
1503
|
+
);
|
|
1082
1504
|
}
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
} catch (err) {
|
|
1087
|
-
const code = err.code;
|
|
1088
|
-
if (code === "ENOSPC") {
|
|
1089
|
-
process.stderr.write(
|
|
1090
|
-
`[vo-mcp] events log write failed (disk full): event dropped
|
|
1091
|
-
`
|
|
1092
|
-
);
|
|
1093
|
-
return;
|
|
1094
|
-
}
|
|
1095
|
-
throw err;
|
|
1505
|
+
const updateData = JSON.parse(await updateResponse.text());
|
|
1506
|
+
if (!updateData.ok) {
|
|
1507
|
+
throw new Error(`PUT /api/v1/agent-config/memory/${memoryId} returned ok=false`);
|
|
1096
1508
|
}
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1509
|
+
updated++;
|
|
1510
|
+
} else {
|
|
1511
|
+
const createUrl = `${controlPlaneUrl}/api/v1/agent-config/memory/me`;
|
|
1512
|
+
const createBody = {
|
|
1513
|
+
entry_type: localFile.entry_type,
|
|
1514
|
+
file_name: localFile.file_name,
|
|
1515
|
+
content: localFile.content,
|
|
1516
|
+
session_id: sessionId
|
|
1517
|
+
};
|
|
1518
|
+
const createResponse = await fetchFn(createUrl, {
|
|
1519
|
+
method: "POST",
|
|
1520
|
+
headers: {
|
|
1521
|
+
authorization: `Bearer ${token}`,
|
|
1522
|
+
"content-type": "application/json"
|
|
1523
|
+
},
|
|
1524
|
+
body: JSON.stringify(createBody)
|
|
1525
|
+
});
|
|
1526
|
+
if (createResponse.status !== 200 && createResponse.status !== 201) {
|
|
1527
|
+
const text = await createResponse.text();
|
|
1528
|
+
throw new Error(
|
|
1529
|
+
`POST /api/v1/agent-config/memory/me returned HTTP ${createResponse.status}: ${text.slice(0, 200)}`
|
|
1530
|
+
);
|
|
1103
1531
|
}
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1532
|
+
const createData = JSON.parse(await createResponse.text());
|
|
1533
|
+
if (!createData.ok) {
|
|
1534
|
+
throw new Error("POST /api/v1/agent-config/memory/me returned ok=false");
|
|
1535
|
+
}
|
|
1536
|
+
created++;
|
|
1109
1537
|
}
|
|
1110
|
-
};
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
// src/tools/common.ts
|
|
1114
|
-
function readVoMcpVersion() {
|
|
1115
|
-
try {
|
|
1116
|
-
const here = dirname3(fileURLToPath2(import.meta.url));
|
|
1117
|
-
const pkgPath = join4(here, "..", "..", "package.json");
|
|
1118
|
-
const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
|
|
1119
|
-
return typeof pkg.version === "string" ? pkg.version : "0.0.0-unknown";
|
|
1120
|
-
} catch {
|
|
1121
|
-
return "0.0.0-unknown";
|
|
1122
1538
|
}
|
|
1539
|
+
return { pushed: localFiles.length, created, updated };
|
|
1123
1540
|
}
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
return
|
|
1127
|
-
}
|
|
1128
|
-
function sha256Hex(s) {
|
|
1129
|
-
return createHash2("sha256").update(s, "utf8").digest("hex");
|
|
1130
|
-
}
|
|
1131
|
-
function invalidParams(toolName, message) {
|
|
1132
|
-
return new McpError(ErrorCode.InvalidParams, `${toolName}: ${message}`);
|
|
1541
|
+
function isNoopSyncReason(reason) {
|
|
1542
|
+
if (!reason) return false;
|
|
1543
|
+
return /not set|No auth configured|Failed to obtain auth token/.test(reason);
|
|
1133
1544
|
}
|
|
1134
|
-
function
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1545
|
+
async function runMemorySync(action, cwd, sessionId, fetchFn = globalThis.fetch) {
|
|
1546
|
+
const controlPlaneUrl = process.env["VO_CONTROL_PLANE_URL"];
|
|
1547
|
+
if (!controlPlaneUrl) {
|
|
1548
|
+
return { synced: false, reason: "VO_CONTROL_PLANE_URL not set \u2014 cloud mode disabled" };
|
|
1549
|
+
}
|
|
1550
|
+
const { createAuthTokenSourceFromEnv: createAuthTokenSourceFromEnv2 } = await Promise.resolve().then(() => (init_auth_token_source(), auth_token_source_exports));
|
|
1551
|
+
const { readStoredCredential: readStoredCredential2 } = await Promise.resolve().then(() => (init_credential_store(), credential_store_exports));
|
|
1552
|
+
const tokenSource = createAuthTokenSourceFromEnv2(process.env, fetchFn, () => readStoredCredential2(process.env));
|
|
1553
|
+
if (!tokenSource) {
|
|
1554
|
+
return { synced: false, reason: "No auth configured. Run `vo-mcp login` to authenticate as an operator." };
|
|
1555
|
+
}
|
|
1556
|
+
const token = await tokenSource.getToken();
|
|
1557
|
+
if (!token) {
|
|
1558
|
+
return { synced: false, reason: "Failed to obtain auth token. Run `vo-mcp login` to re-authenticate." };
|
|
1559
|
+
}
|
|
1560
|
+
const memoryDir = getMemoryDir(cwd);
|
|
1561
|
+
const baseUrl = controlPlaneUrl.replace(/\/+$/, "");
|
|
1562
|
+
try {
|
|
1563
|
+
if (action === "pull") {
|
|
1564
|
+
const result2 = await pullMemory(baseUrl, token, memoryDir, fetchFn);
|
|
1565
|
+
return { synced: true, action: "pull", pulled: result2.pulled, files: result2.files, memory_dir: memoryDir };
|
|
1566
|
+
}
|
|
1567
|
+
const result = await pushMemory(baseUrl, token, memoryDir, sessionId, fetchFn);
|
|
1568
|
+
return {
|
|
1569
|
+
synced: true,
|
|
1570
|
+
action: "push",
|
|
1571
|
+
pushed: result.pushed,
|
|
1572
|
+
created: result.created,
|
|
1573
|
+
updated: result.updated,
|
|
1574
|
+
memory_dir: memoryDir
|
|
1575
|
+
};
|
|
1576
|
+
} catch (err) {
|
|
1577
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1578
|
+
return { synced: false, reason: `Sync failed: ${message}` };
|
|
1579
|
+
}
|
|
1139
1580
|
}
|
|
1140
|
-
function
|
|
1141
|
-
|
|
1142
|
-
if (bytes > maxBytes) {
|
|
1581
|
+
async function handleSyncConfig(deps, rawInput, _signal, fetchFn = globalThis.fetch) {
|
|
1582
|
+
if (!isToolInput22(rawInput)) {
|
|
1143
1583
|
throw invalidParams(
|
|
1144
|
-
|
|
1145
|
-
|
|
1584
|
+
TOOL_NAME22,
|
|
1585
|
+
'invalid input. Required: { action: "pull" | "push" }. Optional: { cwd: "<path>" }.'
|
|
1146
1586
|
);
|
|
1147
1587
|
}
|
|
1588
|
+
const cwd = rawInput.cwd?.trim() || process.cwd();
|
|
1589
|
+
const result = await runMemorySync(rawInput.action, cwd, deps.session.sessionId, fetchFn);
|
|
1590
|
+
return jsonContent({ tool: TOOL_NAME22, schema_version: 1, payload: result });
|
|
1148
1591
|
}
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
dev_override: null,
|
|
1172
|
-
downstream_outcome: null,
|
|
1173
|
-
vo_mcp_version: VO_MCP_VERSION,
|
|
1174
|
-
consensus_engine_version: null,
|
|
1175
|
-
cache_hit: false
|
|
1176
|
-
};
|
|
1177
|
-
}
|
|
1178
|
-
function jsonContent(value) {
|
|
1179
|
-
return {
|
|
1180
|
-
content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
|
|
1181
|
-
};
|
|
1182
|
-
}
|
|
1183
|
-
function toEventPerModelVerdicts(src) {
|
|
1184
|
-
return src.map((v) => {
|
|
1185
|
-
const sanitizedExcerpt = sanitizeExcerpt(v.raw_response_excerpt);
|
|
1186
|
-
return {
|
|
1187
|
-
model: v.model,
|
|
1188
|
-
model_id: v.model,
|
|
1189
|
-
provider: v.provider,
|
|
1190
|
-
verdict: v.verdict,
|
|
1191
|
-
confidence: v.confidence,
|
|
1192
|
-
duration_ms: v.duration_ms,
|
|
1193
|
-
raw_response_excerpt: sanitizedExcerpt,
|
|
1194
|
-
// Hash over the sanitized excerpt — engine doesn't yet thread the full
|
|
1195
|
-
// raw response. Documented limitation on `raw_response_hash` in types.ts.
|
|
1196
|
-
raw_response_hash: sha256Hex(v.raw_response_excerpt),
|
|
1197
|
-
reasoning_summary: typeof v.reasoning_excerpt === "string" && v.reasoning_excerpt.length > 0 ? sanitizeExcerpt(v.reasoning_excerpt, 500) : null,
|
|
1198
|
-
// Engine doesn't yet emit cited_sources; ship empty array so cloud
|
|
1199
|
-
// ingestion doesn't NPE on `Array.isArray()` checks.
|
|
1200
|
-
cited_sources: [],
|
|
1201
|
-
error: v.error ?? null
|
|
1592
|
+
var TOOL_NAME22, inputSchema22, description22;
|
|
1593
|
+
var init_sync_config = __esm({
|
|
1594
|
+
"src/tools/memory/sync-config.ts"() {
|
|
1595
|
+
"use strict";
|
|
1596
|
+
init_common();
|
|
1597
|
+
init_safe_memory_file();
|
|
1598
|
+
TOOL_NAME22 = "vo_sync_config";
|
|
1599
|
+
inputSchema22 = {
|
|
1600
|
+
type: "object",
|
|
1601
|
+
properties: {
|
|
1602
|
+
action: {
|
|
1603
|
+
type: "string",
|
|
1604
|
+
enum: ["pull", "push"],
|
|
1605
|
+
description: "pull: download cloud memory to local files. push: upload local files to cloud."
|
|
1606
|
+
},
|
|
1607
|
+
cwd: {
|
|
1608
|
+
type: "string",
|
|
1609
|
+
description: "Working directory to derive project slug from (default: process.cwd())."
|
|
1610
|
+
}
|
|
1611
|
+
},
|
|
1612
|
+
required: ["action"],
|
|
1613
|
+
additionalProperties: false
|
|
1202
1614
|
};
|
|
1203
|
-
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
}
|
|
1615
|
+
description22 = "Syncs memory entries between local ~/.claude/projects/<slug>/memory/ and cloud control-plane /api/v1/agent-config/memory/me. Requires operator auth (vo-mcp login). Actions: pull (cloud\u2192local), push (local\u2192cloud). Idempotent; push creates/updates as needed.";
|
|
1616
|
+
}
|
|
1617
|
+
});
|
|
1618
|
+
|
|
1619
|
+
// src/cli.ts
|
|
1620
|
+
import { homedir as homedir6, hostname } from "node:os";
|
|
1621
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
1622
|
+
import { join as join8 } from "node:path";
|
|
1623
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
1624
|
+
|
|
1625
|
+
// src/server.ts
|
|
1626
|
+
init_common();
|
|
1627
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
1628
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
1629
|
+
import {
|
|
1630
|
+
CallToolRequestSchema,
|
|
1631
|
+
ListToolsRequestSchema
|
|
1632
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
1212
1633
|
|
|
1213
1634
|
// src/modes/local.ts
|
|
1214
1635
|
function createLocalMode() {
|
|
@@ -1223,6 +1644,7 @@ function createLocalMode() {
|
|
|
1223
1644
|
}
|
|
1224
1645
|
|
|
1225
1646
|
// src/tools/check-assertion-strength.ts
|
|
1647
|
+
init_common();
|
|
1226
1648
|
var TOOL_NAME = "vo_check_assertion_strength";
|
|
1227
1649
|
var GATE_TYPE = "ratchet";
|
|
1228
1650
|
var MAX_SOURCE_BYTES = 512 * 1024;
|
|
@@ -1319,7 +1741,11 @@ async function handleCheckAssertionStrength(deps, rawInput, _signal) {
|
|
|
1319
1741
|
return jsonContent(envelope);
|
|
1320
1742
|
}
|
|
1321
1743
|
|
|
1744
|
+
// src/tools/check-hollow-test.ts
|
|
1745
|
+
init_common();
|
|
1746
|
+
|
|
1322
1747
|
// src/tools/architecture-review-kb-prefilter.ts
|
|
1748
|
+
init_src();
|
|
1323
1749
|
var DEFAULT_STACK = [
|
|
1324
1750
|
"node",
|
|
1325
1751
|
"node-pnpm-monorepo",
|
|
@@ -1405,6 +1831,7 @@ function formatRulesForPrompt(hits, truncated, domainLabel = "ARCHITECTURAL") {
|
|
|
1405
1831
|
}
|
|
1406
1832
|
|
|
1407
1833
|
// src/tools/kb-metadata-prefilter.ts
|
|
1834
|
+
init_src();
|
|
1408
1835
|
function findRulesByMetadata(opts) {
|
|
1409
1836
|
if (opts.category === void 0 && opts.tagsAny === void 0) {
|
|
1410
1837
|
return {
|
|
@@ -1598,6 +2025,7 @@ async function handleCheckHollowTest(deps, rawInput, signal) {
|
|
|
1598
2025
|
}
|
|
1599
2026
|
|
|
1600
2027
|
// src/tools/verify-answer.ts
|
|
2028
|
+
init_common();
|
|
1601
2029
|
var TOOL_NAME3 = "vo_verify_answer";
|
|
1602
2030
|
var SHALLOW_GATE = "mid-exec-verify";
|
|
1603
2031
|
var DEEP_GATE = "final-deep-verify";
|
|
@@ -1779,6 +2207,9 @@ async function handleVerifyAnswer(deps, rawInput, signal) {
|
|
|
1779
2207
|
return jsonContent(envelope);
|
|
1780
2208
|
}
|
|
1781
2209
|
|
|
2210
|
+
// src/tools/consensus-judgment.ts
|
|
2211
|
+
init_common();
|
|
2212
|
+
|
|
1782
2213
|
// src/consensus/gate-types.ts
|
|
1783
2214
|
var LEGACY_GATE_TYPES = [
|
|
1784
2215
|
"test_assertion",
|
|
@@ -2036,6 +2467,8 @@ async function handleConsensusJudgment(deps, rawInput, signal) {
|
|
|
2036
2467
|
...engineResult.synthesized_verdict.confidence_badge !== void 0 ? { confidence_badge: engineResult.synthesized_verdict.confidence_badge } : {},
|
|
2037
2468
|
// Feature 1 (agreement-gate) — fan-out diagnostics (present iff the gate ran).
|
|
2038
2469
|
...engineResult.fan_out_diagnostics !== void 0 ? { fan_out_diagnostics: engineResult.fan_out_diagnostics } : {},
|
|
2470
|
+
// Stage A7-shadow — adaptive-vs-incumbent comparison (PII-free; present iff shadow on).
|
|
2471
|
+
...engineResult.shadow_synthesis !== void 0 ? { shadow_synthesis: engineResult.shadow_synthesis } : {},
|
|
2039
2472
|
// Source-grounded Tier-4 outputs (present iff the call was source-grounded).
|
|
2040
2473
|
...engineResult.source_grounded === true ? { source_grounded: true } : {},
|
|
2041
2474
|
...engineResult.citation_grade !== void 0 ? { citation_grade: engineResult.citation_grade } : {},
|
|
@@ -2056,6 +2489,8 @@ async function handleConsensusJudgment(deps, rawInput, signal) {
|
|
|
2056
2489
|
}
|
|
2057
2490
|
|
|
2058
2491
|
// src/tools/architecture-review.ts
|
|
2492
|
+
init_common();
|
|
2493
|
+
init_events_writer();
|
|
2059
2494
|
var TOOL_NAME5 = "vo_architecture_review";
|
|
2060
2495
|
var GATE_TYPE3 = "architecture-review";
|
|
2061
2496
|
var MAX_DIFF_BYTES = 1024 * 1024;
|
|
@@ -3300,6 +3735,7 @@ function partitionByAllowlist(findings, allowlist) {
|
|
|
3300
3735
|
}
|
|
3301
3736
|
|
|
3302
3737
|
// src/tools/check-ratchets.ts
|
|
3738
|
+
init_common();
|
|
3303
3739
|
var TOOL_NAME6 = "vo_check_ratchets";
|
|
3304
3740
|
var GATE_TYPE4 = "ratchet";
|
|
3305
3741
|
var ALL_RATCHET_IDS = [
|
|
@@ -3432,6 +3868,7 @@ function buildSummary(report) {
|
|
|
3432
3868
|
}
|
|
3433
3869
|
|
|
3434
3870
|
// src/tools/decompose-dispatch.ts
|
|
3871
|
+
init_common();
|
|
3435
3872
|
var TOOL_NAME7 = "vo_decompose_dispatch";
|
|
3436
3873
|
var GATE_TYPE5 = "plan-review";
|
|
3437
3874
|
var MAX_GOAL_BYTES = 32 * 1024;
|
|
@@ -3709,6 +4146,9 @@ Produce the JSON dispatch plan now.`;
|
|
|
3709
4146
|
return jsonContent(envelope);
|
|
3710
4147
|
}
|
|
3711
4148
|
|
|
4149
|
+
// src/tools/heal/trigger-heal.ts
|
|
4150
|
+
init_common();
|
|
4151
|
+
|
|
3712
4152
|
// src/cloud/admin-callable-client.ts
|
|
3713
4153
|
init_auth_token_source();
|
|
3714
4154
|
init_credential_store();
|
|
@@ -3838,6 +4278,7 @@ function buildAdminCallableClientFromEnv(env = process.env) {
|
|
|
3838
4278
|
}
|
|
3839
4279
|
|
|
3840
4280
|
// src/tools/cloud-call.ts
|
|
4281
|
+
init_common();
|
|
3841
4282
|
var ADMIN_READONLY_GATE_REASON = "admin callables are in read-only mode (VO_ADMIN_CALLABLES_READONLY) \u2014 this write tool is gated to its stub. Unset VO_ADMIN_CALLABLES_READONLY to enable write tools.";
|
|
3842
4283
|
async function buildCloudOrStubResponse(args) {
|
|
3843
4284
|
const inputJson = JSON.stringify(args.normalizedInput);
|
|
@@ -3911,6 +4352,7 @@ async function buildCloudOrStubResponse(args) {
|
|
|
3911
4352
|
}
|
|
3912
4353
|
|
|
3913
4354
|
// src/tools/heal/common-heal.ts
|
|
4355
|
+
init_common();
|
|
3914
4356
|
var HEAL_STUB_REASON = 'cloud-mode not yet wired; tool surface is live, admin-callable wiring pending vo-cloud-tenant-model dispatch (see packages/vo-mcp/src/modes/cloud.ts + EXTRACTION_AUDIT.md "Stub remaining")';
|
|
3915
4357
|
var HEAL_GATE_TYPE = "admin-action";
|
|
3916
4358
|
|
|
@@ -3970,6 +4412,7 @@ async function handleTriggerHeal(deps, rawInput, _signal) {
|
|
|
3970
4412
|
}
|
|
3971
4413
|
|
|
3972
4414
|
// src/tools/heal/fix-retry.ts
|
|
4415
|
+
init_common();
|
|
3973
4416
|
var TOOL_NAME9 = "vo_fix_retry";
|
|
3974
4417
|
var MAX_BATCH = 50;
|
|
3975
4418
|
var ADMIN_PATH_SINGLE = "/api/v1/admin/heal/retry-attempt";
|
|
@@ -4062,6 +4505,7 @@ async function handleFixRetry(deps, rawInput, _signal) {
|
|
|
4062
4505
|
}
|
|
4063
4506
|
|
|
4064
4507
|
// src/tools/heal/fix-clear.ts
|
|
4508
|
+
init_common();
|
|
4065
4509
|
var TOOL_NAME10 = "vo_fix_clear";
|
|
4066
4510
|
var CALLABLE_NAME2 = "voClearFixAttempt";
|
|
4067
4511
|
var ADMIN_PATH2 = "/api/v1/admin/heal/clear-attempt";
|
|
@@ -4105,6 +4549,7 @@ async function handleFixClear(deps, rawInput, _signal) {
|
|
|
4105
4549
|
}
|
|
4106
4550
|
|
|
4107
4551
|
// src/tools/heal/stop-workflow.ts
|
|
4552
|
+
init_common();
|
|
4108
4553
|
var TOOL_NAME11 = "vo_stop_workflow";
|
|
4109
4554
|
var CALLABLE_NAME3 = "voStopWorkflow";
|
|
4110
4555
|
var ADMIN_PATH3 = "/api/v1/admin/workflow/stop";
|
|
@@ -4160,6 +4605,7 @@ async function handleStopWorkflow(deps, rawInput, _signal) {
|
|
|
4160
4605
|
}
|
|
4161
4606
|
|
|
4162
4607
|
// src/tools/heal/get-workflow-runs.ts
|
|
4608
|
+
init_common();
|
|
4163
4609
|
var TOOL_NAME12 = "vo_get_workflow_runs";
|
|
4164
4610
|
var CALLABLE_NAME4 = "voGetWorkflowRuns";
|
|
4165
4611
|
var ADMIN_PATH4 = "/api/v1/admin/workflow/runs";
|
|
@@ -4190,7 +4636,11 @@ async function handleGetWorkflowRuns(deps, rawInput, _signal) {
|
|
|
4190
4636
|
});
|
|
4191
4637
|
}
|
|
4192
4638
|
|
|
4639
|
+
// src/tools/pr/list-pending-prs.ts
|
|
4640
|
+
init_common();
|
|
4641
|
+
|
|
4193
4642
|
// src/tools/pr/common-pr.ts
|
|
4643
|
+
init_common();
|
|
4194
4644
|
var PR_STUB_REASON = 'cloud-mode not yet wired; tool surface is live, admin-callable wiring pending vo-cloud-tenant-model dispatch (see packages/vo-mcp/src/modes/cloud.ts + EXTRACTION_AUDIT.md "Stub remaining")';
|
|
4195
4645
|
var PR_GATE_TYPE = "admin-action";
|
|
4196
4646
|
|
|
@@ -4225,6 +4675,7 @@ async function handleListPendingPRs(deps, rawInput, _signal) {
|
|
|
4225
4675
|
}
|
|
4226
4676
|
|
|
4227
4677
|
// src/tools/pr/merge-pr.ts
|
|
4678
|
+
init_common();
|
|
4228
4679
|
var TOOL_NAME14 = "vo_merge_pr";
|
|
4229
4680
|
var CALLABLE_NAME6 = "voMergePR";
|
|
4230
4681
|
var ADMIN_PATH6 = "/api/v1/admin/pr/merge";
|
|
@@ -4269,6 +4720,7 @@ async function handleMergePR(deps, rawInput, _signal) {
|
|
|
4269
4720
|
}
|
|
4270
4721
|
|
|
4271
4722
|
// src/tools/pr/reject-pr.ts
|
|
4723
|
+
init_common();
|
|
4272
4724
|
var TOOL_NAME15 = "vo_reject_pr";
|
|
4273
4725
|
var CALLABLE_NAME7 = "voRejectPR";
|
|
4274
4726
|
var ADMIN_PATH7 = "/api/v1/admin/pr/reject";
|
|
@@ -4313,6 +4765,7 @@ async function handleRejectPR(deps, rawInput, _signal) {
|
|
|
4313
4765
|
}
|
|
4314
4766
|
|
|
4315
4767
|
// src/tools/pr/approve-all-fixes.ts
|
|
4768
|
+
init_common();
|
|
4316
4769
|
var TOOL_NAME16 = "vo_approve_all_fixes";
|
|
4317
4770
|
var CALLABLE_NAME8 = "voApproveAllFixes";
|
|
4318
4771
|
var ADMIN_PATH8 = "/api/v1/admin/pr/approve-all";
|
|
@@ -4341,6 +4794,7 @@ async function handleApproveAllFixes(deps, rawInput, _signal) {
|
|
|
4341
4794
|
}
|
|
4342
4795
|
|
|
4343
4796
|
// src/tools/pr/reject-and-retry.ts
|
|
4797
|
+
init_common();
|
|
4344
4798
|
var TOOL_NAME17 = "vo_reject_and_retry";
|
|
4345
4799
|
var CALLABLE_NAME9 = "voRejectAndRetry";
|
|
4346
4800
|
var ADMIN_PATH9 = "/api/v1/admin/pr/reject-retry";
|
|
@@ -4385,6 +4839,7 @@ async function handleRejectAndRetry(deps, rawInput, _signal) {
|
|
|
4385
4839
|
}
|
|
4386
4840
|
|
|
4387
4841
|
// src/tools/pr/review-merge.ts
|
|
4842
|
+
init_common();
|
|
4388
4843
|
var TOOL_NAME18 = "vo_review_merge";
|
|
4389
4844
|
var LIST_PATH = "/api/v1/admin/pr/list";
|
|
4390
4845
|
var ENGINE_GATE = "final-deep-verify";
|
|
@@ -4563,6 +5018,9 @@ async function handleReviewMerge(deps, rawInput, signal) {
|
|
|
4563
5018
|
});
|
|
4564
5019
|
}
|
|
4565
5020
|
|
|
5021
|
+
// src/tools/session/report-session-state.ts
|
|
5022
|
+
init_common();
|
|
5023
|
+
|
|
4566
5024
|
// src/tools/session/directive.ts
|
|
4567
5025
|
var SESSION_DIRECTIVE_THRESHOLDS = {
|
|
4568
5026
|
prepare_handoff_pct: 70,
|
|
@@ -4594,6 +5052,8 @@ function suggestedHandoffPath(session_id, isoTimestamp) {
|
|
|
4594
5052
|
}
|
|
4595
5053
|
|
|
4596
5054
|
// src/tools/session/report-session-state.ts
|
|
5055
|
+
init_auth_token_source();
|
|
5056
|
+
init_credential_store();
|
|
4597
5057
|
var TOOL_NAME19 = "vo_report_session_state";
|
|
4598
5058
|
var VALID_AGENT_TYPES = ["claude-code", "codex", "cursor", "continue"];
|
|
4599
5059
|
var MAX_GOAL_CHARS = 500;
|
|
@@ -4644,7 +5104,7 @@ var inputSchema19 = {
|
|
|
4644
5104
|
required: ["operator_id", "session_id", "agent_type", "context_used_pct"],
|
|
4645
5105
|
additionalProperties: false
|
|
4646
5106
|
};
|
|
4647
|
-
var description19 = "Reports per-session context-window utilization to VO and returns a directive: 'continue' (under 70%), 'prepare_handoff' (70-84%), or 'execute_handoff_now' (\u226585%). Implements V1 launch gate #9 (fleet context lifecycle management) per the official VO roadmap.
|
|
5107
|
+
var description19 = "Reports per-session context-window utilization to VO and returns a directive: 'continue' (under 70%), 'prepare_handoff' (70-84%), or 'execute_handoff_now' (\u226585%). Implements V1 launch gate #9 (fleet context lifecycle management) per the official VO roadmap. Cloud-control-plane mode when VO_CONTROL_PLANE_URL plus a user/scoped VO 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).";
|
|
4648
5108
|
function isStringArray2(v, maxItems) {
|
|
4649
5109
|
if (!Array.isArray(v)) return false;
|
|
4650
5110
|
if (v.length > maxItems) return false;
|
|
@@ -4669,33 +5129,93 @@ function isToolInput19(v) {
|
|
|
4669
5129
|
}
|
|
4670
5130
|
return true;
|
|
4671
5131
|
}
|
|
4672
|
-
function
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
5132
|
+
async function fetchCloudIdentity(url, token, fetchFn) {
|
|
5133
|
+
try {
|
|
5134
|
+
const response = await fetchFn(`${url}/api/v1/auth/me`, {
|
|
5135
|
+
method: "GET",
|
|
5136
|
+
headers: {
|
|
5137
|
+
"Authorization": `Bearer ${token}`
|
|
5138
|
+
}
|
|
5139
|
+
});
|
|
5140
|
+
if (!response.ok) return null;
|
|
5141
|
+
const data = await response.json();
|
|
5142
|
+
if (!data.ok || !data.provisioned || !data.operator_id || !data.tenant_id) return null;
|
|
5143
|
+
return { operator_id: data.operator_id, tenant_id: data.tenant_id };
|
|
5144
|
+
} catch {
|
|
5145
|
+
return null;
|
|
5146
|
+
}
|
|
5147
|
+
}
|
|
5148
|
+
async function getCloudConfig(fetchFn = fetch) {
|
|
5149
|
+
const url = process.env["VO_CONTROL_PLANE_URL"]?.trim();
|
|
5150
|
+
if (!url) return null;
|
|
5151
|
+
const tokenSource = createAuthTokenSourceFromEnv(
|
|
5152
|
+
process.env,
|
|
5153
|
+
fetchFn,
|
|
5154
|
+
() => readStoredCredential(process.env)
|
|
5155
|
+
);
|
|
5156
|
+
const token = await tokenSource?.getToken();
|
|
5157
|
+
if (!token) return null;
|
|
5158
|
+
const tenant_id = process.env["VO_TENANT_ID"]?.trim();
|
|
5159
|
+
if (tenant_id) return { url, token, tenant_id };
|
|
5160
|
+
const identity = await fetchCloudIdentity(url, token, fetchFn);
|
|
5161
|
+
if (!identity) return null;
|
|
5162
|
+
return { url, token, tenant_id: identity.tenant_id, operator_id: identity.operator_id };
|
|
4677
5163
|
}
|
|
4678
|
-
async function tryCloudReportState(cloud, input) {
|
|
5164
|
+
async function tryCloudReportState(cloud, input, fetchFn = fetch) {
|
|
4679
5165
|
try {
|
|
4680
|
-
const
|
|
5166
|
+
const reportBody = {
|
|
4681
5167
|
context_used_pct: input.context_used_pct
|
|
4682
5168
|
};
|
|
4683
|
-
if (input.current_goal !== void 0)
|
|
5169
|
+
if (input.current_goal !== void 0) reportBody["current_goal"] = input.current_goal;
|
|
4684
5170
|
if (input.recent_files_touched !== void 0) {
|
|
4685
|
-
|
|
5171
|
+
reportBody["recent_files_touched"] = input.recent_files_touched;
|
|
4686
5172
|
}
|
|
4687
5173
|
if (input.recent_tool_uses !== void 0) {
|
|
4688
|
-
|
|
5174
|
+
reportBody["recent_tool_uses"] = input.recent_tool_uses;
|
|
4689
5175
|
}
|
|
4690
|
-
const
|
|
4691
|
-
|
|
5176
|
+
const reportUrl = `${cloud.url}/api/v1/session/${input.session_id}/report-state`;
|
|
5177
|
+
let response = await fetchFn(reportUrl, {
|
|
4692
5178
|
method: "POST",
|
|
4693
5179
|
headers: {
|
|
4694
5180
|
"Content-Type": "application/json",
|
|
4695
5181
|
"Authorization": `Bearer ${cloud.token}`
|
|
4696
5182
|
},
|
|
4697
|
-
body: JSON.stringify(
|
|
5183
|
+
body: JSON.stringify(reportBody)
|
|
4698
5184
|
});
|
|
5185
|
+
if (response.status === 404) {
|
|
5186
|
+
const allocateBody = {
|
|
5187
|
+
operator_id: cloud.operator_id ?? input.operator_id,
|
|
5188
|
+
tenant_id: cloud.tenant_id,
|
|
5189
|
+
agent_type: input.agent_type,
|
|
5190
|
+
current_goal: input.current_goal ?? "Interactive session"
|
|
5191
|
+
};
|
|
5192
|
+
if (input.context_used_pct > 0) {
|
|
5193
|
+
allocateBody["initial_context_used_pct"] = input.context_used_pct;
|
|
5194
|
+
}
|
|
5195
|
+
const allocateUrl = `${cloud.url}/api/v1/session`;
|
|
5196
|
+
const allocateResponse = await fetchFn(allocateUrl, {
|
|
5197
|
+
method: "POST",
|
|
5198
|
+
headers: {
|
|
5199
|
+
"Content-Type": "application/json",
|
|
5200
|
+
"Authorization": `Bearer ${cloud.token}`
|
|
5201
|
+
},
|
|
5202
|
+
body: JSON.stringify(allocateBody)
|
|
5203
|
+
});
|
|
5204
|
+
if (!allocateResponse.ok) {
|
|
5205
|
+
return null;
|
|
5206
|
+
}
|
|
5207
|
+
const allocateData = await allocateResponse.json();
|
|
5208
|
+
const retrySessionId = typeof allocateData.session?.session_id === "string" && allocateData.session.session_id.length > 0 ? allocateData.session.session_id : input.session_id;
|
|
5209
|
+
const retryReportUrl = `${cloud.url}/api/v1/session/${retrySessionId}/report-state`;
|
|
5210
|
+
response = await fetchFn(retryReportUrl, {
|
|
5211
|
+
method: "POST",
|
|
5212
|
+
headers: {
|
|
5213
|
+
"Content-Type": "application/json",
|
|
5214
|
+
"Authorization": `Bearer ${cloud.token}`
|
|
5215
|
+
},
|
|
5216
|
+
body: JSON.stringify(reportBody)
|
|
5217
|
+
});
|
|
5218
|
+
}
|
|
4699
5219
|
if (!response.ok) {
|
|
4700
5220
|
return null;
|
|
4701
5221
|
}
|
|
@@ -4726,7 +5246,7 @@ async function handleReportSessionState(deps, rawInput, _signal) {
|
|
|
4726
5246
|
`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}).`
|
|
4727
5247
|
);
|
|
4728
5248
|
}
|
|
4729
|
-
const cloud = getCloudConfig();
|
|
5249
|
+
const cloud = await getCloudConfig();
|
|
4730
5250
|
if (cloud !== null) {
|
|
4731
5251
|
const cloudPayload = await tryCloudReportState(cloud, rawInput);
|
|
4732
5252
|
if (cloudPayload !== null) {
|
|
@@ -4751,6 +5271,7 @@ async function handleReportSessionState(deps, rawInput, _signal) {
|
|
|
4751
5271
|
}
|
|
4752
5272
|
|
|
4753
5273
|
// src/tools/session/spawn-successor.ts
|
|
5274
|
+
init_common();
|
|
4754
5275
|
import { spawn } from "node:child_process";
|
|
4755
5276
|
import { homedir as homedir4 } from "node:os";
|
|
4756
5277
|
import { join as join6 } from "node:path";
|
|
@@ -4885,6 +5406,9 @@ async function handleSpawnSuccessor(_deps, rawInput, _signal, spawnImpl = spawn)
|
|
|
4885
5406
|
});
|
|
4886
5407
|
}
|
|
4887
5408
|
|
|
5409
|
+
// src/tools/concierge/dispatch.ts
|
|
5410
|
+
init_common();
|
|
5411
|
+
|
|
4888
5412
|
// src/tools/concierge/common-concierge.ts
|
|
4889
5413
|
var CONCIERGE_STUB_REASON = "cloud mode not active in this MCP runtime \u2014 set VO_CONTROL_PLANE_URL + VO_CONTROL_PLANE_ADMIN_TOKEN to enable. The server-side /api/v1/admin/concierge/dispatch endpoint IS built + deployed (vo-control-plane #5724/#5734); in cloud mode this tool returns the routed pack README + file index.";
|
|
4890
5414
|
var CONCIERGE_GATE_TYPE = "concierge-dispatch";
|
|
@@ -4966,252 +5490,106 @@ async function handleConciergeDispatch(deps, rawInput, _signal) {
|
|
|
4966
5490
|
});
|
|
4967
5491
|
}
|
|
4968
5492
|
|
|
4969
|
-
// src/
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
var
|
|
5493
|
+
// src/server.ts
|
|
5494
|
+
init_sync_config();
|
|
5495
|
+
|
|
5496
|
+
// src/tools/memory/private-knowledge.ts
|
|
5497
|
+
init_common();
|
|
5498
|
+
var UPSERT_TOOL_NAME = "vo_private_knowledge_upsert";
|
|
5499
|
+
var CONTEXT_TOOL_NAME = "vo_private_knowledge_context";
|
|
5500
|
+
var KNOWLEDGE_CLASSES = ["memory", "skill", "doctrine", "hook", "command"];
|
|
5501
|
+
var PRECISION_CHAR_BUDGET = 12e3;
|
|
5502
|
+
var upsertInputSchema = {
|
|
4975
5503
|
type: "object",
|
|
4976
5504
|
properties: {
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
},
|
|
4982
|
-
cwd: {
|
|
4983
|
-
type: "string",
|
|
4984
|
-
description: "Working directory to derive project slug from (default: process.cwd())."
|
|
4985
|
-
}
|
|
5505
|
+
knowledge_class: { type: "string", enum: KNOWLEDGE_CLASSES },
|
|
5506
|
+
source_path: { type: "string", description: "Stable private source identifier; not exposed to other users." },
|
|
5507
|
+
title: { type: "string", description: 'Descriptive, retrieval-friendly title (e.g. "AlgoTax OCR redaction architecture", not "notes") \u2014 retrieval matches on it.' },
|
|
5508
|
+
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." }
|
|
4986
5509
|
},
|
|
4987
|
-
required: ["
|
|
5510
|
+
required: ["knowledge_class", "source_path", "title", "content"],
|
|
4988
5511
|
additionalProperties: false
|
|
4989
5512
|
};
|
|
4990
|
-
var
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
5513
|
+
var contextInputSchema = {
|
|
5514
|
+
type: "object",
|
|
5515
|
+
properties: {
|
|
5516
|
+
query: { type: "string" },
|
|
5517
|
+
limit: { type: "number", minimum: 1, maximum: 50 },
|
|
5518
|
+
knowledge_class: { type: "string", enum: KNOWLEDGE_CLASSES }
|
|
5519
|
+
},
|
|
5520
|
+
required: ["query"],
|
|
5521
|
+
additionalProperties: false
|
|
5522
|
+
};
|
|
5523
|
+
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.";
|
|
5524
|
+
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.";
|
|
5525
|
+
function isKnowledgeClass(value) {
|
|
5526
|
+
return typeof value === "string" && KNOWLEDGE_CLASSES.includes(value);
|
|
5527
|
+
}
|
|
5528
|
+
function isUpsertInput(value) {
|
|
5529
|
+
if (typeof value !== "object" || value === null) return false;
|
|
5530
|
+
const input = value;
|
|
5531
|
+
return isKnowledgeClass(input["knowledge_class"]) && typeof input["source_path"] === "string" && typeof input["title"] === "string" && typeof input["content"] === "string";
|
|
5532
|
+
}
|
|
5533
|
+
function isContextInput(value) {
|
|
5534
|
+
if (typeof value !== "object" || value === null) return false;
|
|
5535
|
+
const input = value;
|
|
5536
|
+
if (typeof input["query"] !== "string") return false;
|
|
5537
|
+
if (input["limit"] !== void 0 && typeof input["limit"] !== "number") return false;
|
|
5538
|
+
if (input["knowledge_class"] !== void 0 && !isKnowledgeClass(input["knowledge_class"])) return false;
|
|
4996
5539
|
return true;
|
|
4997
5540
|
}
|
|
4998
|
-
function
|
|
4999
|
-
const
|
|
5000
|
-
return normalized.replace(/^([A-Z]):/i, (_, drive) => `${drive.toUpperCase()}-`).replace(/\/$/g, "").split("/").join("--").replace(/\s+/g, "-");
|
|
5001
|
-
}
|
|
5002
|
-
function getMemoryDir(cwd) {
|
|
5003
|
-
const slug = deriveProjectSlug(cwd);
|
|
5004
|
-
return join7(homedir5(), ".claude", "projects", slug, "memory");
|
|
5005
|
-
}
|
|
5006
|
-
async function pullMemory(controlPlaneUrl, token, memoryDir, sessionId, fetchFn) {
|
|
5007
|
-
const url = `${controlPlaneUrl}/api/v1/agent-config/memory/me`;
|
|
5008
|
-
const response = await fetchFn(url, {
|
|
5009
|
-
method: "GET",
|
|
5010
|
-
headers: {
|
|
5011
|
-
authorization: `Bearer ${token}`
|
|
5012
|
-
}
|
|
5013
|
-
});
|
|
5014
|
-
if (response.status !== 200) {
|
|
5015
|
-
const text = await response.text();
|
|
5016
|
-
throw new Error(`GET /api/v1/agent-config/memory/me returned HTTP ${response.status}: ${text.slice(0, 200)}`);
|
|
5017
|
-
}
|
|
5018
|
-
const data = JSON.parse(await response.text());
|
|
5019
|
-
if (!data.ok || !Array.isArray(data.entries)) {
|
|
5020
|
-
throw new Error("GET /api/v1/agent-config/memory/me response missing ok=true or entries array");
|
|
5021
|
-
}
|
|
5022
|
-
mkdirSync4(memoryDir, { recursive: true });
|
|
5023
|
-
const files = [];
|
|
5024
|
-
for (const entry of data.entries) {
|
|
5025
|
-
const filePath = join7(memoryDir, entry.file_name);
|
|
5026
|
-
writeFileSync3(filePath, entry.content, "utf8");
|
|
5027
|
-
files.push(entry.file_name);
|
|
5028
|
-
}
|
|
5029
|
-
return { pulled: data.entries.length, files };
|
|
5030
|
-
}
|
|
5031
|
-
async function pushMemory(controlPlaneUrl, token, memoryDir, sessionId, fetchFn) {
|
|
5032
|
-
if (!existsSync5(memoryDir)) {
|
|
5033
|
-
return { pushed: 0, created: 0, updated: 0 };
|
|
5034
|
-
}
|
|
5035
|
-
const localFiles = readdirSync4(memoryDir).filter((f) => f.endsWith(".md")).map((f) => ({
|
|
5036
|
-
file_name: f,
|
|
5037
|
-
content: readFileSync7(join7(memoryDir, f), "utf8"),
|
|
5038
|
-
entry_type: f === "MEMORY.md" ? "index" : "topic"
|
|
5039
|
-
}));
|
|
5040
|
-
if (localFiles.length === 0) {
|
|
5041
|
-
return { pushed: 0, created: 0, updated: 0 };
|
|
5042
|
-
}
|
|
5043
|
-
const getUrl = `${controlPlaneUrl}/api/v1/agent-config/memory/me`;
|
|
5044
|
-
const getResponse = await fetchFn(getUrl, {
|
|
5045
|
-
method: "GET",
|
|
5046
|
-
headers: {
|
|
5047
|
-
authorization: `Bearer ${token}`
|
|
5048
|
-
}
|
|
5049
|
-
});
|
|
5050
|
-
const existingMap = /* @__PURE__ */ new Map();
|
|
5051
|
-
if (getResponse.status === 200) {
|
|
5052
|
-
const getData = JSON.parse(await getResponse.text());
|
|
5053
|
-
if (getData.ok && Array.isArray(getData.entries)) {
|
|
5054
|
-
for (const entry of getData.entries) {
|
|
5055
|
-
existingMap.set(entry.file_name, entry.memory_id);
|
|
5056
|
-
}
|
|
5057
|
-
}
|
|
5058
|
-
}
|
|
5059
|
-
let created = 0;
|
|
5060
|
-
let updated = 0;
|
|
5061
|
-
for (const localFile of localFiles) {
|
|
5062
|
-
const memoryId = existingMap.get(localFile.file_name);
|
|
5063
|
-
if (memoryId) {
|
|
5064
|
-
const updateUrl = `${controlPlaneUrl}/api/v1/agent-config/memory/${memoryId}`;
|
|
5065
|
-
const updateBody = {
|
|
5066
|
-
content: localFile.content,
|
|
5067
|
-
session_id: sessionId
|
|
5068
|
-
};
|
|
5069
|
-
const updateResponse = await fetchFn(updateUrl, {
|
|
5070
|
-
method: "PUT",
|
|
5071
|
-
headers: {
|
|
5072
|
-
authorization: `Bearer ${token}`,
|
|
5073
|
-
"content-type": "application/json"
|
|
5074
|
-
},
|
|
5075
|
-
body: JSON.stringify(updateBody)
|
|
5076
|
-
});
|
|
5077
|
-
if (updateResponse.status !== 200) {
|
|
5078
|
-
const text = await updateResponse.text();
|
|
5079
|
-
throw new Error(
|
|
5080
|
-
`PUT /api/v1/agent-config/memory/${memoryId} returned HTTP ${updateResponse.status}: ${text.slice(0, 200)}`
|
|
5081
|
-
);
|
|
5082
|
-
}
|
|
5083
|
-
const updateData = JSON.parse(await updateResponse.text());
|
|
5084
|
-
if (!updateData.ok) {
|
|
5085
|
-
throw new Error(`PUT /api/v1/agent-config/memory/${memoryId} returned ok=false`);
|
|
5086
|
-
}
|
|
5087
|
-
updated++;
|
|
5088
|
-
} else {
|
|
5089
|
-
const createUrl = `${controlPlaneUrl}/api/v1/agent-config/memory/me`;
|
|
5090
|
-
const createBody = {
|
|
5091
|
-
entry_type: localFile.entry_type,
|
|
5092
|
-
file_name: localFile.file_name,
|
|
5093
|
-
content: localFile.content,
|
|
5094
|
-
session_id: sessionId
|
|
5095
|
-
};
|
|
5096
|
-
const createResponse = await fetchFn(createUrl, {
|
|
5097
|
-
method: "POST",
|
|
5098
|
-
headers: {
|
|
5099
|
-
authorization: `Bearer ${token}`,
|
|
5100
|
-
"content-type": "application/json"
|
|
5101
|
-
},
|
|
5102
|
-
body: JSON.stringify(createBody)
|
|
5103
|
-
});
|
|
5104
|
-
if (createResponse.status !== 200 && createResponse.status !== 201) {
|
|
5105
|
-
const text = await createResponse.text();
|
|
5106
|
-
throw new Error(
|
|
5107
|
-
`POST /api/v1/agent-config/memory/me returned HTTP ${createResponse.status}: ${text.slice(0, 200)}`
|
|
5108
|
-
);
|
|
5109
|
-
}
|
|
5110
|
-
const createData = JSON.parse(await createResponse.text());
|
|
5111
|
-
if (!createData.ok) {
|
|
5112
|
-
throw new Error("POST /api/v1/agent-config/memory/me returned ok=false");
|
|
5113
|
-
}
|
|
5114
|
-
created++;
|
|
5115
|
-
}
|
|
5116
|
-
}
|
|
5117
|
-
return { pushed: localFiles.length, created, updated };
|
|
5118
|
-
}
|
|
5119
|
-
async function handleSyncConfig(deps, rawInput, _signal, fetchFn = globalThis.fetch) {
|
|
5120
|
-
if (!isToolInput22(rawInput)) {
|
|
5121
|
-
throw invalidParams(
|
|
5122
|
-
TOOL_NAME22,
|
|
5123
|
-
'invalid input. Required: { action: "pull" | "push" }. Optional: { cwd: "<path>" }.'
|
|
5124
|
-
);
|
|
5125
|
-
}
|
|
5126
|
-
const controlPlaneUrl = process.env["VO_CONTROL_PLANE_URL"];
|
|
5541
|
+
async function getCloudAuth(fetchFn) {
|
|
5542
|
+
const controlPlaneUrl = process.env["VO_CONTROL_PLANE_URL"]?.replace(/\/+$/, "");
|
|
5127
5543
|
if (!controlPlaneUrl) {
|
|
5128
|
-
return
|
|
5129
|
-
tool: TOOL_NAME22,
|
|
5130
|
-
schema_version: 1,
|
|
5131
|
-
payload: {
|
|
5132
|
-
synced: false,
|
|
5133
|
-
reason: "VO_CONTROL_PLANE_URL not set \u2014 cloud mode disabled"
|
|
5134
|
-
}
|
|
5135
|
-
});
|
|
5544
|
+
return { ok: false, reason: "VO_CONTROL_PLANE_URL not set \u2014 cloud mode disabled" };
|
|
5136
5545
|
}
|
|
5137
5546
|
const { createAuthTokenSourceFromEnv: createAuthTokenSourceFromEnv2 } = await Promise.resolve().then(() => (init_auth_token_source(), auth_token_source_exports));
|
|
5138
5547
|
const { readStoredCredential: readStoredCredential2 } = await Promise.resolve().then(() => (init_credential_store(), credential_store_exports));
|
|
5139
5548
|
const tokenSource = createAuthTokenSourceFromEnv2(process.env, fetchFn, () => readStoredCredential2(process.env));
|
|
5140
|
-
if (!tokenSource) {
|
|
5141
|
-
return jsonContent({
|
|
5142
|
-
tool: TOOL_NAME22,
|
|
5143
|
-
schema_version: 1,
|
|
5144
|
-
payload: {
|
|
5145
|
-
synced: false,
|
|
5146
|
-
reason: "No auth configured. Run `vo-mcp login` to authenticate as an operator."
|
|
5147
|
-
}
|
|
5148
|
-
});
|
|
5149
|
-
}
|
|
5549
|
+
if (!tokenSource) return { ok: false, reason: "No auth configured. Run `vo-mcp login`." };
|
|
5150
5550
|
const token = await tokenSource.getToken();
|
|
5151
|
-
if (!token) {
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5551
|
+
if (!token) return { ok: false, reason: "Failed to obtain auth token. Run `vo-mcp login` again." };
|
|
5552
|
+
return { ok: true, controlPlaneUrl, token };
|
|
5553
|
+
}
|
|
5554
|
+
async function callPrivateKnowledge(path3, body, fetchFn) {
|
|
5555
|
+
const auth = await getCloudAuth(fetchFn);
|
|
5556
|
+
if (!auth.ok) return { ok: false, reason: auth.reason };
|
|
5557
|
+
const response = await fetchFn(`${auth.controlPlaneUrl}${path3}`, {
|
|
5558
|
+
method: "POST",
|
|
5559
|
+
headers: {
|
|
5560
|
+
authorization: `Bearer ${auth.token}`,
|
|
5561
|
+
"content-type": "application/json"
|
|
5562
|
+
},
|
|
5563
|
+
body: JSON.stringify(body)
|
|
5564
|
+
});
|
|
5565
|
+
const text = await response.text();
|
|
5566
|
+
const parsed = text ? JSON.parse(text) : null;
|
|
5567
|
+
if (response.status < 200 || response.status >= 300) {
|
|
5568
|
+
return { ok: false, status: response.status, response: parsed ?? text };
|
|
5160
5569
|
}
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
}
|
|
5182
|
-
});
|
|
5183
|
-
} else {
|
|
5184
|
-
const result = await pushMemory(
|
|
5185
|
-
controlPlaneUrl.replace(/\/+$/, ""),
|
|
5186
|
-
token,
|
|
5187
|
-
memoryDir,
|
|
5188
|
-
deps.sessionId,
|
|
5189
|
-
fetchFn
|
|
5190
|
-
);
|
|
5191
|
-
return jsonContent({
|
|
5192
|
-
tool: TOOL_NAME22,
|
|
5193
|
-
schema_version: 1,
|
|
5194
|
-
payload: {
|
|
5195
|
-
synced: true,
|
|
5196
|
-
action: "push",
|
|
5197
|
-
pushed: result.pushed,
|
|
5198
|
-
created: result.created,
|
|
5199
|
-
updated: result.updated,
|
|
5200
|
-
memory_dir: memoryDir
|
|
5201
|
-
}
|
|
5202
|
-
});
|
|
5203
|
-
}
|
|
5204
|
-
} catch (err) {
|
|
5205
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
5206
|
-
return jsonContent({
|
|
5207
|
-
tool: TOOL_NAME22,
|
|
5208
|
-
schema_version: 1,
|
|
5209
|
-
payload: {
|
|
5210
|
-
synced: false,
|
|
5211
|
-
reason: `Sync failed: ${message}`
|
|
5212
|
-
}
|
|
5213
|
-
});
|
|
5570
|
+
return parsed;
|
|
5571
|
+
}
|
|
5572
|
+
async function handlePrivateKnowledgeUpsert(_deps, rawInput, _signal, fetchFn = globalThis.fetch) {
|
|
5573
|
+
if (!isUpsertInput(rawInput)) {
|
|
5574
|
+
throw invalidParams(UPSERT_TOOL_NAME, "expected { knowledge_class, source_path, title, content }.");
|
|
5575
|
+
}
|
|
5576
|
+
const payload = await callPrivateKnowledge("/api/v1/knowledge/private", rawInput, fetchFn);
|
|
5577
|
+
const envelope = {
|
|
5578
|
+
tool: UPSERT_TOOL_NAME,
|
|
5579
|
+
schema_version: 1,
|
|
5580
|
+
payload
|
|
5581
|
+
};
|
|
5582
|
+
if (rawInput.content.length > PRECISION_CHAR_BUDGET) {
|
|
5583
|
+
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}.`;
|
|
5584
|
+
}
|
|
5585
|
+
return jsonContent(envelope);
|
|
5586
|
+
}
|
|
5587
|
+
async function handlePrivateKnowledgeContext(_deps, rawInput, _signal, fetchFn = globalThis.fetch) {
|
|
5588
|
+
if (!isContextInput(rawInput)) {
|
|
5589
|
+
throw invalidParams(CONTEXT_TOOL_NAME, "expected { query, optional limit, optional knowledge_class }.");
|
|
5214
5590
|
}
|
|
5591
|
+
const payload = await callPrivateKnowledge("/api/v1/knowledge/private/context", rawInput, fetchFn);
|
|
5592
|
+
return jsonContent({ tool: CONTEXT_TOOL_NAME, schema_version: 1, payload });
|
|
5215
5593
|
}
|
|
5216
5594
|
|
|
5217
5595
|
// src/server.ts
|
|
@@ -5392,6 +5770,22 @@ function buildToolRegistry() {
|
|
|
5392
5770
|
inputSchema: inputSchema22
|
|
5393
5771
|
},
|
|
5394
5772
|
handler: handleSyncConfig
|
|
5773
|
+
},
|
|
5774
|
+
[UPSERT_TOOL_NAME]: {
|
|
5775
|
+
definition: {
|
|
5776
|
+
name: UPSERT_TOOL_NAME,
|
|
5777
|
+
description: upsertDescription,
|
|
5778
|
+
inputSchema: upsertInputSchema
|
|
5779
|
+
},
|
|
5780
|
+
handler: handlePrivateKnowledgeUpsert
|
|
5781
|
+
},
|
|
5782
|
+
[CONTEXT_TOOL_NAME]: {
|
|
5783
|
+
definition: {
|
|
5784
|
+
name: CONTEXT_TOOL_NAME,
|
|
5785
|
+
description: contextDescription,
|
|
5786
|
+
inputSchema: contextInputSchema
|
|
5787
|
+
},
|
|
5788
|
+
handler: handlePrivateKnowledgeContext
|
|
5395
5789
|
}
|
|
5396
5790
|
};
|
|
5397
5791
|
}
|
|
@@ -5569,6 +5963,9 @@ function createSqliteCache(options) {
|
|
|
5569
5963
|
};
|
|
5570
5964
|
}
|
|
5571
5965
|
|
|
5966
|
+
// src/cli.ts
|
|
5967
|
+
init_events_writer();
|
|
5968
|
+
|
|
5572
5969
|
// src/ratchets/stub-client.ts
|
|
5573
5970
|
var HOLLOW_PATTERNS = [
|
|
5574
5971
|
{
|
|
@@ -5656,6 +6053,8 @@ function buildSummary2(args) {
|
|
|
5656
6053
|
}
|
|
5657
6054
|
|
|
5658
6055
|
// src/consensus/engine-client.ts
|
|
6056
|
+
init_events_writer();
|
|
6057
|
+
init_common();
|
|
5659
6058
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
5660
6059
|
|
|
5661
6060
|
// src/consensus/null-client.ts
|
|
@@ -5678,6 +6077,64 @@ function createNullConsensusEngineClient(reason = NULL_CLIENT_DEFAULT_REASON) {
|
|
|
5678
6077
|
};
|
|
5679
6078
|
}
|
|
5680
6079
|
|
|
6080
|
+
// src/consensus/meta-model-caller.ts
|
|
6081
|
+
var META_MODEL_API_BASE_URL = "https://api.meta.ai/v1";
|
|
6082
|
+
var META_CONSENSUS_MODEL = "muse-spark-1.1";
|
|
6083
|
+
var META_MODEL_API_KEY_ENV = "MODEL_API_KEY";
|
|
6084
|
+
var META_MODEL_API_KEY_ALIAS = "META_API";
|
|
6085
|
+
function resolveMetaKey(env) {
|
|
6086
|
+
return String(env[META_MODEL_API_KEY_ENV] || env[META_MODEL_API_KEY_ALIAS] || "").trim();
|
|
6087
|
+
}
|
|
6088
|
+
function positiveMaxTokens(value) {
|
|
6089
|
+
const parsed = Math.floor(Number(value));
|
|
6090
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 2048;
|
|
6091
|
+
}
|
|
6092
|
+
function createMetaModelCaller(options = {}) {
|
|
6093
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
6094
|
+
const envSource = options.envSource ?? process.env;
|
|
6095
|
+
const reasoningEffort = options.reasoningEffort ?? "high";
|
|
6096
|
+
return async function callMetaWithMetrics2(prompt, systemPrompt, model, maxTokens, _privacyOptions, signal) {
|
|
6097
|
+
const key = resolveMetaKey(envSource);
|
|
6098
|
+
if (!key) throw new Error(`Missing ${META_MODEL_API_KEY_ENV} for Meta Model API`);
|
|
6099
|
+
const messages = [
|
|
6100
|
+
...systemPrompt ? [{ role: "system", content: systemPrompt }] : [],
|
|
6101
|
+
{ role: "user", content: prompt }
|
|
6102
|
+
];
|
|
6103
|
+
const response = await fetchImpl(`${META_MODEL_API_BASE_URL}/chat/completions`, {
|
|
6104
|
+
method: "POST",
|
|
6105
|
+
headers: {
|
|
6106
|
+
Authorization: `Bearer ${key}`,
|
|
6107
|
+
"Content-Type": "application/json"
|
|
6108
|
+
},
|
|
6109
|
+
body: JSON.stringify({
|
|
6110
|
+
model: model || META_CONSENSUS_MODEL,
|
|
6111
|
+
messages,
|
|
6112
|
+
max_tokens: positiveMaxTokens(maxTokens),
|
|
6113
|
+
reasoning_effort: reasoningEffort
|
|
6114
|
+
}),
|
|
6115
|
+
signal
|
|
6116
|
+
});
|
|
6117
|
+
const payload = await response.json();
|
|
6118
|
+
if (!response.ok) {
|
|
6119
|
+
const message = String(payload.error?.message || response.statusText || "request failed").slice(0, 500);
|
|
6120
|
+
throw Object.assign(new Error(`Meta Model API ${response.status}: ${message}`), { status: response.status });
|
|
6121
|
+
}
|
|
6122
|
+
const content = payload.choices?.[0]?.message?.content;
|
|
6123
|
+
if (typeof content !== "string" || !content.trim()) {
|
|
6124
|
+
throw new Error(`Meta Model API returned no text (finish=${payload.choices?.[0]?.finish_reason || "unknown"})`);
|
|
6125
|
+
}
|
|
6126
|
+
const inputTokens = Number(payload.usage?.prompt_tokens || 0);
|
|
6127
|
+
const outputTokens = Number(payload.usage?.completion_tokens || 0);
|
|
6128
|
+
return {
|
|
6129
|
+
content,
|
|
6130
|
+
inputTokens,
|
|
6131
|
+
outputTokens,
|
|
6132
|
+
totalTokens: Number(payload.usage?.total_tokens || inputTokens + outputTokens)
|
|
6133
|
+
};
|
|
6134
|
+
};
|
|
6135
|
+
}
|
|
6136
|
+
var callMetaWithMetrics = createMetaModelCaller();
|
|
6137
|
+
|
|
5681
6138
|
// src/consensus/engine-options.ts
|
|
5682
6139
|
var AGREEMENT_GATE_ENV_VAR = "VO_CONSENSUS_AGREEMENT_GATE";
|
|
5683
6140
|
function isTruthyFlag(raw) {
|
|
@@ -5724,6 +6181,25 @@ function mapFanOutDiagnostics(fd) {
|
|
|
5724
6181
|
refused: fd.refused
|
|
5725
6182
|
};
|
|
5726
6183
|
}
|
|
6184
|
+
var SHADOW_SYNTHESIS_ENV_VAR = "VO_CONSENSUS_SHADOW";
|
|
6185
|
+
function shadowEnabled(env) {
|
|
6186
|
+
const raw = (env ?? {})[SHADOW_SYNTHESIS_ENV_VAR];
|
|
6187
|
+
if (raw === void 0) return true;
|
|
6188
|
+
const norm = raw.trim().toLowerCase();
|
|
6189
|
+
return !(norm === "0" || norm === "false" || norm === "no" || norm === "off" || norm === "");
|
|
6190
|
+
}
|
|
6191
|
+
function mapShadowSynthesis(s) {
|
|
6192
|
+
if (s === void 0) return void 0;
|
|
6193
|
+
return {
|
|
6194
|
+
incumbent: { verdict: s.incumbent.verdict, confidence: s.incumbent.confidence, synthesizer: s.incumbent.synthesizer },
|
|
6195
|
+
adaptive: {
|
|
6196
|
+
verdict: s.adaptive.verdict,
|
|
6197
|
+
confidence: s.adaptive.confidence,
|
|
6198
|
+
...s.adaptive.calibrated_confidence !== void 0 ? { calibrated_confidence: s.adaptive.calibrated_confidence } : {}
|
|
6199
|
+
},
|
|
6200
|
+
agree: s.agree
|
|
6201
|
+
};
|
|
6202
|
+
}
|
|
5727
6203
|
function mapCitationGrade(cg) {
|
|
5728
6204
|
if (cg === void 0) return void 0;
|
|
5729
6205
|
return {
|
|
@@ -5864,7 +6340,12 @@ function createEngineConsensusClient(options) {
|
|
|
5864
6340
|
const engineOptions = {
|
|
5865
6341
|
panel,
|
|
5866
6342
|
...options.per_model_timeout_ms !== void 0 ? { per_model_timeout_ms: options.per_model_timeout_ms } : {},
|
|
5867
|
-
...agreementGate !== void 0 ? { agreement_gate: agreementGate } : {}
|
|
6343
|
+
...agreementGate !== void 0 ? { agreement_gate: agreementGate } : {},
|
|
6344
|
+
// Stage A7-shadow: run the adaptive verdict alongside the live one for grading.
|
|
6345
|
+
// Cheap (pure log-odds over already-fetched verdicts; no extra model calls),
|
|
6346
|
+
// PII-free, and never alters the live verdict. ON by default; kill with
|
|
6347
|
+
// VO_CONSENSUS_SHADOW=0. Cold-start has no skill registry → neutral priors.
|
|
6348
|
+
shadow_synthesis: { enabled: shadowEnabled(options.env) }
|
|
5868
6349
|
};
|
|
5869
6350
|
const sources = request.source_urls;
|
|
5870
6351
|
const useSourceGrounded = sources !== void 0 && sources.length > 0 && typeof engine.runSourceGroundedConsensus === "function";
|
|
@@ -5920,6 +6401,8 @@ function createEngineConsensusClient(options) {
|
|
|
5920
6401
|
...sourceExtras?.escalation_reason !== void 0 ? { escalation_reason: sourceExtras.escalation_reason } : response.escalation_reason !== void 0 ? { escalation_reason: response.escalation_reason } : {},
|
|
5921
6402
|
// Feature 1 (agreement-gate) — fan-out diagnostics (additive telemetry).
|
|
5922
6403
|
...mapFanOutDiagnostics(response.fan_out_diagnostics) !== void 0 ? { fan_out_diagnostics: mapFanOutDiagnostics(response.fan_out_diagnostics) } : {},
|
|
6404
|
+
// Stage A7-shadow — adaptive-vs-incumbent comparison (PII-free; present iff shadow on).
|
|
6405
|
+
...mapShadowSynthesis(response.shadow_synthesis) !== void 0 ? { shadow_synthesis: mapShadowSynthesis(response.shadow_synthesis) } : {},
|
|
5923
6406
|
// Source-grounded additive outputs (Tier-4 features).
|
|
5924
6407
|
...useSourceGrounded ? { source_grounded: true } : {},
|
|
5925
6408
|
...sourceExtras?.citation_grade !== void 0 ? { citation_grade: sourceExtras.citation_grade } : {},
|
|
@@ -5946,7 +6429,8 @@ var DEFAULT_MODELS = {
|
|
|
5946
6429
|
// callGeminiWithMetrics; 2.5-pro REJECTS budget 0 ("only works in thinking mode").
|
|
5947
6430
|
// Flash is also ~10x cheaper. 2026-06-02.
|
|
5948
6431
|
google: "gemini-2.5-flash",
|
|
5949
|
-
deepseek: "deepseek-chat"
|
|
6432
|
+
deepseek: "deepseek-chat",
|
|
6433
|
+
meta: META_CONSENSUS_MODEL
|
|
5950
6434
|
};
|
|
5951
6435
|
function probeProviders(env = process.env) {
|
|
5952
6436
|
const out = [];
|
|
@@ -5954,6 +6438,7 @@ function probeProviders(env = process.env) {
|
|
|
5954
6438
|
if ((env["OPENAI_API_KEY"] ?? "").trim().length > 0) out.push("openai");
|
|
5955
6439
|
if ((env["GOOGLE_API_KEY"] ?? "").trim().length > 0) out.push("google");
|
|
5956
6440
|
if ((env["DEEPSEEK_API_KEY"] ?? "").trim().length > 0) out.push("deepseek");
|
|
6441
|
+
if ((env[META_MODEL_API_KEY_ENV] ?? "").trim().length > 0 || (env[META_MODEL_API_KEY_ALIAS] ?? "").trim().length > 0) out.push("meta");
|
|
5957
6442
|
return out;
|
|
5958
6443
|
}
|
|
5959
6444
|
async function loadFactoryAndCallers(injectedEngine, injectedShared) {
|
|
@@ -5999,21 +6484,24 @@ async function tryCreateEngineConsensusClientFromEnvAsync(options = {}) {
|
|
|
5999
6484
|
anthropic: loaded.shared.callAnthropicWithMetrics,
|
|
6000
6485
|
openai: loaded.shared.callOpenAIWithMetrics,
|
|
6001
6486
|
google: loaded.shared.callGeminiWithMetrics,
|
|
6002
|
-
deepseek: loaded.shared.callDeepSeekWithMetrics
|
|
6487
|
+
deepseek: loaded.shared.callDeepSeekWithMetrics,
|
|
6488
|
+
meta: options.metaCaller ?? callMetaWithMetrics
|
|
6003
6489
|
};
|
|
6004
6490
|
const modelByProvider = {
|
|
6005
6491
|
anthropic: options.models?.anthropic ?? DEFAULT_MODELS.anthropic,
|
|
6006
6492
|
openai: options.models?.openai ?? DEFAULT_MODELS.openai,
|
|
6007
6493
|
google: options.models?.google ?? DEFAULT_MODELS.google,
|
|
6008
|
-
deepseek: options.models?.deepseek ?? DEFAULT_MODELS.deepseek
|
|
6494
|
+
deepseek: options.models?.deepseek ?? DEFAULT_MODELS.deepseek,
|
|
6495
|
+
meta: options.models?.meta ?? DEFAULT_MODELS.meta
|
|
6009
6496
|
};
|
|
6497
|
+
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;
|
|
6010
6498
|
const panel = [];
|
|
6011
6499
|
for (const p of providers) {
|
|
6012
6500
|
try {
|
|
6013
6501
|
const adapter = loaded.engine.createAdapter(p, {
|
|
6014
6502
|
model: modelByProvider[p],
|
|
6015
6503
|
caller: callerByProvider[p],
|
|
6016
|
-
envSource:
|
|
6504
|
+
envSource: adapterEnv
|
|
6017
6505
|
});
|
|
6018
6506
|
panel.push(adapter);
|
|
6019
6507
|
} catch {
|
|
@@ -6235,7 +6723,7 @@ async function runLogin(opts = {}) {
|
|
|
6235
6723
|
const nowIso = opts.nowIso ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
6236
6724
|
const openBrowser = opts.openBrowser ?? defaultOpenBrowser;
|
|
6237
6725
|
const state = randomBytes(32).toString("base64url");
|
|
6238
|
-
return new Promise((
|
|
6726
|
+
return new Promise((resolve2, reject) => {
|
|
6239
6727
|
let settled = false;
|
|
6240
6728
|
const finish = (err, result) => {
|
|
6241
6729
|
if (settled) return;
|
|
@@ -6243,7 +6731,7 @@ async function runLogin(opts = {}) {
|
|
|
6243
6731
|
clearTimeout(timer);
|
|
6244
6732
|
server.close();
|
|
6245
6733
|
if (err) reject(err);
|
|
6246
|
-
else
|
|
6734
|
+
else resolve2(result);
|
|
6247
6735
|
};
|
|
6248
6736
|
const server = createServer2((req, res) => {
|
|
6249
6737
|
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
@@ -6354,6 +6842,7 @@ async function exchangeForVoCredential(opts) {
|
|
|
6354
6842
|
}
|
|
6355
6843
|
|
|
6356
6844
|
// src/cli.ts
|
|
6845
|
+
init_common();
|
|
6357
6846
|
function defaultCacheDbPath() {
|
|
6358
6847
|
const env = process.env["VO_MCP_DB_PATH"];
|
|
6359
6848
|
if (env && env.length > 0) return env;
|
|
@@ -6486,6 +6975,31 @@ if (process.argv[2] === "login") {
|
|
|
6486
6975
|
console.error("[vo-mcp] login failed:", err instanceof Error ? err.message : String(err));
|
|
6487
6976
|
process.exit(1);
|
|
6488
6977
|
});
|
|
6978
|
+
} else if (process.argv[2] === "sync") {
|
|
6979
|
+
const action = process.argv[3];
|
|
6980
|
+
if (action !== "push" && action !== "pull") {
|
|
6981
|
+
console.error("[vo-mcp] usage: vo-mcp sync <push|pull> [--cwd <path>]");
|
|
6982
|
+
process.exit(2);
|
|
6983
|
+
}
|
|
6984
|
+
const cwdFlag = process.argv.indexOf("--cwd");
|
|
6985
|
+
const cwd = cwdFlag >= 0 && typeof process.argv[cwdFlag + 1] === "string" ? process.argv[cwdFlag + 1] : process.cwd();
|
|
6986
|
+
const sessionId = randomUUID5();
|
|
6987
|
+
Promise.resolve().then(() => (init_sync_config(), sync_config_exports)).then(async ({ runMemorySync: runMemorySync2, isNoopSyncReason: isNoopSyncReason2 }) => {
|
|
6988
|
+
const r = await runMemorySync2(action, cwd, sessionId);
|
|
6989
|
+
if (r.synced) {
|
|
6990
|
+
console.error(`[vo-mcp] sync ${action} ok: ${JSON.stringify(r)}`);
|
|
6991
|
+
process.exit(0);
|
|
6992
|
+
}
|
|
6993
|
+
if (isNoopSyncReason2(r.reason)) {
|
|
6994
|
+
console.error(`[vo-mcp] sync ${action} skipped: ${r.reason}`);
|
|
6995
|
+
process.exit(0);
|
|
6996
|
+
}
|
|
6997
|
+
console.error(`[vo-mcp] sync ${action} failed: ${r.reason}`);
|
|
6998
|
+
process.exit(1);
|
|
6999
|
+
}).catch((err) => {
|
|
7000
|
+
console.error("[vo-mcp] sync fatal:", err instanceof Error ? err.message : String(err));
|
|
7001
|
+
process.exit(1);
|
|
7002
|
+
});
|
|
6489
7003
|
} else {
|
|
6490
7004
|
main().catch((err) => {
|
|
6491
7005
|
console.error("[vo-mcp] fatal:", err);
|