hive-intelligence 1.6.3 → 1.7.1

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.
Files changed (57) hide show
  1. package/README.md +10 -4
  2. package/agent-skills/.claude-plugin/plugin.json +10 -0
  3. package/agent-skills/README.md +2 -0
  4. package/agent-skills/SKILL.md +18 -6
  5. package/agent-skills/evals/trigger-queries.json +70 -0
  6. package/agent-skills/package.json +3 -1
  7. package/agent-skills/packs/hive/SKILL.md +382 -0
  8. package/agent-skills/packs/hive/evals/evals.json +50 -0
  9. package/agent-skills/packs/hive/evals/triggers.json +15 -0
  10. package/agent-skills/packs/hive-build-onboarding/SKILL.md +1 -1
  11. package/agent-skills/packs/hive-cli/SKILL.md +2 -2
  12. package/agent-skills/packs/hive-mcp/SKILL.md +9 -5
  13. package/agent-skills/packs/hive-prediction-markets/SKILL.md +145 -0
  14. package/agent-skills/packs/hive-prediction-markets/evals/evals.json +29 -0
  15. package/agent-skills/packs/hive-prediction-markets/evals/triggers.json +14 -0
  16. package/agent-skills/packs/hive-query/SKILL.md +2 -1
  17. package/agent-skills/packs/hive-stateful-monitoring/SKILL.md +3 -3
  18. package/agent-skills/references/agents-routing.md +4 -1
  19. package/agent-skills/scripts/validate-agent-skills-package.mjs +2 -0
  20. package/agent-skills/skills.sh.json +9 -1
  21. package/build/{api-client-6AM3HVHQ.js → api-client-UGNFUZ2L.js} +12 -3
  22. package/build/{auth-4LPSQJXD.js → auth-5TLYP2DC.js} +1 -1
  23. package/build/call-args-NVOYX6DL.js +27 -0
  24. package/build/{chunk-XZJICKAL.js → chunk-2C5FBG7B.js} +4 -3
  25. package/build/{chunk-IJ5KOZH5.js → chunk-3SQPLZW6.js} +42730 -39105
  26. package/build/{chunk-S4HBVZ7O.js → chunk-3Z5VR53Z.js} +36 -7
  27. package/build/{chunk-GADPH72D.js → chunk-4QDGA6GP.js} +52 -1
  28. package/build/{chunk-2SPCKQBJ.js → chunk-5QAXJON7.js} +71 -0
  29. package/build/{chunk-GYVF75XB.js → chunk-FQNW6SZ6.js} +1 -1
  30. package/build/{chunk-LW2DHE32.js → chunk-L4KVJDA7.js} +1 -1
  31. package/build/chunk-POPAEM4E.js +474 -0
  32. package/build/{chunk-P7NLFSZQ.js → chunk-TWYGJEVN.js} +100 -9
  33. package/build/{chunk-32NRUWXA.js → chunk-UPWYFTTJ.js} +3 -3
  34. package/build/{chunk-KUF754NT.js → chunk-WII7MTYZ.js} +52 -12
  35. package/build/chunk-XN3T6YOG.js +32 -0
  36. package/build/chunk-XT6RURQV.js +394 -0
  37. package/build/{chunk-LP5ECG7A.js → chunk-ZDXMZPC6.js} +26 -2
  38. package/build/cli.js +120 -48
  39. package/build/{completion-YVSWWF6D.js → completion-ROZAH667.js} +1 -1
  40. package/build/{doctor-YBCU5FLQ.js → doctor-44WSVNGJ.js} +42 -5
  41. package/build/{errors-EL6UMXNJ.js → errors-W3GXQN34.js} +7 -1
  42. package/build/feedback-FRRLZDAF.js +113 -0
  43. package/build/{init-all-54G3CVZA.js → init-all-LYRFE2YQ.js} +5 -5
  44. package/build/{mcpServer-V4XUSCX7.js → mcpServer-43LTLURB.js} +2 -2
  45. package/build/monitor-worker.js +62 -10
  46. package/build/{namespace-PA2IDNRT.js → namespace-XMFFW47T.js} +1 -1
  47. package/build/{output-XGTE4EWJ.js → output-BBHBJI32.js} +5 -1
  48. package/build/release.json +4 -4
  49. package/build/routing-CP43PP5F.js +43 -0
  50. package/build/{serve-SU67DIY3.js → serve-LTMDRKXS.js} +1 -1
  51. package/build/server.js +295 -41
  52. package/build/stdio.js +2 -2
  53. package/build/{tools-FNPMELSU.js → tools-VA7JXD3K.js} +81 -33
  54. package/build/{uninstall-P3JJZCXT.js → uninstall-3KJLHTNM.js} +26 -7
  55. package/build/{upgrade-ATIFZANV.js → upgrade-3NR3LAGU.js} +2 -2
  56. package/build/{watch-FG2C5Y2Z.js → watch-HJOTI32P.js} +5 -3
  57. package/package.json +1 -1
