@awesomate/hosting-mcp 0.92.2 → 0.93.0
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/dist/index.js +197 -32
- package/package.json +1 -1
- package/skill/CHANGELOG.json +11 -0
- package/skill/awesomate-knowledge/SKILL.md +1 -0
package/dist/index.js
CHANGED
|
@@ -40747,6 +40747,21 @@ function installedSkillVersion(name) {
|
|
|
40747
40747
|
return null;
|
|
40748
40748
|
}
|
|
40749
40749
|
}
|
|
40750
|
+
function compareVersions(a, b) {
|
|
40751
|
+
const pa = a.split(/[.-]/).map((x) => Number.parseInt(x, 10) || 0);
|
|
40752
|
+
const pb = b.split(/[.-]/).map((x) => Number.parseInt(x, 10) || 0);
|
|
40753
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
40754
|
+
const d = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
40755
|
+
if (d !== 0) return d < 0 ? -1 : 1;
|
|
40756
|
+
}
|
|
40757
|
+
return 0;
|
|
40758
|
+
}
|
|
40759
|
+
function oldestInstalledVersion(perSkill) {
|
|
40760
|
+
return perSkill.reduce(
|
|
40761
|
+
(min, s) => s.installed !== null && (min === null || compareVersions(s.installed, min) < 0) ? s.installed : min,
|
|
40762
|
+
null
|
|
40763
|
+
);
|
|
40764
|
+
}
|
|
40750
40765
|
function skillVersions(sourceRoot = DEFAULT_SOURCE_ROOT) {
|
|
40751
40766
|
return bundledSkillNames(sourceRoot).map((name) => ({ name, installed: installedSkillVersion(name) }));
|
|
40752
40767
|
}
|
|
@@ -40803,8 +40818,9 @@ function installSkills(version2, sourceRoot = DEFAULT_SOURCE_ROOT) {
|
|
|
40803
40818
|
}
|
|
40804
40819
|
|
|
40805
40820
|
// src/knowledge.ts
|
|
40806
|
-
|
|
40807
|
-
|
|
40821
|
+
import { createHash } from "node:crypto";
|
|
40822
|
+
function scrubKeyMaterial(text, opts = {}) {
|
|
40823
|
+
return text.replace(/amk_([a-z])_[A-Za-z0-9]{6,}/g, (m, cls) => opts.keepWebsiteKeys && cls === "p" ? m : "[redacted:platform-key]").replace(/amt_[a-z]+_[A-Za-z0-9]{6,}/g, "[redacted:hub-token]");
|
|
40808
40824
|
}
|
|
40809
40825
|
var KNOWLEDGE_GATE_COPY = {
|
|
40810
40826
|
displayName: "Knowledge Base",
|
|
@@ -40890,13 +40906,33 @@ function storageFullPayload(body) {
|
|
|
40890
40906
|
note: "The Knowledge Base storage is full and the source was NOT added. Relay the message exactly as it is; offer no purchase, because none is open to this account or this person right now. Removing sources they no longer need frees space."
|
|
40891
40907
|
};
|
|
40892
40908
|
}
|
|
40909
|
+
async function deleteOrNotFound(config3, path) {
|
|
40910
|
+
try {
|
|
40911
|
+
return await hubDelete(config3, path) ?? { deleted: true };
|
|
40912
|
+
} catch (err) {
|
|
40913
|
+
if (err instanceof HubApiError && err.status === 404 && bodyOf(err)?.error === "not_found") {
|
|
40914
|
+
return {
|
|
40915
|
+
deleted: false,
|
|
40916
|
+
not_found: true,
|
|
40917
|
+
note: "Nothing with that id was there. It may already be deleted (an earlier attempt can go through even when its answer never arrives), or the id is wrong. List again to check before telling the user either way."
|
|
40918
|
+
};
|
|
40919
|
+
}
|
|
40920
|
+
throw err;
|
|
40921
|
+
}
|
|
40922
|
+
}
|
|
40893
40923
|
function bodyOf(err) {
|
|
40894
40924
|
return err.body && typeof err.body === "object" ? err.body : null;
|
|
40895
40925
|
}
|
|
40896
40926
|
function mapKnowledgeError(err, apiBase) {
|
|
40897
40927
|
if (!(err instanceof HubApiError)) throw err;
|
|
40898
40928
|
const body = bodyOf(err);
|
|
40899
|
-
if (err.status === 404)
|
|
40929
|
+
if (err.status === 404) {
|
|
40930
|
+
if (typeof body?.error !== "string" || !/^[a-z0-9_]+$/.test(body.error)) return notAvailablePayload(apiBase);
|
|
40931
|
+
if (body.error === "not_found") {
|
|
40932
|
+
return { error: "not_found", note: typeof body.message === "string" ? body.message : "There is no such item on this account. List again for the current ids." };
|
|
40933
|
+
}
|
|
40934
|
+
throw err;
|
|
40935
|
+
}
|
|
40900
40936
|
if ((err.status === 403 || err.status === 402) && body?.upgrade_required) return upsellPayload(apiBase, body.plan, body);
|
|
40901
40937
|
if (err.status === 403 && body?.consent_required) {
|
|
40902
40938
|
return consentPayload(typeof body.error === "string" ? body.error : 'Knowledge is not using your content: "Use your content for Knowledge" is off', apiBase);
|
|
@@ -41147,10 +41183,7 @@ async function knowledgeSources(config3, args) {
|
|
|
41147
41183
|
}
|
|
41148
41184
|
if (args.action === "remove") {
|
|
41149
41185
|
if (!args.sourceId) throw new Error("sourceId is required for remove");
|
|
41150
|
-
return
|
|
41151
|
-
config3,
|
|
41152
|
-
`/api/knowledge/sources/${encodeURIComponent(args.sourceId)}`
|
|
41153
|
-
);
|
|
41186
|
+
return deleteOrNotFound(config3, `/api/knowledge/sources/${encodeURIComponent(args.sourceId)}`);
|
|
41154
41187
|
}
|
|
41155
41188
|
if (!args.url && !args.sitemap) {
|
|
41156
41189
|
return {
|
|
@@ -41194,15 +41227,18 @@ function renderAnswer(meta, streamedAnswer, streamError = null) {
|
|
|
41194
41227
|
const session = typeof meta.session === "string" && meta.session ? { session: meta.session } : {};
|
|
41195
41228
|
const fallback = typeof meta.answer_plain === "string" && meta.answer_plain || typeof meta.no_answer_message === "string" && meta.no_answer_message || null;
|
|
41196
41229
|
if (status === "error") {
|
|
41230
|
+
const errorCode = typeof meta.error_code === "string" ? meta.error_code : null;
|
|
41231
|
+
const ownKey = errorCode === "byok_key_invalid" || errorCode === "byok_no_credit";
|
|
41197
41232
|
return {
|
|
41198
41233
|
status: "error",
|
|
41199
41234
|
answer: null,
|
|
41200
41235
|
platform_error: true,
|
|
41201
41236
|
not_in_verified_content: false,
|
|
41237
|
+
...errorCode ? { error_code: errorCode } : {},
|
|
41202
41238
|
detail: streamError ?? (streamedAnswer || null),
|
|
41203
41239
|
configured_fallback: fallback,
|
|
41204
41240
|
...session,
|
|
41205
|
-
note: "The knowledge platform failed to answer: a service problem, NOT a content gap. Do not tell the user their content lacks this. Say the service hit a problem, retry once, and if it persists raise it via awesomate_support. configured_fallback is only what an end customer would have seen meanwhile."
|
|
41241
|
+
note: ownKey ? `The agent runs on this account's own OpenRouter key, and OpenRouter ${errorCode === "byok_no_credit" ? "says it is out of credit" : "refused it"}. This is NOT a content gap. Tell the user to top up or replace the key at Settings, Integrations (hub.awesomate.ai/settings?tab=integrations), or to clear the agent's model so it runs on Awesomate's.` : "The knowledge platform failed to answer: a service problem, NOT a content gap. Do not tell the user their content lacks this. Say the service hit a problem, retry once, and if it persists raise it via awesomate_support. configured_fallback is only what an end customer would have seen meanwhile."
|
|
41206
41242
|
};
|
|
41207
41243
|
}
|
|
41208
41244
|
if (status !== "ok") {
|
|
@@ -41636,6 +41672,17 @@ function mapAgentsError(err, apiBase) {
|
|
|
41636
41672
|
}
|
|
41637
41673
|
if (err instanceof HubApiError && err.status === 403) {
|
|
41638
41674
|
const body = bodyOf(err);
|
|
41675
|
+
if (body && body.error === "upgrade_required" && body.capability === "public_widget") {
|
|
41676
|
+
const requiredPlan = typeof body.requiredPlan === "string" ? body.requiredPlan : "Support Plus";
|
|
41677
|
+
return {
|
|
41678
|
+
upgrade_required: true,
|
|
41679
|
+
capability: "public_widget",
|
|
41680
|
+
requiredPlan,
|
|
41681
|
+
...typeof body.message === "string" ? { message: body.message } : {},
|
|
41682
|
+
billingUrl: `${apiBase}/billing?upgrade=${encodeURIComponent(requiredPlan)}`,
|
|
41683
|
+
note: "Nothing was made. Relay the message as it is and let the user decide; never retry."
|
|
41684
|
+
};
|
|
41685
|
+
}
|
|
41639
41686
|
if (body && body.error === "upgrade_required" && body.capability === "knowledge_questions") {
|
|
41640
41687
|
const requiredPlan = typeof body.requiredPlan === "string" ? body.requiredPlan : "Support Plus";
|
|
41641
41688
|
return {
|
|
@@ -41650,6 +41697,107 @@ function mapAgentsError(err, apiBase) {
|
|
|
41650
41697
|
}
|
|
41651
41698
|
return mapKnowledgeError(err, apiBase);
|
|
41652
41699
|
}
|
|
41700
|
+
var WEBSITE_KEY_MAX_ORIGINS = 16;
|
|
41701
|
+
function websiteOrigins(input) {
|
|
41702
|
+
const raw = (input ?? []).map((s) => s.trim()).filter(Boolean);
|
|
41703
|
+
if (!raw.length) return { ok: false, error: "website_key needs origins: the website addresses the chat will run on, like https://example.com.au" };
|
|
41704
|
+
const out = [];
|
|
41705
|
+
for (const r of raw) {
|
|
41706
|
+
if (r.includes("*")) return { ok: false, error: `"${r}": a wildcard is not allowed, because the key would then answer on any website.` };
|
|
41707
|
+
let u;
|
|
41708
|
+
try {
|
|
41709
|
+
u = new URL(r.includes("://") ? r : `https://${r}`);
|
|
41710
|
+
} catch {
|
|
41711
|
+
return { ok: false, error: `"${r}" is not a website address.` };
|
|
41712
|
+
}
|
|
41713
|
+
if (u.protocol !== "https:") return { ok: false, error: `"${r}" must be an https:// address.` };
|
|
41714
|
+
if (u.username || u.password) return { ok: false, error: `"${r}" must not include a username or password.` };
|
|
41715
|
+
if (u.pathname !== "/" || u.search || u.hash) return { ok: false, error: `"${r}" should be just the site address, without a path, for example ${u.origin}` };
|
|
41716
|
+
if (!out.includes(u.origin)) out.push(u.origin);
|
|
41717
|
+
}
|
|
41718
|
+
if (out.length > WEBSITE_KEY_MAX_ORIGINS) return { ok: false, error: `At most ${WEBSITE_KEY_MAX_ORIGINS} websites per key.` };
|
|
41719
|
+
return { ok: true, origins: out };
|
|
41720
|
+
}
|
|
41721
|
+
var htmlAttr = (v) => v.replace(/&/g, "&").replace(/"/g, """);
|
|
41722
|
+
function websiteSnippet(widgetUrl, key, agentId, title) {
|
|
41723
|
+
const attrs = [["data-key", key], ["data-agent", agentId]];
|
|
41724
|
+
if (title?.trim()) attrs.push(["data-title", title.trim()]);
|
|
41725
|
+
const lines = attrs.map(([k, v], i) => ` ${k}="${htmlAttr(v)}"${i === attrs.length - 1 ? "></script>" : ""}`);
|
|
41726
|
+
return [`<script src="${htmlAttr(widgetUrl)}"`, ...lines].join("\n");
|
|
41727
|
+
}
|
|
41728
|
+
function websiteKeyRequestId(agentId, origins, label, cap, liveKeyIds) {
|
|
41729
|
+
const h = createHash("sha256").update(`${agentId}|${[...origins].sort().join(",")}|${label}|${cap ?? "default"}|${[...liveKeyIds].sort().join(",")}`).digest("hex");
|
|
41730
|
+
return `${h.slice(0, 8)}-${h.slice(8, 12)}-4${h.slice(13, 16)}-8${h.slice(17, 20)}-${h.slice(20, 32)}`;
|
|
41731
|
+
}
|
|
41732
|
+
function liveWebsiteKeys(keys, agentId) {
|
|
41733
|
+
const now = Date.now();
|
|
41734
|
+
return keys.filter(
|
|
41735
|
+
(k) => k.class === "p" && (!agentId || k.agent_id === agentId) && !k.revoked_at && !(typeof k.expires_at === "string" && new Date(k.expires_at).getTime() <= now)
|
|
41736
|
+
);
|
|
41737
|
+
}
|
|
41738
|
+
function websiteAgentRefusal(agent) {
|
|
41739
|
+
const name = typeof agent.name === "string" && agent.name ? `"${agent.name}"` : "This agent";
|
|
41740
|
+
if (agent.status !== "published") return `${name} is not published yet, so a website chat would have nothing to answer with. Test it, then publish it (with the user's yes) first.`;
|
|
41741
|
+
if (agent.audience !== "public") return `${name} serves a ${typeof agent.audience === "string" ? agent.audience : "private"} audience, and a website chat answers the public. Change its audience to public and publish again (with the user's yes) first.`;
|
|
41742
|
+
if (agent.suspended_at) return `${name} is paused, so a website chat would not answer. Resume it (with the user's yes) first.`;
|
|
41743
|
+
return null;
|
|
41744
|
+
}
|
|
41745
|
+
var WEBSITE_KEY_NOTE = "A website key is meant to sit in page source: it only answers from the websites listed, only as this agent, and only from sources marked public. It carries its own monthly answer cap (by default half the account's answers), so one busy site cannot use up every answer.";
|
|
41746
|
+
async function websiteKeyAction(config3, args) {
|
|
41747
|
+
const base = "/api/knowledge/agents";
|
|
41748
|
+
if (args.action === "website_keys") {
|
|
41749
|
+
const r = await hubGet(config3, `${base}/keys`);
|
|
41750
|
+
return { keys: liveWebsiteKeys(r.keys ?? [], args.agentId), note: `${WEBSITE_KEY_NOTE} The key itself is shown only once, when it is made; it cannot be read back.` };
|
|
41751
|
+
}
|
|
41752
|
+
if (args.action === "revoke_website_key") {
|
|
41753
|
+
if (!args.keyId) return { error: "invalid_request", note: "revoke_website_key needs keyId (from website_keys)." };
|
|
41754
|
+
const r = await hubGet(config3, `${base}/keys`);
|
|
41755
|
+
if (!liveWebsiteKeys(r.keys ?? []).some((k) => k.key_id === args.keyId)) {
|
|
41756
|
+
return { error: "not_found", note: "There is no working website key with that id. Read website_keys for the ids; other kinds of key are managed in the hub only." };
|
|
41757
|
+
}
|
|
41758
|
+
if (args.confirm !== true) {
|
|
41759
|
+
return { error: "confirmation_required", note: "Revoking this key stops the chat on every website using it at once. Tell the owner, and call again with confirm:true only after they say yes." };
|
|
41760
|
+
}
|
|
41761
|
+
return deleteOrNotFound(config3, `${base}/keys/${encodeURIComponent(args.keyId)}`);
|
|
41762
|
+
}
|
|
41763
|
+
if (!args.agentId) return { error: "invalid_request", note: "website_key needs agentId (from list) and origins." };
|
|
41764
|
+
const origins = websiteOrigins(args.origins);
|
|
41765
|
+
if (!origins.ok) return { error: "invalid_request", note: `${origins.error} Nothing was made.` };
|
|
41766
|
+
const agent = agentOf(await hubGet(config3, `${base}/${encodeURIComponent(args.agentId)}`));
|
|
41767
|
+
const notReady = websiteAgentRefusal(agent);
|
|
41768
|
+
if (notReady) return { error: "agent_not_ready", note: `${notReady} Nothing was made.` };
|
|
41769
|
+
if (args.confirm !== true) {
|
|
41770
|
+
return {
|
|
41771
|
+
error: "confirmation_required",
|
|
41772
|
+
note: `Making a website key puts this agent on ${origins.origins.join(", ")}, where anyone visiting can ask it questions, and those answers count against the account's monthly answers. Tell the owner that, and call again with confirm:true only after they say yes.`
|
|
41773
|
+
};
|
|
41774
|
+
}
|
|
41775
|
+
const status = await hubGet(config3, "/api/knowledge/status");
|
|
41776
|
+
const widgetUrl = typeof status.widget_url === "string" && status.widget_url ? status.widget_url : null;
|
|
41777
|
+
if (!widgetUrl) return { error: "widget_unavailable", note: "This hub has no website chat loader configured, so a key would have nowhere to run. Nothing was made; tell the user to contact Awesomate support." };
|
|
41778
|
+
const listed = await hubGet(config3, `${base}/keys`);
|
|
41779
|
+
const liveIds = liveWebsiteKeys(listed.keys ?? [], args.agentId).map((k) => String(k.key_id));
|
|
41780
|
+
const label = (args.label?.trim() || `website: ${origins.origins.map((o) => o.replace(/^https:\/\//, "")).join(", ")}`).slice(0, 120);
|
|
41781
|
+
const minted = await hubPost(config3, `${base}/keys`, {
|
|
41782
|
+
class: "p",
|
|
41783
|
+
agent_id: args.agentId,
|
|
41784
|
+
allowed_origins: origins.origins,
|
|
41785
|
+
label,
|
|
41786
|
+
request_id: websiteKeyRequestId(args.agentId, origins.origins, label, args.monthlyAnswerCap, liveIds),
|
|
41787
|
+
...args.monthlyAnswerCap !== void 0 ? { monthly_answer_cap: args.monthlyAnswerCap } : {}
|
|
41788
|
+
});
|
|
41789
|
+
const key = typeof minted.key === "string" ? minted.key : "";
|
|
41790
|
+
const title = args.title?.trim() || (typeof agent.name === "string" ? agent.name : "");
|
|
41791
|
+
const cap = typeof minted.monthly_answer_cap === "number" ? minted.monthly_answer_cap : null;
|
|
41792
|
+
return {
|
|
41793
|
+
key_id: minted.key_id ?? null,
|
|
41794
|
+
key,
|
|
41795
|
+
allowed_origins: origins.origins,
|
|
41796
|
+
monthly_answer_cap: cap,
|
|
41797
|
+
snippet: key ? websiteSnippet(widgetUrl, key, args.agentId, title) : null,
|
|
41798
|
+
note: `${WEBSITE_KEY_NOTE} The key is shown ONCE: hand the snippet straight to the user to paste before </body> on those sites (or add it to the site for them), and do not repeat the key anywhere else.` + (cap !== null ? ` Tell the user this key has its own limit of ${cap} answers a month. A key's limit cannot be changed once it is made: to change it, make a new key with monthlyAnswerCap, swap the snippet on the site, then revoke this one.` : "")
|
|
41799
|
+
};
|
|
41800
|
+
}
|
|
41653
41801
|
var VISITOR_TEST_MAX_CHARS = 2e3;
|
|
41654
41802
|
var POLICY_TAGS_MAX = 50;
|
|
41655
41803
|
function policyScope(args) {
|
|
@@ -41668,6 +41816,9 @@ var POLICY_NOTE = "An access policy narrows what a key or a signed-in person may
|
|
|
41668
41816
|
async function knowledgeAgents(config3, args) {
|
|
41669
41817
|
try {
|
|
41670
41818
|
const base = "/api/knowledge/agents";
|
|
41819
|
+
if (args.action === "website_key" || args.action === "website_keys" || args.action === "revoke_website_key") {
|
|
41820
|
+
return await websiteKeyAction(config3, args);
|
|
41821
|
+
}
|
|
41671
41822
|
if (args.action === "logs") {
|
|
41672
41823
|
const q2 = new URLSearchParams();
|
|
41673
41824
|
if (args.agentId) q2.set("agent_id", args.agentId);
|
|
@@ -41745,19 +41896,20 @@ async function knowledgeAgents(config3, args) {
|
|
|
41745
41896
|
const current = scope && typeof scope === "object" && !Array.isArray(scope) ? scope : {};
|
|
41746
41897
|
return touchesScope(scopeArgs) ? { scope: agentPatchBody(scopeArgs, current).scope } : scope !== void 0 ? { scope } : {};
|
|
41747
41898
|
};
|
|
41899
|
+
const callerFields = agentPatchBody({ ...args, collectionIds: void 0, kinds: void 0, sourceIds: void 0 });
|
|
41748
41900
|
let created;
|
|
41749
41901
|
let sentScope;
|
|
41750
41902
|
let extra = {};
|
|
41751
41903
|
if (args.goal) {
|
|
41752
41904
|
const draft = await hubPost(config3, "/api/knowledge/agents/draft", { goal: args.goal });
|
|
41753
41905
|
if (!draft.proposal) return { error: "draft_failed", detail: draft };
|
|
41754
|
-
const body = { ...draft.proposal, ...withScope(draft.proposal.scope)
|
|
41906
|
+
const body = { ...draft.proposal, ...callerFields, ...withScope(draft.proposal.scope) };
|
|
41755
41907
|
sentScope = body.scope;
|
|
41756
41908
|
created = await hubPost(config3, "/api/knowledge/agents", body);
|
|
41757
41909
|
extra = { test_questions: draft.test_questions ?? [], status_note: `Created as a private DRAFT serving audience '${args.audience ?? "private"}': test it, then publish when the user approves.` };
|
|
41758
41910
|
} else {
|
|
41759
41911
|
if (!args.name) return { error: "name (or goal) is required for create" };
|
|
41760
|
-
const body = { name: args.name, ...
|
|
41912
|
+
const body = { ...callerFields, name: args.name, ...withScope(void 0) };
|
|
41761
41913
|
sentScope = body.scope;
|
|
41762
41914
|
created = await hubPost(config3, "/api/knowledge/agents", body);
|
|
41763
41915
|
}
|
|
@@ -41856,7 +42008,7 @@ async function knowledgeCollections(config3, args) {
|
|
|
41856
42008
|
return await hubPatch(config3, `/api/knowledge/collections/${encodeURIComponent(args.collectionId)}`, body);
|
|
41857
42009
|
}
|
|
41858
42010
|
if (args.action === "delete") {
|
|
41859
|
-
return
|
|
42011
|
+
return deleteOrNotFound(config3, `/api/knowledge/collections/${encodeURIComponent(args.collectionId)}`);
|
|
41860
42012
|
}
|
|
41861
42013
|
if (!args.sourceIds?.length) return { error: "invalid_request", note: `${args.action} needs sourceIds` };
|
|
41862
42014
|
return await hubPost(config3, `/api/knowledge/collections/${encodeURIComponent(args.collectionId)}/sources`, {
|
|
@@ -42135,10 +42287,7 @@ function changelogSince(installed) {
|
|
|
42135
42287
|
function skillUpdateInfo() {
|
|
42136
42288
|
const perSkill = skillVersions();
|
|
42137
42289
|
const staleSkills = perSkill.filter((s) => s.installed !== SERVER_VERSION).map((s) => s.name);
|
|
42138
|
-
const oldest = perSkill
|
|
42139
|
-
(min, s) => s.installed !== null && (min === null || s.installed < min) ? s.installed : min,
|
|
42140
|
-
null
|
|
42141
|
-
);
|
|
42290
|
+
const oldest = oldestInstalledVersion(perSkill);
|
|
42142
42291
|
const updateAvailable = perSkill.length === 0 || staleSkills.length > 0;
|
|
42143
42292
|
return {
|
|
42144
42293
|
serverVersion: SERVER_VERSION,
|
|
@@ -43426,13 +43575,13 @@ server.registerTool(
|
|
|
43426
43575
|
}
|
|
43427
43576
|
}
|
|
43428
43577
|
);
|
|
43429
|
-
function knowledgeResult(data) {
|
|
43578
|
+
function knowledgeResult(data, opts = {}) {
|
|
43430
43579
|
return {
|
|
43431
43580
|
content: [
|
|
43432
43581
|
{
|
|
43433
43582
|
type: "text",
|
|
43434
43583
|
text: `${accountStamp()}
|
|
43435
|
-
${scrubKeyMaterial(JSON.stringify(data, null, 2))}`
|
|
43584
|
+
${scrubKeyMaterial(JSON.stringify(data, null, 2), opts)}`
|
|
43436
43585
|
}
|
|
43437
43586
|
]
|
|
43438
43587
|
};
|
|
@@ -43564,7 +43713,7 @@ server.registerTool(
|
|
|
43564
43713
|
author: external_exports.array(external_exports.string().max(200)).max(10).optional(),
|
|
43565
43714
|
year: external_exports.array(external_exports.string().regex(/^\d{4}$/)).max(10).optional(),
|
|
43566
43715
|
doc: external_exports.array(external_exports.string().max(200)).max(50).optional().describe("doc_ids from search hits")
|
|
43567
|
-
}).optional().describe("Ask within a slice of the library: EXACTLY the awesomate_knowledge_search input shapes, reusable verbatim; filters only ever narrow")
|
|
43716
|
+
}).optional().describe("Ask within a slice of the library: EXACTLY the awesomate_knowledge_search input shapes, reusable verbatim; filters only ever narrow. A filtered question is answered by the workspace's 'knowledge' agent (the default agent takes no filters), so its tone can differ from an unfiltered one")
|
|
43568
43717
|
}
|
|
43569
43718
|
},
|
|
43570
43719
|
async ({ question, session, filters }) => {
|
|
@@ -43607,7 +43756,7 @@ server.registerTool(
|
|
|
43607
43756
|
"awesomate_knowledge_agents",
|
|
43608
43757
|
{
|
|
43609
43758
|
annotations: annotate("awesomate_knowledge_agents", MUTATING),
|
|
43610
|
-
description: "The agent builder (multi-agent; the older awesomate_knowledge_agent tool is the single workspace default). action 'list': every agent with status (draft/published vN/suspended). 'get' {agentId}: full config incl. system message and scope. 'create' {goal}: AI drafts the whole setup (instructions, scope, tone, test questions) from the account's own content and saves it as a PRIVATE DRAFT (never live, nothing lost); or {name} for a blank draft. The drafted scope is often too wide: it can come back limited to no collection (so it answers from every business's and every audience's content) or limited to kinds that leave out PDFs (stored as 'book'), FAQs ('faq') or images. Pass collectionIds (and kinds if needed) WITH create to scope it in one step; when the account has collections and the new agent reads none of them, or its kinds skip content the library holds, the result carries a warning listing the collections: fix it with update before testing or publishing. 'update' {agentId, ...fields}: edit the DRAFT config: name, description, systemMessage, noAnswerMessage, grounding, audience, collectionIds (scope to collections from awesomate_knowledge_collections), kinds, sourceIds, maxSources, temperature. Nothing changes for callers until 'publish'. 'test' {agentId, message, sessionId?}: chat with the DRAFT config: free, unmetered, the right way to check wording and scope before going live. WARNING: the draft test ignores the agent's audience, so it is NOT what the public sees for anything touching internal or private sources (seen live: a public-audience draft quoted an internal staff handbook's door code and Wi-Fi password, which the published agent correctly refused a visitor). 'visitor_test' {agentId, message (max 2000 chars), sessionId?}: ask the PUBLISHED agent through the real public visitor path, the only way to check what a website visitor actually gets; run it after publishing whenever the agent is public-facing or its scope includes internal/private sources. It needs the plan the public widget ships with (upgrade_required below it: relay, never retry), and a question it cannot answer lands in the owner's Questions inbox like a real visitor's, so say so before using it. 'publish' {agentId}: makes the draft LIVE immediately for every key bound to the agent: get the user's explicit approval first, and read the version back. 'suspend' / 'resume' {agentId}: stop the agent answering on every key bound to it, or start it again (its last published version); both change what live callers get at once, so get the owner's explicit approval first. 'delete' {agentId, confirm:true}: removes the agent for good and every key bound to it stops answering; tell the owner that and pass confirm:true only after they say yes. 'logs' {agentId?, since?, until?, endpoint?, status?, limit?, cursor?}: the request log (each call to the agents: when, which endpoint, its status), newest first. 'answer_rates' {days? 1-92, default 30}: how each agent is answering, in bands, counts only. 'where_used': each agent with its audience, scope and status, to see which agent a website or automation is using. 'ai_key_status': whether the account has its own AI key saved (names and dates, never a key); saving or removing one is the owner's, in the hub at Settings, Integrations. 'models': the models the account's own AI key can use. Access policies (customer groups): 'policies' lists them; 'policy_get' {policyId}; 'policy_create' {name, description?, collectionIds?|kinds?|sourceIds?|tags?}: a policy named segment:all or segment:<group> is what an agent's customer groups use, so a person in that group answers only from what it allows; 'policy_update' {policyId, name?|description?|scope fields} (a scope REPLACES the stored one); 'policy_delete', 'policy_suspend', 'policy_resume' {policyId}. Every policy change applies to live callers at once: show the owner what changes and get a yes first.
|
|
43759
|
+
description: "The agent builder (multi-agent; the older awesomate_knowledge_agent tool is the single workspace default). action 'list': every agent with status (draft/published vN/suspended). 'get' {agentId}: full config incl. system message and scope. 'create' {goal}: AI drafts the whole setup (instructions, scope, tone, test questions) from the account's own content and saves it as a PRIVATE DRAFT (never live, nothing lost); or {name} for a blank draft. The drafted scope is often too wide: it can come back limited to no collection (so it answers from every business's and every audience's content) or limited to kinds that leave out PDFs (stored as 'book'), FAQs ('faq') or images. Pass collectionIds (and kinds if needed) WITH create to scope it in one step; when the account has collections and the new agent reads none of them, or its kinds skip content the library holds, the result carries a warning listing the collections: fix it with update before testing or publishing. 'update' {agentId, ...fields}: edit the DRAFT config: name, description, systemMessage, noAnswerMessage, grounding, audience, collectionIds (scope to collections from awesomate_knowledge_collections), kinds, sourceIds, maxSources, temperature. Nothing changes for callers until 'publish'. 'test' {agentId, message, sessionId?}: chat with the DRAFT config: free, unmetered, the right way to check wording and scope before going live. WARNING: the draft test ignores the agent's audience, so it is NOT what the public sees for anything touching internal or private sources (seen live: a public-audience draft quoted an internal staff handbook's door code and Wi-Fi password, which the published agent correctly refused a visitor). 'visitor_test' {agentId, message (max 2000 chars), sessionId?}: ask the PUBLISHED agent through the real public visitor path, the only way to check what a website visitor actually gets; run it after publishing whenever the agent is public-facing or its scope includes internal/private sources. It needs the plan the public widget ships with (upgrade_required below it: relay, never retry), and a question it cannot answer lands in the owner's Questions inbox like a real visitor's, so say so before using it. 'publish' {agentId}: makes the draft LIVE immediately for every key bound to the agent: get the user's explicit approval first, and read the version back. 'suspend' / 'resume' {agentId}: stop the agent answering on every key bound to it, or start it again (its last published version); both change what live callers get at once, so get the owner's explicit approval first. 'delete' {agentId, confirm:true}: removes the agent for good and every key bound to it stops answering; tell the owner that and pass confirm:true only after they say yes. 'logs' {agentId?, since?, until?, endpoint?, status?, limit?, cursor?}: the request log (each call to the agents: when, which endpoint, its status), newest first. 'answer_rates' {days? 1-92, default 30}: how each agent is answering, in bands, counts only. 'where_used': each agent with its audience, scope and status, to see which agent a website or automation is using. 'ai_key_status': whether the account has its own AI key saved (names and dates, never a key); saving or removing one is the owner's, in the hub at Settings, Integrations. 'models': the models the account's own AI key can use. Access policies (customer groups): 'policies' lists them; 'policy_get' {policyId}; 'policy_create' {name, description?, collectionIds?|kinds?|sourceIds?|tags?}: a policy named segment:all or segment:<group> is what an agent's customer groups use, so a person in that group answers only from what it allows; 'policy_update' {policyId, name?|description?|scope fields} (a scope REPLACES the stored one); 'policy_delete', 'policy_suspend', 'policy_resume' {policyId}. Every policy change applies to live callers at once: show the owner what changes and get a yes first. Website chat (owner only; Support Plus and above, upgrade_required below it: relay it, never retry): 'website_keys' {agentId?}: the working website keys (no key text: a key is shown once, when made). 'website_key' {agentId, origins, title?, label?, monthlyAnswerCap?, confirm:true}: makes a website key for a PUBLISHED, public-audience agent (refused otherwise, with what to do first) and returns it ONCE with the ready <script> snippet to paste before </body>; anyone on those sites can then ask the agent, and answers count against the account's monthly answers. Each key has its own monthly answer limit (by default half the account's at the time it is made), returned as monthly_answer_cap: tell the user the number. A key's limit cannot be changed later; a different limit is a new key, then revoke the old one. Ask the owner which sites, say what it means, and pass confirm:true only after they say yes. 'revoke_website_key' {keyId, confirm:true}: stops the chat on every site using that key at once; ask first. Other keys (for automations and servers) are made in the hub (Knowledge, Agents) and never pass through this tool.",
|
|
43611
43760
|
inputSchema: {
|
|
43612
43761
|
action: external_exports.enum([
|
|
43613
43762
|
"list",
|
|
@@ -43631,9 +43780,17 @@ server.registerTool(
|
|
|
43631
43780
|
"policy_update",
|
|
43632
43781
|
"policy_delete",
|
|
43633
43782
|
"policy_suspend",
|
|
43634
|
-
"policy_resume"
|
|
43783
|
+
"policy_resume",
|
|
43784
|
+
"website_key",
|
|
43785
|
+
"website_keys",
|
|
43786
|
+
"revoke_website_key"
|
|
43635
43787
|
]),
|
|
43636
|
-
confirm: external_exports.boolean().optional().describe("delete: true only after the owner said yes
|
|
43788
|
+
confirm: external_exports.boolean().optional().describe("delete, website_key, revoke_website_key: true only after the owner said yes"),
|
|
43789
|
+
origins: external_exports.array(external_exports.string().max(200)).max(16).optional().describe("website_key: the websites the chat runs on, https://host only (no path, no wildcard), e.g. https://example.com.au and https://www.example.com.au"),
|
|
43790
|
+
title: external_exports.string().max(60).optional().describe("website_key: the chat window's title in the snippet (data-title); defaults to the agent's name"),
|
|
43791
|
+
label: external_exports.string().max(120).optional().describe("website_key: a name for the key in key lists"),
|
|
43792
|
+
monthlyAnswerCap: external_exports.number().int().min(1).optional().describe("website_key: this key's own monthly answer cap; leave it out for half the account's monthly answers"),
|
|
43793
|
+
keyId: external_exports.string().max(128).optional().describe("revoke_website_key: key_id from website_keys"),
|
|
43637
43794
|
since: external_exports.string().max(40).optional().describe("logs: ISO time to read from"),
|
|
43638
43795
|
until: external_exports.string().max(40).optional().describe("logs: ISO time to read to"),
|
|
43639
43796
|
endpoint: external_exports.string().max(200).optional().describe("logs: only calls to this endpoint, e.g. /v1/answer"),
|
|
@@ -43647,22 +43804,22 @@ server.registerTool(
|
|
|
43647
43804
|
name: external_exports.string().max(120).optional().describe("create: blank draft with this name (max 80); update: rename; policy_create/update: the policy name, e.g. segment:members"),
|
|
43648
43805
|
goal: external_exports.string().max(2e3).optional().describe("create: describe the agent and AI drafts it from the account content"),
|
|
43649
43806
|
audience: external_exports.enum(["private", "internal", "public"]).optional().describe("create/update: who the agent serves: 'public' for anything customers or a website will talk to (it then answers from sources marked public ONLY), 'internal' for the team, 'private' (default) for the owner's own tools. Widening later is a publish, so choose now."),
|
|
43650
|
-
description: external_exports.string().max(300).optional().describe("update: one-line description shown in the hub; policy_create/update: what the policy is for"),
|
|
43651
|
-
systemMessage: external_exports.string().max(8e3).optional().describe("update: the agent instructions (system message)"),
|
|
43652
|
-
noAnswerMessage: external_exports.string().max(500).optional().describe("update: what the agent says when the grounding gate declines to answer"),
|
|
43653
|
-
grounding: external_exports.enum(["strict", "grounded_chat"]).optional().describe("update: 'strict' refuses anything not in the retrieved content (use it for anything customer-facing); 'grounded_chat' may add general knowledge around the cited content"),
|
|
43807
|
+
description: external_exports.string().max(300).optional().describe("create/update: one-line description shown in the hub; policy_create/update: what the policy is for"),
|
|
43808
|
+
systemMessage: external_exports.string().max(8e3).optional().describe("create/update: the agent instructions (system message); with goal, it replaces the drafted ones"),
|
|
43809
|
+
noAnswerMessage: external_exports.string().max(500).optional().describe("create/update: what the agent says when the grounding gate declines to answer"),
|
|
43810
|
+
grounding: external_exports.enum(["strict", "grounded_chat"]).optional().describe("create/update: 'strict' refuses anything not in the retrieved content (use it for anything customer-facing); 'grounded_chat' may add general knowledge around the cited content"),
|
|
43654
43811
|
collectionIds: external_exports.array(external_exports.string().max(64)).max(16).optional().describe("create/update: scope the agent to these collections (collection_id from awesomate_knowledge_collections list); replaces the collection scope only, other scope fields and free-form tags are kept. policy_create/update: the collections the policy allows"),
|
|
43655
43812
|
kinds: external_exports.array(external_exports.string().max(40)).max(16).optional().describe("create/update: restrict retrieval to source kinds (web, document, video, audio, image, book, faq, dataset; PDFs are stored as 'book'); omit for all kinds. policy_create/update: the kinds the policy allows"),
|
|
43656
43813
|
sourceIds: external_exports.array(external_exports.string().max(200)).max(500).optional().describe("create/update, policy_create/update: pin the scope to specific sources"),
|
|
43657
|
-
maxSources: external_exports.number().int().min(1).max(10).optional().describe("update: how many sources one answer may cite (1-10)"),
|
|
43658
|
-
temperature: external_exports.number().min(0).max(1).optional().describe("update: 0 is the most literal"),
|
|
43814
|
+
maxSources: external_exports.number().int().min(1).max(10).optional().describe("create/update: how many sources one answer may cite (1-10)"),
|
|
43815
|
+
temperature: external_exports.number().min(0).max(1).optional().describe("create/update: 0 is the most literal"),
|
|
43659
43816
|
message: external_exports.string().max(4e3).optional().describe("test: the question to ask the draft; visitor_test: the question a visitor asks the published agent (max 2000 characters)"),
|
|
43660
43817
|
sessionId: external_exports.string().max(128).optional().describe("test/visitor_test: keep follow-ups in one thread")
|
|
43661
43818
|
}
|
|
43662
43819
|
},
|
|
43663
43820
|
async (args) => {
|
|
43664
43821
|
try {
|
|
43665
|
-
return knowledgeResult(await knowledgeAgents(requireConfig(), args));
|
|
43822
|
+
return knowledgeResult(await knowledgeAgents(requireConfig(), args), { keepWebsiteKeys: args.action === "website_key" });
|
|
43666
43823
|
} catch (err) {
|
|
43667
43824
|
return knowledgeError(err);
|
|
43668
43825
|
}
|
|
@@ -43805,7 +43962,7 @@ server.registerTool(
|
|
|
43805
43962
|
"awesomate_knowledge_data",
|
|
43806
43963
|
{
|
|
43807
43964
|
annotations: annotate("awesomate_knowledge_data", MUTATING),
|
|
43808
|
-
description: "The knowledge platform's business-data warehouse (every plan, within the plan's row and dataset limits). action 'metrics': headline numbers for the data tab. 'datasets': the datasets imported and
|
|
43965
|
+
description: "The knowledge platform's business-data warehouse (every plan, within the plan's row and dataset limits). action 'metrics': headline numbers for the data tab. 'datasets': the datasets imported (names and ids, no columns): read this FIRST, then 'dataset' {datasetId} for a dataset's columns, because measures/dimensions must name real columns. 'imports': import job statuses. 'query' {dataset, measures:[{column, agg}], plus optional dimensions/filters passed through}: answer QUANTITATIVE questions from the user's own imported business data (revenue by month, top customers); read-only, results come back as rows to present honestly. 'dataset' {datasetId}: one dataset with its columns and how each is read. 'dataset_update' {datasetId, name?, description?, columns?:[{column_id, name?, semantic_type?, role?, unit?, currency?, canonical_metric_key?, pii?}]}: rename it or settle how its columns are read (show the owner the change first). 'import' {importId}: one import, with any questions it is waiting on. 'import_action' {importId, importAction: approve|reject|withdraw|reimport|answer, answers?}: approve lets a waiting import in, reject turns one away, withdraw takes an import that is already in back out again (confirm:true, after the owner says yes), reimport runs it again, answer {answers: {question key: answer}} replies to its questions. Every import_action changes the owner's numbers: say what it does and get a yes first. New data is imported in the hub UI, not here.",
|
|
43809
43966
|
inputSchema: {
|
|
43810
43967
|
action: external_exports.enum(["metrics", "datasets", "imports", "query", "dataset", "dataset_update", "import", "import_action"]),
|
|
43811
43968
|
datasetId: external_exports.string().max(200).optional().describe("dataset, dataset_update: the dataset id from datasets"),
|
|
@@ -45298,19 +45455,25 @@ server.registerTool(
|
|
|
45298
45455
|
"awesomate_knowledge_upload",
|
|
45299
45456
|
{
|
|
45300
45457
|
annotations: annotate("awesomate_knowledge_upload", MUTATING),
|
|
45301
|
-
description: "Ingest ONE file from the user's own computer into their Knowledge Base. Pass a LOCAL PATH
|
|
45458
|
+
description: "Ingest ONE file from the user's own computer into their Knowledge Base. Pass a LOCAL PATH: this server runs on their machine and streams the file to the hub itself, so the file contents never pass through the conversation. Handles documents (pdf, md, txt), spreadsheets (csv, tsv, xls, xlsx, xlsm, json; these NEED kind, see below), audio and video (transcribed, Pro and above), and images (OCR, Pro and above). Word/PowerPoint/RTF/EPUB files are NOT parseable yet: the tool refuses them with the workaround (export to PDF, or save as .md/.txt). Max 100 MB per file; bigger media goes through the hub's Knowledge \u2192 Sources page. SPREADSHEETS: pass kind, or the tool refuses before uploading. kind 'sheet' makes each row a searchable, citable record: price lists, fee tables, timetables, FAQs, policies, product lists, anything a customer asks about. kind 'data-import' builds a reportable dataset for totals and trends: job or invoice history, sales by month, read with awesomate_knowledge_data. If it is not obvious which the user wants, ask them; never guess. The same goes for a spreadsheet in the File Manager (fromFilesPath): pass kind with it. The source's title comes from the filename, slugified (spaces, punctuation and non-ASCII letters become _), even when title is passed; fix it afterwards with awesomate_knowledge_sources {action:'rename'}. visibility 'public' publishes the file, so it needs confirm: the account slug, passed only after the user agreed in so many words (the same rule as awesomate_knowledge_sources add). INGESTING COSTS MONEY and counts against the monthly allowance, so ALWAYS get explicit approval for the specific file(s) first and say what it will consume. For several files, call once per file and report progress; do not loop silently. Returns a job with its live status; poll awesomate_knowledge_sources {action:'jobs'} until it succeeds (a duplicate upload replays the earlier job, and a response already reading succeeded needs no polling), then probe the content with awesomate_knowledge_ask before building anything on it. A pack_required response means the allowance is exhausted: nothing was ingested and nothing was purchased.",
|
|
45302
45459
|
inputSchema: {
|
|
45303
45460
|
path: external_exports.string().min(1).max(4096).optional().describe("Path to the file on the user's machine (~ is expanded). Omit when using fromFilesPath."),
|
|
45304
45461
|
fromFilesPath: external_exports.string().max(1024).optional().describe("Instead of a local file: a path in the account's File Manager (their n8n file system), relative to files/, e.g. 'public/reports/weekly.md'. The hub streams it from their storage \u2014 nothing leaves this machine."),
|
|
45305
45462
|
title: external_exports.string().max(300).optional().describe("Used as the uploaded file's name (local uploads only). The platform slugifies it (spaces, punctuation and non-ASCII letters become _), so for a readable title rename the source after the job succeeds"),
|
|
45306
45463
|
kind: external_exports.enum(["sheet", "data-import"]).optional().describe("Spreadsheets only (csv, tsv, xls, xlsx, xlsm, json), and required for them: 'sheet' = each row a searchable, citable record (price lists, timetables, fee tables, FAQs); 'data-import' = a reportable dataset for totals and trends (job or invoice history). Ask the user if unclear; never guess"),
|
|
45307
|
-
visibility: external_exports.enum(["private", "internal", "public"]).optional().describe("Who may retrieve this source (default private). public = customers and public chatbots; irreversible once fetched
|
|
45464
|
+
visibility: external_exports.enum(["private", "internal", "public"]).optional().describe("Who may retrieve this source (default private). public = customers and public chatbots; irreversible once fetched, so it also needs confirm."),
|
|
45465
|
+
confirm: external_exports.string().max(64).optional().describe("visibility 'public' only: the account slug, after the user agreed in so many words"),
|
|
45308
45466
|
tags: external_exports.array(external_exports.string().max(64)).max(64).optional().describe("Free-form tags for the new source"),
|
|
45309
45467
|
collectionIds: external_exports.array(external_exports.string().max(64)).max(16).optional().describe("Put the new source in these collections (awesomate_knowledge_collections list)")
|
|
45310
45468
|
}
|
|
45311
45469
|
},
|
|
45312
|
-
async ({ path: rawPath, fromFilesPath, title, kind, visibility, tags, collectionIds }) => {
|
|
45470
|
+
async ({ path: rawPath, fromFilesPath, title, kind, visibility, confirm, tags, collectionIds }) => {
|
|
45313
45471
|
try {
|
|
45472
|
+
if (visibility === "public" && !confirm) {
|
|
45473
|
+
return errorResult(
|
|
45474
|
+
new Error("Adding a file as public cannot be undone once it has been fetched or crawled. Ask the user in plain words, then call again with confirm: the account slug. Nothing was uploaded.")
|
|
45475
|
+
);
|
|
45476
|
+
}
|
|
45314
45477
|
if (fromFilesPath) {
|
|
45315
45478
|
const dot = fromFilesPath.lastIndexOf(".");
|
|
45316
45479
|
const filesExt = dot > fromFilesPath.lastIndexOf("/") ? fromFilesPath.slice(dot).toLowerCase() : "";
|
|
@@ -45325,6 +45488,7 @@ server.registerTool(
|
|
|
45325
45488
|
path: fromFilesPath,
|
|
45326
45489
|
...kind ? { kind } : {},
|
|
45327
45490
|
...visibility ? { visibility } : {},
|
|
45491
|
+
...visibility === "public" && confirm ? { confirm } : {},
|
|
45328
45492
|
...tags?.length ? { tags } : {},
|
|
45329
45493
|
...collectionIds?.length ? { collection_ids: collectionIds } : {}
|
|
45330
45494
|
});
|
|
@@ -45379,6 +45543,7 @@ server.registerTool(
|
|
|
45379
45543
|
const filename = title?.trim() ? `${title.trim()}${ext}` : basename(abs);
|
|
45380
45544
|
const uploadQuery = new URLSearchParams();
|
|
45381
45545
|
if (visibility) uploadQuery.set("visibility", visibility);
|
|
45546
|
+
if (visibility === "public" && confirm) uploadQuery.set("confirm", confirm);
|
|
45382
45547
|
if (kind) uploadQuery.set("kind", kind);
|
|
45383
45548
|
for (const t of tags ?? []) uploadQuery.append("tags", t);
|
|
45384
45549
|
for (const c of collectionIds ?? []) uploadQuery.append("collection_ids", c);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@awesomate/hosting-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.93.0",
|
|
4
4
|
"description": "Awesomate MCP server — lets Claude manage your Awesomate WordPress hosting, plan, limits, n8n automations, and build Node/static apps + databases",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
package/skill/CHANGELOG.json
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"versions": [
|
|
3
|
+
{
|
|
4
|
+
"version": "0.93.0",
|
|
5
|
+
"highlights": [
|
|
6
|
+
"Claude can put a published agent on your website: it makes a website key for the sites you name, after your yes, and hands you the code to paste. Each website key has its own monthly answer limit, half your account's by default, and Claude tells you the number",
|
|
7
|
+
"Uploading a file as public now needs the same typed confirmation as adding a public page",
|
|
8
|
+
"A delete that finds nothing says the item may already be gone or the id may be wrong, and a delete that timed out says it may have gone through instead of saying nothing changed",
|
|
9
|
+
"Asking within filters works with the workspace's default agent, and a new agent drafted from a goal keeps the name you gave it",
|
|
10
|
+
"Skill update checks compare version numbers properly (0.10 is newer than 0.9)",
|
|
11
|
+
"When a Knowledge agent fails because the account's own OpenRouter key is refused or out of credit, Claude says so and where to fix it, instead of a generic service problem"
|
|
12
|
+
]
|
|
13
|
+
},
|
|
3
14
|
{
|
|
4
15
|
"version": "0.92.2",
|
|
5
16
|
"highlights": [
|
|
@@ -164,6 +164,7 @@ confirm:true}`, both after the owner's yes.
|
|
|
164
164
|
| Agent builder (what `awesomate_knowledge_agents` calls) | `GET\|POST /api/knowledge/agents` · `GET\|PATCH\|DELETE /api/knowledge/agents/:agent_id` · `POST /api/knowledge/agents/:agent_id/{publish,suspend,resume}` · `GET /api/knowledge/agents/{logs,answer-rates,where-used}` · `GET\|POST /api/knowledge/agents/policies` · `GET\|PATCH\|DELETE /api/knowledge/agents/policies/:policy_id` · `POST /api/knowledge/agents/policies/:policy_id/{suspend,resume}` |
|
|
165
165
|
| **Test a DRAFT agent** (`action:'test'`) | `POST /api/knowledge/agents/:agent_id/chat` `{message, session_id?}`. There is **no** `/test` endpoint — guessing one returns 404. It ignores the agent's audience. |
|
|
166
166
|
| **Ask the PUBLISHED agent as a visitor** (`action:'visitor_test'`) | `POST /api/knowledge/agents/:agent_id/visitor-chat` `{message (max 2000), session_id?}`. 403 `upgrade_required` below the public-widget plan; an unanswered question lands in the Questions inbox. |
|
|
167
|
+
| **Website chat key** (`action:'website_key'`, `website_keys`, `revoke_website_key`; owner only, Support Plus and above) | `POST /api/knowledge/agents/keys` `{class:'p', agent_id, allowed_origins (https only, no path or wildcard), label?, request_id?, monthly_answer_cap?}` returns the key ONCE with its `monthly_answer_cap`; with no cap the hub sets half the account's monthly answers at that moment, and a key's cap cannot be changed later (a new limit is a new key). The tool refuses an agent that is not published, not public-audience, or paused. List with `GET /api/knowledge/agents/keys` (class `p`), revoke with `DELETE /api/knowledge/agents/keys/:key_id`. Every one only after the owner's yes. |
|
|
167
168
|
| Entity layer probe (`available` false = not enabled yet, stop) | `GET /api/knowledge/entities` |
|
|
168
169
|
| People list / detail | `GET /api/knowledge/people?status=named\|unknown\|hidden\|all` · `GET /api/knowledge/people/:id` — returns `{people, next_cursor}` and **no `counts` key**. |
|
|
169
170
|
| Entity COUNTS + availability | `GET /api/knowledge/entities` → `{available, counts: {...}}` — counts are nested under `counts`. Reading `people.counts?.x ?? 0` prints a confident row of zeros next to data that plainly exists. |
|