@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.
- package/dist/backfill-cycle-metrics.js +104 -40
- package/dist/index.js +2826 -1005
- package/dist/prompts.js +70 -14
- package/dist/statusline.js +168 -0
- package/package.json +5 -6
|
@@ -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://
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
resolution_method
|
|
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
|
|
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 {
|