@papi-ai/server 0.7.85 → 0.7.98

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.
@@ -30,7 +30,6 @@ function resolveDeploymentProfile(env) {
30
30
  dataEndpoint: cleanUrl(env.dataEndpoint) ?? (hostedSupabaseUrl ? `${hostedSupabaseUrl}/functions/v1/data-proxy` : void 0) ?? (selfHosted ? void 0 : `${HOSTED_SUPABASE_URL}/functions/v1/data-proxy`),
31
31
  providers: {
32
32
  vercel: optionalBoolean(env.vercelEnabled, !selfHosted),
33
- railway: optionalBoolean(env.railwayEnabled, !selfHosted),
34
33
  managedSupabase: optionalBoolean(env.managedSupabaseEnabled, !selfHosted)
35
34
  },
36
35
  auth: {
@@ -100,6 +99,21 @@ var init_dist = __esm({
100
99
  "../shared/dist/index.js"() {
101
100
  "use strict";
102
101
  CAPABILITY_REGISTRY = [
102
+ // task-3384: the PLAN step. The landing page has advertised these three since
103
+ // the v5 control beat (components/marketing/landing/ControlC.tsx) with a code
104
+ // comment reading "shipping soon, shown now" — the registry had no plan-step
105
+ // entry at all, so the Planning group rendered empty and was dropped. These
106
+ // are NOT new behaviour: each one gates a section of the planner instructions
107
+ // that already runs on every full-mode plan, so ON reproduces today exactly.
108
+ //
109
+ // All three default ON. Turning one off removes its step from the planner
110
+ // prompt, which is the whole mechanism — the planner does what the prompt
111
+ // tells it to, so an omitted step is genuinely not performed (and the matching
112
+ // plan-prepare progress checkpoint is not recorded, so the hub does not claim
113
+ // a step that never ran).
114
+ { key: "planHealthCheck", label: "Health check", description: "Flags cycle gaps, stale tasks and AD conflicts before planning.", step: "plan" },
115
+ { key: "backlogTriage", label: "Backlog triage", description: "Cleans up and prioritises unreviewed backlog items during planning.", step: "plan" },
116
+ { key: "buildOrder", label: "Build order", description: "Detects intra-cycle dependencies and sequences the build.", step: "plan" },
103
117
  { key: "prReviewer", label: "Auto code review", description: "Runs a code review of the branch diff before accepting.", step: "review" },
104
118
  { key: "securityScan", label: "Security scan", description: "Flags a security pass on risk-tier changes at review time.", step: "review" },
105
119
  { key: "changelog", label: "Changelog & cycle update", description: "Curates a cycle-update post when a release ships.", step: "release" },
@@ -154,7 +168,7 @@ var init_dist = __esm({
154
168
  CAPABILITY_KEYS = CAPABILITY_REGISTRY.map((c) => c.key);
155
169
  HOSTED_APP_URL = "https://getpapi.ai";
156
170
  HOSTED_MCP_URL = "https://mcp.getpapi.ai";
157
- HOSTED_SUPABASE_URL = "https://guewgygcpcmrcoppihzx.supabase.co";
171
+ HOSTED_SUPABASE_URL = "https://api.getpapi.ai";
158
172
  WAB_WINDOW_DAYS = 7;
159
173
  WAB_WEEK_MS = WAB_WINDOW_DAYS * 24 * 60 * 60 * 1e3;
160
174
  EFFORT_SCALE = {
@@ -1471,6 +1485,16 @@ __export(proxy_adapter_exports, {
1471
1485
  createProxyAdapter: () => createProxyAdapter,
1472
1486
  wrapWithForwarding: () => wrapWithForwarding
1473
1487
  });
1488
+ function formatRateLimitError(route, response, errorBody, displayMessage = errorBody) {
1489
+ let reason;
1490
+ try {
1491
+ const parsed = JSON.parse(errorBody);
1492
+ if (typeof parsed.reason === "string") reason = parsed.reason;
1493
+ } catch {
1494
+ }
1495
+ const retryAfter = response.headers.get("Retry-After");
1496
+ return `Proxy rate limit on ${route}${reason ? ` (${reason})` : ""}${retryAfter ? `; retry after ${retryAfter}s` : ""}: ${displayMessage}`;
1497
+ }
1474
1498
  function snakeToCamel(str) {
1475
1499
  return str.replace(/_([a-z0-9])/g, (_, c) => c.toUpperCase());
1476
1500
  }
@@ -1512,7 +1536,7 @@ function wrapWithForwarding(instance) {
1512
1536
  return new Proxy(instance, {
1513
1537
  get(target, prop, receiver) {
1514
1538
  const existing = Reflect.get(target, prop, receiver);
1515
- if (existing !== void 0) return existing;
1539
+ if (Reflect.has(target, prop)) return existing;
1516
1540
  if (typeof prop !== "string" || prop === "then" || prop.startsWith("_") || NO_FORWARD.has(prop)) {
1517
1541
  return existing;
1518
1542
  }
@@ -1644,8 +1668,14 @@ var init_proxy_adapter = __esm({
1644
1668
  "linkPhasesToStage",
1645
1669
  "listContributors",
1646
1670
  "listConventions",
1671
+ "listDecisionPositions",
1672
+ "upsertDecisionPosition",
1673
+ "proposeDecision",
1674
+ "resolveDecisionState",
1675
+ "getUserEmailsByIds",
1647
1676
  "listMyBugReports",
1648
1677
  "listOwnerActionsForBlockerScan",
1678
+ "listOwnerActionsForOwner",
1649
1679
  "logEntityReferences",
1650
1680
  "markAgendaTopicsAddressed",
1651
1681
  "markCycleLearningResolved",
@@ -1709,6 +1739,12 @@ var init_proxy_adapter = __esm({
1709
1739
  // (1) local-only
1710
1740
  "close",
1711
1741
  "initRls",
1742
+ // task-3667: the exit-criteria evaluators probe adapter.sql to pick their
1743
+ // transport branch (direct SQL vs adapter reads). Forwarding `sql` to the
1744
+ // edge 403'd INSIDE sqlOf — every SQL-backed evaluator then threw
1745
+ // 'evaluator failed' instead of engaging its hosted branch or degrading to
1746
+ // an honest unevaluated reason. `sql` is local-only by definition.
1747
+ "sql",
1712
1748
  // task-3207 (C357): commitBuildComplete, commitReviewSubmit, commitRelease have no
1713
1749
  // edge case handler and no ALLOWED_METHODS entry — forwarding them 403s at the edge
1714
1750
  // with no try/catch at the call site, crashing build_execute/review_submit/release
@@ -1795,6 +1831,15 @@ var init_proxy_adapter = __esm({
1795
1831
  this.projectId = config.projectId ?? "";
1796
1832
  this.onAuthRejected = config.onAuthRejected;
1797
1833
  }
1834
+ /**
1835
+ * Headers shared by every authenticated proxy route.
1836
+ */
1837
+ authenticatedHeaders() {
1838
+ return {
1839
+ "Content-Type": "application/json",
1840
+ "Authorization": `Bearer ${this.apiKey}`
1841
+ };
1842
+ }
1798
1843
  /**
1799
1844
  * Does the hosted path genuinely serve `name`? (task-3022, C362)
1800
1845
  *
@@ -1833,10 +1878,7 @@ var init_proxy_adapter = __esm({
1833
1878
  try {
1834
1879
  const response = await fetch(`${this.endpoint}/project-list`, {
1835
1880
  method: "POST",
1836
- headers: {
1837
- "Content-Type": "application/json",
1838
- "Authorization": `Bearer ${this.apiKey}`
1839
- },
1881
+ headers: this.authenticatedHeaders(),
1840
1882
  body: "{}",
1841
1883
  signal: AbortSignal.timeout(5e3)
1842
1884
  });
@@ -1875,10 +1917,7 @@ var init_proxy_adapter = __esm({
1875
1917
  const url = `${this.endpoint}/ensure-project`;
1876
1918
  const response = await fetch(url, {
1877
1919
  method: "POST",
1878
- headers: {
1879
- "Content-Type": "application/json",
1880
- "Authorization": `Bearer ${this.apiKey}`
1881
- },
1920
+ headers: this.authenticatedHeaders(),
1882
1921
  body: JSON.stringify({
1883
1922
  ...projectName ? { projectName } : {},
1884
1923
  ...repoUrl ? { repoUrl } : {},
@@ -1898,6 +1937,9 @@ var init_proxy_adapter = __esm({
1898
1937
  } catch {
1899
1938
  message = errorBody;
1900
1939
  }
1940
+ if (response.status === 429) {
1941
+ throw new Error(formatRateLimitError("ensureProject", response, errorBody, message));
1942
+ }
1901
1943
  throw new Error(`Auto-provision failed (${response.status}): ${message}`);
1902
1944
  }
1903
1945
  const body = await response.json();
@@ -1918,10 +1960,7 @@ var init_proxy_adapter = __esm({
1918
1960
  try {
1919
1961
  response = await fetch(url, {
1920
1962
  method: "POST",
1921
- headers: {
1922
- "Content-Type": "application/json",
1923
- "Authorization": `Bearer ${this.apiKey}`
1924
- },
1963
+ headers: this.authenticatedHeaders(),
1925
1964
  body: JSON.stringify({
1926
1965
  projectId: this.projectId,
1927
1966
  method,
@@ -1964,6 +2003,9 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
1964
2003
  (${response.status} on ${method}: ${message})`
1965
2004
  );
1966
2005
  }
2006
+ if (response.status === 429) {
2007
+ throw new Error(formatRateLimitError(method, response, errorBody, message));
2008
+ }
1967
2009
  throw new Error(`Proxy error (${response.status}) on ${method}: ${message}`);
1968
2010
  }
1969
2011
  const body = await response.json();
@@ -2025,10 +2067,7 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
2025
2067
  try {
2026
2068
  const response = await fetch(`${this.endpoint}/invoke`, {
2027
2069
  method: "POST",
2028
- headers: {
2029
- "Content-Type": "application/json",
2030
- "Authorization": `Bearer ${this.apiKey}`
2031
- },
2070
+ headers: this.authenticatedHeaders(),
2032
2071
  body: JSON.stringify({ projectId, method: "projectExists", args: [] }),
2033
2072
  signal: AbortSignal.timeout(5e3)
2034
2073
  });
@@ -2059,8 +2098,8 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
2059
2098
  writeCycleLogEntry(entry) {
2060
2099
  return this.invoke("writeCycleLogEntry", [entry]);
2061
2100
  }
2062
- updateActiveDecision(id, body, cycleNumber) {
2063
- return this.invoke("updateActiveDecision", [id, body, cycleNumber]);
2101
+ updateActiveDecision(id, body, cycleNumber, action) {
2102
+ return this.invoke("updateActiveDecision", [id, body, cycleNumber, action]);
2064
2103
  }
2065
2104
  upsertActiveDecision(id, body, title, confidence, cycleNumber) {
2066
2105
  return this.invoke("upsertActiveDecision", [id, body, title, confidence, cycleNumber]);
@@ -2187,7 +2226,7 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
2187
2226
  * task-2288 (C308): emit a telemetry event via the proxy's /telemetry endpoint
2188
2227
  * using THIS adapter's per-request bearer (this.apiKey) — the same credential
2189
2228
  * invoke() uses. This is the fix for the hosted-transport blackout: on the
2190
- * multi-tenant Railway pod there is no PAPI_DATA_API_KEY env var, so the
2229
+ * multi-tenant server has no PAPI_DATA_API_KEY env var, so the
2191
2230
  * env-var emitTelemetryEvent path (lib/telemetry.ts) dropped every event since
2192
2231
  * 2026-06-16. Fire-and-forget — never throws, never blocks the tool response.
2193
2232
  * Not routed through invoke() because /telemetry is a distinct endpoint and a
@@ -2203,10 +2242,7 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
2203
2242
  if (event.projectId) payload["projectId"] = event.projectId;
2204
2243
  fetch(`${this.endpoint}/telemetry`, {
2205
2244
  method: "POST",
2206
- headers: {
2207
- "Content-Type": "application/json",
2208
- "Authorization": `Bearer ${this.apiKey}`
2209
- },
2245
+ headers: this.authenticatedHeaders(),
2210
2246
  body: JSON.stringify(payload),
2211
2247
  signal: AbortSignal.timeout(5e3)
2212
2248
  }).then((res) => {
@@ -2293,6 +2329,19 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
2293
2329
  getLatestDecisionScores() {
2294
2330
  return this.invoke("getLatestDecisionScores");
2295
2331
  }
2332
+ // --- Decision lifecycle — propose / resolve / positions (task-3408, C366) ---
2333
+ proposeDecision(id, decisionType, proposedByUserId, cycleNumber) {
2334
+ return this.invoke("proposeDecision", [id, decisionType, proposedByUserId, cycleNumber]);
2335
+ }
2336
+ resolveDecisionState(id, state, actorUserId, cycleNumber) {
2337
+ return this.invoke("resolveDecisionState", [id, state, actorUserId, cycleNumber]);
2338
+ }
2339
+ listDecisionPositions(decisionId) {
2340
+ return this.invoke("listDecisionPositions", [decisionId]);
2341
+ }
2342
+ upsertDecisionPosition(decisionId, userId, status, comments) {
2343
+ return this.invoke("upsertDecisionPosition", [decisionId, userId, status, comments ?? null]);
2344
+ }
2296
2345
  logEntityReferences(refs) {
2297
2346
  return this.invoke("logEntityReferences", [refs]);
2298
2347
  }
@@ -2488,7 +2537,18 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
2488
2537
  }
2489
2538
  // --- Project metadata (path-identity guardrail) ---
2490
2539
  async getProjectInfo() {
2491
- return this.invoke("getProjectInfo", []);
2540
+ const raw = await this.invoke("getProjectInfo", []);
2541
+ if (!raw) return null;
2542
+ return {
2543
+ name: raw.name,
2544
+ slug: raw.slug,
2545
+ papi_dir: raw.papi_dir ?? raw.papiDir ?? null,
2546
+ repo_url: raw.repo_url ?? raw.repoUrl ?? null,
2547
+ capabilities: raw.capabilities ?? {},
2548
+ discord_channel_id: raw.discord_channel_id ?? raw.discordChannelId ?? null,
2549
+ decision_resolution_mode: raw.decision_resolution_mode ?? raw.decisionResolutionMode ?? "owner",
2550
+ decision_type_resolvers: raw.decision_type_resolvers ?? raw.decisionTypeResolvers ?? {}
2551
+ };
2492
2552
  }
2493
2553
  /**
2494
2554
  * task-2052 (C288): owner-gate identity for the proxy transport. Both sides
@@ -2528,10 +2588,7 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
2528
2588
  async postRoute(route, payload) {
2529
2589
  const response = await fetch(`${this.endpoint}/${route}`, {
2530
2590
  method: "POST",
2531
- headers: {
2532
- "Content-Type": "application/json",
2533
- "Authorization": `Bearer ${this.apiKey}`
2534
- },
2591
+ headers: this.authenticatedHeaders(),
2535
2592
  body: JSON.stringify(payload),
2536
2593
  signal: AbortSignal.timeout(15e3)
2537
2594
  });
@@ -2543,6 +2600,9 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
2543
2600
  } catch {
2544
2601
  message = errorBody;
2545
2602
  }
2603
+ if (response.status === 429) {
2604
+ throw new Error(formatRateLimitError(route, response, errorBody, message));
2605
+ }
2546
2606
  if (response.status === 401) this.onAuthRejected?.();
2547
2607
  throw new Error(`Proxy error (${response.status}) on ${route}: ${message}`);
2548
2608
  }
@@ -2692,7 +2752,6 @@ function serverDeploymentProfile() {
2692
2752
  dataEndpoint: process.env["PAPI_DATA_ENDPOINT"],
2693
2753
  hostedSupabaseUrl: process.env["PAPI_HOSTED_SUPABASE_URL"],
2694
2754
  vercelEnabled: process.env["PAPI_ENABLE_VERCEL"],
2695
- railwayEnabled: process.env["PAPI_ENABLE_RAILWAY"],
2696
2755
  managedSupabaseEnabled: process.env["PAPI_ENABLE_MANAGED_SUPABASE"]
2697
2756
  });
2698
2757
  }
@@ -2863,16 +2922,21 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
2863
2922
  });
2864
2923
  }
2865
2924
  } else {
2866
- if (!existing.root_commit_hash && rootHash) {
2925
+ if (!existing.root_commit_hash && rootHash || !existing.repo_url && originUrl) {
2867
2926
  try {
2868
- await pgAdapter.updateProject(projectId, {
2869
- root_commit_hash: rootHash,
2870
- repo_url: originUrl ?? void 0,
2871
- resolution_method: "root_hash"
2872
- });
2927
+ const update = {};
2928
+ if (!existing.root_commit_hash && rootHash) {
2929
+ update.root_commit_hash = rootHash;
2930
+ update.resolution_method = "root_hash";
2931
+ }
2932
+ if (!existing.repo_url && originUrl) {
2933
+ update.repo_url = originUrl;
2934
+ if (!update.resolution_method && !existing.resolution_method) update.resolution_method = "repo_url";
2935
+ }
2936
+ await pgAdapter.updateProject(projectId, update);
2873
2937
  } catch (err) {
2874
2938
  console.error(
2875
- `[papi] \u26A0 Failed to backfill root_commit_hash: ${err instanceof Error ? err.message : String(err)}`
2939
+ `[papi] \u26A0 Failed to backfill project resolution signals: ${err instanceof Error ? err.message : String(err)}`
2876
2940
  );
2877
2941
  }
2878
2942
  }
@@ -2913,7 +2977,7 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
2913
2977
  } catch {
2914
2978
  }
2915
2979
  }
2916
- const adapter = new PgPapiAdapter(config, projectId);
2980
+ const adapter = new PgPapiAdapter({ ...config, userId: resolveUserId }, projectId);
2917
2981
  try {
2918
2982
  await adapter.initRls();
2919
2983
  } catch {