@papi-ai/server 0.7.108 → 0.7.110
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/README.md +8 -0
- package/dist/index.js +60 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,6 +43,14 @@ PAPI collects anonymous usage data (tool name, duration, project UUID — no cod
|
|
|
43
43
|
|
|
44
44
|
PAPI is configured with `PAPI_PROJECT_ID` and `PAPI_DATA_API_KEY` — both are generated by the onboarding wizard at [getpapi.ai](https://getpapi.ai/) and pasted into your `.mcp.json`. If those env vars aren't set, PAPI will fall back to local file storage (md mode) and emit a stderr warning that your cycles aren't visible on the dashboard. To get on the dashboard, sign up at [getpapi.ai](https://getpapi.ai/) and use the config it gives you.
|
|
45
45
|
|
|
46
|
+
### Stdio idle shutdown
|
|
47
|
+
|
|
48
|
+
The stdio server exits cleanly after two minutes without input, including its
|
|
49
|
+
database adapter pool, so abandoned client sessions do not accumulate. Set
|
|
50
|
+
`PAPI_STDIO_IDLE_TIMEOUT_MS=0` to disable this behavior, or provide another
|
|
51
|
+
non-negative millisecond value in the `.mcp.json` environment block. This
|
|
52
|
+
setting applies only to stdio mode; hosted HTTP instances are not idle-killed.
|
|
53
|
+
|
|
46
54
|
## License
|
|
47
55
|
|
|
48
56
|
[Elastic License 2.0](https://www.elastic.co/licensing/elastic-license) — free to use, self-host, and modify. Commercial hosting requires a license.
|
package/dist/index.js
CHANGED
|
@@ -7558,9 +7558,10 @@ function resolveServerName(serverName) {
|
|
|
7558
7558
|
const candidate = serverName?.trim();
|
|
7559
7559
|
return candidate && SAFE_SERVER_NAME.test(candidate) ? candidate : DEFAULT_SERVER_NAME;
|
|
7560
7560
|
}
|
|
7561
|
-
function formatConnectionBanner(identity) {
|
|
7561
|
+
function formatConnectionBanner(identity, health = "connected") {
|
|
7562
7562
|
const projectIdShort = identity.projectId ? ` (${identity.projectId.slice(0, 8)})` : "";
|
|
7563
|
-
|
|
7563
|
+
const lead = health === "degraded" ? "Degraded \u2014 data may be stale" : health === "offline" ? "Offline \u2014 no database connection" : "Connected";
|
|
7564
|
+
return `[papi] ${lead} \u2014 server: ${resolveServerName(identity.serverName)}, project: ${identity.projectName}${projectIdShort}, adapter: ${identity.adapterType}, v${identity.pkgVersion}`;
|
|
7564
7565
|
}
|
|
7565
7566
|
|
|
7566
7567
|
// src/server.ts
|
|
@@ -11034,7 +11035,7 @@ function isBlockerResolved(blocker, ctx) {
|
|
|
11034
11035
|
if (decision?.outcome && resolvedOutcomes.has(decision.outcome)) return true;
|
|
11035
11036
|
if (decision?.resolutionState === "resolved") return true;
|
|
11036
11037
|
const hasRecentEvent = ctx.decisionEvents.some(
|
|
11037
|
-
(e) => idMatches(e.decisionId, blocker.ref) && e.cycle >= blocker.blockedCycle && !PROPOSAL_EVENT_TYPES.includes(e.eventType)
|
|
11038
|
+
(e) => idMatches(e.decisionId, blocker.ref) && e.cycle >= blocker.blockedCycle && !PROPOSAL_EVENT_TYPES.includes(e.eventType) && e.source !== "build_complete"
|
|
11038
11039
|
);
|
|
11039
11040
|
return hasRecentEvent;
|
|
11040
11041
|
}
|
|
@@ -32781,23 +32782,23 @@ ${formatRuntimeIdentity(getRuntimeIdentity(config2, serverVersion))}`;
|
|
|
32781
32782
|
}
|
|
32782
32783
|
}
|
|
32783
32784
|
function enrichClaudeMd(projectRoot, cycleNumber, adapterType, collector, clientName) {
|
|
32784
|
-
|
|
32785
|
+
const targetFile = shouldWriteClaudeMd(clientName) ? "CLAUDE.md" : "AGENTS.md";
|
|
32785
32786
|
if (adapterType === "proxy") {
|
|
32786
32787
|
const additions2 = [];
|
|
32787
32788
|
if (cycleNumber >= 6) additions2.push(CLAUDE_MD_TIER_1);
|
|
32788
32789
|
if (cycleNumber >= 21) additions2.push(CLAUDE_MD_TIER_2);
|
|
32789
32790
|
if (additions2.length === 0) return "";
|
|
32790
|
-
collector.add({ path:
|
|
32791
|
+
collector.add({ path: targetFile, content: additions2.join(""), mode: "append" });
|
|
32791
32792
|
const tierNames2 = [];
|
|
32792
32793
|
if (cycleNumber >= 6) tierNames2.push("Established (batch building, strategy reviews, AD lifecycle)");
|
|
32793
32794
|
if (cycleNumber >= 21) tierNames2.push("Mature (idea pipeline, doc registry, advanced patterns)");
|
|
32794
32795
|
return `
|
|
32795
32796
|
|
|
32796
|
-
\u{1F4DD}
|
|
32797
|
+
\u{1F4DD} **${targetFile} enriched** \u2014 added ${tierNames2.join(" + ")} guidance for cycle ${cycleNumber}+ projects.`;
|
|
32797
32798
|
}
|
|
32798
|
-
const
|
|
32799
|
-
if (!existsSync11(
|
|
32800
|
-
const content = readFileSync13(
|
|
32799
|
+
const targetPath = join18(projectRoot, targetFile);
|
|
32800
|
+
if (!existsSync11(targetPath)) return "";
|
|
32801
|
+
const content = readFileSync13(targetPath, "utf-8");
|
|
32801
32802
|
const additions = [];
|
|
32802
32803
|
if (cycleNumber >= 6 && !content.includes(CLAUDE_MD_ENRICHMENT_SENTINEL_T1)) {
|
|
32803
32804
|
additions.push(dedupeEnrichmentBlob(content, CLAUDE_MD_TIER_1));
|
|
@@ -32806,13 +32807,13 @@ function enrichClaudeMd(projectRoot, cycleNumber, adapterType, collector, client
|
|
|
32806
32807
|
additions.push(dedupeEnrichmentBlob(content, CLAUDE_MD_TIER_2));
|
|
32807
32808
|
}
|
|
32808
32809
|
if (additions.length === 0) return "";
|
|
32809
|
-
writeFileSync7(
|
|
32810
|
+
writeFileSync7(targetPath, content + additions.join(""), "utf-8");
|
|
32810
32811
|
const tierNames = [];
|
|
32811
32812
|
if (additions.some((a) => a.includes(CLAUDE_MD_ENRICHMENT_SENTINEL_T1))) tierNames.push("Established (batch building, strategy reviews, AD lifecycle)");
|
|
32812
32813
|
if (additions.some((a) => a.includes(CLAUDE_MD_ENRICHMENT_SENTINEL_T2))) tierNames.push("Mature (idea pipeline, doc registry, advanced patterns)");
|
|
32813
32814
|
return `
|
|
32814
32815
|
|
|
32815
|
-
\u{1F4DD}
|
|
32816
|
+
\u{1F4DD} **${targetFile} enriched** \u2014 added ${tierNames.join(" + ")} guidance for cycle ${cycleNumber}+ projects.`;
|
|
32816
32817
|
}
|
|
32817
32818
|
|
|
32818
32819
|
// src/tools/hierarchy.ts
|
|
@@ -36707,6 +36708,43 @@ function shouldCheckForUpdate(version, selfHostFlag) {
|
|
|
36707
36708
|
return version !== "unknown" && !isSelfHostedDeployment(selfHostFlag);
|
|
36708
36709
|
}
|
|
36709
36710
|
|
|
36711
|
+
// src/lib/stdio-idle-timeout.ts
|
|
36712
|
+
var DEFAULT_STDIO_IDLE_TIMEOUT_MS = 12e4;
|
|
36713
|
+
function parseStdioIdleTimeoutMs(raw = process.env["PAPI_STDIO_IDLE_TIMEOUT_MS"]) {
|
|
36714
|
+
if (raw === void 0 || raw.trim() === "") return DEFAULT_STDIO_IDLE_TIMEOUT_MS;
|
|
36715
|
+
const timeoutMs = Number(raw);
|
|
36716
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs < 0) {
|
|
36717
|
+
throw new Error(
|
|
36718
|
+
`PAPI_STDIO_IDLE_TIMEOUT_MS must be a non-negative integer in milliseconds; received ${JSON.stringify(raw)}`
|
|
36719
|
+
);
|
|
36720
|
+
}
|
|
36721
|
+
return timeoutMs;
|
|
36722
|
+
}
|
|
36723
|
+
function startStdioIdleTimeout(input, timeoutMs, onTimeout) {
|
|
36724
|
+
if (timeoutMs === 0) return () => void 0;
|
|
36725
|
+
let timer2;
|
|
36726
|
+
let stopped = false;
|
|
36727
|
+
const stop = () => {
|
|
36728
|
+
if (stopped) return;
|
|
36729
|
+
stopped = true;
|
|
36730
|
+
if (timer2 !== void 0) clearTimeout(timer2);
|
|
36731
|
+
input.off("data", refresh);
|
|
36732
|
+
};
|
|
36733
|
+
const refresh = () => {
|
|
36734
|
+
if (stopped) return;
|
|
36735
|
+
if (timer2 !== void 0) clearTimeout(timer2);
|
|
36736
|
+
timer2 = setTimeout(() => {
|
|
36737
|
+
if (stopped) return;
|
|
36738
|
+
stopped = true;
|
|
36739
|
+
input.off("data", refresh);
|
|
36740
|
+
onTimeout();
|
|
36741
|
+
}, timeoutMs);
|
|
36742
|
+
};
|
|
36743
|
+
input.on("data", refresh);
|
|
36744
|
+
refresh();
|
|
36745
|
+
return stop;
|
|
36746
|
+
}
|
|
36747
|
+
|
|
36710
36748
|
// src/index.ts
|
|
36711
36749
|
init_dist();
|
|
36712
36750
|
var __dirname = dirname7(fileURLToPath5(import.meta.url));
|
|
@@ -36808,7 +36846,7 @@ async function gracefulShutdown(signal) {
|
|
|
36808
36846
|
} catch (err) {
|
|
36809
36847
|
console.error(`[papi] Adapter close failed during ${signal}: ${err instanceof Error ? err.message : String(err)}`);
|
|
36810
36848
|
}
|
|
36811
|
-
process.exit(signal === "SIGTERM" || signal === "SIGINT" ? 0 : 1);
|
|
36849
|
+
process.exit(signal === "SIGTERM" || signal === "SIGINT" || signal === "idle-timeout" ? 0 : 1);
|
|
36812
36850
|
}
|
|
36813
36851
|
process.on("SIGTERM", () => {
|
|
36814
36852
|
void gracefulShutdown("SIGTERM");
|
|
@@ -36922,13 +36960,22 @@ if (isHttpMode && httpPort !== void 0) {
|
|
|
36922
36960
|
} catch {
|
|
36923
36961
|
}
|
|
36924
36962
|
const transport = new StdioServerTransport();
|
|
36963
|
+
let stopStdioIdleTimeout = () => void 0;
|
|
36964
|
+
transport.onclose = () => stopStdioIdleTimeout();
|
|
36925
36965
|
await server.connect(transport);
|
|
36966
|
+
stopStdioIdleTimeout = startStdioIdleTimeout(
|
|
36967
|
+
process.stdin,
|
|
36968
|
+
parseStdioIdleTimeoutMs(),
|
|
36969
|
+
() => {
|
|
36970
|
+
void gracefulShutdown("idle-timeout");
|
|
36971
|
+
}
|
|
36972
|
+
);
|
|
36926
36973
|
process.stderr.write(`${formatConnectionBanner({
|
|
36927
36974
|
serverName: config.serverName,
|
|
36928
36975
|
projectName: basename2(config.projectRoot),
|
|
36929
36976
|
projectId: config.projectId ?? process.env.PAPI_PROJECT_ID,
|
|
36930
36977
|
adapterType: config.adapterType,
|
|
36931
36978
|
pkgVersion
|
|
36932
|
-
})}
|
|
36979
|
+
}, getConnectionStatus())}
|
|
36933
36980
|
`);
|
|
36934
36981
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@papi-ai/server",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.110",
|
|
4
4
|
"description": "PAPI MCP server — AI-powered sprint planning, build execution, and strategy review for software projects",
|
|
5
5
|
"license": "Elastic-2.0",
|
|
6
6
|
"mcpName": "io.github.getpapi/papi",
|