package/build/server.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  ensureWallet,
7
7
  executeToolByName,
8
8
  getWalletStatus
9
- } from "./chunk-XZJICKAL.js";
9
+ } from "./chunk-2C5FBG7B.js";
10
10
  import {
11
11
  CategoryEndpoints,
12
12
  HERO_TOOL_NAMES,
@@ -35,6 +35,7 @@ import {
35
35
  createOAuthStore,
36
36
  createToolNotFoundResult,
37
37
  endpointAllowedForInvoker,
38
+ errorDocUrl,
38
39
  executeEndpoint,
39
40
  getAllWrappedEndpoints,
40
41
  getAnalyticsWriter,
@@ -64,6 +65,7 @@ import {
64
65
  heroTools,
65
66
  isB2BSubjectsEnabled,
66
67
  isEndpointInvokerName,
68
+ isFreeWriteEndpoint,
67
69
  isHiveOAuthAccessToken,
68
70
  isProduction,
69
71
  isRedisAvailable,
@@ -74,6 +76,7 @@ import {
74
76
  listKeys,
75
77
  materializeSubjectContext,
76
78
  normalizeInvokeArgs,
79
+ principalKeyFromRaw,
77
80
  recordProviderCanary,
78
81
  recordSubjectAuditEvent,
79
82
  resolveEndpoint,
@@ -87,19 +90,21 @@ import {
87
90
  runProviderCanaryQuick,
88
91
  runTaskCanariesQuick,
89
92
  runTaskCanary,
93
+ runWithRequestPrincipal,
90
94
  runWithSubjectContext,
91
95
  runWorkflowRegressionCanaries,
92
96
  secureLogger,
97
+ submitFeedback,
93
98
  trackCliCommand,
94
99
  trackOAuthFirstMaterialReceipt,
95
100
  trackServerEvent,
96
101
  validateEndpointArgs,
97
102
  verifyApiKey,
98
103
  waitForRedisAvailability
99
- } from "./chunk-IJ5KOZH5.js";
104
+ } from "./chunk-3SQPLZW6.js";
100
105
  import {
101
106
  HIVE_AGENT_SKILLS
102
- } from "./chunk-GADPH72D.js";
107
+ } from "./chunk-4QDGA6GP.js";
103
108
  import "./chunk-EPF36Q3Z.js";
104
109
  import {
105
110
  PUBLIC_MCP_REGISTRY_ID,
@@ -762,19 +767,6 @@ import rateLimit from "express-rate-limit";
762
767
  import RedisStore from "rate-limit-redis";
763
768
 
764
769
  // src/utils/api-errors.ts
765
- var ERROR_DOC_BASE = "https://www.hiveintelligence.xyz/errors";
766
- var ERROR_DOC_URLS = {
767
- MISSING_PARAM: `${ERROR_DOC_BASE}#missing-param`,
768
- TOOL_NOT_FOUND: `${ERROR_DOC_BASE}#tool-not-found`,
769
- TOOL_ERROR: `${ERROR_DOC_BASE}#tool-error`,
770
- PROVIDER_UNAVAILABLE: `${ERROR_DOC_BASE}#provider-unavailable`,
771
- RATE_LIMITED: `${ERROR_DOC_BASE}#rate-limited`,
772
- INTERNAL_ERROR: `${ERROR_DOC_BASE}#internal-error`,
773
- ENDPOINT_NOT_FOUND: `${ERROR_DOC_BASE}#endpoint-not-found`,
774
- SHUTTING_DOWN: `${ERROR_DOC_BASE}#shutting-down`,
775
- UNAUTHORIZED: `${ERROR_DOC_BASE}#unauthorized`,
776
- VALIDATION_ERROR: `${ERROR_DOC_BASE}#validation-error`
777
- };
778
770
  function apiError(type, code, message, opts) {
779
771
  return {
780
772
  ok: false,
@@ -784,7 +776,11 @@ function apiError(type, code, message, opts) {
784
776
  message,
785
777
  ...opts?.param && { param: opts.param },
786
778
  ...opts?.requestId && { request_id: opts.requestId },
787
- doc_url: ERROR_DOC_URLS[code] ?? `${ERROR_DOC_BASE}#${code.toLowerCase().replace(/_/g, "-")}`
779
+ ...opts?.cause && { cause: opts.cause },
780
+ ...opts?.next_action && { next_action: opts.next_action },
781
+ ...opts?.retryable !== void 0 && { retryable: opts.retryable },
782
+ ...opts?.retry_after !== void 0 && { retry_after: opts.retry_after },
783
+ doc_url: errorDocUrl(code)
788
784
  }
789
785
  };
790
786
  }
@@ -7191,8 +7187,10 @@ function classifyAnonymousMcpRequest(body) {
7191
7187
  return "auth_required";
7192
7188
  }
7193
7189
  if (exposedToolName.startsWith("hive_")) return "auth_required";
7190
+ if (isFreeWriteEndpoint(exposedToolName)) return "open";
7194
7191
  if (isEndpointInvokerName(exposedToolName)) {
7195
7192
  const target = getInvokeEndpointName(body?.params?.arguments);
7193
+ if (target && isFreeWriteEndpoint(target)) return "open";
7196
7194
  if (target?.startsWith("hive_")) return "auth_required";
7197
7195
  return "material";
7198
7196
  }
@@ -7413,6 +7411,13 @@ function createStatelessMcpHandler(scope) {
7413
7411
  } else if (decision.kind === "quota_exhausted") {
7414
7412
  res.setHeader("Retry-After", String(decision.resetSeconds));
7415
7413
  const exhaustedMessage = decision.scope === "global" ? ANON_GLOBAL_CAP_MESSAGE : ANON_QUOTA_EXHAUSTED_MESSAGE;
7414
+ const exhaustedCode = decision.scope === "global" ? "ANON_GLOBAL_CAP_EXCEEDED" : "ANON_QUOTA_EXCEEDED";
7415
+ recordPaywall(req, {
7416
+ code: exhaustedCode,
7417
+ authLane: "anonymous",
7418
+ transport: "mcp",
7419
+ toolName: req.body?.params?.name
7420
+ });
7416
7421
  if (mcpOAuth.status === "ready") {
7417
7422
  const requiredScopes = getRequiredMcpScopes(req.body);
7418
7423
  res.setHeader(
@@ -7434,9 +7439,12 @@ function createStatelessMcpHandler(scope) {
7434
7439
  code: -32001,
7435
7440
  message: exhaustedMessage,
7436
7441
  data: {
7437
- code: decision.scope === "global" ? "ANON_GLOBAL_CAP_EXCEEDED" : "ANON_QUOTA_EXCEEDED",
7442
+ code: exhaustedCode,
7438
7443
  retryable: true,
7439
- retry_after: decision.resetSeconds
7444
+ retry_after: decision.resetSeconds,
7445
+ cause: decision.scope === "global" ? "Hive's shared keyless allowance for today is spent." : "This IP's 25 free material calls for today are spent.",
7446
+ next_action: "Set HIVE_API_KEY (free at https://hiveintelligence.xyz/dashboard/keys) or sign in via OAuth; keys are not subject to the keyless cap.",
7447
+ doc_url: errorDocUrl(exhaustedCode)
7440
7448
  }
7441
7449
  },
7442
7450
  id: req.body?.id ?? null
@@ -7668,6 +7676,7 @@ function createStatelessMcpHandler(scope) {
7668
7676
  } : void 0,
7669
7677
  beforeToolExecution: keyedAuth?.userId ? async (executedToolName) => {
7670
7678
  const creditCost = getMcpCreditCost(executedToolName);
7679
+ let keyedBalance;
7671
7680
  res.setHeader("X-Hive-Credit-Cost", String(creditCost));
7672
7681
  if (creditCost > 0) {
7673
7682
  const debit = await debitMcpCredits({
@@ -7687,7 +7696,16 @@ function createStatelessMcpHandler(scope) {
7687
7696
  next_action: "Visit https://hiveintelligence.xyz/dashboard to initialize billing, or contact support. Retrying will not help until the wallet exists."
7688
7697
  };
7689
7698
  }
7690
- if (debit.rateLimited) res.setHeader("Retry-After", "60");
7699
+ if (debit.rateLimited) {
7700
+ res.setHeader("Retry-After", "60");
7701
+ recordPaywall(req, {
7702
+ code: "QUOTA_EXCEEDED",
7703
+ authLane: keyedAuth.authMethod,
7704
+ transport: "mcp",
7705
+ principalRaw: keyedAuth.userId,
7706
+ toolName: executedToolName
7707
+ });
7708
+ }
7691
7709
  return {
7692
7710
  type: debit.rateLimited ? "rate_limit_error" : "internal_error",
7693
7711
  code: debit.rateLimited ? "QUOTA_EXCEEDED" : "QUOTA_SERVICE_UNAVAILABLE",
@@ -7701,6 +7719,7 @@ function createStatelessMcpHandler(scope) {
7701
7719
  "X-Quota-Remaining",
7702
7720
  debit.balance === null ? "unlimited" : String(debit.balance)
7703
7721
  );
7722
+ keyedBalance = debit.balance;
7704
7723
  }
7705
7724
  if (subjectContext) {
7706
7725
  const materialized = await materializeSubjectContext(subjectContext);
@@ -7725,12 +7744,24 @@ function createStatelessMcpHandler(scope) {
7725
7744
  requestId: req.requestId
7726
7745
  });
7727
7746
  if (auditEvent) recordSubjectAuditEvent(auditEvent);
7728
- return void 0;
7747
+ return {
7748
+ type: "billing",
7749
+ credits_used: creditCost,
7750
+ ...creditCost > 0 && { credits_remaining: keyedBalance }
7751
+ };
7729
7752
  } : anonymousLane ? async (executedToolName) => {
7730
7753
  const creditCost = getMcpCreditCost(executedToolName);
7731
7754
  res.setHeader("X-Hive-Credit-Cost", String(creditCost));
7732
- if (creditCost === 0) return void 0;
7755
+ if (creditCost === 0) {
7756
+ return { type: "billing", credits_used: 0 };
7757
+ }
7733
7758
  if (!anonymousLane.materialGranted || executedToolName.startsWith("hive_")) {
7759
+ recordPaywall(req, {
7760
+ code: "ANON_AUTH_REQUIRED",
7761
+ authLane: "anonymous",
7762
+ transport: "mcp",
7763
+ toolName: executedToolName
7764
+ });
7734
7765
  return {
7735
7766
  type: "authentication_error",
7736
7767
  code: "ANON_AUTH_REQUIRED",
@@ -7738,7 +7769,11 @@ function createStatelessMcpHandler(scope) {
7738
7769
  runtimeStatus: "failing"
7739
7770
  };
7740
7771
  }
7741
- return void 0;
7772
+ return {
7773
+ type: "billing",
7774
+ credits_used: creditCost,
7775
+ credits_remaining: anonymousLane.quotaRemaining ?? null
7776
+ };
7742
7777
  } : void 0
7743
7778
  }).getServer();
7744
7779
  const transport = new StreamableHTTPServerTransport({
@@ -7752,7 +7787,17 @@ function createStatelessMcpHandler(scope) {
7752
7787
  await server.connect(transport);
7753
7788
  await runWithSubjectContext(
7754
7789
  subjectContext,
7755
- () => transport.handleRequest(req, res, body)
7790
+ () => runWithRequestPrincipal(
7791
+ analytics.principalRaw && analytics.authLane ? {
7792
+ key: principalKeyFromRaw(
7793
+ analytics.principalRaw,
7794
+ analytics.authLane
7795
+ ),
7796
+ lane: analytics.authLane,
7797
+ client: analytics.clientName ?? null
7798
+ } : void 0,
7799
+ () => transport.handleRequest(req, res, body)
7800
+ )
7756
7801
  );
7757
7802
  secureLogger.debug("MCP request completed", {
7758
7803
  sessionId: sessionCorrelationId,
@@ -7968,6 +8013,161 @@ var REST_EXECUTE_STATUS_CODES = {
7968
8013
  STATE_SUBJECT_ARCHIVED: 410,
7969
8014
  STATE_BACKEND_UNAVAILABLE: 503
7970
8015
  };
8016
+ function restErrorOptionsFromReceipt(meta) {
8017
+ return {
8018
+ ...meta.cause && { cause: meta.cause },
8019
+ ...meta.next_action && { next_action: meta.next_action },
8020
+ retryable: meta.runtime_status === "rate_limited" || meta.runtime_status === "degraded" || meta.runtime_status === "failing"
8021
+ };
8022
+ }
8023
+ function recordPaywall(req, event) {
8024
+ try {
8025
+ getAnalyticsWriter().enqueuePaywall({
8026
+ code: event.code,
8027
+ authLane: event.authLane,
8028
+ transport: event.transport,
8029
+ principalRaw: event.principalRaw ?? `anon:${anonymousIpHash(ipRateLimitKeyFromRequest(req))}`,
8030
+ toolName: typeof event.toolName === "string" ? event.toolName : null,
8031
+ isInternal: isMonitorSecretRequest(req)
8032
+ });
8033
+ } catch {
8034
+ }
8035
+ }
8036
+ async function resolveOptionalRestPrincipal(req) {
8037
+ if (mcpCredentialPresented(req)) {
8038
+ const auth = await validateMcpCredential(req);
8039
+ if (!auth.ok) {
8040
+ return {
8041
+ ok: false,
8042
+ status: 401,
8043
+ code: "UNAUTHORIZED",
8044
+ message: "The credential presented was rejected."
8045
+ };
8046
+ }
8047
+ const principalId = auth.userId ?? auth.keyId ?? "static";
8048
+ return {
8049
+ ok: true,
8050
+ key: `user:${principalId}`,
8051
+ lane: auth.authMethod,
8052
+ userId: auth.userId
8053
+ };
8054
+ }
8055
+ return {
8056
+ ok: true,
8057
+ key: `anon:${anonymousIpHash(ipRateLimitKeyFromRequest(req))}`,
8058
+ lane: "anonymous"
8059
+ };
8060
+ }
8061
+ app.post("/api/v1/feedback", async (req, res) => {
8062
+ const principal = await resolveOptionalRestPrincipal(req);
8063
+ if (!principal.ok) {
8064
+ res.status(principal.status).json(
8065
+ apiError("authentication_error", principal.code, principal.message, {
8066
+ requestId: req.requestId
8067
+ })
8068
+ );
8069
+ return;
8070
+ }
8071
+ const body = req.body ?? {};
8072
+ const result = await submitFeedback({
8073
+ principalKey: principal.key,
8074
+ lane: principal.lane,
8075
+ client: typeof body.client === "string" ? body.client : null,
8076
+ message: body.message,
8077
+ tool: body.tool,
8078
+ receiptId: body.receipt_id
8079
+ });
8080
+ if (result.ok) {
8081
+ res.json({
8082
+ ok: true,
8083
+ data: { id: result.id, remaining_today: result.remaining, reset_at: result.resetAt }
8084
+ });
8085
+ return;
8086
+ }
8087
+ const status = result.code === "VALIDATION_ERROR" ? 400 : result.code === "FEEDBACK_RATE_LIMITED" ? 429 : 503;
8088
+ if (result.resetAt) {
8089
+ const seconds = Math.max(
8090
+ 1,
8091
+ Math.round((Date.parse(result.resetAt) - Date.now()) / 1e3)
8092
+ );
8093
+ res.setHeader("Retry-After", String(seconds));
8094
+ }
8095
+ res.status(status).json({
8096
+ ...apiError(
8097
+ result.code === "VALIDATION_ERROR" ? "invalid_request_error" : result.code === "FEEDBACK_RATE_LIMITED" ? "rate_limit_error" : "internal_error",
8098
+ result.code,
8099
+ result.message,
8100
+ {
8101
+ requestId: req.requestId,
8102
+ retryable: result.retryable,
8103
+ ...result.resetAt && {
8104
+ next_action: `Retry after ${result.resetAt}.`
8105
+ }
8106
+ }
8107
+ ),
8108
+ ...result.resetAt && { reset_at: result.resetAt }
8109
+ });
8110
+ });
8111
+ app.get("/api/v1/quota", async (req, res) => {
8112
+ const principal = await resolveOptionalRestPrincipal(req);
8113
+ if (!principal.ok) {
8114
+ res.status(principal.status).json(
8115
+ apiError("authentication_error", principal.code, principal.message, {
8116
+ requestId: req.requestId
8117
+ })
8118
+ );
8119
+ return;
8120
+ }
8121
+ try {
8122
+ if (principal.lane === "anonymous") {
8123
+ if (!isSupabaseConfigured()) {
8124
+ res.json({
8125
+ ok: true,
8126
+ data: { lane: "anonymous", remaining: null, limit: getAnonDailyMaterialCap(), reset_at: null }
8127
+ });
8128
+ return;
8129
+ }
8130
+ const { data, error } = await getSupabaseAdmin().rpc(
8131
+ "read_anon_daily_quota",
8132
+ {
8133
+ p_ip_hash: anonymousIpHash(ipRateLimitKeyFromRequest(req)),
8134
+ p_ip_limit: getAnonDailyMaterialCap()
8135
+ }
8136
+ );
8137
+ if (error) throw new Error(error.message);
8138
+ const row = Array.isArray(data) ? data[0] : data;
8139
+ const resetSeconds = Math.max(1, row?.reset_seconds ?? 1);
8140
+ res.json({
8141
+ ok: true,
8142
+ data: {
8143
+ lane: "anonymous",
8144
+ remaining: row?.remaining ?? null,
8145
+ limit: row?.limit_value ?? getAnonDailyMaterialCap(),
8146
+ reset_at: new Date(Date.now() + resetSeconds * 1e3).toISOString()
8147
+ }
8148
+ });
8149
+ return;
8150
+ }
8151
+ const wallet = principal.userId ? await getWalletStatus(principal.userId) : null;
8152
+ res.json({
8153
+ ok: true,
8154
+ data: {
8155
+ lane: principal.lane,
8156
+ remaining: wallet ? wallet.balance : null,
8157
+ limit: wallet ? wallet.monthlyGrant : null,
8158
+ reset_at: wallet ? wallet.cycleEnd : null
8159
+ }
8160
+ });
8161
+ } catch (error) {
8162
+ secureLogger.error("Quota read failed", error, { requestId: req.requestId });
8163
+ res.status(503).json(
8164
+ apiError("internal_error", "QUOTA_SERVICE_UNAVAILABLE", "The quota read is unavailable right now.", {
8165
+ requestId: req.requestId,
8166
+ retryable: true
8167
+ })
8168
+ );
8169
+ }
8170
+ });
7971
8171
  function buildRestExecutionMeta(toolName, durationMs, meta) {
7972
8172
  return {
7973
8173
  tool: toolName,
@@ -7990,6 +8190,14 @@ function buildRestExecutionMeta(toolName, durationMs, meta) {
7990
8190
  origin_source: meta.origin_source,
7991
8191
  truncated: meta.truncated,
7992
8192
  warnings: meta.warnings,
8193
+ ...meta.credit_cost !== void 0 && { credit_cost: meta.credit_cost },
8194
+ ...meta.credits_used !== void 0 && { credits_used: meta.credits_used },
8195
+ ...meta.credits_remaining !== void 0 && {
8196
+ credits_remaining: meta.credits_remaining
8197
+ },
8198
+ ...meta.cause && { cause: meta.cause },
8199
+ ...meta.next_action && { next_action: meta.next_action },
8200
+ ...meta.doc_url && { doc_url: meta.doc_url },
7993
8201
  // Agent-actionable copy travels on every transport, not only MCP: REST and
7994
8202
  // CLI callers were getting "not found" with no replacement (2026-09-14).
7995
8203
  ...meta.cause !== void 0 && { cause: meta.cause },
@@ -8086,13 +8294,27 @@ var anonymousHeroExecuteMiddleware = asyncHandler(
8086
8294
  return;
8087
8295
  }
8088
8296
  const globalCap = quota.reason === "global_cap";
8297
+ const capCode = globalCap ? "ANON_GLOBAL_CAP_EXCEEDED" : "ANON_QUOTA_EXCEEDED";
8298
+ recordPaywall(req, {
8299
+ code: capCode,
8300
+ authLane: "anonymous",
8301
+ transport: "rest",
8302
+ principalRaw: `anon:${ipHash}`,
8303
+ toolName
8304
+ });
8089
8305
  res.setHeader("Retry-After", String(quota.resetSeconds));
8090
8306
  res.status(429).json(
8091
8307
  apiError(
8092
8308
  "rate_limit_error",
8093
- globalCap ? "ANON_GLOBAL_CAP_EXCEEDED" : "ANON_QUOTA_EXCEEDED",
8309
+ capCode,
8094
8310
  globalCap ? ANON_GLOBAL_CAP_MESSAGE : ANON_QUOTA_EXHAUSTED_MESSAGE,
8095
- { requestId: req.requestId }
8311
+ {
8312
+ requestId: req.requestId,
8313
+ cause: globalCap ? "Hive's shared keyless allowance for today is spent." : "This IP's free material calls for today are spent.",
8314
+ next_action: "Set HIVE_API_KEY (free at https://hiveintelligence.xyz/dashboard/keys); keys are not subject to the keyless cap.",
8315
+ retryable: true,
8316
+ retry_after: quota.resetSeconds
8317
+ }
8096
8318
  )
8097
8319
  );
8098
8320
  return;
@@ -8102,6 +8324,12 @@ var anonymousHeroExecuteMiddleware = asyncHandler(
8102
8324
  transport: "rest",
8103
8325
  requestId: req.requestId,
8104
8326
  clients: getSharedClients(),
8327
+ creditCost: getMcpCreditCost(toolName),
8328
+ beforeExecution: async () => ({
8329
+ type: "billing",
8330
+ credits_used: getMcpCreditCost(toolName),
8331
+ credits_remaining: quota.remaining
8332
+ }),
8105
8333
  analytics: {
8106
8334
  principalRaw: `anon:${ipHash}`,
8107
8335
  authLane: "anonymous",
@@ -8125,7 +8353,8 @@ var anonymousHeroExecuteMiddleware = asyncHandler(
8125
8353
  };
8126
8354
  res.status(REST_EXECUTE_STATUS_CODES[error.code] ?? 500).json({
8127
8355
  ...apiError(error.type, error.code, error.message, {
8128
- requestId: req.requestId
8356
+ requestId: req.requestId,
8357
+ ...restErrorOptionsFromReceipt(result.hive)
8129
8358
  }),
8130
8359
  meta,
8131
8360
  ...result.validationErrors && {
@@ -8163,7 +8392,18 @@ app.post(
8163
8392
  const endpointArgs = authReq.resolvedRestToolArgs ?? { ...args ?? {} };
8164
8393
  const keyedBeforeExecution = async (endpoint) => {
8165
8394
  const blocked = await debitApiKeyCreditForExecution(req, res);
8166
- if (blocked) return blocked;
8395
+ if (blocked) {
8396
+ if (blocked.code === "QUOTA_EXCEEDED") {
8397
+ recordPaywall(req, {
8398
+ code: "QUOTA_EXCEEDED",
8399
+ authLane: "api_key",
8400
+ transport: "rest",
8401
+ principalRaw: authReq.userId,
8402
+ toolName: endpoint.tool.name
8403
+ });
8404
+ }
8405
+ return blocked;
8406
+ }
8167
8407
  authReq.idempotencyExecutionStarted = true;
8168
8408
  if (authReq.hiveSubjectContext) {
8169
8409
  const materialized = await materializeSubjectContext(
@@ -8190,7 +8430,15 @@ app.post(
8190
8430
  requestId: req.requestId
8191
8431
  });
8192
8432
  if (restAuditEvent) recordSubjectAuditEvent(restAuditEvent);
8193
- return void 0;
8433
+ const remainingHeader = res.getHeader("X-Quota-Remaining");
8434
+ const creditCost = getMcpCreditCost(endpoint.tool.name);
8435
+ return {
8436
+ type: "billing",
8437
+ credits_used: creditCost,
8438
+ ...creditCost > 0 && remainingHeader !== void 0 && {
8439
+ credits_remaining: String(remainingHeader) === "unlimited" ? null : Number(remainingHeader)
8440
+ }
8441
+ };
8194
8442
  };
8195
8443
  const heroEndpoint = HERO_TOOL_NAMES.has(executionToolName) ? getRestHeroEndpoint(executionToolName) : void 0;
8196
8444
  const result = heroEndpoint ? await (async () => {
@@ -8198,6 +8446,7 @@ app.post(
8198
8446
  transport: "rest",
8199
8447
  requestId: req.requestId,
8200
8448
  clients: getSharedClients(),
8449
+ creditCost: getMcpCreditCost(executionToolName),
8201
8450
  analytics: {
8202
8451
  principalRaw: authReq.userId,
8203
8452
  authLane: "api_key",
@@ -8215,17 +8464,21 @@ app.post(
8215
8464
  };
8216
8465
  })() : await runWithSubjectContext(
8217
8466
  authReq.hiveSubjectContext,
8218
- () => executeToolByName(executionToolName, endpointArgs, {
8219
- requestId: req.requestId,
8220
- // Same identity context the hero branch passes — without it every
8221
- // non-hero keyed REST call wrote a NULL-principal analytics row.
8222
- analytics: {
8223
- principalRaw: authReq.userId,
8224
- authLane: "api_key",
8225
- isInternal: isMonitorSecretRequest(req)
8226
- },
8227
- beforeExecution: keyedBeforeExecution
8228
- })
8467
+ () => runWithRequestPrincipal(
8468
+ authReq.userId ? { key: `user:${authReq.userId}`, lane: "api_key", client: null } : void 0,
8469
+ () => executeToolByName(executionToolName, endpointArgs, {
8470
+ requestId: req.requestId,
8471
+ creditCost: getMcpCreditCost(executionToolName),
8472
+ // Same identity context the hero branch passes — without it every
8473
+ // non-hero keyed REST call wrote a NULL-principal analytics row.
8474
+ analytics: {
8475
+ principalRaw: authReq.userId,
8476
+ authLane: "api_key",
8477
+ isInternal: isMonitorSecretRequest(req)
8478
+ },
8479
+ beforeExecution: keyedBeforeExecution
8480
+ })
8481
+ )
8229
8482
  );
8230
8483
  const meta = buildRestExecutionMeta(
8231
8484
  executionToolName,
@@ -8239,7 +8492,8 @@ app.post(
8239
8492
  const status = REST_EXECUTE_STATUS_CODES[error.code] ?? 500;
8240
8493
  return res.status(status).json({
8241
8494
  ...apiError(error.type, error.code, error.message, {
8242
- requestId: req.requestId
8495
+ requestId: req.requestId,
8496
+ ...restErrorOptionsFromReceipt(result.meta)
8243
8497
  }),
8244
8498
  meta,
8245
8499
  ...result.validationErrors && {
package/build/stdio.js CHANGED
@@ -6,8 +6,8 @@ import {
6
6
  import {
7
7
  createHiveMcpServer,
8
8
  secureLogger
9
- } from "./chunk-IJ5KOZH5.js";
10
- import "./chunk-GADPH72D.js";
9
+ } from "./chunk-3SQPLZW6.js";
10
+ import "./chunk-4QDGA6GP.js";
11
11
  import "./chunk-EPF36Q3Z.js";
12
12
  import {
13
13
  SERVER_NAME,