@algosuite/vo-mcp 0.2.0-beta.2 → 0.2.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +178 -153
- package/bin/vo-mcp +44 -38
- package/dist/autostart-cli.js +104 -14
- package/dist/autostart-cli.js.map +2 -2
- package/dist/ci/check-local-pr-overlap.js +102449 -0
- package/dist/cli.js +1880 -786
- package/dist/cli.js.map +4 -4
- package/dist/index.js +869 -129
- package/dist/index.js.map +4 -4
- package/dist/install-cli.js +365 -327
- package/dist/install-cli.js.map +4 -4
- package/dist/login-cli.js +4 -4
- package/dist/login-cli.js.map +2 -2
- package/dist/pair-cli.js +1 -1
- package/dist/pair-cli.js.map +2 -2
- package/dist/runner-cli.js +9605 -1946
- package/dist/runner-cli.js.map +4 -4
- package/dist/runner-supervisor.js +2333 -0
- package/dist/runner-supervisor.js.map +7 -0
- package/dist/set-key-cli.js +44 -4
- package/dist/set-key-cli.js.map +2 -2
- package/dist/supervisor-credential-helper.js +233 -0
- package/dist/supervisor-credential-helper.js.map +7 -0
- package/dist/update-cli.js +125 -0
- package/dist/update-cli.js.map +7 -0
- package/package.json +4 -2
package/dist/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;
|
|
@@ -1109,19 +967,38 @@ function createFileEventsWriter(opts = {}) {
|
|
|
1109
967
|
}
|
|
1110
968
|
};
|
|
1111
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
|
+
});
|
|
1112
979
|
|
|
1113
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";
|
|
1114
986
|
function readVoMcpVersion() {
|
|
1115
987
|
try {
|
|
1116
988
|
const here = dirname3(fileURLToPath2(import.meta.url));
|
|
1117
|
-
const
|
|
1118
|
-
|
|
1119
|
-
|
|
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";
|
|
1120
998
|
} catch {
|
|
1121
999
|
return "0.0.0-unknown";
|
|
1122
1000
|
}
|
|
1123
1001
|
}
|
|
1124
|
-
var VO_MCP_VERSION = readVoMcpVersion();
|
|
1125
1002
|
function bytesOf(s) {
|
|
1126
1003
|
return Buffer.byteLength(s, "utf8");
|
|
1127
1004
|
}
|
|
@@ -1175,40 +1052,701 @@ function buildBaseEvent(args) {
|
|
|
1175
1052
|
cache_hit: false
|
|
1176
1053
|
};
|
|
1177
1054
|
}
|
|
1178
|
-
function jsonContent(value) {
|
|
1179
|
-
return {
|
|
1180
|
-
content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
|
|
1181
|
-
};
|
|
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
|
+
readStoredCredentialKeychainOnly: () => readStoredCredentialKeychainOnly,
|
|
1265
|
+
writeStoredCredential: () => writeStoredCredential
|
|
1266
|
+
});
|
|
1267
|
+
import { homedir as homedir3 } from "node:os";
|
|
1268
|
+
import { join as join5, dirname as dirname4 } from "node:path";
|
|
1269
|
+
import {
|
|
1270
|
+
existsSync as existsSync3,
|
|
1271
|
+
mkdirSync as mkdirSync2,
|
|
1272
|
+
readFileSync as readFileSync5,
|
|
1273
|
+
writeFileSync as writeFileSync2,
|
|
1274
|
+
chmodSync as chmodSync2,
|
|
1275
|
+
rmSync
|
|
1276
|
+
} from "node:fs";
|
|
1277
|
+
function credentialPath(env = process.env) {
|
|
1278
|
+
const override = env["VO_MCP_CREDENTIALS_PATH"]?.trim();
|
|
1279
|
+
if (override) return override;
|
|
1280
|
+
return join5(homedir3(), ".config", "vo-mcp", "credentials.json");
|
|
1281
|
+
}
|
|
1282
|
+
function keychainEnabled(env, keychain) {
|
|
1283
|
+
const disabled = (env["VO_MCP_DISABLE_KEYCHAIN"] ?? "").trim().toLowerCase();
|
|
1284
|
+
if (disabled === "1" || disabled === "true" || disabled === "yes") return false;
|
|
1285
|
+
return keychain.available();
|
|
1286
|
+
}
|
|
1287
|
+
function deserialize(raw) {
|
|
1288
|
+
try {
|
|
1289
|
+
const parsed = JSON.parse(raw);
|
|
1290
|
+
const refresh = typeof parsed.refresh_token === "string" ? parsed.refresh_token.trim() : "";
|
|
1291
|
+
const apiKey = typeof parsed.api_key === "string" ? parsed.api_key.trim() : "";
|
|
1292
|
+
const voCred = typeof parsed.vo_credential === "string" ? parsed.vo_credential.trim() : "";
|
|
1293
|
+
if (!voCred && (!refresh || !apiKey)) return null;
|
|
1294
|
+
return {
|
|
1295
|
+
...refresh ? { refresh_token: refresh } : {},
|
|
1296
|
+
...apiKey ? { api_key: apiKey } : {},
|
|
1297
|
+
...voCred ? { vo_credential: voCred } : {},
|
|
1298
|
+
...typeof parsed.vo_credential_expires_at === "string" ? { vo_credential_expires_at: parsed.vo_credential_expires_at } : {},
|
|
1299
|
+
...typeof parsed.email === "string" ? { email: parsed.email } : {},
|
|
1300
|
+
...typeof parsed.stored_at === "string" ? { stored_at: parsed.stored_at } : {}
|
|
1301
|
+
};
|
|
1302
|
+
} catch {
|
|
1303
|
+
return null;
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
function readFromFile(env) {
|
|
1307
|
+
try {
|
|
1308
|
+
const p = credentialPath(env);
|
|
1309
|
+
if (!existsSync3(p)) return null;
|
|
1310
|
+
return deserialize(readFileSync5(p, "utf8"));
|
|
1311
|
+
} catch {
|
|
1312
|
+
return null;
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
function readStoredCredential(env = process.env, keychain = realKeychain) {
|
|
1316
|
+
if (keychainEnabled(env, keychain)) {
|
|
1317
|
+
const raw = keychain.get();
|
|
1318
|
+
const fromKeychain = raw ? deserialize(raw) : null;
|
|
1319
|
+
if (fromKeychain) return fromKeychain;
|
|
1320
|
+
}
|
|
1321
|
+
return readFromFile(env);
|
|
1322
|
+
}
|
|
1323
|
+
function readStoredCredentialKeychainOnly(env = process.env, keychain = realKeychain) {
|
|
1324
|
+
if (!keychainEnabled(env, keychain)) return null;
|
|
1325
|
+
const raw = keychain.get();
|
|
1326
|
+
return raw ? deserialize(raw) : null;
|
|
1327
|
+
}
|
|
1328
|
+
function deleteFile(env) {
|
|
1329
|
+
try {
|
|
1330
|
+
rmSync(credentialPath(env), { force: true });
|
|
1331
|
+
} catch {
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
function writeToFile(payload, env) {
|
|
1335
|
+
const p = credentialPath(env);
|
|
1336
|
+
mkdirSync2(dirname4(p), { recursive: true });
|
|
1337
|
+
writeFileSync2(p, `${JSON.stringify(payload, null, 2)}
|
|
1338
|
+
`, { mode: 384 });
|
|
1339
|
+
try {
|
|
1340
|
+
chmodSync2(p, 384);
|
|
1341
|
+
} catch {
|
|
1342
|
+
}
|
|
1343
|
+
return p;
|
|
1344
|
+
}
|
|
1345
|
+
function writeStoredCredential(cred, storedAt, env = process.env, keychain = realKeychain) {
|
|
1346
|
+
const payload = {
|
|
1347
|
+
...cred.refresh_token ? { refresh_token: cred.refresh_token } : {},
|
|
1348
|
+
...cred.api_key ? { api_key: cred.api_key } : {},
|
|
1349
|
+
...cred.vo_credential ? { vo_credential: cred.vo_credential } : {},
|
|
1350
|
+
...cred.vo_credential_expires_at ? { vo_credential_expires_at: cred.vo_credential_expires_at } : {},
|
|
1351
|
+
...cred.email ? { email: cred.email } : {},
|
|
1352
|
+
stored_at: cred.stored_at ?? storedAt
|
|
1353
|
+
};
|
|
1354
|
+
if (keychainEnabled(env, keychain) && keychain.set(JSON.stringify(payload))) {
|
|
1355
|
+
deleteFile(env);
|
|
1356
|
+
return KEYCHAIN_LOCATION;
|
|
1357
|
+
}
|
|
1358
|
+
const p = writeToFile(payload, env);
|
|
1359
|
+
if (keychainEnabled(env, keychain)) keychain.delete();
|
|
1360
|
+
return p;
|
|
1361
|
+
}
|
|
1362
|
+
var realKeychain, KEYCHAIN_LOCATION;
|
|
1363
|
+
var init_credential_store = __esm({
|
|
1364
|
+
"src/cloud/credential-store.ts"() {
|
|
1365
|
+
"use strict";
|
|
1366
|
+
init_keychain();
|
|
1367
|
+
realKeychain = {
|
|
1368
|
+
available: keychainAvailable,
|
|
1369
|
+
get: keychainGet,
|
|
1370
|
+
set: keychainSet,
|
|
1371
|
+
delete: keychainDelete
|
|
1372
|
+
};
|
|
1373
|
+
KEYCHAIN_LOCATION = 'OS keychain (service "vo-mcp")';
|
|
1374
|
+
}
|
|
1375
|
+
});
|
|
1376
|
+
|
|
1377
|
+
// src/tools/memory/safe-memory-file.ts
|
|
1378
|
+
import { resolve, sep } from "node:path";
|
|
1379
|
+
function isSafeMemoryFileName(fileName) {
|
|
1380
|
+
return fileName.length <= 200 && fileName.trim() === fileName && !fileName.includes("/") && !fileName.includes("\\") && !fileName.includes(":") && SAFE_MEMORY_FILE_RE.test(fileName);
|
|
1381
|
+
}
|
|
1382
|
+
function resolveMemoryFilePath(memoryDir, fileName) {
|
|
1383
|
+
if (!isSafeMemoryFileName(fileName)) {
|
|
1384
|
+
throw new Error(`unsafe memory file_name: ${fileName.slice(0, 80)}`);
|
|
1385
|
+
}
|
|
1386
|
+
const root = resolve(memoryDir);
|
|
1387
|
+
const filePath = resolve(root, fileName);
|
|
1388
|
+
const rootPrefix = root.endsWith(sep) ? root : `${root}${sep}`;
|
|
1389
|
+
if (filePath !== root && !filePath.startsWith(rootPrefix)) {
|
|
1390
|
+
throw new Error(`memory file path escapes memory directory: ${fileName.slice(0, 80)}`);
|
|
1391
|
+
}
|
|
1392
|
+
return filePath;
|
|
1393
|
+
}
|
|
1394
|
+
var SAFE_MEMORY_FILE_RE;
|
|
1395
|
+
var init_safe_memory_file = __esm({
|
|
1396
|
+
"src/tools/memory/safe-memory-file.ts"() {
|
|
1397
|
+
"use strict";
|
|
1398
|
+
SAFE_MEMORY_FILE_RE = /^[A-Za-z0-9][A-Za-z0-9._ -]*\.md$/i;
|
|
1399
|
+
}
|
|
1400
|
+
});
|
|
1401
|
+
|
|
1402
|
+
// src/tools/memory/memory-knowledge-bridge.ts
|
|
1403
|
+
var memory_knowledge_bridge_exports = {};
|
|
1404
|
+
__export(memory_knowledge_bridge_exports, {
|
|
1405
|
+
extractMemoryTitle: () => extractMemoryTitle,
|
|
1406
|
+
upsertMemoryFilesAsKnowledge: () => upsertMemoryFilesAsKnowledge
|
|
1407
|
+
});
|
|
1408
|
+
import { existsSync as existsSync5, readdirSync as readdirSync4, readFileSync as readFileSync7 } from "node:fs";
|
|
1409
|
+
function extractMemoryTitle(fileName, content) {
|
|
1410
|
+
const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
1411
|
+
if (frontmatter) {
|
|
1412
|
+
const description23 = frontmatter[1].match(/^description:\s*(.+)$/m);
|
|
1413
|
+
if (description23 && description23[1].trim()) return description23[1].trim().slice(0, 200);
|
|
1414
|
+
}
|
|
1415
|
+
const heading = content.match(/^#\s+(.+)$/m);
|
|
1416
|
+
if (heading && heading[1].trim()) return heading[1].trim().slice(0, 200);
|
|
1417
|
+
return fileName;
|
|
1418
|
+
}
|
|
1419
|
+
async function upsertMemoryFilesAsKnowledge(options) {
|
|
1420
|
+
const { controlPlaneUrl, token, memoryDir, fetchFn } = options;
|
|
1421
|
+
let files;
|
|
1422
|
+
try {
|
|
1423
|
+
if (!existsSync5(memoryDir)) {
|
|
1424
|
+
return { attempted: 0, upserted: 0, failed: 0, failures: [] };
|
|
1425
|
+
}
|
|
1426
|
+
files = readdirSync4(memoryDir).filter(
|
|
1427
|
+
(f) => f.endsWith(".md") && f.toUpperCase() !== "MEMORY.MD"
|
|
1428
|
+
);
|
|
1429
|
+
} catch (err) {
|
|
1430
|
+
return {
|
|
1431
|
+
attempted: 0,
|
|
1432
|
+
upserted: 0,
|
|
1433
|
+
failed: 1,
|
|
1434
|
+
failures: [`memory dir scan: ${err instanceof Error ? err.message : String(err)}`]
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
let upserted = 0;
|
|
1438
|
+
const failures = [];
|
|
1439
|
+
for (const fileName of files) {
|
|
1440
|
+
try {
|
|
1441
|
+
const content = readFileSync7(resolveMemoryFilePath(memoryDir, fileName), "utf8");
|
|
1442
|
+
if (content.length > CONTENT_HARD_LIMIT) {
|
|
1443
|
+
failures.push(`${fileName}: ${content.length} chars exceeds the ${CONTENT_HARD_LIMIT} server limit \u2014 split the memory file`);
|
|
1444
|
+
continue;
|
|
1445
|
+
}
|
|
1446
|
+
const title = extractMemoryTitle(fileName, content);
|
|
1447
|
+
const base = {
|
|
1448
|
+
knowledge_class: "memory",
|
|
1449
|
+
source_path: `memory/${fileName}`,
|
|
1450
|
+
title,
|
|
1451
|
+
content
|
|
1452
|
+
};
|
|
1453
|
+
const post = (body) => fetchFn(`${controlPlaneUrl}/api/v1/knowledge/private`, {
|
|
1454
|
+
method: "POST",
|
|
1455
|
+
headers: {
|
|
1456
|
+
authorization: `Bearer ${token}`,
|
|
1457
|
+
"content-type": "application/json"
|
|
1458
|
+
},
|
|
1459
|
+
body: JSON.stringify(body)
|
|
1460
|
+
});
|
|
1461
|
+
let response = await post({
|
|
1462
|
+
...base,
|
|
1463
|
+
provenance: { written_by: "memory-bridge", source_kind: "operator_memory" }
|
|
1464
|
+
});
|
|
1465
|
+
if (response.status === 400) {
|
|
1466
|
+
response = await post(base);
|
|
1467
|
+
}
|
|
1468
|
+
if (response.status >= 200 && response.status < 300) {
|
|
1469
|
+
upserted += 1;
|
|
1470
|
+
} else {
|
|
1471
|
+
const text = await response.text();
|
|
1472
|
+
failures.push(`${fileName}: HTTP ${response.status} ${text.slice(0, 80)}`);
|
|
1473
|
+
}
|
|
1474
|
+
} catch (err) {
|
|
1475
|
+
failures.push(`${fileName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
return {
|
|
1479
|
+
attempted: files.length,
|
|
1480
|
+
upserted,
|
|
1481
|
+
failed: failures.length,
|
|
1482
|
+
failures: failures.slice(0, 5)
|
|
1483
|
+
};
|
|
1484
|
+
}
|
|
1485
|
+
var CONTENT_HARD_LIMIT;
|
|
1486
|
+
var init_memory_knowledge_bridge = __esm({
|
|
1487
|
+
"src/tools/memory/memory-knowledge-bridge.ts"() {
|
|
1488
|
+
"use strict";
|
|
1489
|
+
init_safe_memory_file();
|
|
1490
|
+
CONTENT_HARD_LIMIT = 5e5;
|
|
1491
|
+
}
|
|
1492
|
+
});
|
|
1493
|
+
|
|
1494
|
+
// src/tools/memory/sync-config.ts
|
|
1495
|
+
var sync_config_exports = {};
|
|
1496
|
+
__export(sync_config_exports, {
|
|
1497
|
+
TOOL_NAME: () => TOOL_NAME22,
|
|
1498
|
+
deriveProjectSlug: () => deriveProjectSlug,
|
|
1499
|
+
description: () => description22,
|
|
1500
|
+
getMemoryDir: () => getMemoryDir,
|
|
1501
|
+
handleSyncConfig: () => handleSyncConfig,
|
|
1502
|
+
inputSchema: () => inputSchema22,
|
|
1503
|
+
isNoopSyncReason: () => isNoopSyncReason,
|
|
1504
|
+
runMemorySync: () => runMemorySync
|
|
1505
|
+
});
|
|
1506
|
+
import { homedir as homedir5 } from "node:os";
|
|
1507
|
+
import { join as join7 } from "node:path";
|
|
1508
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync3, readdirSync as readdirSync5 } from "node:fs";
|
|
1509
|
+
function isToolInput22(v) {
|
|
1510
|
+
if (typeof v !== "object" || v === null) return false;
|
|
1511
|
+
const o = v;
|
|
1512
|
+
if (o["action"] !== "pull" && o["action"] !== "push") return false;
|
|
1513
|
+
if (o["cwd"] !== void 0 && typeof o["cwd"] !== "string") return false;
|
|
1514
|
+
return true;
|
|
1515
|
+
}
|
|
1516
|
+
function deriveProjectSlug(cwd) {
|
|
1517
|
+
return cwd.replace(/([^:\\/])[\\/]+$/, "$1").replace(/\\/g, "/").replace(/^([a-zA-Z]):/, (_m, drive) => `${drive.toUpperCase()}:`).replace(/[^a-zA-Z0-9]/g, "-");
|
|
1518
|
+
}
|
|
1519
|
+
function getMemoryDir(cwd) {
|
|
1520
|
+
const slug = deriveProjectSlug(cwd);
|
|
1521
|
+
return join7(homedir5(), ".claude", "projects", slug, "memory");
|
|
1522
|
+
}
|
|
1523
|
+
async function pullMemory(controlPlaneUrl, token, memoryDir, fetchFn) {
|
|
1524
|
+
const url = `${controlPlaneUrl}/api/v1/agent-config/memory/me`;
|
|
1525
|
+
const response = await fetchFn(url, {
|
|
1526
|
+
method: "GET",
|
|
1527
|
+
headers: {
|
|
1528
|
+
authorization: `Bearer ${token}`
|
|
1529
|
+
}
|
|
1530
|
+
});
|
|
1531
|
+
if (response.status !== 200) {
|
|
1532
|
+
const text = await response.text();
|
|
1533
|
+
throw new Error(`GET /api/v1/agent-config/memory/me returned HTTP ${response.status}: ${text.slice(0, 200)}`);
|
|
1534
|
+
}
|
|
1535
|
+
const data = JSON.parse(await response.text());
|
|
1536
|
+
if (!data.ok || !Array.isArray(data.entries)) {
|
|
1537
|
+
throw new Error("GET /api/v1/agent-config/memory/me response missing ok=true or entries array");
|
|
1538
|
+
}
|
|
1539
|
+
const writes = data.entries.map((entry) => ({
|
|
1540
|
+
entry,
|
|
1541
|
+
filePath: resolveMemoryFilePath(memoryDir, entry.file_name)
|
|
1542
|
+
}));
|
|
1543
|
+
mkdirSync4(memoryDir, { recursive: true });
|
|
1544
|
+
const files = [];
|
|
1545
|
+
for (const { entry, filePath } of writes) {
|
|
1546
|
+
writeFileSync3(filePath, entry.content, "utf8");
|
|
1547
|
+
files.push(entry.file_name);
|
|
1548
|
+
}
|
|
1549
|
+
return { pulled: data.entries.length, files };
|
|
1550
|
+
}
|
|
1551
|
+
async function pushMemory(controlPlaneUrl, token, memoryDir, sessionId, fetchFn) {
|
|
1552
|
+
if (!existsSync6(memoryDir)) {
|
|
1553
|
+
return { pushed: 0, created: 0, updated: 0 };
|
|
1554
|
+
}
|
|
1555
|
+
const localFiles = readdirSync5(memoryDir).filter((f) => f.endsWith(".md")).map((f) => ({
|
|
1556
|
+
file_name: f,
|
|
1557
|
+
content: readFileSync8(resolveMemoryFilePath(memoryDir, f), "utf8"),
|
|
1558
|
+
entry_type: f === "MEMORY.md" ? "index" : "topic"
|
|
1559
|
+
}));
|
|
1560
|
+
if (localFiles.length === 0) {
|
|
1561
|
+
return { pushed: 0, created: 0, updated: 0 };
|
|
1562
|
+
}
|
|
1563
|
+
const getUrl = `${controlPlaneUrl}/api/v1/agent-config/memory/me`;
|
|
1564
|
+
const getResponse = await fetchFn(getUrl, {
|
|
1565
|
+
method: "GET",
|
|
1566
|
+
headers: {
|
|
1567
|
+
authorization: `Bearer ${token}`
|
|
1568
|
+
}
|
|
1569
|
+
});
|
|
1570
|
+
const existingMap = /* @__PURE__ */ new Map();
|
|
1571
|
+
if (getResponse.status === 200) {
|
|
1572
|
+
const getData = JSON.parse(await getResponse.text());
|
|
1573
|
+
if (getData.ok && Array.isArray(getData.entries)) {
|
|
1574
|
+
for (const entry of getData.entries) {
|
|
1575
|
+
existingMap.set(entry.file_name, entry.memory_id);
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
let created = 0;
|
|
1580
|
+
let updated = 0;
|
|
1581
|
+
for (const localFile of localFiles) {
|
|
1582
|
+
const memoryId = existingMap.get(localFile.file_name);
|
|
1583
|
+
if (memoryId) {
|
|
1584
|
+
const updateUrl = `${controlPlaneUrl}/api/v1/agent-config/memory/${memoryId}`;
|
|
1585
|
+
const updateBody = {
|
|
1586
|
+
content: localFile.content,
|
|
1587
|
+
session_id: sessionId
|
|
1588
|
+
};
|
|
1589
|
+
const updateResponse = await fetchFn(updateUrl, {
|
|
1590
|
+
method: "PUT",
|
|
1591
|
+
headers: {
|
|
1592
|
+
authorization: `Bearer ${token}`,
|
|
1593
|
+
"content-type": "application/json"
|
|
1594
|
+
},
|
|
1595
|
+
body: JSON.stringify(updateBody)
|
|
1596
|
+
});
|
|
1597
|
+
if (updateResponse.status !== 200) {
|
|
1598
|
+
const text = await updateResponse.text();
|
|
1599
|
+
throw new Error(
|
|
1600
|
+
`PUT /api/v1/agent-config/memory/${memoryId} returned HTTP ${updateResponse.status}: ${text.slice(0, 200)}`
|
|
1601
|
+
);
|
|
1602
|
+
}
|
|
1603
|
+
const updateData = JSON.parse(await updateResponse.text());
|
|
1604
|
+
if (!updateData.ok) {
|
|
1605
|
+
throw new Error(`PUT /api/v1/agent-config/memory/${memoryId} returned ok=false`);
|
|
1606
|
+
}
|
|
1607
|
+
updated++;
|
|
1608
|
+
} else {
|
|
1609
|
+
const createUrl = `${controlPlaneUrl}/api/v1/agent-config/memory/me`;
|
|
1610
|
+
const createBody = {
|
|
1611
|
+
entry_type: localFile.entry_type,
|
|
1612
|
+
file_name: localFile.file_name,
|
|
1613
|
+
content: localFile.content,
|
|
1614
|
+
session_id: sessionId
|
|
1615
|
+
};
|
|
1616
|
+
const createResponse = await fetchFn(createUrl, {
|
|
1617
|
+
method: "POST",
|
|
1618
|
+
headers: {
|
|
1619
|
+
authorization: `Bearer ${token}`,
|
|
1620
|
+
"content-type": "application/json"
|
|
1621
|
+
},
|
|
1622
|
+
body: JSON.stringify(createBody)
|
|
1623
|
+
});
|
|
1624
|
+
if (createResponse.status !== 200 && createResponse.status !== 201) {
|
|
1625
|
+
const text = await createResponse.text();
|
|
1626
|
+
throw new Error(
|
|
1627
|
+
`POST /api/v1/agent-config/memory/me returned HTTP ${createResponse.status}: ${text.slice(0, 200)}`
|
|
1628
|
+
);
|
|
1629
|
+
}
|
|
1630
|
+
const createData = JSON.parse(await createResponse.text());
|
|
1631
|
+
if (!createData.ok) {
|
|
1632
|
+
throw new Error("POST /api/v1/agent-config/memory/me returned ok=false");
|
|
1633
|
+
}
|
|
1634
|
+
created++;
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
return { pushed: localFiles.length, created, updated };
|
|
1638
|
+
}
|
|
1639
|
+
function isNoopSyncReason(reason) {
|
|
1640
|
+
if (!reason) return false;
|
|
1641
|
+
return /not set|No auth configured|Failed to obtain auth token/.test(reason);
|
|
1182
1642
|
}
|
|
1183
|
-
function
|
|
1184
|
-
|
|
1185
|
-
|
|
1643
|
+
async function runMemorySync(action, cwd, sessionId, fetchFn = globalThis.fetch) {
|
|
1644
|
+
const controlPlaneUrl = process.env["VO_CONTROL_PLANE_URL"];
|
|
1645
|
+
if (!controlPlaneUrl) {
|
|
1646
|
+
return { synced: false, reason: "VO_CONTROL_PLANE_URL not set \u2014 cloud mode disabled" };
|
|
1647
|
+
}
|
|
1648
|
+
const { createAuthTokenSourceFromEnv: createAuthTokenSourceFromEnv2 } = await Promise.resolve().then(() => (init_auth_token_source(), auth_token_source_exports));
|
|
1649
|
+
const { readStoredCredential: readStoredCredential2 } = await Promise.resolve().then(() => (init_credential_store(), credential_store_exports));
|
|
1650
|
+
const tokenSource = createAuthTokenSourceFromEnv2(process.env, fetchFn, () => readStoredCredential2(process.env));
|
|
1651
|
+
if (!tokenSource) {
|
|
1652
|
+
return { synced: false, reason: "No auth configured. Run `vo-mcp login` to authenticate as an operator." };
|
|
1653
|
+
}
|
|
1654
|
+
const token = await tokenSource.getToken();
|
|
1655
|
+
if (!token) {
|
|
1656
|
+
return { synced: false, reason: "Failed to obtain auth token. Run `vo-mcp login` to re-authenticate." };
|
|
1657
|
+
}
|
|
1658
|
+
const memoryDir = getMemoryDir(cwd);
|
|
1659
|
+
const baseUrl = controlPlaneUrl.replace(/\/+$/, "");
|
|
1660
|
+
try {
|
|
1661
|
+
if (action === "pull") {
|
|
1662
|
+
const result2 = await pullMemory(baseUrl, token, memoryDir, fetchFn);
|
|
1663
|
+
return { synced: true, action: "pull", pulled: result2.pulled, files: result2.files, memory_dir: memoryDir };
|
|
1664
|
+
}
|
|
1665
|
+
const result = await pushMemory(baseUrl, token, memoryDir, sessionId, fetchFn);
|
|
1666
|
+
let bridge = { upserted: 0, failed: 0, failures: [] };
|
|
1667
|
+
try {
|
|
1668
|
+
const { upsertMemoryFilesAsKnowledge: upsertMemoryFilesAsKnowledge2 } = await Promise.resolve().then(() => (init_memory_knowledge_bridge(), memory_knowledge_bridge_exports));
|
|
1669
|
+
bridge = await upsertMemoryFilesAsKnowledge2({
|
|
1670
|
+
controlPlaneUrl: baseUrl,
|
|
1671
|
+
token,
|
|
1672
|
+
memoryDir,
|
|
1673
|
+
fetchFn
|
|
1674
|
+
});
|
|
1675
|
+
} catch (err) {
|
|
1676
|
+
bridge = {
|
|
1677
|
+
upserted: 0,
|
|
1678
|
+
failed: 1,
|
|
1679
|
+
failures: [`bridge unavailable: ${err instanceof Error ? err.message : String(err)}`]
|
|
1680
|
+
};
|
|
1681
|
+
}
|
|
1186
1682
|
return {
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
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
|
|
1683
|
+
synced: true,
|
|
1684
|
+
action: "push",
|
|
1685
|
+
pushed: result.pushed,
|
|
1686
|
+
created: result.created,
|
|
1687
|
+
updated: result.updated,
|
|
1688
|
+
memory_dir: memoryDir,
|
|
1689
|
+
knowledge_upserted: bridge.upserted,
|
|
1690
|
+
knowledge_failed: bridge.failed,
|
|
1691
|
+
...bridge.failed > 0 ? { knowledge_failures: bridge.failures } : {}
|
|
1202
1692
|
};
|
|
1203
|
-
})
|
|
1693
|
+
} catch (err) {
|
|
1694
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1695
|
+
return { synced: false, reason: `Sync failed: ${message}` };
|
|
1696
|
+
}
|
|
1204
1697
|
}
|
|
1205
|
-
function
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1698
|
+
async function handleSyncConfig(deps, rawInput, _signal, fetchFn = globalThis.fetch) {
|
|
1699
|
+
if (!isToolInput22(rawInput)) {
|
|
1700
|
+
throw invalidParams(
|
|
1701
|
+
TOOL_NAME22,
|
|
1702
|
+
'invalid input. Required: { action: "pull" | "push" }. Optional: { cwd: "<path>" }.'
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1705
|
+
const cwd = rawInput.cwd?.trim() || process.cwd();
|
|
1706
|
+
const result = await runMemorySync(rawInput.action, cwd, deps.session.sessionId, fetchFn);
|
|
1707
|
+
return jsonContent({ tool: TOOL_NAME22, schema_version: 1, payload: result });
|
|
1211
1708
|
}
|
|
1709
|
+
var TOOL_NAME22, inputSchema22, description22;
|
|
1710
|
+
var init_sync_config = __esm({
|
|
1711
|
+
"src/tools/memory/sync-config.ts"() {
|
|
1712
|
+
"use strict";
|
|
1713
|
+
init_common();
|
|
1714
|
+
init_safe_memory_file();
|
|
1715
|
+
TOOL_NAME22 = "vo_sync_config";
|
|
1716
|
+
inputSchema22 = {
|
|
1717
|
+
type: "object",
|
|
1718
|
+
properties: {
|
|
1719
|
+
action: {
|
|
1720
|
+
type: "string",
|
|
1721
|
+
enum: ["pull", "push"],
|
|
1722
|
+
description: "pull: download cloud memory to local files. push: upload local files to cloud."
|
|
1723
|
+
},
|
|
1724
|
+
cwd: {
|
|
1725
|
+
type: "string",
|
|
1726
|
+
description: "Working directory to derive project slug from (default: process.cwd())."
|
|
1727
|
+
}
|
|
1728
|
+
},
|
|
1729
|
+
required: ["action"],
|
|
1730
|
+
additionalProperties: false
|
|
1731
|
+
};
|
|
1732
|
+
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.";
|
|
1733
|
+
}
|
|
1734
|
+
});
|
|
1735
|
+
|
|
1736
|
+
// src/cli.ts
|
|
1737
|
+
import { homedir as homedir6, hostname } from "node:os";
|
|
1738
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
1739
|
+
import { join as join10 } from "node:path";
|
|
1740
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
1741
|
+
|
|
1742
|
+
// src/server.ts
|
|
1743
|
+
init_common();
|
|
1744
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
1745
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
1746
|
+
import {
|
|
1747
|
+
CallToolRequestSchema,
|
|
1748
|
+
ListToolsRequestSchema
|
|
1749
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
1212
1750
|
|
|
1213
1751
|
// src/modes/local.ts
|
|
1214
1752
|
function createLocalMode() {
|
|
@@ -1223,6 +1761,7 @@ function createLocalMode() {
|
|
|
1223
1761
|
}
|
|
1224
1762
|
|
|
1225
1763
|
// src/tools/check-assertion-strength.ts
|
|
1764
|
+
init_common();
|
|
1226
1765
|
var TOOL_NAME = "vo_check_assertion_strength";
|
|
1227
1766
|
var GATE_TYPE = "ratchet";
|
|
1228
1767
|
var MAX_SOURCE_BYTES = 512 * 1024;
|
|
@@ -1319,7 +1858,11 @@ async function handleCheckAssertionStrength(deps, rawInput, _signal) {
|
|
|
1319
1858
|
return jsonContent(envelope);
|
|
1320
1859
|
}
|
|
1321
1860
|
|
|
1861
|
+
// src/tools/check-hollow-test.ts
|
|
1862
|
+
init_common();
|
|
1863
|
+
|
|
1322
1864
|
// src/tools/architecture-review-kb-prefilter.ts
|
|
1865
|
+
init_src();
|
|
1323
1866
|
var DEFAULT_STACK = [
|
|
1324
1867
|
"node",
|
|
1325
1868
|
"node-pnpm-monorepo",
|
|
@@ -1405,6 +1948,7 @@ function formatRulesForPrompt(hits, truncated, domainLabel = "ARCHITECTURAL") {
|
|
|
1405
1948
|
}
|
|
1406
1949
|
|
|
1407
1950
|
// src/tools/kb-metadata-prefilter.ts
|
|
1951
|
+
init_src();
|
|
1408
1952
|
function findRulesByMetadata(opts) {
|
|
1409
1953
|
if (opts.category === void 0 && opts.tagsAny === void 0) {
|
|
1410
1954
|
return {
|
|
@@ -1598,6 +2142,7 @@ async function handleCheckHollowTest(deps, rawInput, signal) {
|
|
|
1598
2142
|
}
|
|
1599
2143
|
|
|
1600
2144
|
// src/tools/verify-answer.ts
|
|
2145
|
+
init_common();
|
|
1601
2146
|
var TOOL_NAME3 = "vo_verify_answer";
|
|
1602
2147
|
var SHALLOW_GATE = "mid-exec-verify";
|
|
1603
2148
|
var DEEP_GATE = "final-deep-verify";
|
|
@@ -1779,6 +2324,9 @@ async function handleVerifyAnswer(deps, rawInput, signal) {
|
|
|
1779
2324
|
return jsonContent(envelope);
|
|
1780
2325
|
}
|
|
1781
2326
|
|
|
2327
|
+
// src/tools/consensus-judgment.ts
|
|
2328
|
+
init_common();
|
|
2329
|
+
|
|
1782
2330
|
// src/consensus/gate-types.ts
|
|
1783
2331
|
var LEGACY_GATE_TYPES = [
|
|
1784
2332
|
"test_assertion",
|
|
@@ -2036,13 +2584,19 @@ async function handleConsensusJudgment(deps, rawInput, signal) {
|
|
|
2036
2584
|
...engineResult.synthesized_verdict.confidence_badge !== void 0 ? { confidence_badge: engineResult.synthesized_verdict.confidence_badge } : {},
|
|
2037
2585
|
// Feature 1 (agreement-gate) — fan-out diagnostics (present iff the gate ran).
|
|
2038
2586
|
...engineResult.fan_out_diagnostics !== void 0 ? { fan_out_diagnostics: engineResult.fan_out_diagnostics } : {},
|
|
2587
|
+
// Stage A7-shadow — adaptive-vs-incumbent comparison (PII-free; present iff shadow on).
|
|
2588
|
+
...engineResult.shadow_synthesis !== void 0 ? { shadow_synthesis: engineResult.shadow_synthesis } : {},
|
|
2039
2589
|
// Source-grounded Tier-4 outputs (present iff the call was source-grounded).
|
|
2040
2590
|
...engineResult.source_grounded === true ? { source_grounded: true } : {},
|
|
2041
2591
|
...engineResult.citation_grade !== void 0 ? { citation_grade: engineResult.citation_grade } : {},
|
|
2042
2592
|
...engineResult.low_confidence_sources !== void 0 ? { low_confidence_sources: engineResult.low_confidence_sources } : {},
|
|
2043
2593
|
// Escalation (from citation grade or human-tiebreak synthesizer).
|
|
2044
2594
|
...engineResult.escalation_required !== void 0 ? { escalation_required: engineResult.escalation_required } : {},
|
|
2045
|
-
...engineResult.escalation_reason !== void 0 ? { escalation_reason: engineResult.escalation_reason } : {}
|
|
2595
|
+
...engineResult.escalation_reason !== void 0 ? { escalation_reason: engineResult.escalation_reason } : {},
|
|
2596
|
+
// Critique-uptake (2026-07-20 red-team fix) — the engine computes this
|
|
2597
|
+
// on every call; this spread closes the gap where the visibility report
|
|
2598
|
+
// was itself silently dropped at the payload boundary.
|
|
2599
|
+
...engineResult.critique_uptake !== void 0 ? { critique_uptake: engineResult.critique_uptake } : {}
|
|
2046
2600
|
};
|
|
2047
2601
|
const envelope = {
|
|
2048
2602
|
tool: TOOL_NAME4,
|
|
@@ -2056,6 +2610,8 @@ async function handleConsensusJudgment(deps, rawInput, signal) {
|
|
|
2056
2610
|
}
|
|
2057
2611
|
|
|
2058
2612
|
// src/tools/architecture-review.ts
|
|
2613
|
+
init_common();
|
|
2614
|
+
init_events_writer();
|
|
2059
2615
|
var TOOL_NAME5 = "vo_architecture_review";
|
|
2060
2616
|
var GATE_TYPE3 = "architecture-review";
|
|
2061
2617
|
var MAX_DIFF_BYTES = 1024 * 1024;
|
|
@@ -3300,6 +3856,7 @@ function partitionByAllowlist(findings, allowlist) {
|
|
|
3300
3856
|
}
|
|
3301
3857
|
|
|
3302
3858
|
// src/tools/check-ratchets.ts
|
|
3859
|
+
init_common();
|
|
3303
3860
|
var TOOL_NAME6 = "vo_check_ratchets";
|
|
3304
3861
|
var GATE_TYPE4 = "ratchet";
|
|
3305
3862
|
var ALL_RATCHET_IDS = [
|
|
@@ -3432,6 +3989,7 @@ function buildSummary(report) {
|
|
|
3432
3989
|
}
|
|
3433
3990
|
|
|
3434
3991
|
// src/tools/decompose-dispatch.ts
|
|
3992
|
+
init_common();
|
|
3435
3993
|
var TOOL_NAME7 = "vo_decompose_dispatch";
|
|
3436
3994
|
var GATE_TYPE5 = "plan-review";
|
|
3437
3995
|
var MAX_GOAL_BYTES = 32 * 1024;
|
|
@@ -3709,6 +4267,9 @@ Produce the JSON dispatch plan now.`;
|
|
|
3709
4267
|
return jsonContent(envelope);
|
|
3710
4268
|
}
|
|
3711
4269
|
|
|
4270
|
+
// src/tools/heal/trigger-heal.ts
|
|
4271
|
+
init_common();
|
|
4272
|
+
|
|
3712
4273
|
// src/cloud/admin-callable-client.ts
|
|
3713
4274
|
init_auth_token_source();
|
|
3714
4275
|
init_credential_store();
|
|
@@ -3838,6 +4399,7 @@ function buildAdminCallableClientFromEnv(env = process.env) {
|
|
|
3838
4399
|
}
|
|
3839
4400
|
|
|
3840
4401
|
// src/tools/cloud-call.ts
|
|
4402
|
+
init_common();
|
|
3841
4403
|
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
4404
|
async function buildCloudOrStubResponse(args) {
|
|
3843
4405
|
const inputJson = JSON.stringify(args.normalizedInput);
|
|
@@ -3911,6 +4473,7 @@ async function buildCloudOrStubResponse(args) {
|
|
|
3911
4473
|
}
|
|
3912
4474
|
|
|
3913
4475
|
// src/tools/heal/common-heal.ts
|
|
4476
|
+
init_common();
|
|
3914
4477
|
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
4478
|
var HEAL_GATE_TYPE = "admin-action";
|
|
3916
4479
|
|
|
@@ -3970,6 +4533,7 @@ async function handleTriggerHeal(deps, rawInput, _signal) {
|
|
|
3970
4533
|
}
|
|
3971
4534
|
|
|
3972
4535
|
// src/tools/heal/fix-retry.ts
|
|
4536
|
+
init_common();
|
|
3973
4537
|
var TOOL_NAME9 = "vo_fix_retry";
|
|
3974
4538
|
var MAX_BATCH = 50;
|
|
3975
4539
|
var ADMIN_PATH_SINGLE = "/api/v1/admin/heal/retry-attempt";
|
|
@@ -4062,6 +4626,7 @@ async function handleFixRetry(deps, rawInput, _signal) {
|
|
|
4062
4626
|
}
|
|
4063
4627
|
|
|
4064
4628
|
// src/tools/heal/fix-clear.ts
|
|
4629
|
+
init_common();
|
|
4065
4630
|
var TOOL_NAME10 = "vo_fix_clear";
|
|
4066
4631
|
var CALLABLE_NAME2 = "voClearFixAttempt";
|
|
4067
4632
|
var ADMIN_PATH2 = "/api/v1/admin/heal/clear-attempt";
|
|
@@ -4105,6 +4670,7 @@ async function handleFixClear(deps, rawInput, _signal) {
|
|
|
4105
4670
|
}
|
|
4106
4671
|
|
|
4107
4672
|
// src/tools/heal/stop-workflow.ts
|
|
4673
|
+
init_common();
|
|
4108
4674
|
var TOOL_NAME11 = "vo_stop_workflow";
|
|
4109
4675
|
var CALLABLE_NAME3 = "voStopWorkflow";
|
|
4110
4676
|
var ADMIN_PATH3 = "/api/v1/admin/workflow/stop";
|
|
@@ -4160,6 +4726,7 @@ async function handleStopWorkflow(deps, rawInput, _signal) {
|
|
|
4160
4726
|
}
|
|
4161
4727
|
|
|
4162
4728
|
// src/tools/heal/get-workflow-runs.ts
|
|
4729
|
+
init_common();
|
|
4163
4730
|
var TOOL_NAME12 = "vo_get_workflow_runs";
|
|
4164
4731
|
var CALLABLE_NAME4 = "voGetWorkflowRuns";
|
|
4165
4732
|
var ADMIN_PATH4 = "/api/v1/admin/workflow/runs";
|
|
@@ -4190,7 +4757,11 @@ async function handleGetWorkflowRuns(deps, rawInput, _signal) {
|
|
|
4190
4757
|
});
|
|
4191
4758
|
}
|
|
4192
4759
|
|
|
4760
|
+
// src/tools/pr/list-pending-prs.ts
|
|
4761
|
+
init_common();
|
|
4762
|
+
|
|
4193
4763
|
// src/tools/pr/common-pr.ts
|
|
4764
|
+
init_common();
|
|
4194
4765
|
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
4766
|
var PR_GATE_TYPE = "admin-action";
|
|
4196
4767
|
|
|
@@ -4203,7 +4774,7 @@ var inputSchema13 = {
|
|
|
4203
4774
|
properties: {},
|
|
4204
4775
|
additionalProperties: false
|
|
4205
4776
|
};
|
|
4206
|
-
var description13 = "Lists open
|
|
4777
|
+
var description13 = "Lists open AlgoHQ-source pull requests with blocker / source / tester / specialist-context metadata. Read-only diagnostic for Command Center reads. Wraps `voListPendingPRs` admin Cloud Function. V1 stub: returns `verdict: 'unimplemented'` \u2014 cloud-mode wiring pending.";
|
|
4207
4778
|
function isToolInput13(v) {
|
|
4208
4779
|
return typeof v === "object" && v !== null;
|
|
4209
4780
|
}
|
|
@@ -4225,6 +4796,7 @@ async function handleListPendingPRs(deps, rawInput, _signal) {
|
|
|
4225
4796
|
}
|
|
4226
4797
|
|
|
4227
4798
|
// src/tools/pr/merge-pr.ts
|
|
4799
|
+
init_common();
|
|
4228
4800
|
var TOOL_NAME14 = "vo_merge_pr";
|
|
4229
4801
|
var CALLABLE_NAME6 = "voMergePR";
|
|
4230
4802
|
var ADMIN_PATH6 = "/api/v1/admin/pr/merge";
|
|
@@ -4239,7 +4811,7 @@ var inputSchema14 = {
|
|
|
4239
4811
|
required: ["pr_number"],
|
|
4240
4812
|
additionalProperties: false
|
|
4241
4813
|
};
|
|
4242
|
-
var description14 = "Approves + merges a single
|
|
4814
|
+
var description14 = "Approves + merges a single AlgoHQ-source pull request by number. Wraps `voMergePR` admin Cloud Function (server-side refuses non-AlgoHQ PRs with permission-denied). V1 stub: returns `verdict: 'unimplemented'` \u2014 cloud-mode wiring pending.";
|
|
4243
4815
|
function isToolInput14(v) {
|
|
4244
4816
|
if (typeof v !== "object" || v === null) return false;
|
|
4245
4817
|
const o = v;
|
|
@@ -4269,6 +4841,7 @@ async function handleMergePR(deps, rawInput, _signal) {
|
|
|
4269
4841
|
}
|
|
4270
4842
|
|
|
4271
4843
|
// src/tools/pr/reject-pr.ts
|
|
4844
|
+
init_common();
|
|
4272
4845
|
var TOOL_NAME15 = "vo_reject_pr";
|
|
4273
4846
|
var CALLABLE_NAME7 = "voRejectPR";
|
|
4274
4847
|
var ADMIN_PATH7 = "/api/v1/admin/pr/reject";
|
|
@@ -4313,6 +4886,7 @@ async function handleRejectPR(deps, rawInput, _signal) {
|
|
|
4313
4886
|
}
|
|
4314
4887
|
|
|
4315
4888
|
// src/tools/pr/approve-all-fixes.ts
|
|
4889
|
+
init_common();
|
|
4316
4890
|
var TOOL_NAME16 = "vo_approve_all_fixes";
|
|
4317
4891
|
var CALLABLE_NAME8 = "voApproveAllFixes";
|
|
4318
4892
|
var ADMIN_PATH8 = "/api/v1/admin/pr/approve-all";
|
|
@@ -4321,7 +4895,7 @@ var inputSchema16 = {
|
|
|
4321
4895
|
properties: {},
|
|
4322
4896
|
additionalProperties: false
|
|
4323
4897
|
};
|
|
4324
|
-
var description16 = "Iterates all open
|
|
4898
|
+
var description16 = "Iterates all open AlgoHQ-source pull requests and merges (or arms auto-merge) on each. Returns counts of merged / accepted / total plus per-PR results. Wraps `voApproveAllFixes` admin Cloud Function. V1 stub: returns `verdict: 'unimplemented'` \u2014 cloud-mode wiring pending.";
|
|
4325
4899
|
function isToolInput16(v) {
|
|
4326
4900
|
return typeof v === "object" && v !== null;
|
|
4327
4901
|
}
|
|
@@ -4341,6 +4915,7 @@ async function handleApproveAllFixes(deps, rawInput, _signal) {
|
|
|
4341
4915
|
}
|
|
4342
4916
|
|
|
4343
4917
|
// src/tools/pr/reject-and-retry.ts
|
|
4918
|
+
init_common();
|
|
4344
4919
|
var TOOL_NAME17 = "vo_reject_and_retry";
|
|
4345
4920
|
var CALLABLE_NAME9 = "voRejectAndRetry";
|
|
4346
4921
|
var ADMIN_PATH9 = "/api/v1/admin/pr/reject-retry";
|
|
@@ -4355,7 +4930,7 @@ var inputSchema17 = {
|
|
|
4355
4930
|
required: ["pr_number"],
|
|
4356
4931
|
additionalProperties: false
|
|
4357
4932
|
};
|
|
4358
|
-
var description17 = "Closes
|
|
4933
|
+
var description17 = "Closes an AlgoHQ pull request and dispatches a self-heal pass to retry the same focus page. Cloud callable refuses non-AlgoHQ PRs and respects the self-heal kill switch + per-PR retry block. Wraps `voRejectAndRetry` admin Cloud Function. V1 stub: returns `verdict: 'unimplemented'` \u2014 cloud-mode wiring pending.";
|
|
4359
4934
|
function isToolInput17(v) {
|
|
4360
4935
|
if (typeof v !== "object" || v === null) return false;
|
|
4361
4936
|
const o = v;
|
|
@@ -4385,6 +4960,7 @@ async function handleRejectAndRetry(deps, rawInput, _signal) {
|
|
|
4385
4960
|
}
|
|
4386
4961
|
|
|
4387
4962
|
// src/tools/pr/review-merge.ts
|
|
4963
|
+
init_common();
|
|
4388
4964
|
var TOOL_NAME18 = "vo_review_merge";
|
|
4389
4965
|
var LIST_PATH = "/api/v1/admin/pr/list";
|
|
4390
4966
|
var ENGINE_GATE = "final-deep-verify";
|
|
@@ -4431,7 +5007,7 @@ function buildPrompt4(pr, notes) {
|
|
|
4431
5007
|
const lines = [
|
|
4432
5008
|
"You are a release gatekeeper deciding whether a pull request is safe to MERGE.",
|
|
4433
5009
|
"Recommend exactly one of: merge / hold / reject. Be conservative \u2014 this is a high-stakes irreversible action.",
|
|
4434
|
-
"Rules: HOLD if CI is failing/blocked, there is a merge conflict, or the change is a draft. REJECT if the PR is not a legitimate
|
|
5010
|
+
"Rules: HOLD if CI is failing/blocked, there is a merge conflict, or the change is a draft. REJECT if the PR is not a legitimate AlgoHQ-source change or has no clear purpose. MERGE only if it looks complete, scoped, and unblocked.",
|
|
4435
5011
|
"",
|
|
4436
5012
|
`PR #${pr.number}: ${pr.title}`,
|
|
4437
5013
|
`Source: ${pr.source ?? "unknown"}`,
|
|
@@ -4499,7 +5075,7 @@ async function handleReviewMerge(deps, rawInput, signal) {
|
|
|
4499
5075
|
}
|
|
4500
5076
|
if (pr === null) {
|
|
4501
5077
|
return emit(
|
|
4502
|
-
emptyPayload("hold", `PR #${prNumber} is not among open
|
|
5078
|
+
emptyPayload("hold", `PR #${prNumber} is not among open AlgoHQ PRs (already merged/closed, or not an AlgoHQ-source PR).`, null)
|
|
4503
5079
|
);
|
|
4504
5080
|
}
|
|
4505
5081
|
const hasBlocker = pr.blocker !== null && pr.blocker !== "none";
|
|
@@ -4563,6 +5139,9 @@ async function handleReviewMerge(deps, rawInput, signal) {
|
|
|
4563
5139
|
});
|
|
4564
5140
|
}
|
|
4565
5141
|
|
|
5142
|
+
// src/tools/session/report-session-state.ts
|
|
5143
|
+
init_common();
|
|
5144
|
+
|
|
4566
5145
|
// src/tools/session/directive.ts
|
|
4567
5146
|
var SESSION_DIRECTIVE_THRESHOLDS = {
|
|
4568
5147
|
prepare_handoff_pct: 70,
|
|
@@ -4594,6 +5173,8 @@ function suggestedHandoffPath(session_id, isoTimestamp) {
|
|
|
4594
5173
|
}
|
|
4595
5174
|
|
|
4596
5175
|
// src/tools/session/report-session-state.ts
|
|
5176
|
+
init_auth_token_source();
|
|
5177
|
+
init_credential_store();
|
|
4597
5178
|
var TOOL_NAME19 = "vo_report_session_state";
|
|
4598
5179
|
var VALID_AGENT_TYPES = ["claude-code", "codex", "cursor", "continue"];
|
|
4599
5180
|
var MAX_GOAL_CHARS = 500;
|
|
@@ -4644,7 +5225,7 @@ var inputSchema19 = {
|
|
|
4644
5225
|
required: ["operator_id", "session_id", "agent_type", "context_used_pct"],
|
|
4645
5226
|
additionalProperties: false
|
|
4646
5227
|
};
|
|
4647
|
-
var description19 = "Reports per-session context-window utilization to
|
|
5228
|
+
var description19 = "Reports per-session context-window utilization to AlgoHQ and returns a directive: 'continue' (under 70%), 'prepare_handoff' (70-84%), or 'execute_handoff_now' (\u226585%). Implements V1 launch gate #9 (fleet context lifecycle management) per the official AlgoHQ roadmap. Cloud-control-plane mode when VO_CONTROL_PLANE_URL plus a user/scoped HQ credential (or legacy admin token) is available; auto-allocates the session on first report so interactive agents (Claude Code, Cursor, Codex, Continue) appear on the live fleet whiteboard. Stub-local fallback when cloud config is absent or fails. The response shape stays stable across modes (`backend_mode` field in the payload tells the caller which mode produced the verdict).";
|
|
4648
5229
|
function isStringArray2(v, maxItems) {
|
|
4649
5230
|
if (!Array.isArray(v)) return false;
|
|
4650
5231
|
if (v.length > maxItems) return false;
|
|
@@ -4669,33 +5250,93 @@ function isToolInput19(v) {
|
|
|
4669
5250
|
}
|
|
4670
5251
|
return true;
|
|
4671
5252
|
}
|
|
4672
|
-
function
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
5253
|
+
async function fetchCloudIdentity(url, token, fetchFn) {
|
|
5254
|
+
try {
|
|
5255
|
+
const response = await fetchFn(`${url}/api/v1/auth/me`, {
|
|
5256
|
+
method: "GET",
|
|
5257
|
+
headers: {
|
|
5258
|
+
"Authorization": `Bearer ${token}`
|
|
5259
|
+
}
|
|
5260
|
+
});
|
|
5261
|
+
if (!response.ok) return null;
|
|
5262
|
+
const data = await response.json();
|
|
5263
|
+
if (!data.ok || !data.provisioned || !data.operator_id || !data.tenant_id) return null;
|
|
5264
|
+
return { operator_id: data.operator_id, tenant_id: data.tenant_id };
|
|
5265
|
+
} catch {
|
|
5266
|
+
return null;
|
|
5267
|
+
}
|
|
5268
|
+
}
|
|
5269
|
+
async function getCloudConfig(fetchFn = fetch) {
|
|
5270
|
+
const url = process.env["VO_CONTROL_PLANE_URL"]?.trim();
|
|
5271
|
+
if (!url) return null;
|
|
5272
|
+
const tokenSource = createAuthTokenSourceFromEnv(
|
|
5273
|
+
process.env,
|
|
5274
|
+
fetchFn,
|
|
5275
|
+
() => readStoredCredential(process.env)
|
|
5276
|
+
);
|
|
5277
|
+
const token = await tokenSource?.getToken();
|
|
5278
|
+
if (!token) return null;
|
|
5279
|
+
const tenant_id = process.env["VO_TENANT_ID"]?.trim();
|
|
5280
|
+
if (tenant_id) return { url, token, tenant_id };
|
|
5281
|
+
const identity = await fetchCloudIdentity(url, token, fetchFn);
|
|
5282
|
+
if (!identity) return null;
|
|
5283
|
+
return { url, token, tenant_id: identity.tenant_id, operator_id: identity.operator_id };
|
|
4677
5284
|
}
|
|
4678
|
-
async function tryCloudReportState(cloud, input) {
|
|
5285
|
+
async function tryCloudReportState(cloud, input, fetchFn = fetch) {
|
|
4679
5286
|
try {
|
|
4680
|
-
const
|
|
5287
|
+
const reportBody = {
|
|
4681
5288
|
context_used_pct: input.context_used_pct
|
|
4682
5289
|
};
|
|
4683
|
-
if (input.current_goal !== void 0)
|
|
5290
|
+
if (input.current_goal !== void 0) reportBody["current_goal"] = input.current_goal;
|
|
4684
5291
|
if (input.recent_files_touched !== void 0) {
|
|
4685
|
-
|
|
5292
|
+
reportBody["recent_files_touched"] = input.recent_files_touched;
|
|
4686
5293
|
}
|
|
4687
5294
|
if (input.recent_tool_uses !== void 0) {
|
|
4688
|
-
|
|
5295
|
+
reportBody["recent_tool_uses"] = input.recent_tool_uses;
|
|
4689
5296
|
}
|
|
4690
|
-
const
|
|
4691
|
-
|
|
5297
|
+
const reportUrl = `${cloud.url}/api/v1/session/${input.session_id}/report-state`;
|
|
5298
|
+
let response = await fetchFn(reportUrl, {
|
|
4692
5299
|
method: "POST",
|
|
4693
5300
|
headers: {
|
|
4694
5301
|
"Content-Type": "application/json",
|
|
4695
5302
|
"Authorization": `Bearer ${cloud.token}`
|
|
4696
5303
|
},
|
|
4697
|
-
body: JSON.stringify(
|
|
5304
|
+
body: JSON.stringify(reportBody)
|
|
4698
5305
|
});
|
|
5306
|
+
if (response.status === 404) {
|
|
5307
|
+
const allocateBody = {
|
|
5308
|
+
operator_id: cloud.operator_id ?? input.operator_id,
|
|
5309
|
+
tenant_id: cloud.tenant_id,
|
|
5310
|
+
agent_type: input.agent_type,
|
|
5311
|
+
current_goal: input.current_goal ?? "Interactive session"
|
|
5312
|
+
};
|
|
5313
|
+
if (input.context_used_pct > 0) {
|
|
5314
|
+
allocateBody["initial_context_used_pct"] = input.context_used_pct;
|
|
5315
|
+
}
|
|
5316
|
+
const allocateUrl = `${cloud.url}/api/v1/session`;
|
|
5317
|
+
const allocateResponse = await fetchFn(allocateUrl, {
|
|
5318
|
+
method: "POST",
|
|
5319
|
+
headers: {
|
|
5320
|
+
"Content-Type": "application/json",
|
|
5321
|
+
"Authorization": `Bearer ${cloud.token}`
|
|
5322
|
+
},
|
|
5323
|
+
body: JSON.stringify(allocateBody)
|
|
5324
|
+
});
|
|
5325
|
+
if (!allocateResponse.ok) {
|
|
5326
|
+
return null;
|
|
5327
|
+
}
|
|
5328
|
+
const allocateData = await allocateResponse.json();
|
|
5329
|
+
const retrySessionId = typeof allocateData.session?.session_id === "string" && allocateData.session.session_id.length > 0 ? allocateData.session.session_id : input.session_id;
|
|
5330
|
+
const retryReportUrl = `${cloud.url}/api/v1/session/${retrySessionId}/report-state`;
|
|
5331
|
+
response = await fetchFn(retryReportUrl, {
|
|
5332
|
+
method: "POST",
|
|
5333
|
+
headers: {
|
|
5334
|
+
"Content-Type": "application/json",
|
|
5335
|
+
"Authorization": `Bearer ${cloud.token}`
|
|
5336
|
+
},
|
|
5337
|
+
body: JSON.stringify(reportBody)
|
|
5338
|
+
});
|
|
5339
|
+
}
|
|
4699
5340
|
if (!response.ok) {
|
|
4700
5341
|
return null;
|
|
4701
5342
|
}
|
|
@@ -4726,7 +5367,7 @@ async function handleReportSessionState(deps, rawInput, _signal) {
|
|
|
4726
5367
|
`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
5368
|
);
|
|
4728
5369
|
}
|
|
4729
|
-
const cloud = getCloudConfig();
|
|
5370
|
+
const cloud = await getCloudConfig();
|
|
4730
5371
|
if (cloud !== null) {
|
|
4731
5372
|
const cloudPayload = await tryCloudReportState(cloud, rawInput);
|
|
4732
5373
|
if (cloudPayload !== null) {
|
|
@@ -4751,6 +5392,7 @@ async function handleReportSessionState(deps, rawInput, _signal) {
|
|
|
4751
5392
|
}
|
|
4752
5393
|
|
|
4753
5394
|
// src/tools/session/spawn-successor.ts
|
|
5395
|
+
init_common();
|
|
4754
5396
|
import { spawn } from "node:child_process";
|
|
4755
5397
|
import { homedir as homedir4 } from "node:os";
|
|
4756
5398
|
import { join as join6 } from "node:path";
|
|
@@ -4809,7 +5451,7 @@ var MANDATORY_READS = [
|
|
|
4809
5451
|
function buildSuccessorPrompt(handoffMarkdown, goal) {
|
|
4810
5452
|
const reads = MANDATORY_READS.map((r, i) => ` ${i + 1}. ${r}`).join("\n");
|
|
4811
5453
|
const lines = [
|
|
4812
|
-
"You are the SUCCESSOR agent for
|
|
5454
|
+
"You are the SUCCESSOR agent for an AlgoHQ lane. The previous session",
|
|
4813
5455
|
"exhausted its context and wrote the handoff below. Read it fully, verify its",
|
|
4814
5456
|
'"verification needed" items against live state (a handoff is a claim, not',
|
|
4815
5457
|
"evidence \u2014 verify via `git show origin/main:<path>`), then continue the lane.",
|
|
@@ -4820,7 +5462,7 @@ function buildSuccessorPrompt(handoffMarkdown, goal) {
|
|
|
4820
5462
|
"NON-NEGOTIABLES: multi-model consensus verification is the core; test honesty",
|
|
4821
5463
|
"(verified-answer-only, no fake green); verify-before-act + human merge approval;",
|
|
4822
5464
|
"never a full functions-shared deploy; Gen2 only; work in a worktree on your own",
|
|
4823
|
-
"branch; finish line is MERGED + DEPLOYED + LIVE-VERIFIED, and
|
|
5465
|
+
"branch; finish line is MERGED + DEPLOYED + LIVE-VERIFIED, and AlgoHQ changes update",
|
|
4824
5466
|
"the roadmap in the same PR.",
|
|
4825
5467
|
"",
|
|
4826
5468
|
"--- HANDOFF ---",
|
|
@@ -4885,6 +5527,9 @@ async function handleSpawnSuccessor(_deps, rawInput, _signal, spawnImpl = spawn)
|
|
|
4885
5527
|
});
|
|
4886
5528
|
}
|
|
4887
5529
|
|
|
5530
|
+
// src/tools/concierge/dispatch.ts
|
|
5531
|
+
init_common();
|
|
5532
|
+
|
|
4888
5533
|
// src/tools/concierge/common-concierge.ts
|
|
4889
5534
|
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
5535
|
var CONCIERGE_GATE_TYPE = "concierge-dispatch";
|
|
@@ -4966,252 +5611,467 @@ async function handleConciergeDispatch(deps, rawInput, _signal) {
|
|
|
4966
5611
|
});
|
|
4967
5612
|
}
|
|
4968
5613
|
|
|
4969
|
-
// src/
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
var
|
|
5614
|
+
// src/server.ts
|
|
5615
|
+
init_sync_config();
|
|
5616
|
+
|
|
5617
|
+
// src/tools/memory/private-knowledge.ts
|
|
5618
|
+
init_common();
|
|
5619
|
+
var UPSERT_TOOL_NAME = "vo_private_knowledge_upsert";
|
|
5620
|
+
var CONTEXT_TOOL_NAME = "vo_private_knowledge_context";
|
|
5621
|
+
var INVALIDATE_TOOL_NAME = "vo_private_knowledge_invalidate";
|
|
5622
|
+
var KNOWLEDGE_CLASSES = ["memory", "skill", "doctrine", "hook", "command"];
|
|
5623
|
+
var PRECISION_CHAR_BUDGET = 12e3;
|
|
5624
|
+
var upsertInputSchema = {
|
|
4975
5625
|
type: "object",
|
|
4976
5626
|
properties: {
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
},
|
|
4982
|
-
cwd: {
|
|
4983
|
-
type: "string",
|
|
4984
|
-
description: "Working directory to derive project slug from (default: process.cwd())."
|
|
4985
|
-
}
|
|
5627
|
+
knowledge_class: { type: "string", enum: KNOWLEDGE_CLASSES },
|
|
5628
|
+
source_path: { type: "string", description: "Stable private source identifier; not exposed to other users." },
|
|
5629
|
+
title: { type: "string", description: 'Descriptive, retrieval-friendly title (e.g. "AlgoTax OCR redaction architecture", not "notes") \u2014 retrieval matches on it.' },
|
|
5630
|
+
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
5631
|
},
|
|
4987
|
-
required: ["
|
|
5632
|
+
required: ["knowledge_class", "source_path", "title", "content"],
|
|
4988
5633
|
additionalProperties: false
|
|
4989
5634
|
};
|
|
4990
|
-
var
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
5635
|
+
var contextInputSchema = {
|
|
5636
|
+
type: "object",
|
|
5637
|
+
properties: {
|
|
5638
|
+
query: { type: "string" },
|
|
5639
|
+
limit: { type: "number", minimum: 1, maximum: 50 },
|
|
5640
|
+
knowledge_class: { type: "string", enum: KNOWLEDGE_CLASSES }
|
|
5641
|
+
},
|
|
5642
|
+
required: ["query"],
|
|
5643
|
+
additionalProperties: false
|
|
5644
|
+
};
|
|
5645
|
+
var invalidateInputSchema = {
|
|
5646
|
+
type: "object",
|
|
5647
|
+
properties: {
|
|
5648
|
+
knowledge_class: { type: "string", enum: KNOWLEDGE_CLASSES },
|
|
5649
|
+
source_path: { type: "string", minLength: 1, maxLength: 400, description: "Stable private source identifier of the entry to invalidate \u2014 must match the source_path used at upsert." }
|
|
5650
|
+
},
|
|
5651
|
+
required: ["knowledge_class", "source_path"],
|
|
5652
|
+
additionalProperties: false
|
|
5653
|
+
};
|
|
5654
|
+
var upsertDescription = "Uploads or refreshes the authenticated operator\u2019s private cloud knowledge. Works for Claude, Codex, Cursor, and cowork clients via the same vo-mcp login credential. Returns metadata only, not raw stored content. PRECISION DISCIPLINE: keep each entry tight and focused (~1-3 pages) with a descriptive retrieval-friendly title \u2014 retrieval surfaces whole entries, so small dense entries beat bulk dumps. Split large corpora into focused entries, then run a retrieval self-test via vo_private_knowledge_context before relying on the knowledge.";
|
|
5655
|
+
var contextDescription = "Retrieves prompt-ready private knowledge context for the authenticated operator. Returns snippets/context only; no raw corpus download. Also the retrieval self-test surface: after upserting critical knowledge, query for it here and confirm the entry surfaces before trusting it in downstream work.";
|
|
5656
|
+
var invalidateDescription = `Soft-deletes one private-knowledge entry for the authenticated operator: closes the live entry\u2019s validity window (bi-temporal) so it stops surfacing in retrieval. Never destroys data \u2014 invalidated versions remain queryable server-side via include_invalidated. Identify the entry by the same { knowledge_class, source_path } used at upsert; a not_found response means no live entry matches. After invalidating, self-test via ${CONTEXT_TOOL_NAME} to confirm the entry no longer surfaces.`;
|
|
5657
|
+
function isKnowledgeClass(value) {
|
|
5658
|
+
return typeof value === "string" && KNOWLEDGE_CLASSES.includes(value);
|
|
5659
|
+
}
|
|
5660
|
+
function isUpsertInput(value) {
|
|
5661
|
+
if (typeof value !== "object" || value === null) return false;
|
|
5662
|
+
const input = value;
|
|
5663
|
+
return isKnowledgeClass(input["knowledge_class"]) && typeof input["source_path"] === "string" && typeof input["title"] === "string" && typeof input["content"] === "string";
|
|
5664
|
+
}
|
|
5665
|
+
function isInvalidateInput(value) {
|
|
5666
|
+
if (typeof value !== "object" || value === null) return false;
|
|
5667
|
+
const input = value;
|
|
5668
|
+
return isKnowledgeClass(input["knowledge_class"]) && typeof input["source_path"] === "string";
|
|
5669
|
+
}
|
|
5670
|
+
function isContextInput(value) {
|
|
5671
|
+
if (typeof value !== "object" || value === null) return false;
|
|
5672
|
+
const input = value;
|
|
5673
|
+
if (typeof input["query"] !== "string") return false;
|
|
5674
|
+
if (input["limit"] !== void 0 && typeof input["limit"] !== "number") return false;
|
|
5675
|
+
if (input["knowledge_class"] !== void 0 && !isKnowledgeClass(input["knowledge_class"])) return false;
|
|
4996
5676
|
return true;
|
|
4997
5677
|
}
|
|
4998
|
-
function
|
|
4999
|
-
const
|
|
5000
|
-
|
|
5001
|
-
}
|
|
5002
|
-
|
|
5003
|
-
const
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
const
|
|
5008
|
-
|
|
5009
|
-
|
|
5678
|
+
async function getCloudAuth(fetchFn) {
|
|
5679
|
+
const controlPlaneUrl = process.env["VO_CONTROL_PLANE_URL"]?.replace(/\/+$/, "");
|
|
5680
|
+
if (!controlPlaneUrl) {
|
|
5681
|
+
return { ok: false, reason: "VO_CONTROL_PLANE_URL not set \u2014 cloud mode disabled" };
|
|
5682
|
+
}
|
|
5683
|
+
const { createAuthTokenSourceFromEnv: createAuthTokenSourceFromEnv2 } = await Promise.resolve().then(() => (init_auth_token_source(), auth_token_source_exports));
|
|
5684
|
+
const { readStoredCredential: readStoredCredential2 } = await Promise.resolve().then(() => (init_credential_store(), credential_store_exports));
|
|
5685
|
+
const tokenSource = createAuthTokenSourceFromEnv2(process.env, fetchFn, () => readStoredCredential2(process.env));
|
|
5686
|
+
if (!tokenSource) return { ok: false, reason: "No auth configured. Run `vo-mcp login`." };
|
|
5687
|
+
const token = await tokenSource.getToken();
|
|
5688
|
+
if (!token) return { ok: false, reason: "Failed to obtain auth token. Run `vo-mcp login` again." };
|
|
5689
|
+
return { ok: true, controlPlaneUrl, token };
|
|
5690
|
+
}
|
|
5691
|
+
async function callPrivateKnowledge(path3, body, fetchFn) {
|
|
5692
|
+
const auth = await getCloudAuth(fetchFn);
|
|
5693
|
+
if (!auth.ok) return { ok: false, reason: auth.reason };
|
|
5694
|
+
const response = await fetchFn(`${auth.controlPlaneUrl}${path3}`, {
|
|
5695
|
+
method: "POST",
|
|
5010
5696
|
headers: {
|
|
5011
|
-
authorization: `Bearer ${token}
|
|
5012
|
-
|
|
5697
|
+
authorization: `Bearer ${auth.token}`,
|
|
5698
|
+
"content-type": "application/json"
|
|
5699
|
+
},
|
|
5700
|
+
body: JSON.stringify(body)
|
|
5013
5701
|
});
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5702
|
+
const text = await response.text();
|
|
5703
|
+
let parsed;
|
|
5704
|
+
try {
|
|
5705
|
+
parsed = text ? JSON.parse(text) : null;
|
|
5706
|
+
} catch {
|
|
5707
|
+
parsed = null;
|
|
5017
5708
|
}
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
throw new Error("GET /api/v1/agent-config/memory/me response missing ok=true or entries array");
|
|
5709
|
+
if (response.status < 200 || response.status >= 300) {
|
|
5710
|
+
return { ok: false, status: response.status, response: parsed ?? text };
|
|
5021
5711
|
}
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
files.push(entry.file_name);
|
|
5712
|
+
return parsed;
|
|
5713
|
+
}
|
|
5714
|
+
async function handlePrivateKnowledgeUpsert(_deps, rawInput, _signal, fetchFn = globalThis.fetch) {
|
|
5715
|
+
if (!isUpsertInput(rawInput)) {
|
|
5716
|
+
throw invalidParams(UPSERT_TOOL_NAME, "expected { knowledge_class, source_path, title, content }.");
|
|
5028
5717
|
}
|
|
5029
|
-
|
|
5718
|
+
const payload = await callPrivateKnowledge("/api/v1/knowledge/private", rawInput, fetchFn);
|
|
5719
|
+
const envelope = {
|
|
5720
|
+
tool: UPSERT_TOOL_NAME,
|
|
5721
|
+
schema_version: 1,
|
|
5722
|
+
payload
|
|
5723
|
+
};
|
|
5724
|
+
if (rawInput.content.length > PRECISION_CHAR_BUDGET) {
|
|
5725
|
+
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}.`;
|
|
5726
|
+
}
|
|
5727
|
+
return jsonContent(envelope);
|
|
5030
5728
|
}
|
|
5031
|
-
async function
|
|
5032
|
-
if (!
|
|
5033
|
-
|
|
5729
|
+
async function handlePrivateKnowledgeInvalidate(_deps, rawInput, _signal, fetchFn = globalThis.fetch) {
|
|
5730
|
+
if (!isInvalidateInput(rawInput)) {
|
|
5731
|
+
throw invalidParams(INVALIDATE_TOOL_NAME, "expected { knowledge_class, source_path }.");
|
|
5034
5732
|
}
|
|
5035
|
-
const
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
return { pushed: 0, created: 0, updated: 0 };
|
|
5733
|
+
const payload = await callPrivateKnowledge("/api/v1/knowledge/private/invalidate", rawInput, fetchFn);
|
|
5734
|
+
return jsonContent({ tool: INVALIDATE_TOOL_NAME, schema_version: 1, payload });
|
|
5735
|
+
}
|
|
5736
|
+
async function handlePrivateKnowledgeContext(_deps, rawInput, _signal, fetchFn = globalThis.fetch) {
|
|
5737
|
+
if (!isContextInput(rawInput)) {
|
|
5738
|
+
throw invalidParams(CONTEXT_TOOL_NAME, "expected { query, optional limit, optional knowledge_class }.");
|
|
5042
5739
|
}
|
|
5043
|
-
const
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5740
|
+
const payload = await callPrivateKnowledge("/api/v1/knowledge/private/context", rawInput, fetchFn);
|
|
5741
|
+
return jsonContent({ tool: CONTEXT_TOOL_NAME, schema_version: 1, payload });
|
|
5742
|
+
}
|
|
5743
|
+
|
|
5744
|
+
// src/tools/hq/whiteboard.ts
|
|
5745
|
+
init_auth_token_source();
|
|
5746
|
+
init_credential_store();
|
|
5747
|
+
init_common();
|
|
5748
|
+
var POST_TOOL_NAME = "hq_whiteboard_post";
|
|
5749
|
+
var READ_TOOL_NAME = "hq_whiteboard_read";
|
|
5750
|
+
var postDescription = "Post an append-only coordination note to the live AlgoHQ whiteboard. Uses the scoped credential from vo-mcp login; operator and tenant ownership are derived by the server.";
|
|
5751
|
+
var readDescription = "Read recent coordination notes from the caller's live AlgoHQ whiteboard. Uses the scoped credential from vo-mcp login and cannot widen tenant scope.";
|
|
5752
|
+
var postInputSchema = {
|
|
5753
|
+
type: "object",
|
|
5754
|
+
properties: {
|
|
5755
|
+
from: { type: "string", minLength: 1, maxLength: 100, description: "Agent/session display name." },
|
|
5756
|
+
type: { type: "string", minLength: 1, maxLength: 64, description: "Message kind, such as intent, worklog, blocker, or completion." },
|
|
5757
|
+
content: { type: "string", minLength: 1, maxLength: 500, description: "Short coordination note." },
|
|
5758
|
+
targetAgent: { type: "string", maxLength: 100 },
|
|
5759
|
+
tester: { type: "string", maxLength: 100 },
|
|
5760
|
+
tier: { type: "string", maxLength: 32 }
|
|
5761
|
+
},
|
|
5762
|
+
required: ["from", "type", "content"],
|
|
5763
|
+
additionalProperties: false
|
|
5764
|
+
};
|
|
5765
|
+
var readInputSchema = {
|
|
5766
|
+
type: "object",
|
|
5767
|
+
properties: {
|
|
5768
|
+
limit: { type: "integer", minimum: 1, maximum: 100, default: 25 },
|
|
5769
|
+
since: { type: "string", description: "Optional ISO-8601 lower bound." },
|
|
5770
|
+
type: { type: "string", minLength: 1, maxLength: 64 }
|
|
5771
|
+
},
|
|
5772
|
+
additionalProperties: false
|
|
5773
|
+
};
|
|
5774
|
+
function resolveTimeoutMs() {
|
|
5775
|
+
const parsed = Number(process.env["HQ_WHITEBOARD_TIMEOUT_MS"]);
|
|
5776
|
+
return Number.isFinite(parsed) && parsed >= 10 && parsed <= 12e4 ? parsed : 1e4;
|
|
5777
|
+
}
|
|
5778
|
+
function isRecord(value) {
|
|
5779
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5780
|
+
}
|
|
5781
|
+
function onlyKeys(value, allowed) {
|
|
5782
|
+
return Object.keys(value).every((key) => allowed.includes(key));
|
|
5783
|
+
}
|
|
5784
|
+
function isBoundedString(value, min, max) {
|
|
5785
|
+
return typeof value === "string" && value.trim().length >= min && value.trim().length <= max;
|
|
5786
|
+
}
|
|
5787
|
+
function parsePostInput(value) {
|
|
5788
|
+
if (!isRecord(value) || !onlyKeys(value, ["from", "type", "content", "targetAgent", "tester", "tier"])) return null;
|
|
5789
|
+
if (!isBoundedString(value["from"], 1, 100)) return null;
|
|
5790
|
+
if (!isBoundedString(value["type"], 1, 64) || !/^[a-zA-Z0-9_-]+$/.test(value["type"].trim())) return null;
|
|
5791
|
+
if (!isBoundedString(value["content"], 1, 500)) return null;
|
|
5792
|
+
for (const [key, max] of [["targetAgent", 100], ["tester", 100], ["tier", 32]]) {
|
|
5793
|
+
if (value[key] !== void 0 && !isBoundedString(value[key], 0, max)) return null;
|
|
5794
|
+
}
|
|
5795
|
+
return {
|
|
5796
|
+
from: value["from"].trim(),
|
|
5797
|
+
type: value["type"].trim(),
|
|
5798
|
+
content: value["content"].trim(),
|
|
5799
|
+
...typeof value["targetAgent"] === "string" ? { targetAgent: value["targetAgent"].trim() } : {},
|
|
5800
|
+
...typeof value["tester"] === "string" ? { tester: value["tester"].trim() } : {},
|
|
5801
|
+
...typeof value["tier"] === "string" ? { tier: value["tier"].trim() } : {}
|
|
5802
|
+
};
|
|
5803
|
+
}
|
|
5804
|
+
function parseReadInput(value) {
|
|
5805
|
+
if (!isRecord(value) || !onlyKeys(value, ["limit", "since", "type"])) return null;
|
|
5806
|
+
if (value["limit"] !== void 0 && (!Number.isInteger(value["limit"]) || Number(value["limit"]) < 1 || Number(value["limit"]) > 100)) return null;
|
|
5807
|
+
if (value["since"] !== void 0 && (typeof value["since"] !== "string" || Number.isNaN(Date.parse(value["since"])))) return null;
|
|
5808
|
+
if (value["type"] !== void 0 && !isBoundedString(value["type"], 1, 64)) return null;
|
|
5809
|
+
return {
|
|
5810
|
+
...typeof value["limit"] === "number" ? { limit: value["limit"] } : {},
|
|
5811
|
+
...typeof value["since"] === "string" ? { since: value["since"] } : {},
|
|
5812
|
+
...typeof value["type"] === "string" ? { type: value["type"].trim() } : {}
|
|
5813
|
+
};
|
|
5814
|
+
}
|
|
5815
|
+
async function resolveCloud(fetchFn) {
|
|
5816
|
+
const url = process.env["VO_CONTROL_PLANE_URL"]?.trim().replace(/\/$/, "");
|
|
5817
|
+
if (!url) return null;
|
|
5818
|
+
try {
|
|
5819
|
+
const source = createAuthTokenSourceFromEnv(process.env, fetchFn, () => readStoredCredential(process.env));
|
|
5820
|
+
const token = await source?.getToken();
|
|
5821
|
+
return token ? { url, token } : null;
|
|
5822
|
+
} catch {
|
|
5823
|
+
return null;
|
|
5058
5824
|
}
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
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",
|
|
5825
|
+
}
|
|
5826
|
+
async function callWhiteboard(method, bodyOrQuery, signal, fetchFn = fetch) {
|
|
5827
|
+
const cloud = await resolveCloud(fetchFn);
|
|
5828
|
+
if (!cloud) {
|
|
5829
|
+
return {
|
|
5830
|
+
ok: false,
|
|
5831
|
+
error: "hq_whiteboard_not_configured",
|
|
5832
|
+
message: "Set VO_CONTROL_PLANE_URL and run vo-mcp login to install a scoped HQ credential."
|
|
5833
|
+
};
|
|
5834
|
+
}
|
|
5835
|
+
const timeoutSignal = AbortSignal.timeout(resolveTimeoutMs());
|
|
5836
|
+
const requestSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
5837
|
+
const query = new URLSearchParams();
|
|
5838
|
+
if (method === "GET") {
|
|
5839
|
+
const input = bodyOrQuery;
|
|
5840
|
+
query.set("limit", String(input.limit ?? 25));
|
|
5841
|
+
if (input.since) query.set("since", input.since);
|
|
5842
|
+
if (input.type) query.set("type", input.type);
|
|
5843
|
+
}
|
|
5844
|
+
try {
|
|
5845
|
+
const response = await fetchFn(
|
|
5846
|
+
`${cloud.url}/api/v1/hq/whiteboard/messages${query.size ? `?${query}` : ""}`,
|
|
5847
|
+
{
|
|
5848
|
+
method,
|
|
5098
5849
|
headers: {
|
|
5099
|
-
|
|
5100
|
-
"
|
|
5850
|
+
Authorization: `Bearer ${cloud.token}`,
|
|
5851
|
+
...method === "POST" ? { "Content-Type": "application/json" } : {}
|
|
5101
5852
|
},
|
|
5102
|
-
body: JSON.stringify(
|
|
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
|
-
);
|
|
5853
|
+
...method === "POST" ? { body: JSON.stringify(bodyOrQuery) } : {},
|
|
5854
|
+
signal: requestSignal
|
|
5109
5855
|
}
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5856
|
+
);
|
|
5857
|
+
const text = await response.text();
|
|
5858
|
+
let payload;
|
|
5859
|
+
try {
|
|
5860
|
+
payload = JSON.parse(text);
|
|
5861
|
+
} catch {
|
|
5862
|
+
payload = { ok: false, error: "invalid_response", message: text.slice(0, 200) };
|
|
5863
|
+
}
|
|
5864
|
+
if (!response.ok) {
|
|
5865
|
+
return { ok: false, error: "hq_whiteboard_http_error", status: response.status, response: payload };
|
|
5115
5866
|
}
|
|
5867
|
+
return payload;
|
|
5868
|
+
} catch (error) {
|
|
5869
|
+
return {
|
|
5870
|
+
ok: false,
|
|
5871
|
+
error: signal?.aborted ? "cancelled" : timeoutSignal.aborted ? "hq_whiteboard_timeout" : "hq_whiteboard_unreachable",
|
|
5872
|
+
message: error instanceof Error ? error.message.slice(0, 200) : String(error).slice(0, 200)
|
|
5873
|
+
};
|
|
5116
5874
|
}
|
|
5117
|
-
return { pushed: localFiles.length, created, updated };
|
|
5118
5875
|
}
|
|
5119
|
-
async function
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5876
|
+
async function handleHqWhiteboardPost(_deps, rawInput, signal) {
|
|
5877
|
+
const input = parsePostInput(rawInput);
|
|
5878
|
+
if (!input) throw invalidParams(POST_TOOL_NAME, "requires from, type, and 1-500 character content; unknown fields are rejected");
|
|
5879
|
+
return jsonContent(await callWhiteboard("POST", input, signal));
|
|
5880
|
+
}
|
|
5881
|
+
async function handleHqWhiteboardRead(_deps, rawInput, signal) {
|
|
5882
|
+
const input = parseReadInput(rawInput);
|
|
5883
|
+
if (!input) throw invalidParams(READ_TOOL_NAME, "limit must be 1-100, since must be ISO-8601, and unknown fields are rejected");
|
|
5884
|
+
return jsonContent(await callWhiteboard("GET", input, signal));
|
|
5885
|
+
}
|
|
5886
|
+
|
|
5887
|
+
// src/tools/skills/skill-corpus.ts
|
|
5888
|
+
import { existsSync as existsSync7, statSync as statSync5 } from "node:fs";
|
|
5889
|
+
import { dirname as dirname5, isAbsolute, join as join9, resolve as resolve2 } from "node:path";
|
|
5890
|
+
|
|
5891
|
+
// ../skill-registry/src/loader.ts
|
|
5892
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync9, statSync as statSync4 } from "node:fs";
|
|
5893
|
+
import { join as join8 } from "node:path";
|
|
5894
|
+
var InvalidSkillFrontmatterError = class extends Error {
|
|
5895
|
+
constructor(skillFile, reason) {
|
|
5896
|
+
super(`Invalid frontmatter in ${skillFile}: ${reason}`);
|
|
5897
|
+
this.skillFile = skillFile;
|
|
5898
|
+
this.reason = reason;
|
|
5899
|
+
}
|
|
5900
|
+
skillFile;
|
|
5901
|
+
reason;
|
|
5902
|
+
name = "InvalidSkillFrontmatterError";
|
|
5903
|
+
};
|
|
5904
|
+
var FRONTMATTER_DELIMITER = "---";
|
|
5905
|
+
function parseFrontmatter(rawInput, sourcePath) {
|
|
5906
|
+
const raw = rawInput.replace(/\r\n/g, "\n");
|
|
5907
|
+
if (!raw.startsWith(`${FRONTMATTER_DELIMITER}
|
|
5908
|
+
`)) {
|
|
5909
|
+
throw new InvalidSkillFrontmatterError(sourcePath, 'file does not start with frontmatter delimiter "---"');
|
|
5910
|
+
}
|
|
5911
|
+
const afterFirst = raw.slice(FRONTMATTER_DELIMITER.length + 1);
|
|
5912
|
+
const closingIdx = afterFirst.indexOf(`
|
|
5913
|
+
${FRONTMATTER_DELIMITER}
|
|
5914
|
+
`);
|
|
5915
|
+
if (closingIdx === -1) {
|
|
5916
|
+
throw new InvalidSkillFrontmatterError(sourcePath, 'missing closing frontmatter delimiter "---"');
|
|
5917
|
+
}
|
|
5918
|
+
const frontmatterText = afterFirst.slice(0, closingIdx);
|
|
5919
|
+
const body = afterFirst.slice(closingIdx + `
|
|
5920
|
+
${FRONTMATTER_DELIMITER}
|
|
5921
|
+
`.length);
|
|
5922
|
+
let name = "";
|
|
5923
|
+
let description23 = "";
|
|
5924
|
+
for (const line of frontmatterText.split("\n")) {
|
|
5925
|
+
const trimmed = line.trim();
|
|
5926
|
+
if (trimmed.length === 0) continue;
|
|
5927
|
+
const colonIdx = trimmed.indexOf(":");
|
|
5928
|
+
if (colonIdx === -1) continue;
|
|
5929
|
+
const key = trimmed.slice(0, colonIdx).trim();
|
|
5930
|
+
const value = trimmed.slice(colonIdx + 1).trim();
|
|
5931
|
+
if (key === "name") name = value;
|
|
5932
|
+
else if (key === "description") description23 = value;
|
|
5933
|
+
}
|
|
5934
|
+
if (name.length === 0) {
|
|
5935
|
+
throw new InvalidSkillFrontmatterError(sourcePath, 'missing required field "name"');
|
|
5936
|
+
}
|
|
5937
|
+
if (description23.length === 0) {
|
|
5938
|
+
throw new InvalidSkillFrontmatterError(sourcePath, 'missing required field "description"');
|
|
5939
|
+
}
|
|
5940
|
+
return { name, description: description23, body };
|
|
5941
|
+
}
|
|
5942
|
+
function loadSkillsFromDir(skillsDir) {
|
|
5943
|
+
const entries = readdirSync6(skillsDir);
|
|
5944
|
+
const skills = [];
|
|
5945
|
+
for (const entry of entries) {
|
|
5946
|
+
const entryPath = join8(skillsDir, entry);
|
|
5947
|
+
let stat;
|
|
5948
|
+
try {
|
|
5949
|
+
stat = statSync4(entryPath);
|
|
5950
|
+
} catch {
|
|
5951
|
+
continue;
|
|
5952
|
+
}
|
|
5953
|
+
if (!stat.isDirectory()) continue;
|
|
5954
|
+
const skillFile = join8(entryPath, "SKILL.md");
|
|
5955
|
+
let raw;
|
|
5956
|
+
try {
|
|
5957
|
+
raw = readFileSync9(skillFile, "utf8");
|
|
5958
|
+
} catch {
|
|
5959
|
+
continue;
|
|
5960
|
+
}
|
|
5961
|
+
const { name, description: description23, body } = parseFrontmatter(raw, skillFile);
|
|
5962
|
+
skills.push({ name, description: description23, body, sourcePath: skillFile });
|
|
5136
5963
|
}
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5964
|
+
return [...skills].sort((a, b) => a.name.localeCompare(b.name));
|
|
5965
|
+
}
|
|
5966
|
+
|
|
5967
|
+
// src/tools/skills/skill-corpus.ts
|
|
5968
|
+
init_common();
|
|
5969
|
+
var LIST_TOOL_NAME = "vo_skill_list";
|
|
5970
|
+
var GET_TOOL_NAME = "vo_skill_get";
|
|
5971
|
+
var listDescription = "List the Algosuite skill corpus (name + trigger description for every skill). Call once near session start to learn which skills exist; then fetch the full instructions for a relevant skill with vo_skill_get. This is the same corpus Claude Code loads natively from .claude/skills \u2014 served over MCP so every vendor works from identical playbooks. Pass refresh:true to re-scan from disk.";
|
|
5972
|
+
var getDescription = "Fetch the full markdown instructions of one Algosuite skill by name. Follow the returned instructions for the current task the same way a native skill invocation would. Use vo_skill_list to discover skill names.";
|
|
5973
|
+
var listInputSchema = {
|
|
5974
|
+
type: "object",
|
|
5975
|
+
properties: {
|
|
5976
|
+
refresh: {
|
|
5977
|
+
type: "boolean",
|
|
5978
|
+
description: "Re-scan the skills directory instead of using the cached corpus."
|
|
5979
|
+
}
|
|
5980
|
+
},
|
|
5981
|
+
required: []
|
|
5982
|
+
};
|
|
5983
|
+
var getInputSchema = {
|
|
5984
|
+
type: "object",
|
|
5985
|
+
properties: {
|
|
5986
|
+
name: {
|
|
5987
|
+
type: "string",
|
|
5988
|
+
description: "Skill name exactly as returned by vo_skill_list."
|
|
5989
|
+
}
|
|
5990
|
+
},
|
|
5991
|
+
required: ["name"]
|
|
5992
|
+
};
|
|
5993
|
+
var MAX_WALK_UP_LEVELS = 8;
|
|
5994
|
+
var cachedCorpus = null;
|
|
5995
|
+
function resolveSkillsDir(env = process.env, startDir = process.cwd()) {
|
|
5996
|
+
const override = env.VO_SKILLS_DIR;
|
|
5997
|
+
if (typeof override === "string" && override.length > 0) {
|
|
5998
|
+
const abs = isAbsolute(override) ? override : resolve2(startDir, override);
|
|
5999
|
+
return existsSync7(abs) && statSync5(abs).isDirectory() ? abs : null;
|
|
6000
|
+
}
|
|
6001
|
+
let dir = resolve2(startDir);
|
|
6002
|
+
for (let i = 0; i < MAX_WALK_UP_LEVELS; i += 1) {
|
|
6003
|
+
const candidate = join9(dir, ".claude", "skills");
|
|
6004
|
+
if (existsSync7(candidate) && statSync5(candidate).isDirectory()) return candidate;
|
|
6005
|
+
const parent = dirname5(dir);
|
|
6006
|
+
if (parent === dir) break;
|
|
6007
|
+
dir = parent;
|
|
5149
6008
|
}
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
}
|
|
6009
|
+
return null;
|
|
6010
|
+
}
|
|
6011
|
+
function loadCorpus() {
|
|
6012
|
+
const skillsDir = resolveSkillsDir();
|
|
6013
|
+
if (skillsDir === null) {
|
|
6014
|
+
return {
|
|
6015
|
+
skills: [],
|
|
6016
|
+
skillsDir: null,
|
|
6017
|
+
unavailableReason: "No skills directory found. Set VO_SKILLS_DIR or run inside a repo with .claude/skills."
|
|
6018
|
+
};
|
|
5160
6019
|
}
|
|
5161
|
-
const cwd = rawInput.cwd?.trim() || process.cwd();
|
|
5162
|
-
const memoryDir = getMemoryDir(cwd);
|
|
5163
6020
|
try {
|
|
5164
|
-
|
|
5165
|
-
const result = await pullMemory(
|
|
5166
|
-
controlPlaneUrl.replace(/\/+$/, ""),
|
|
5167
|
-
token,
|
|
5168
|
-
memoryDir,
|
|
5169
|
-
deps.sessionId,
|
|
5170
|
-
fetchFn
|
|
5171
|
-
);
|
|
5172
|
-
return jsonContent({
|
|
5173
|
-
tool: TOOL_NAME22,
|
|
5174
|
-
schema_version: 1,
|
|
5175
|
-
payload: {
|
|
5176
|
-
synced: true,
|
|
5177
|
-
action: "pull",
|
|
5178
|
-
pulled: result.pulled,
|
|
5179
|
-
files: result.files,
|
|
5180
|
-
memory_dir: memoryDir
|
|
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
|
-
}
|
|
6021
|
+
return { skills: loadSkillsFromDir(skillsDir), skillsDir, unavailableReason: null };
|
|
5204
6022
|
} catch (err) {
|
|
5205
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
6023
|
+
const message = err instanceof Error ? `${err.name}: ${err.message}` : String(err);
|
|
6024
|
+
return { skills: [], skillsDir, unavailableReason: message };
|
|
6025
|
+
}
|
|
6026
|
+
}
|
|
6027
|
+
function getCorpus(refresh) {
|
|
6028
|
+
if (refresh || cachedCorpus === null) {
|
|
6029
|
+
cachedCorpus = loadCorpus();
|
|
6030
|
+
}
|
|
6031
|
+
return cachedCorpus;
|
|
6032
|
+
}
|
|
6033
|
+
async function handleSkillList(_deps, rawInput) {
|
|
6034
|
+
const input = rawInput ?? {};
|
|
6035
|
+
const refresh = input.refresh === true;
|
|
6036
|
+
const corpus = getCorpus(refresh);
|
|
6037
|
+
return jsonContent({
|
|
6038
|
+
corpus_available: corpus.unavailableReason === null,
|
|
6039
|
+
skills_dir: corpus.skillsDir,
|
|
6040
|
+
unavailable_reason: corpus.unavailableReason,
|
|
6041
|
+
skill_count: corpus.skills.length,
|
|
6042
|
+
skills: corpus.skills.map((s) => ({ name: s.name, description: s.description }))
|
|
6043
|
+
});
|
|
6044
|
+
}
|
|
6045
|
+
async function handleSkillGet(_deps, rawInput) {
|
|
6046
|
+
const input = rawInput ?? {};
|
|
6047
|
+
if (typeof input.name !== "string" || input.name.trim().length === 0) {
|
|
6048
|
+
throw invalidParams(GET_TOOL_NAME, 'input field "name" (non-empty string) is required');
|
|
6049
|
+
}
|
|
6050
|
+
const requested = input.name.trim();
|
|
6051
|
+
const corpus = getCorpus(false);
|
|
6052
|
+
if (corpus.unavailableReason !== null) {
|
|
5206
6053
|
return jsonContent({
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
synced: false,
|
|
5211
|
-
reason: `Sync failed: ${message}`
|
|
5212
|
-
}
|
|
6054
|
+
corpus_available: false,
|
|
6055
|
+
unavailable_reason: corpus.unavailableReason,
|
|
6056
|
+
skill: null
|
|
5213
6057
|
});
|
|
5214
6058
|
}
|
|
6059
|
+
const skill = corpus.skills.find((s) => s.name === requested);
|
|
6060
|
+
if (skill === void 0) {
|
|
6061
|
+
throw invalidParams(
|
|
6062
|
+
GET_TOOL_NAME,
|
|
6063
|
+
`unknown skill "${requested}". Known skills: ${corpus.skills.map((s) => s.name).join(", ")}`
|
|
6064
|
+
);
|
|
6065
|
+
}
|
|
6066
|
+
return jsonContent({
|
|
6067
|
+
corpus_available: true,
|
|
6068
|
+
skill: {
|
|
6069
|
+
name: skill.name,
|
|
6070
|
+
description: skill.description,
|
|
6071
|
+
instructions: skill.body,
|
|
6072
|
+
source_path: skill.sourcePath
|
|
6073
|
+
}
|
|
6074
|
+
});
|
|
5215
6075
|
}
|
|
5216
6076
|
|
|
5217
6077
|
// src/server.ts
|
|
@@ -5392,6 +6252,62 @@ function buildToolRegistry() {
|
|
|
5392
6252
|
inputSchema: inputSchema22
|
|
5393
6253
|
},
|
|
5394
6254
|
handler: handleSyncConfig
|
|
6255
|
+
},
|
|
6256
|
+
[UPSERT_TOOL_NAME]: {
|
|
6257
|
+
definition: {
|
|
6258
|
+
name: UPSERT_TOOL_NAME,
|
|
6259
|
+
description: upsertDescription,
|
|
6260
|
+
inputSchema: upsertInputSchema
|
|
6261
|
+
},
|
|
6262
|
+
handler: handlePrivateKnowledgeUpsert
|
|
6263
|
+
},
|
|
6264
|
+
[CONTEXT_TOOL_NAME]: {
|
|
6265
|
+
definition: {
|
|
6266
|
+
name: CONTEXT_TOOL_NAME,
|
|
6267
|
+
description: contextDescription,
|
|
6268
|
+
inputSchema: contextInputSchema
|
|
6269
|
+
},
|
|
6270
|
+
handler: handlePrivateKnowledgeContext
|
|
6271
|
+
},
|
|
6272
|
+
[INVALIDATE_TOOL_NAME]: {
|
|
6273
|
+
definition: {
|
|
6274
|
+
name: INVALIDATE_TOOL_NAME,
|
|
6275
|
+
description: invalidateDescription,
|
|
6276
|
+
inputSchema: invalidateInputSchema
|
|
6277
|
+
},
|
|
6278
|
+
handler: handlePrivateKnowledgeInvalidate
|
|
6279
|
+
},
|
|
6280
|
+
[POST_TOOL_NAME]: {
|
|
6281
|
+
definition: {
|
|
6282
|
+
name: POST_TOOL_NAME,
|
|
6283
|
+
description: postDescription,
|
|
6284
|
+
inputSchema: postInputSchema
|
|
6285
|
+
},
|
|
6286
|
+
handler: handleHqWhiteboardPost
|
|
6287
|
+
},
|
|
6288
|
+
[READ_TOOL_NAME]: {
|
|
6289
|
+
definition: {
|
|
6290
|
+
name: READ_TOOL_NAME,
|
|
6291
|
+
description: readDescription,
|
|
6292
|
+
inputSchema: readInputSchema
|
|
6293
|
+
},
|
|
6294
|
+
handler: handleHqWhiteboardRead
|
|
6295
|
+
},
|
|
6296
|
+
[LIST_TOOL_NAME]: {
|
|
6297
|
+
definition: {
|
|
6298
|
+
name: LIST_TOOL_NAME,
|
|
6299
|
+
description: listDescription,
|
|
6300
|
+
inputSchema: listInputSchema
|
|
6301
|
+
},
|
|
6302
|
+
handler: handleSkillList
|
|
6303
|
+
},
|
|
6304
|
+
[GET_TOOL_NAME]: {
|
|
6305
|
+
definition: {
|
|
6306
|
+
name: GET_TOOL_NAME,
|
|
6307
|
+
description: getDescription,
|
|
6308
|
+
inputSchema: getInputSchema
|
|
6309
|
+
},
|
|
6310
|
+
handler: handleSkillGet
|
|
5395
6311
|
}
|
|
5396
6312
|
};
|
|
5397
6313
|
}
|
|
@@ -5447,7 +6363,7 @@ function createServer(options) {
|
|
|
5447
6363
|
// src/cache/sqlite-cache.ts
|
|
5448
6364
|
import { createHash as createHash3 } from "node:crypto";
|
|
5449
6365
|
import { chmodSync as chmodSync3, mkdirSync as mkdirSync5 } from "node:fs";
|
|
5450
|
-
import { dirname as
|
|
6366
|
+
import { dirname as dirname6 } from "node:path";
|
|
5451
6367
|
import { DatabaseSync } from "node:sqlite";
|
|
5452
6368
|
|
|
5453
6369
|
// src/cache/canonicalize.ts
|
|
@@ -5492,7 +6408,7 @@ function normalizeString(s) {
|
|
|
5492
6408
|
function createSqliteCache(options) {
|
|
5493
6409
|
const fileBacked = options.dbPath !== ":memory:";
|
|
5494
6410
|
if (fileBacked) {
|
|
5495
|
-
mkdirSync5(
|
|
6411
|
+
mkdirSync5(dirname6(options.dbPath), { recursive: true, mode: 448 });
|
|
5496
6412
|
}
|
|
5497
6413
|
const versionNamespace = options.cacheVersionNamespace ?? "";
|
|
5498
6414
|
const db = new DatabaseSync(options.dbPath);
|
|
@@ -5569,6 +6485,9 @@ function createSqliteCache(options) {
|
|
|
5569
6485
|
};
|
|
5570
6486
|
}
|
|
5571
6487
|
|
|
6488
|
+
// src/cli.ts
|
|
6489
|
+
init_events_writer();
|
|
6490
|
+
|
|
5572
6491
|
// src/ratchets/stub-client.ts
|
|
5573
6492
|
var HOLLOW_PATTERNS = [
|
|
5574
6493
|
{
|
|
@@ -5656,6 +6575,8 @@ function buildSummary2(args) {
|
|
|
5656
6575
|
}
|
|
5657
6576
|
|
|
5658
6577
|
// src/consensus/engine-client.ts
|
|
6578
|
+
init_events_writer();
|
|
6579
|
+
init_common();
|
|
5659
6580
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
5660
6581
|
|
|
5661
6582
|
// src/consensus/null-client.ts
|
|
@@ -5678,6 +6599,44 @@ function createNullConsensusEngineClient(reason = NULL_CLIENT_DEFAULT_REASON) {
|
|
|
5678
6599
|
};
|
|
5679
6600
|
}
|
|
5680
6601
|
|
|
6602
|
+
// src/consensus/meta-model-caller.ts
|
|
6603
|
+
var META_CONSENSUS_MODEL = "muse-spark-1.1";
|
|
6604
|
+
var META_MODEL_API_KEY_ENV = "MODEL_API_KEY";
|
|
6605
|
+
var META_MODEL_API_KEY_ALIAS = "META_API";
|
|
6606
|
+
function createMetaModelCaller(options = {}) {
|
|
6607
|
+
void options;
|
|
6608
|
+
return async function callMetaWithMetrics2() {
|
|
6609
|
+
throw new Error(
|
|
6610
|
+
"Muse Spark direct consensus is disabled. Use an explicit sanitized task capsule through the AlgoSuite Model Firewall."
|
|
6611
|
+
);
|
|
6612
|
+
};
|
|
6613
|
+
}
|
|
6614
|
+
var callMetaWithMetrics = createMetaModelCaller();
|
|
6615
|
+
|
|
6616
|
+
// src/consensus/consensus-panel.ts
|
|
6617
|
+
var VO_MCP_CONSENSUS_PANEL = {
|
|
6618
|
+
anthropic: "claude-opus-4-7",
|
|
6619
|
+
openai: "gpt-5",
|
|
6620
|
+
// gemini-2.5-FLASH (not -pro): flash accepts the default thinkingBudget=0 from
|
|
6621
|
+
// callGeminiWithMetrics; 2.5-pro REJECTS budget 0 ("only works in thinking mode").
|
|
6622
|
+
// Flash is also ~10x cheaper. 2026-06-02.
|
|
6623
|
+
google: "gemini-2.5-flash",
|
|
6624
|
+
deepseek: "deepseek-chat",
|
|
6625
|
+
// Muse Spark identity is owned by meta-model-caller.ts (single source of
|
|
6626
|
+
// truth for the meta slot); re-exported here so the panel stays complete.
|
|
6627
|
+
meta: META_CONSENSUS_MODEL
|
|
6628
|
+
};
|
|
6629
|
+
function getVoMcpConsensusPanel(panel = VO_MCP_CONSENSUS_PANEL) {
|
|
6630
|
+
for (const [provider, modelId] of Object.entries(panel)) {
|
|
6631
|
+
if (typeof modelId !== "string" || modelId.trim().length === 0) {
|
|
6632
|
+
throw new Error(
|
|
6633
|
+
`getVoMcpConsensusPanel: panel slot "${provider}" has a missing or blank model ID`
|
|
6634
|
+
);
|
|
6635
|
+
}
|
|
6636
|
+
}
|
|
6637
|
+
return panel;
|
|
6638
|
+
}
|
|
6639
|
+
|
|
5681
6640
|
// src/consensus/engine-options.ts
|
|
5682
6641
|
var AGREEMENT_GATE_ENV_VAR = "VO_CONSENSUS_AGREEMENT_GATE";
|
|
5683
6642
|
function isTruthyFlag(raw) {
|
|
@@ -5724,6 +6683,25 @@ function mapFanOutDiagnostics(fd) {
|
|
|
5724
6683
|
refused: fd.refused
|
|
5725
6684
|
};
|
|
5726
6685
|
}
|
|
6686
|
+
var SHADOW_SYNTHESIS_ENV_VAR = "VO_CONSENSUS_SHADOW";
|
|
6687
|
+
function shadowEnabled(env) {
|
|
6688
|
+
const raw = (env ?? {})[SHADOW_SYNTHESIS_ENV_VAR];
|
|
6689
|
+
if (raw === void 0) return true;
|
|
6690
|
+
const norm = raw.trim().toLowerCase();
|
|
6691
|
+
return !(norm === "0" || norm === "false" || norm === "no" || norm === "off" || norm === "");
|
|
6692
|
+
}
|
|
6693
|
+
function mapShadowSynthesis(s) {
|
|
6694
|
+
if (s === void 0) return void 0;
|
|
6695
|
+
return {
|
|
6696
|
+
incumbent: { verdict: s.incumbent.verdict, confidence: s.incumbent.confidence, synthesizer: s.incumbent.synthesizer },
|
|
6697
|
+
adaptive: {
|
|
6698
|
+
verdict: s.adaptive.verdict,
|
|
6699
|
+
confidence: s.adaptive.confidence,
|
|
6700
|
+
...s.adaptive.calibrated_confidence !== void 0 ? { calibrated_confidence: s.adaptive.calibrated_confidence } : {}
|
|
6701
|
+
},
|
|
6702
|
+
agree: s.agree
|
|
6703
|
+
};
|
|
6704
|
+
}
|
|
5727
6705
|
function mapCitationGrade(cg) {
|
|
5728
6706
|
if (cg === void 0) return void 0;
|
|
5729
6707
|
return {
|
|
@@ -5864,7 +6842,12 @@ function createEngineConsensusClient(options) {
|
|
|
5864
6842
|
const engineOptions = {
|
|
5865
6843
|
panel,
|
|
5866
6844
|
...options.per_model_timeout_ms !== void 0 ? { per_model_timeout_ms: options.per_model_timeout_ms } : {},
|
|
5867
|
-
...agreementGate !== void 0 ? { agreement_gate: agreementGate } : {}
|
|
6845
|
+
...agreementGate !== void 0 ? { agreement_gate: agreementGate } : {},
|
|
6846
|
+
// Stage A7-shadow: run the adaptive verdict alongside the live one for grading.
|
|
6847
|
+
// Cheap (pure log-odds over already-fetched verdicts; no extra model calls),
|
|
6848
|
+
// PII-free, and never alters the live verdict. ON by default; kill with
|
|
6849
|
+
// VO_CONSENSUS_SHADOW=0. Cold-start has no skill registry → neutral priors.
|
|
6850
|
+
shadow_synthesis: { enabled: shadowEnabled(options.env) }
|
|
5868
6851
|
};
|
|
5869
6852
|
const sources = request.source_urls;
|
|
5870
6853
|
const useSourceGrounded = sources !== void 0 && sources.length > 0 && typeof engine.runSourceGroundedConsensus === "function";
|
|
@@ -5920,6 +6903,12 @@ function createEngineConsensusClient(options) {
|
|
|
5920
6903
|
...sourceExtras?.escalation_reason !== void 0 ? { escalation_reason: sourceExtras.escalation_reason } : response.escalation_reason !== void 0 ? { escalation_reason: response.escalation_reason } : {},
|
|
5921
6904
|
// Feature 1 (agreement-gate) — fan-out diagnostics (additive telemetry).
|
|
5922
6905
|
...mapFanOutDiagnostics(response.fan_out_diagnostics) !== void 0 ? { fan_out_diagnostics: mapFanOutDiagnostics(response.fan_out_diagnostics) } : {},
|
|
6906
|
+
// Stage A7-shadow — adaptive-vs-incumbent comparison (PII-free; present iff shadow on).
|
|
6907
|
+
...mapShadowSynthesis(response.shadow_synthesis) !== void 0 ? { shadow_synthesis: mapShadowSynthesis(response.shadow_synthesis) } : {},
|
|
6908
|
+
// Critique-uptake (2026-07-20 red-team fix) — verifier-critique
|
|
6909
|
+
// visibility report; previously computed by the engine on every
|
|
6910
|
+
// call but dropped at this boundary.
|
|
6911
|
+
...response.critique_uptake !== void 0 ? { critique_uptake: response.critique_uptake } : {},
|
|
5923
6912
|
// Source-grounded additive outputs (Tier-4 features).
|
|
5924
6913
|
...useSourceGrounded ? { source_grounded: true } : {},
|
|
5925
6914
|
...sourceExtras?.citation_grade !== void 0 ? { citation_grade: sourceExtras.citation_grade } : {},
|
|
@@ -5935,25 +6924,12 @@ function createEngineConsensusClient(options) {
|
|
|
5935
6924
|
}
|
|
5936
6925
|
};
|
|
5937
6926
|
}
|
|
5938
|
-
var DEFAULT_MODELS =
|
|
5939
|
-
// These ids match the strategic-roadmap §4 `newsStandard` / `newsDeep` panel
|
|
5940
|
-
// intent — current production model ids. Per handoff §C-3 these MUST come
|
|
5941
|
-
// from `CONSENSUS_PANELS` in `functions-shared/shared-model-resolvers.ts`
|
|
5942
|
-
// for V1; placeholder defaults here keep Phase 2 Lane A non-blocking.
|
|
5943
|
-
anthropic: "claude-opus-4-7",
|
|
5944
|
-
openai: "gpt-5",
|
|
5945
|
-
// gemini-2.5-FLASH (not -pro): flash accepts the default thinkingBudget=0 from
|
|
5946
|
-
// callGeminiWithMetrics; 2.5-pro REJECTS budget 0 ("only works in thinking mode").
|
|
5947
|
-
// Flash is also ~10x cheaper. 2026-06-02.
|
|
5948
|
-
google: "gemini-2.5-flash",
|
|
5949
|
-
deepseek: "deepseek-chat"
|
|
5950
|
-
};
|
|
6927
|
+
var DEFAULT_MODELS = getVoMcpConsensusPanel();
|
|
5951
6928
|
function probeProviders(env = process.env) {
|
|
5952
6929
|
const out = [];
|
|
5953
6930
|
if ((env["ANTHROPIC_API_KEY"] ?? "").trim().length > 0) out.push("anthropic");
|
|
5954
6931
|
if ((env["OPENAI_API_KEY"] ?? "").trim().length > 0) out.push("openai");
|
|
5955
6932
|
if ((env["GOOGLE_API_KEY"] ?? "").trim().length > 0) out.push("google");
|
|
5956
|
-
if ((env["DEEPSEEK_API_KEY"] ?? "").trim().length > 0) out.push("deepseek");
|
|
5957
6933
|
return out;
|
|
5958
6934
|
}
|
|
5959
6935
|
async function loadFactoryAndCallers(injectedEngine, injectedShared) {
|
|
@@ -5999,21 +6975,24 @@ async function tryCreateEngineConsensusClientFromEnvAsync(options = {}) {
|
|
|
5999
6975
|
anthropic: loaded.shared.callAnthropicWithMetrics,
|
|
6000
6976
|
openai: loaded.shared.callOpenAIWithMetrics,
|
|
6001
6977
|
google: loaded.shared.callGeminiWithMetrics,
|
|
6002
|
-
deepseek: loaded.shared.callDeepSeekWithMetrics
|
|
6978
|
+
deepseek: loaded.shared.callDeepSeekWithMetrics,
|
|
6979
|
+
meta: options.metaCaller ?? callMetaWithMetrics
|
|
6003
6980
|
};
|
|
6004
6981
|
const modelByProvider = {
|
|
6005
6982
|
anthropic: options.models?.anthropic ?? DEFAULT_MODELS.anthropic,
|
|
6006
6983
|
openai: options.models?.openai ?? DEFAULT_MODELS.openai,
|
|
6007
6984
|
google: options.models?.google ?? DEFAULT_MODELS.google,
|
|
6008
|
-
deepseek: options.models?.deepseek ?? DEFAULT_MODELS.deepseek
|
|
6985
|
+
deepseek: options.models?.deepseek ?? DEFAULT_MODELS.deepseek,
|
|
6986
|
+
meta: options.models?.meta ?? DEFAULT_MODELS.meta
|
|
6009
6987
|
};
|
|
6988
|
+
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
6989
|
const panel = [];
|
|
6011
6990
|
for (const p of providers) {
|
|
6012
6991
|
try {
|
|
6013
6992
|
const adapter = loaded.engine.createAdapter(p, {
|
|
6014
6993
|
model: modelByProvider[p],
|
|
6015
6994
|
caller: callerByProvider[p],
|
|
6016
|
-
envSource:
|
|
6995
|
+
envSource: adapterEnv
|
|
6017
6996
|
});
|
|
6018
6997
|
panel.push(adapter);
|
|
6019
6998
|
} catch {
|
|
@@ -6170,6 +7149,68 @@ function tryCreateMoatConsensusClientFromEnv(env = process.env, fetchFn) {
|
|
|
6170
7149
|
});
|
|
6171
7150
|
}
|
|
6172
7151
|
|
|
7152
|
+
// src/consensus/fallback-client.ts
|
|
7153
|
+
var MIN_VALID_LOCAL_VERDICTS = 2;
|
|
7154
|
+
var INSUFFICIENT_LOCAL_VERDICTS_REASON = "local-panel-insufficient-valid-verdicts";
|
|
7155
|
+
function createConsensusFallbackClient(primary, fallback, options = {}) {
|
|
7156
|
+
return {
|
|
7157
|
+
async run(request) {
|
|
7158
|
+
const primaryResult = await primary.run(request);
|
|
7159
|
+
if (request.signal?.aborted || !primaryResult.ok && primaryResult.reason === CANCELLED_REASON) {
|
|
7160
|
+
return primaryResult;
|
|
7161
|
+
}
|
|
7162
|
+
if (primaryResult.ok) {
|
|
7163
|
+
const validVerdicts = primaryResult.per_model_verdicts.filter(
|
|
7164
|
+
(verdict) => verdict.verdict !== "error"
|
|
7165
|
+
);
|
|
7166
|
+
if (validVerdicts.length >= MIN_VALID_LOCAL_VERDICTS) return primaryResult;
|
|
7167
|
+
options.onFallback?.(INSUFFICIENT_LOCAL_VERDICTS_REASON);
|
|
7168
|
+
return fallback.run(request);
|
|
7169
|
+
}
|
|
7170
|
+
options.onFallback?.(primaryResult.reason);
|
|
7171
|
+
return fallback.run(request);
|
|
7172
|
+
}
|
|
7173
|
+
};
|
|
7174
|
+
}
|
|
7175
|
+
|
|
7176
|
+
// src/consensus/local-credential-env.ts
|
|
7177
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
7178
|
+
var require2 = createRequire2(import.meta.url);
|
|
7179
|
+
var KEY_SERVICE = "algosuite-vo";
|
|
7180
|
+
var KEYCHAIN_TARGETS = [
|
|
7181
|
+
{ account: "anthropic-api-key", envVar: "ANTHROPIC_API_KEY" },
|
|
7182
|
+
{ account: "openai-api-key", envVar: "OPENAI_API_KEY" },
|
|
7183
|
+
{ account: "meta-api-key", envVar: "MODEL_API_KEY" }
|
|
7184
|
+
];
|
|
7185
|
+
function loadEntryCtor() {
|
|
7186
|
+
try {
|
|
7187
|
+
return require2("@napi-rs/keyring").Entry ?? null;
|
|
7188
|
+
} catch {
|
|
7189
|
+
return null;
|
|
7190
|
+
}
|
|
7191
|
+
}
|
|
7192
|
+
function readKey(EntryCtor, account) {
|
|
7193
|
+
try {
|
|
7194
|
+
return new EntryCtor(KEY_SERVICE, account).getPassword()?.trim() || null;
|
|
7195
|
+
} catch {
|
|
7196
|
+
return null;
|
|
7197
|
+
}
|
|
7198
|
+
}
|
|
7199
|
+
function withLocalConsensusCredentials(baseEnv = process.env, options = {}) {
|
|
7200
|
+
const env = { ...baseEnv };
|
|
7201
|
+
if (!env.OPENAI_API_KEY?.trim() && env.CODEX_API_KEY?.trim()) {
|
|
7202
|
+
env.OPENAI_API_KEY = env.CODEX_API_KEY;
|
|
7203
|
+
}
|
|
7204
|
+
const EntryCtor = options.EntryCtor === void 0 ? loadEntryCtor() : options.EntryCtor;
|
|
7205
|
+
if (!EntryCtor) return env;
|
|
7206
|
+
for (const target of KEYCHAIN_TARGETS) {
|
|
7207
|
+
if (env[target.envVar]?.trim()) continue;
|
|
7208
|
+
const key = readKey(EntryCtor, target.account);
|
|
7209
|
+
if (key) env[target.envVar] = key;
|
|
7210
|
+
}
|
|
7211
|
+
return env;
|
|
7212
|
+
}
|
|
7213
|
+
|
|
6173
7214
|
// src/cloud/login.ts
|
|
6174
7215
|
init_credential_store();
|
|
6175
7216
|
import { createServer as createServer2 } from "node:http";
|
|
@@ -6204,7 +7245,7 @@ function processCapture(rawBody, expectedState, store) {
|
|
|
6204
7245
|
};
|
|
6205
7246
|
}
|
|
6206
7247
|
function captureHtml() {
|
|
6207
|
-
return `<!doctype html><html><head><meta charset="utf-8"><title>
|
|
7248
|
+
return `<!doctype html><html><head><meta charset="utf-8"><title>AlgoHQ login</title></head>
|
|
6208
7249
|
<body style="font-family:system-ui;max-width:32rem;margin:4rem auto;text-align:center">
|
|
6209
7250
|
<h2 id="m">Completing sign-in\u2026</h2>
|
|
6210
7251
|
<script>
|
|
@@ -6220,7 +7261,7 @@ function captureHtml() {
|
|
|
6220
7261
|
function defaultOpenBrowser(url) {
|
|
6221
7262
|
const platform = process.platform;
|
|
6222
7263
|
if (platform === "win32") {
|
|
6223
|
-
spawn2("
|
|
7264
|
+
spawn2("rundll32", ["url.dll,FileProtocolHandler", url], { detached: true, stdio: "ignore" }).unref();
|
|
6224
7265
|
} else if (platform === "darwin") {
|
|
6225
7266
|
spawn2("open", [url], { detached: true, stdio: "ignore" }).unref();
|
|
6226
7267
|
} else {
|
|
@@ -6235,7 +7276,7 @@ async function runLogin(opts = {}) {
|
|
|
6235
7276
|
const nowIso = opts.nowIso ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
6236
7277
|
const openBrowser = opts.openBrowser ?? defaultOpenBrowser;
|
|
6237
7278
|
const state = randomBytes(32).toString("base64url");
|
|
6238
|
-
return new Promise((
|
|
7279
|
+
return new Promise((resolve3, reject) => {
|
|
6239
7280
|
let settled = false;
|
|
6240
7281
|
const finish = (err, result) => {
|
|
6241
7282
|
if (settled) return;
|
|
@@ -6243,7 +7284,7 @@ async function runLogin(opts = {}) {
|
|
|
6243
7284
|
clearTimeout(timer);
|
|
6244
7285
|
server.close();
|
|
6245
7286
|
if (err) reject(err);
|
|
6246
|
-
else
|
|
7287
|
+
else resolve3(result);
|
|
6247
7288
|
};
|
|
6248
7289
|
const server = createServer2((req, res) => {
|
|
6249
7290
|
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
@@ -6291,7 +7332,7 @@ async function runLogin(opts = {}) {
|
|
|
6291
7332
|
result = { ...capt.email ? { email: capt.email } : {}, credentialPath: path3 };
|
|
6292
7333
|
}
|
|
6293
7334
|
res.writeHead(outcome.httpStatus, { "content-type": "text/html; charset=utf-8" });
|
|
6294
|
-
res.end(outcome.ok ? "<h2>
|
|
7335
|
+
res.end(outcome.ok ? "<h2>AlgoHQ login complete \u2014 you can close this tab.</h2>" : `<h2>Login failed: ${outcome.error}</h2>`);
|
|
6295
7336
|
finish(outcome.ok ? null : new Error(outcome.error ?? "login failed"), result);
|
|
6296
7337
|
})();
|
|
6297
7338
|
});
|
|
@@ -6354,10 +7395,11 @@ async function exchangeForVoCredential(opts) {
|
|
|
6354
7395
|
}
|
|
6355
7396
|
|
|
6356
7397
|
// src/cli.ts
|
|
7398
|
+
init_common();
|
|
6357
7399
|
function defaultCacheDbPath() {
|
|
6358
7400
|
const env = process.env["VO_MCP_DB_PATH"];
|
|
6359
7401
|
if (env && env.length > 0) return env;
|
|
6360
|
-
return
|
|
7402
|
+
return join10(homedir6(), ".claude", "vo-mcp-cache.db");
|
|
6361
7403
|
}
|
|
6362
7404
|
async function probeEngineVersion() {
|
|
6363
7405
|
try {
|
|
@@ -6432,11 +7474,23 @@ async function main() {
|
|
|
6432
7474
|
const ratchets = createStubRatchetClient();
|
|
6433
7475
|
const testModule = process.env["VO_MCP_TEST_ENGINE_MODULE"];
|
|
6434
7476
|
const testClient = testModule !== void 0 && testModule.length > 0 ? await loadTestEngineClient(testModule) : null;
|
|
7477
|
+
const localEnv = withLocalConsensusCredentials();
|
|
7478
|
+
const localProviders = probeProviders(localEnv);
|
|
7479
|
+
for (const key of ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GOOGLE_API_KEY", "DEEPSEEK_API_KEY", "MODEL_API_KEY"]) {
|
|
7480
|
+
if (!process.env[key] && localEnv[key]) process.env[key] = localEnv[key];
|
|
7481
|
+
}
|
|
7482
|
+
const localConsensus = tryCreateEngineConsensusClientFromEnv({ envSource: localEnv });
|
|
6435
7483
|
const cloudConsensus = testClient ? null : tryCreateMoatConsensusClientFromEnv();
|
|
6436
|
-
|
|
6437
|
-
|
|
7484
|
+
let consensus = testClient ?? localConsensus;
|
|
7485
|
+
if (!testClient && cloudConsensus && localProviders.length >= 2) {
|
|
7486
|
+
console.error(`[vo-mcp] local-first consensus active (${localProviders.join(", ")}); cloud moat is fallback-only`);
|
|
7487
|
+
consensus = createConsensusFallbackClient(localConsensus, cloudConsensus, {
|
|
7488
|
+
onFallback: (reason) => console.error(`[vo-mcp] local consensus unavailable (${reason}); using cloud moat fallback`)
|
|
7489
|
+
});
|
|
7490
|
+
} else if (!testClient && cloudConsensus) {
|
|
7491
|
+
console.error("[vo-mcp] fewer than 2 linked local providers; cloud moat consensus active");
|
|
7492
|
+
consensus = cloudConsensus;
|
|
6438
7493
|
}
|
|
6439
|
-
const consensus = testClient ?? cloudConsensus ?? tryCreateEngineConsensusClientFromEnv();
|
|
6440
7494
|
let adminCallables = null;
|
|
6441
7495
|
try {
|
|
6442
7496
|
adminCallables = buildAdminCallableClientFromEnv();
|
|
@@ -6481,10 +7535,50 @@ if (process.argv[2] === "login") {
|
|
|
6481
7535
|
console.error(`[vo-mcp] login successful${r.email ? ` as ${r.email}` : ""}. Credential stored at ${r.credentialPath}.`);
|
|
6482
7536
|
console.error("[vo-mcp] You can now remove VO_CONTROL_PLANE_ADMIN_TOKEN (the god-token) from your MCP config.");
|
|
6483
7537
|
console.error("[vo-mcp] NOTE: per-user auth requires VO_OPERATOR_ALLOWED_EMAILS (with your email) on the deployed control-plane.");
|
|
6484
|
-
process.exit(0);
|
|
6485
7538
|
}).catch((err) => {
|
|
6486
7539
|
console.error("[vo-mcp] login failed:", err instanceof Error ? err.message : String(err));
|
|
6487
|
-
process.
|
|
7540
|
+
process.exitCode = 1;
|
|
7541
|
+
});
|
|
7542
|
+
} else if (process.argv[2] === "sync") {
|
|
7543
|
+
const action = process.argv[3];
|
|
7544
|
+
if (action !== "push" && action !== "pull") {
|
|
7545
|
+
console.error("[vo-mcp] usage: vo-mcp sync <push|pull> [--cwd <path>]");
|
|
7546
|
+
process.exit(2);
|
|
7547
|
+
}
|
|
7548
|
+
const cwdFlag = process.argv.indexOf("--cwd");
|
|
7549
|
+
const cwd = cwdFlag >= 0 && typeof process.argv[cwdFlag + 1] === "string" ? process.argv[cwdFlag + 1] : process.cwd();
|
|
7550
|
+
const sessionId = randomUUID5();
|
|
7551
|
+
const appendSyncLog = async (line) => {
|
|
7552
|
+
try {
|
|
7553
|
+
const { appendFileSync: appendFileSync2, mkdirSync: mkdirSync6 } = await import("node:fs");
|
|
7554
|
+
const { join: join11 } = await import("node:path");
|
|
7555
|
+
const { homedir: homedir7 } = await import("node:os");
|
|
7556
|
+
const dir = join11(homedir7(), ".claude");
|
|
7557
|
+
mkdirSync6(dir, { recursive: true });
|
|
7558
|
+
appendFileSync2(join11(dir, "vo-mcp-sync.log"), `${line}
|
|
7559
|
+
`, "utf8");
|
|
7560
|
+
} catch {
|
|
7561
|
+
}
|
|
7562
|
+
};
|
|
7563
|
+
Promise.resolve().then(() => (init_sync_config(), sync_config_exports)).then(async ({ runMemorySync: runMemorySync2, isNoopSyncReason: isNoopSyncReason2 }) => {
|
|
7564
|
+
const r = await runMemorySync2(action, cwd, sessionId);
|
|
7565
|
+
const stamp = `${sessionId} ${action}`;
|
|
7566
|
+
if (r.synced) {
|
|
7567
|
+
console.error(`[vo-mcp] sync ${action} ok: ${JSON.stringify(r)}`);
|
|
7568
|
+
await appendSyncLog(`ok ${stamp} ${JSON.stringify(r)}`);
|
|
7569
|
+
} else if (isNoopSyncReason2(r.reason)) {
|
|
7570
|
+
console.error(`[vo-mcp] sync ${action} skipped: ${r.reason}`);
|
|
7571
|
+
await appendSyncLog(`skip ${stamp} ${r.reason ?? ""}`);
|
|
7572
|
+
} else {
|
|
7573
|
+
console.error(`[vo-mcp] sync ${action} failed: ${r.reason}`);
|
|
7574
|
+
await appendSyncLog(`FAIL ${stamp} ${r.reason ?? ""}`);
|
|
7575
|
+
process.exitCode = 1;
|
|
7576
|
+
}
|
|
7577
|
+
}).catch(async (err) => {
|
|
7578
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
7579
|
+
console.error("[vo-mcp] sync fatal:", message);
|
|
7580
|
+
await appendSyncLog(`FATAL ${sessionId} ${action} ${message}`);
|
|
7581
|
+
process.exitCode = 1;
|
|
6488
7582
|
});
|
|
6489
7583
|
} else {
|
|
6490
7584
|
main().catch((err) => {
|