hermoso 0.1.267 → 0.1.268
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/mcp/tools.mjs +14 -3
- package/package.json +1 -1
package/mcp/tools.mjs
CHANGED
|
@@ -2318,9 +2318,14 @@ export const holdReasonText = (name, why, ctx = null) => {
|
|
|
2318
2318
|
// ticked accounts on the same brand). The gate is a snapshot; the refusal must not be. Before saying "not connected",
|
|
2319
2319
|
// re-read the connection set ONCE and re-gate (the same regateForWorkspace use_brand runs); a failed read fails open
|
|
2320
2320
|
// as everywhere else, so the worst case is the old answer, never a wrong refusal of a connector that exists.
|
|
2321
|
-
async function holdReasonRechecked(name, ctx) {
|
|
2321
|
+
async function holdReasonRechecked(name, ctx, args = null) {
|
|
2322
2322
|
let why = holdReasonFor(name, ctx);
|
|
2323
2323
|
if (why !== 'not_connected') return why;
|
|
2324
|
+
// A PER-CALL BRAND IS NOT THE PIN (2026-09-21). The connection snapshot above is the PINNED brand's; a tool that
|
|
2325
|
+
// takes `brand` runs against the brand the caller NAMED, which may have the connector the pin lacks. Refusing it
|
|
2326
|
+
// here answered "not connected" for an account that was connected. Run it: the route gates the named brand itself
|
|
2327
|
+
// and answers the one "not connected" shape (401 + connector) when it really is missing.
|
|
2328
|
+
if (callNamesOwnBrand(name, ctx, args)) return null;
|
|
2324
2329
|
// The re-read must be a GOOD read before it replaces the snapshot: regateForWorkspace fails OPEN on a failed read
|
|
2325
2330
|
// (right for use_brand, wrong here — it would turn "not connected" into "run it and 401"), so a read that did not
|
|
2326
2331
|
// succeed keeps the snapshot's answer and the refusal it already earned.
|
|
@@ -2330,6 +2335,12 @@ async function holdReasonRechecked(name, ctx) {
|
|
|
2330
2335
|
try { await regateForWorkspace(ctx, fresh); } catch { return why; }
|
|
2331
2336
|
return holdReasonFor(name, ctx);
|
|
2332
2337
|
}
|
|
2338
|
+
function callNamesOwnBrand(name, ctx, args) {
|
|
2339
|
+
const b = args && typeof args === 'object' ? args.brand : null;
|
|
2340
|
+
if (typeof b !== 'string' || !b.trim()) return false;
|
|
2341
|
+
const h = ctx && ctx.handleOf && ctx.handleOf[name];
|
|
2342
|
+
try { return !!(h && h.inputSchema && h.inputSchema.shape && Object.prototype.hasOwnProperty.call(h.inputSchema.shape, 'brand')); } catch { return false; }
|
|
2343
|
+
}
|
|
2333
2344
|
// MODULE SCOPE, NOT INSIDE buildTools — and the reason is worth keeping. tools/generation-matrix-check.mjs
|
|
2334
2345
|
// derives the generation surface by slicing the source between registerTool() calls and asking which
|
|
2335
2346
|
// /api/generate/* routes each slice touches. Written just above find_tools, this helper's apiGet fell
|
|
@@ -2376,7 +2387,7 @@ export function installHeldToolCalls(mcp, ctx) {
|
|
|
2376
2387
|
const h = name && ctx.handleOf[name];
|
|
2377
2388
|
if (!h && LEGACY_TOOL_NAMES[name]) return legacyToolAnswer(name, request, extra, ctx); // a name only an old snapshot still holds
|
|
2378
2389
|
if (h && h.enabled === false) {
|
|
2379
|
-
const why = await holdReasonRechecked(name, ctx);
|
|
2390
|
+
const why = await holdReasonRechecked(name, ctx, request?.params?.arguments);
|
|
2380
2391
|
if (why) { const t = holdReasonText(name, why, ctx); reportDeadEnd(why, name, t); return withHints({ content: [{ type: 'text', text: t }], isError: true }, holdHints(name, why, ctx)); }
|
|
2381
2392
|
// The call itself is the evidence: this host's tool list still names a tool the session holds out on size,
|
|
2382
2393
|
// i.e. the host is serving a stale roster. Run it (that is the point) and record that it happened.
|
|
@@ -3103,7 +3114,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
3103
3114
|
return { content: [{ type: 'text', text: `No tool named "${n}".${near.length ? ` Did you mean: ${near.join(', ')}?` : ''} find_tools({query}) searches every tool by name or task.` }], isError: true };
|
|
3104
3115
|
}
|
|
3105
3116
|
if (n === 'call_tool' || n === 'find_tools' || n === 'enable_tools') return { content: [{ type: 'text', text: `${n} is a roster tool; call it directly.` }], isError: true };
|
|
3106
|
-
const why = await holdReasonRechecked(n, ctx);
|
|
3117
|
+
const why = await holdReasonRechecked(n, ctx, args);
|
|
3107
3118
|
// ONE sentence per hold, from holdReasonText. call_tool used to spell its own copies, so the connect link added there
|
|
3108
3119
|
// never reached claude.ai or ChatGPT, the two hosts that run held tools through here.
|
|
3109
3120
|
if (why) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hermoso",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.268",
|
|
4
4
|
"mcpName": "io.github.hermoso-ai/hermoso",
|
|
5
5
|
"description": "Marketing on autopilot, run from your own AI agent. 855 tools. Publishing, scheduling, ad campaign management, comments, DMs and analytics cost no credits on every plan; credits are only for generating creative and for Ad Spy research. AD PLATFORMS: Meta, Google Ads, TikTok Ads, LinkedIn Ads, Reddit Ads, X Ads, Pinterest Ads, Snapchat Ads, Microsoft Advertising, Apple Search Ads and ChatGPT Ads, plus product feeds in Google Merchant Center. PUBLISHING AND SCHEDULING: Facebook, Instagram, Threads, TikTok, YouTube, X, LinkedIn, Pinterest, Bluesky and Telegram. AD RESEARCH: the Meta, Google and LinkedIn ad libraries plus organic TikTok, Instagram, YouTube, Threads and Reddit. ANALYTICS: Google Analytics 4, Google Search Console and every connected platform's own post and campaign insights. Also brand onboarding, 50+ image and video generation models, ad scoring, competitor teardowns, Google Drive and OneDrive, a CLI and installable Claude skills.",
|
|
6
6
|
"type": "module",
|